skynet:網關服務與封包/解包

TCP 是基於數據流的,但一般需要以帶長度信息的數據包來做數據交換,skynet 提供了一個通用模板 lualib/snax/gateserver.lua 來啓動一個網關服務器,gateserver 做的就是這個工作。

一、編寫網關服務

mygateserver.lua

local skynet = require "skynet"
local gateserver = require "snax.gateserver"
​
local handler = {}--當一個客戶端鏈接進來,gateserver自動處理鏈接,並且調用該函數,必須要有
function handler.connect(fd, ipaddr)   
    skynet.error("ipaddr:",ipaddr,"fd:",fd,"connect")
    gateserver.openclient(fd) --鏈接成功不代表馬上可以讀到數據,需要打開這個套接字,允許fd接收數據
end
​
--當一個客戶端斷開鏈接後調用該函數,必須要有
function handler.disconnect(fd)   
    skynet.error("fd:", fd, "disconnect")
end
​
​
--當fd有數據到達了,會調用這個函數,前提是fd需要調用gateserver.openclient打開
function handler.message(fd, msg, sz)
    skynet.error("recv message from fd:", fd)
​
end
​
​--向gateserver註冊網絡事件處理
gateserver.start(handler)

二、啓動網關服務

main.lua

local skynet = require "skynet"
​
skynet.start(function()
    skynet.error("Server start")
    local gateserver = skynet.newservice("myservice/mygateserver") --啓動前面寫的網關服務
    skynet.call(gateserver, "lua", "open", {   --需要給網關服務發送open消息,來啓動監聽
        port = 8002,            --監聽的端口
        maxclient = 64,         --客戶端最大連接數
        nodelay = true,         --是否延遲TCP
    })
​
    skynet.error("gate server setup on", 8002)
    skynet.exit()
end)

三、測試

3.1 客戶端測試腳本

socketclient.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>#define MAXLINE 128
#define SERV_PORT 8002void* readthread(void* arg)
{
    pthread_detach(pthread_self());
    int sockfd = (int)arg;
    int n = 0;
    char buf[MAXLINE];
    while (1) 
    {
        n = read(sockfd, buf, MAXLINE);
        if (n == 0)
        {
            printf("the other side has been closed.\n");
            close(sockfd);
            exit(0);
        }
        else
            write(STDOUT_FILENO, buf, n);
    }   
    return (void*)0;
}int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    int sockfd;
    char buf[MAXLINE];
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
​
    pthread_t thid;
    pthread_create(&thid, NULL, readthread, (void*)sockfd);while (fgets(buf, MAXLINE, stdin) != NULL) 
        write(sockfd, buf, strlen(buf));
    close(sockfd);
    return 0;
}

腳本的功能是:從控制檯讀取用戶的輸入,並將輸入的數據發送到服務端。

編譯測試腳本:

gcc socketclient.c -lpthread -o socketclient

3.2 啓動網關

/root/skynet-master/skynet ./config/config

3.3 測試

啓動客戶端,並在控制檯輸入“123”,回車,終止客戶端進程。
在這裏插入圖片描述
在這裏插入圖片描述
可以看到:客戶端連接與斷開連接,網關服務都有收到,但 handler.message 並沒有執行。這是由於 snax.gateserver 基於TCP協議包裝了一個兩字節數據長度的協議,而客戶端 socketclient 並沒有按照這種協議發送數據。

四、gateserver 應用協議

gateserver 應用協議是基於TCP協議的簡單封裝,前兩個字節表示數據包的長度len(不計算這兩個表示長度的字節),高字節在前低字節在後(大端序),後面緊跟len字節數的數據。例如:

\x00\x05    \x31\x32\x33\x34\x35
    |            |
  len           data

由於數據包的長度用兩個字節表示,因此 data 部分最大可到 65535 個字節,這種協議包方式可以解決TCP粘包的問題。
因此,若想通過TCP與gateserver通信,則必須要按照這種協議進行組包解包。否則gateserver無法識別。

五、封包與解包

封包/解包TCP網路數據可使用skynet.netpack庫:

local netpack = require "skynet.netpack" 

--打包數據str,返回一個C指針msg,sz,申請內存
netpack.pack(str)--解包數據,返回一個lua的字符串,會釋放內存
netpack.tostring(msg, sz)

六、改良

6.1、修改網關服務

修改第一章節網關服務 handler.message() 函數:

local skynet = require "skynet"
local gateserver = require "snax.gateserver"
local netpack = require "skynet.netpack" --使用netpack
​
local handler = {}--當一個客戶端鏈接進來,gateserver自動處理鏈接,並且調用該函數
function handler.connect(fd, ipaddr)   
    skynet.error("ipaddr:",ipaddr,"fd:",fd,"connect")
    gateserver.openclient(fd)
end
​
--當一個客戶端斷開鏈接後調用該函數
function handler.disconnect(fd)
    skynet.error("fd:", fd, "disconnect")
end
​
​
--接收消息
function handler.message(fd, msg, sz)
    skynet.error("recv message from fd:", fd)
    skynet.error(netpack.tostring(msg, sz))   --把 handler.message 方法收到的 msg,sz 轉換成一個 lua string,並釋放 msg 佔用的 C 內存。
end
​
gateserver.start(handler)

注意:
msg是一個指向一塊堆空間的C指針,即使不進行任何操作,始終需要調用 skynet.trash 來釋放底層的內存。

6.2、修改客戶端測試腳本

修改3.1小節中,客戶端測試腳本發包部分:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>#define MAXLINE 128void* readthread(void* arg)
{
    pthread_detach(pthread_self());
    int sockfd = (int)arg;
    int n = 0;
    char buf[MAXLINE];
    while (1) 
        {
        n = read(sockfd, buf, MAXLINE);
        if (n == 0)
        {
            printf("the other side has been closed.\n");
            close(sockfd);
            exit(0);
        }
        else
            write(STDOUT_FILENO, buf, n);
    }   
    return (void*)0;
}int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("usage:%s port", argv[0]);
        return -1;
    }
    int port = atoi(argv[1]);
    struct sockaddr_in servaddr;
    int sockfd;
    short size, nsize;
    char buf[MAXLINE];
    unsigned char sendbuf[MAXLINE];
​
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
​
    pthread_t thid;
    pthread_create(&thid, NULL, readthread, (void*)sockfd);while (fgets(buf, MAXLINE, stdin) != NULL) 
    {
        size = (short)strlen(buf);   //計算需要發送的數據包長度
        nsize = htons(size);        //轉換成大端序
        memcpy(sendbuf, &nsize, sizeof(nsize));  //nsize先填入sendbuf
        memcpy(sendbuf+sizeof(nsize), buf, size); //再填入buf內容
        write(sockfd, sendbuf, size + sizeof(nsize));
    }
    close(sockfd);
    return 0;
}
6.3、測試

啓動網關服務與客戶端:
在這裏插入圖片描述
在這裏插入圖片描述

問題一、lua 腳本從 windows 拷貝到 linux 後,運行時出現錯誤:unexpected symbol near ‘<\194>’。

在這裏插入圖片描述
解決方法:在 linux 上以編輯模式打開腳本(vim),可能看到比 windows 多出了一些字符或者格式完全亂套,刪除異常字符並對格式重新編輯後即可解決。

問題二、C 腳本在 linux 上編譯是,出現錯誤:socketclient.c:32:1: error: stray 鈥榎200鈥in program。

在這裏插入圖片描述
這是因爲代碼裏有非法 Ascll 碼字符、非法空格所造成的,解決方法同問題一,用 vim 命令打開腳本後,刪除非法字符即可。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章