skynet网络基础剖析

千呼万唤始出来,终于有时间开始分析skynet网络部分的代码了。skynet是个网络服务器框架,网络才是他的核心所在,读懂了网络模块的代码才算是对skynet有深入的了解。

花了两天时间读了网络部分的代码,底层网络模型是基于epoll的,这个在llinux高并发上是最高效的模型。底层网络的各种操作或响应(诸如accept,msg,close)也是通过发送消息告知lua层的回调函数,这点与前面讲的服务之间通信统一起来了。

网络服务这块有个很重要的数据结构,socket_server,他的定义如下:

struct socket_server {
	int recvctrl_fd;
	int sendctrl_fd;
	int checkctrl;
	poll_fd event_fd;
	int alloc_id;
	int event_n;
	int event_index;
	struct socket_object_interface soi;
	struct event ev[MAX_EVENT];
	struct socket slot[MAX_SOCKET];
	char buffer[MAX_INFO];
	uint8_t udpbuffer[MAX_UDP_PACKAGE];
	fd_set rfds;
}

比较重要的有epoll句柄event_fd,管道相关的recvctrl_fd,sendctrl_fd,socket池等等。

他的一个对象SOCKET_SERVER,始终贯穿所有网络服务。他是在进程开始,初始化各个模块时创建的。

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

由于网络部分比较复杂,我通过一个小示例来说明一个典型的网络通信是如何开展的,为了简化消息过程,我直接在bootstrap里面写了代码,只是简单的监听某个端口号,然后等待连接和接收数据,代码如下:

local skynet = require "skynet"
local socket = require "socket"

skynet.start(function ()
    local id = socket.listen('127.0.0.1', 8876)

    socket.start(id, function(id, addr)
            print('connnect from '..addr..' '..id)
            socket.start(id)
            local ret = socket.read(id)
        end
        )
end)

socket.listen会调用c层的listen函数,c层要做的是绑定并监听了端口号,然后发送了一个监听相关的消息。消息发送给谁,发送的什么内容?

消息发送给管道的一端,即socket_server的sendctrl_fd结构体。什么是管道呢?管道有两端,往一头发送消息,另一头则可以获取消息。为什么要发送给管道呢?为了统一接收和管理,在网络消息循环socket_server_poll函数中有对这个管道消息的读取。读取分为两步,首先读取类型,然后读取数据,例如上面的listen,他会发送type为'L',数据包含listen_fd,socket的唯一标识id等数据。解析这个数据会创建一个新的socket 文件描述符。说新建也不是很准确,因为他是从socket_server里的socket 结构体池中取出的,类似于线程池,内存池等等,都是为了效率或方便统一管理。

这个socket结构体有哪些字段?文件描述符字段fd肯定是有的,还有一个id字段,用于lua层的的标识,所以这个id是全局唯一的,他通过reserve_id(socket_server) 递增id获得。还有type字段,用来表明这个socket的状态,如监听状态,已连接状态。

此时,要监听socket的type为SOCKET_TYPE_PLISTEN(2),然后返回那个唯一标识id给lua层。lua层必须调用socket.start开启这个监听socket才能继续后面的网络服务。lua层的socket.start()又是调用c层start()。类似于listen,他也给管道发送了相应的消息数据,包括id,通过这个可以获得其对应的socket。我们可以在start函数里设置一个回调函数,这个回调函数是与id号相关联的。然后当前协程挂起,为什么挂起?因为他在等要监听的socket变为监听状态。

在socket_server_poll消息循环中,管道收到start这个消息后,将会调用start_socket(),他会给这个socket注册epoll事件,目前只有读事件。相当于就把这个socket纳入epoll的监听中了。此时的socket type变为SOCKET_TYPE_LISTEN(3),也就是进入真正的监听状态。socket_server_poll返回SOCKET_OPEN(2),这个时候给消息队列发送一个SKYNET_SOCKET_TYPE_CONNECT(2)的消息,连接状态变为true,lua层收到之后就会恢复之前的协程。

我们用一个客户端(随便找一个tcp调试工具,或者用python写简单的连接即可)连接这个socket,epoll_wait会返回数据。由于之前socket type为SOCKET_TYPE_LISTEN(3),那么他会调用report_accept。在这里就会正在的调用socket api accept,返回一个新的socket fd。根据这个新的fd再从SOCKET_SERVER的socket池中获得一个socket对象,产生一个新的全局唯一id,将这个socket设为非阻塞模式等等操作。这个新的socket type为SOCKET_TYPE_PACCEPT(7)。socket_server_poll返回SOCKET_ACCEPT(3),这样给消息队列发送一条SKYNET_SOCKET_TYPE_ACCEPT(4)的消息,该消息中包含了新连接socket的id号。注意是针对监听socket关联的消息队列,而不是新的socket。

lua层收到SKYNET_SOCKET_TYPE_ACCEPT(4)的消息之后,会根据id号找到之前关联的回调函数,去执行。我们看到在这个回调函数中我们继续start(new id ),过程与上面的start监听socket一样,作用是把新连接的socket加入到epoll读的监控之中。我们注意到start函数给队列发送SKYNET_SOCKET_TYPE_CONNECT(2)这个消息,连接状态变为true。这里的连接是指socket加入epoll的监控之中,而不是客服端连接的意思,因为这个适用于那个监听的socket。

之后就是读数据。客服端发送数据,我们正确的读取了数据。关于数据的发送和读取也是比较复杂,我们下篇再讲。

总结一下,为什么listen的过程有点小复杂,曲折,还需要发送'L'指令,然后经lua层的start调用才真正开始监听。我的理由是,方便lua层的逻辑业务编写。因为脚本语言适合回调而不适合自上而下的函数调用,也可以把第一个socket.start理解为socket.accept(id, func)。socket的绑定,监听,accept等待都是lua层发送指令来驱动的,c底层不会主动去干这些事情。建立连接后才会主动接收数据,并保存在缓存中。

短短的一篇文章,差不到写了快一整天,剖析原理实在是辛苦,因为每个细节都要去推敲,保证正确性。希望我能坚持,继续加油。

 

欢迎加入QQ群 858791125 讨论sknet,游戏后台开发,lua脚本语言等问题。

 

 

 

 

 

 

 

<think>好的,用户现在问的是关于Skynet框架的基础信息。我需要先回顾之前的对话历史,用户之前询问过如何使用Skynet开发MMORPG服务器,现在可能想更深入了解框架本身。 首先,用户可能是一个刚开始接触Skynet的开发者,或者对游戏服务器开发感兴趣,尤其是MMORPG类型。之前的问题涉及部署和项目结构,现在转向基础信息,可能是为了打好基础,理解框架的核心概念,以便更好地进行开发。 接下来,我需要整理Skynet的基础信息,包括定义、核心特性、架构、适用场景等。用户可能需要这些信息来评估Skynet是否适合他们的项目,或者作为学习资料。 要注意用户的背景,可能对技术术语不太熟悉,所以需要用简洁易懂的语言解释。比如,解释Actor模型时,可以说明每个服务是独立的,通过消息传递通信,这样有助于并发处理。 另外,用户之前提到在Windows系统部署,可能需要强调Skynet的跨平台支持,但也要指出Linux在生产环境更常见,可能潜在的问题需要提前说明。 还要考虑用户可能关心Skynet的学习资源和社区支持,所以需要提供官方文档和开源项目链接,帮助他们进一步学习。同时,指出优缺点,让用户有全面的了解,比如单线程的轻量级适合高并发,但复杂计算可能需要额外处理。 最后,总结部分要强调Skynet在游戏服务器开发中的优势,以及适合的场景,帮助用户判断是否选择该框架。可能需要提醒用户,如果是新手,学习曲线可能较陡,但社区资源丰富,可以逐步掌握。</think>以下是关于 **Skynet框架** 的基础信息介绍,涵盖其核心设计、功能特性及适用场景: --- ### **一、Skynet是什么?** - **定位**: 轻量级、高性能的分布式服务框架,专为在线游戏、实时通信等高并发场景设计。 - **作者**: 中国开发者[云风(吴云洋)](https://blog.codingnow.com/),原网易游戏资深架构师。 - **语言**: 底层使用C实现,逻辑层支持Lua脚本(默认开发语言)。 - **开源地址**: [GitHub - cloudwu/skynet](https://github.com/cloudwu/skynet) --- ### **二、核心特性** 1. **Actor模型驱动** - 基于消息传递的异步并发模型,每个服务(Service)独立运行,通过消息队列通信。 - 天然支持分布式部署,服务可跨进程或跨机器调度。 2. **轻量级单线程** - 单进程内通过协程(Coroutine)实现高并发,避免多线程锁竞争问题。 - 事件驱动机制(Epoll/kqueue),支持数万级并发连接。 3. **模块化设计** - 提供基础服务框架(如网络、数据库、定时器),开发者按需扩展。 - 支持热更新(Hotfix),无需停机即可修复逻辑。 4. **跨平台支持** - 原生支持Linux/macOS,Windows需通过MinGW编译(部分功能受限)。 --- ### **三、核心组件** 1. **服务(Service)** - 最小执行单元,每个服务有独立的消息队列和Lua虚拟机。 - 通过`skynet.newservice`动态创建,支持服务间消息(`skynet.send`/`skynet.call`)。 2. **网络库(socket)** - 提供TCP/UDP通信封装,支持异步非阻塞IO。 - 示例:监听端口、处理客户端连接、收发数据包。 3. **数据库集成** - 支持MySQL、Redis等数据库,通过第三方模块(如`skynet-mysql`)实现异步操作。 4. **定时器(timer)** - 毫秒级精度定时任务,用于心跳检测、状态刷新等场景。 --- ### **四、架构示意图** ``` +---------------------+ | Skynet进程 | +---------------------+ | 服务A | 服务B | 服务C | ← 消息队列通信 +--+ | Lua虚拟机环境 | +---------------------+ ``` --- ### **五、适用场景** 1. **游戏服务器** - MMORPG、SLG、MOBA等类型的战斗/逻辑服务器。 2. **实时通信** - 即时聊天、直播弹幕、物联网设备消息转发。 3. **微服务架构** - 高并发业务模块的拆分与协作(如电商秒杀系统)。 --- ### **六、优缺点分析** | **优点** | **缺点** | |--| | 轻量级,资源占用低 | Windows支持较弱,建议优先Linux | | 适合高并发实时场景 | 学习曲线较陡(需熟悉Actor模型)| | 社区活跃,文档完善 | 原生工具链较少,需自研中间件 | --- ### **七、学习资源推荐** 1. **官方文档**: [Skynet Wiki](https://github.com/cloudwu/skynet/wiki) 2. **书籍**: 《Skynet框架设计与实现》(云风著) 3. **开源案例**: - [TinyWorld](https://github.com/cloudwu/skynet_tinyworld)(官方Demo) - [SKYNET-MMO](https://github.com/fztcjjl/skynet-mmo)(MMO框架示例) --- ### **总结** Skynet凭借其高效的Actor模型和轻量化设计,特别适合需要高并发、低延迟的实时服务端开发。若您计划开发类似《三界奇缘》的MMORPG,可通过其服务化架构快速实现分布式逻辑(如登录、战斗、场景服务分离),但需注意Windows环境下调试可能受限,建议生产环境部署Linux。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值