最近看了云风的skynet框架,感觉获益良多。再次为云风大神的无私奉献致谢。下面是我看skynet框架时,看底层网络层时的一些心得体会,如果有写错的地方欢迎指正,本文是我的原创,如果转载请注明。
skynet的网络底层 采用了 epoll模型 (linux平台下),支持 linux,apple,freebsd,openbsd,netbsd等平台。
bool sp_invalid(poll_fd fd) --判断句柄fd是否是有效句柄,为-1是无效句柄poll_fd sp_create() -- 创建一个epoll,返回对应的句柄int sp_add(poll_fd fd, int sock, void *ud) -- 将sock句柄,添加到epoll中 epoll对应的句柄为fd,用户数据为ud,默认是关注in事件,返回0代表成功,1为失败void sp_del(poll_fd fd, int sock); -从epoll fd中移除sock;void sp_write(poll_fd, int sock, void *ud, bool enable);将epoll fd中已有的sock设置它是否关注out事件int sp_wait(poll_fd, struct event *e, int max);--等待epoll,e为一个数组,最大长度为max,它返回wait到的相关事件。返回的是真实的事件个数。void sp_nonblocking(int sock);--设置sock为非阻塞
struct write_buffer { //这结构是用保存要发送的数据 一个缓存节点
struct write_buffer * next; //由于是链表结构,这个用于指向下个节点
void *buffer;//要发送的内存数据块
char *ptr;//已发送数据的偏移位置,也就是下次发送从这开始
int sz;//剩余多少数据没有发送
bool userobject;//是否是用户数据
uint8_t udp_address[UDP_ADDRESS_SIZE]; //udp专用,这个数据发送的目标地址
};
//tcp 协议udp_address 项数据是无用的,所以分配内存时 就不会分配这块的内存
#define SIZEOF_TCPBUFFER (offsetof(struct write_buffer, udp_address[0]))
#define SIZEOF_UDPBUFFER (sizeof(struct write_buffer))
//发送数据的缓存列表
struct wb_list {
struct write_buffer * head;
struct write_buffer * tail;
};
struct socket {
uintptr_t opaque;//保存使用这个socket对应的服务的地址,因为有些网络事件需要返回到使用这个socket的服务
struct wb_list high;//发送缓存链表,要发送的数据都会优先存这两个列表,high是优先发送的
struct wb_list low;
int64_t wb_size;//两个缓存列表待发送的数据长度总和
int fd; //socket句柄
int id; //对应 socket_server 中一个socket数组的下标 可以理解为连接id,供上层逻辑使用。
uint16_t protocol;//指定是udp还是 tcp 协议
uint16_t type;//socket的相关状态
union {
int size;//下次从 fd中读取数据的长度,是变动的,初始为MIN_READ_BUFFER = 64,根据实际读取到的长度 扩张和收缩
uint8_t udp_address[UDP_ADDRESS_SIZE];//udp绑定的地址
} p;
};
struct socket_server {
int recvctrl_fd;//通过管道piple创建出来的一对句柄,用于向网络底层传输请求消息
int sendctrl_fd;
int checkctrl;//标识当前是否需要检测recvctrl_fd内是否有请求消息可读
poll_fd event_fd;//创建epoll返回的对应句柄
int alloc_id;//socket 的id 是通过这个id一定算法分配出来的 需要了解算法的话去看reserve_id函数
int event_n;//保存每次从epoll中wait出来的真实事件个数
int event_index;//当前处理到第几个epoll事件
struct socket_object_interface soi;
struct event ev[MAX_EVENT];//保存epoll 中wait出来的事件
struct socket slot[MAX_SOCKET];//socket数组
char buffer[MAX_INFO];//保存一些控制命令操作的相关临时数据,返回结果有相关指针指向该处
uint8_t udpbuffer[MAX_UDP_PACKAGE];
fd_set rfds;//主要给recvctrl_fd用,让其通过select函数判断是否有数据可读
};
下面可以看 网络请求消息的定义
/*发送给网络层的请求消息,不同的请求使用联合体内对应的消息体,从第六个字节开始发送,header中的7,8 字节分别代表类型和长度*/
struct request_package {
uint8_t header[8]; // 6 bytes dummy
union {
char buffer[256];
struct request_open open;
struct request_send send;
struct request_send_udp send_udp;
struct request_close close;
struct request_listen listen;
struct request_bind bind;
struct request_start start;
struct request_setopt setopt;
struct request_udp udp;
struct request_setudp set_udp;
} u;
uint8_t dummy[256];
};
我们可以看lua-socket.c文件,里面里面定义了服务端 网络层相关的相关的一些接口,这些接口都是抛出给lua逻辑层使用的。对应的lua网络相关的逻辑封装在 socket.lua文件中定义。现在先分析抛出的相关接口 可以看luaopen_socketdriver函数,可以知道抛出了那些接口:
local function connect(id, func)
local newbuffer
if func == nil then
newbuffer = driver.buffer()
end
local s = {
id = id,
buffer = newbuffer,
connected = false,
connecting = true,
read_required = false,
co = false,
callback = func,
protocol = "TCP",
}
assert(not socket_pool[id], "socket is not closed")
socket_pool[id] = s
suspend(s)
local err = s.connecting
s.connecting = nil
if s.connected then
return id
else
socket_pool[id] = nil
return nil, err
end
end
-- SKYNET_SOCKET_TYPE_CONNECT = 2
socket_message[2] = function(id, _ , addr)
local s = socket_pool[id]
if s == nil then
return
end
-- log remote addr
s.connected = true
wakeup(s)
end
成功会将connected 设置为 true, 并且重新唤醒 socket s对应的协程。
-- SKYNET_SOCKET_TYPE_ERROR = 5
socket_message[5] = function(id, _, err)
local s = socket_pool[id]
if s == nil then
skynet.error("socket: error on unknown", id, err)
return
end
if s.connected then
skynet.error("socket: error on", id, err)
elseif s.connecting then
s.connecting = err
end
s.connected = false
driver.shutdown(id)
wakeup(s)
end
失败的话 connecting将是一些错误信息,同时 会shutdown掉对应的socket.
-- SKYNET_SOCKET_TYPE_ACCEPT = 4
socket_message[4] = function(id, newid, addr)
local s = socket_pool[id]
if s == nil then
driver.close(newid)
return
end
s.callback(newid, addr)
end
可以知道他会调用我们刚刚注册的cb函数。