skynet源码剖析02 socket-server下 具体实现

主要结构

// 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结构中字段实际上就对应下面这张事件循环的图

skynet源码剖析02 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不适合大流量的网络应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值