skynet 的 C API 采用异步读写,你可以使用 C 调用,监听一个端口,或发起一个 TCP 连接。但具体的操作结果要等待 skynet 的事件回调。skynet 会把结果以 PTYPE_SOCKET 类型的消息发送给发起请求的服务。(参考skynet_socket.h)

在处理实际业务中,这样的 API 很难使用,所以又提供了一组阻塞模式的 lua API 用于 TCP socket 的读写。它是对 C API 的封装。

所谓阻塞模式,实际上是利用了 lua 的 coroutine 机制。当你调用 socket api 时,服务有可能被挂起(时间片被让给其他业务处理),待结果通过 socket 消息返回,coroutine 将延续执行。

 

SocketChannel


请求回应模式是和外部服务交互时所用到的最常用模式之一。通常的协议设计方式有两种。

  1. 每个请求包对应一个回应包,由 TCP 协议保证时序。redis 的协议就是一个典型。每个 redis 请求都必须有一个回应,但不必收到回应才可以发送下一个请求。
  2. 发起每个请求时带一个唯一 session 标识,在发送回应时,带上这个标识。这样设计可以不要求每个请求都一定要有回应,且不必遵循先提出的请求先回应的时序。MongoDB 的通讯协议就是这样设计的。

对于第一种模式,用 skynet 的 Socket API 很容易实现,但如果在一个 coroutine 中读写一个 socket 的话,由于读的过程是阻塞的,这会导致吞吐量下降(前一个回应没有收到时,无法发送下一个请求)。

对于第二种模式,需要用 skynet.fork 开启一个新线程来收取回应包,并自行和请求对应起来,实现比较繁琐。

所以、skynet 提供了一个更高层的封装:socket channel 。

 

关于 socket channel 的具体用法除了阅读 lualib/socketchannel.lua (同时这也是理解 socket 模块的好材料)的实现外,也可以阅读 lualib/redis.lua 和 lualib/mongo.lua 这两个为 skynet 编写的数据库 driver 。

 


EPOLL封装层


./skynet-src/socket_poll.h

网络服务模块通常会有一个大的循环来读取网络消息,skynet也不例外,socket_server_poll函数就是来干这事的。在这个循环中将会有两个不同来源的消息系统,一个是管道消息,另一个则是网络消息了。管道消息后面会提到。网络消息是通过epoll模型的epoll_wait来读取的,采用默认的水平触发模式,这样连续读取数据较为简单。

 

运行流程:

  1. 在skynet_start() 中 调用 skynet_socket_init() 初始化socket服务
  2. 每个socket 服务都有 写缓存队列,所以 框架会异步的实现读写。
  3. socket 的open close listen apect 等操作是通过给 socket_server 的管道写入请求信息,在server_poll循环中再去处理他。
  4. socket 在发送数据时 会尝试的直接发送数据!如果不能直接发送数据 才会把数据写入 socket 对应的写缓存 。

 

socket框架 unity socket框架和skynet_API