最近看了一些开源的网络库源码,有libevent,muduo,redis,类nginx等等。再看skynet网络部分就觉得很容易了,因为他们都是基于reactor模式,套路都差不多。不过skynet的网络部分要稍微复杂点,因为他最终要面向的是lua逻辑端。为了让lua socket api这块设计的合理,好用,整个网络架构上相比前面介绍的网络框架会有点绕,比方说socket的状态,比其他框架要多出几种来。
socket状态有哪些呢?查看定义如下:
#define SOCKET_TYPE_INVALID 0
#define SOCKET_TYPE_RESERVE 1
#define SOCKET_TYPE_PLISTEN 2
#define SOCKET_TYPE_LISTEN 3
#define SOCKET_TYPE_CONNECTING 4
#define SOCKET_TYPE_CONNECTED 5
#define SOCKET_TYPE_HALFCLOSE 6
#define SOCKET_TYPE_PACCEPT 7
#define SOCKET_TYPE_BIND 8
我们看到listen之前还有个plisten,没有accept,却有paccept。这个前缀p是什么意思呢?
要想解释这个,先得说说lua socket api如何设计和使用。一个典型的socket处理如下:
local id = socket.listen('127.0.0.1', 8876)
local acceptFun = function(id, addr)
print('connnect from '..addr..' '..id)
socket.start(id)
while true do
local ret = socket.read(id)
print('ret is ', ret)
end
end
socket.start(id, acceptFun)
在lua逻辑中,我们不能像写c服务器那样,listen之后调用在while循环中调用accept等待客户端连接,我们必须善用脚本语言的回调。感谢云风大侠设计出如此优秀的方案,在sockt.start中接受用户连接的回调函数。
这里的socket.start相当于要打开了一个socket,可是按照一般的网络框架理解,listen不正是打开了一个监听的socket吗。所以云风的设计方案是,listen底层确实建立了一个监听socket,但是他还没有纳入epoll的监管之中。等到socket.start才将监听socket纳入epoll的监管,所以在此之前,监听socket的状态是SOCKET_TYPE_PLISTEN,等到socket.start之后变为SOCKET_TYPE_LISTEN。
同理,当有客户端连接时,会生成一个新的连接socket,此时的状态为SOCKET_TYPE_PACCEPT,注意此时也不会将该socket纳入epoll的监管之中。等到调用socket.start之后状态变为SOCKET_TYPE_CONNECTED,同时将该连接socket纳入epoll的监管中。
总结一下,要想启用socket服务,lua调用socket.start必不可少,他都是将监听的socket或连接的socket纳入epoll的监管之中,在lua逻辑端会使所在服务暂停,等待连接或发送消息。然后他会给所在的服务发送类型为SKYNET_SOCKET_TYPE_CONNECT的socket消息。lua逻辑收到该消息的处理方式是恢复服务,服务得以继续进行。
在lua逻辑中充当客户端的角色时,要调用id = socket.open(ip, port),此时底层会去connect服务器,成功后连接socket的状态为SOCKET_TYPE_CONNECTED。这种情况可以不用调用socket.start(id),为什么呢,难道不需要将socket纳入epoll的监管吗?其实在处理open消息,建立新的socket时已经将其纳入epoll的监管中了,new_fd(ss, id, sock, PROTOCOL_TCP, request->opaque, true),最后一个参数为true,表明要加入epoll中。
当socket收到消息(关闭消息也算)或有错误产生时,skynet则没有给出socket的状态,只是将收到的数据push到服务的消息队列。