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 
#include 
#include 
#include 
#include 
#include 
#include #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”,回车,终止客户端进程。
在这里插入图片描述
skynet:网关服务与封包/解包_第1张图片
可以看到:客户端连接与断开连接,网关服务都有收到,但 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 
#include 
#include 
#include 
#include 
#include 
#include #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。

skynet:网关服务与封包/解包_第2张图片
这是因为代码里有非法 Ascll 码字符、非法空格所造成的,解决方法同问题一,用 vim 命令打开脚本后,删除非法字符即可。
skynet:网关服务与封包/解包_第3张图片

你可能感兴趣的:(lua,skynet)