主要结构
// socket_server_poll返回的socket消息类型 #define SOCKET_DATA 0 // data 到来 #define SOCKET_CLOSE 1 // close conn #define SOCKET_OPEN 2 // conn ok #define SOCKET_ACCEPT 3 // 被动连接建立 (Accept返回了连接的fd 但是未加入epoll来管理) #define SOCKET_ERROR 4 // error #define SOCKET_EXIT 5 // exit struct socket_server; // socket_server对应的msg struct socket_message { int id; // 应用层的socket fd uintptr_t opaque; // 在skynet中对应一个actor实体的handler int ud; // 对于accept连接来说是新连接的fd 对于数据到来是数据的大小 char * data; };
socket_message 对应 socket_server
skynet_socket_message 对应 skynet_socket_server
skynet_message 对应 Actor之间
socket fd的一些类型和状态
#define MAX_INFO 128 // MAX_SOCKET will be 2^MAX_SOCKET_P #define MAX_SOCKET_P 16 #define MAX_EVENT 64 // 用于epoll_wait的第三个参数 每次返回事件的多少 #define MIN_READ_BUFFER 64 // 最小分配的读缓冲大小 为了减少read的调用 尽可能分配大的读缓冲区 muduo是用了reav来减少read系统调用次数的 它可以准备多块缓冲区 #define SOCKET_TYPE_INVALID 0 // 无效的sock fe #define SOCKET_TYPE_RESERVE 1 // 预留已经被申请 即将被使用 #define SOCKET_TYPE_PLISTEN 2 // listen fd但是未加入epoll管理 #define SOCKET_TYPE_LISTEN 3 // 监听到套接字已经加入epoll管理 #define SOCKET_TYPE_CONNECTING 4 // 尝试连接的socket fd #define SOCKET_TYPE_CONNECTED 5 // 已经建立连接的socket 主动conn或者被动accept的套接字 已经加入epoll管理 #define SOCKET_TYPE_HALFCLOSE 6 // 已经在应用层关闭了fd 但是数据还没有发送完 还没有close #define SOCKET_TYPE_PACCEPT 7 // accept返回的fd 未加入epoll #define SOCKET_TYPE_BIND 8 // 其他类型的fd 如 stdin stdout等
#define MAX_SOCKET (1<<MAX_SOCKET_P) // 1 << 16 -> 64K
应用层socket结构
// 应用层的socket struct socket { int fd; // 对应内核分配的fd int id; // 应用层维护的一个与fd对应的id int type; // socket类型或者状态 int size; // 下一次read操作要分配的缓冲区大小 int64_t wb_size; // 发送缓冲区未发送的数据 uintptr_t opaque; // 在skynet中用于保存服务的handle struct write_buffer * head; // 发送缓冲区链表头指针和尾指针 struct write_buffer * tail; // };
// 发送缓冲区构成一个链表 struct write_buffer { struct write_buffer * next; char *ptr; // 指向当前未发送的数据首部 int sz; void *buffer; };
这里的handler( opaque字段 )的含义是
skynet是基于Actor模型的并发框架 在skynet中每一个服务就是一个Actor实体 用handle来标示 这样当事件循环EventLoop中捕捉到事件后 就知道这个事件属于哪个Actor实体 然后就把这个事件转发给对应的Actor skynet_socket_poll接受到socket事件 转发给对应的服务
socket_server结构
struct event { void * s; bool read; bool write; };
struct socket_server { int recvctrl_fd; // 管道读端 int sendctrl_fd; // 管道写端 int checkctrl; // 释放检测命令 poll_fd event_fd; // epoll fd int alloc_id; // 应用层分配id 用的 int event_n; // epoll_wait 返回的事件数 int event_index; // 当前处理的事件序号 struct event ev[MAX_EVENT]; // epoll_wait返回的事件集 struct socket slot[MAX_SOCKET]; // 应用层预先分配的socket char buffer[MAX_INFO]; // 临时数据的保存 比如保存对等方的地址信息等 fd_set rfds; // 用于select的fd集 };
这里的socket_server结构中字段实际上就对应下面这张事件循环的图
以下6个结构用于控制包体结构
struct request_open { int id; int port; uintptr_t opaque; char host[1]; }; struct request_send { int id; int sz; char * buffer; }; struct request_close { int id; uintptr_t opaque; }; struct request_listen { int id; int fd; uintptr_t opaque; char host[1]; }; struct request_bind { int id; int fd; uintptr_t opaque; }; struct request_start { int id; uintptr_t opaque; };
控制命令请求包
struct request_package {
uint8_t header[8]; // 6 bytes dummy 6个字节未用 [0-5] [6]是type [7]长度 长度指的是包体的长度
union {
char buffer[256];
struct request_open open;
struct request_send send;
struct request_close close;
struct request_listen listen;
struct request_bind bind;
struct request_start start;
} u;
uint8_t dummy[256];
};
具体的实现
socket_server_poll() 函数 事件循环核心
socket_server_create() 函数 一些初始化工作 epoll_fd pipe_fd 应用层socket池等初始化工作
// epoll关注管道读端的可读事件
if (sp_add(efd, fd[0], NULL)) {
// add recvctrl_fd to event poll
fprintf(stderr, "socket-server: can't add server fd to event pool.\n");
close(fd[0]);
close(fd[1]);
sp_release(efd);
return NULL;
}
在我现在这个版本中这段代码还是存在的 这里将管道的读端也加入到epoll来管理 一方面历史版本是这样管理的 实际上这里留下来也是可以的方面在一些程序中将管道加入到epoll来管理 由于在socket_server_poll()函数中是优先处理命令的即先select关注管道的读事件 所以到后面epoll再去关注时 实际上select已经处理了 所以不会有什么问题
socket_server_release() 函数 释放资源 关闭申请的fd 释放应用层的socket
socket_server_listen()
socket_server_bind()
socket_server_start()
socket_server_exit()
socket_server_close()
socket_server_exit()
socket_server_send()
socket_server_connect() 这几个函数调用send_request()函数来向管道的写端写入命令
写入的格式 请求包的[6-7]写入管道的写端
request->header[6] = (uint8_t)type; request->header[7] = (uint8_t)len; for (;;) { int n = write(ss->sendctrl_fd, &request->header[6], len+2); // 向管道的写端写入数据
在关闭的时候 处理不够完善 因为第二次发送的时候直接关闭了socket 没有继续发送完毕 所以skynet不适合大流量的网络应用