游戏服务器开发为什么很少使用c#?

游戏服务器开发为什么很少使用c#?,第1张

对这一行不熟悉不敢乱判断。猜测一下可能的原因:

如果用windows当服务器,IOCP很成熟所以选择C++

C#本身带有内存回收机制,对于某些类型的服务器需要自己管理内存回收

技术上没问题,听说过用C#当网页游戏服务器的成功案例

用C#的成本在这一行不算低(综合服务器,开发效率,招人难度等)

现成有许多成熟的公司框架不需要自己重新写,大家跳跳槽也都有了……

设计 mmo 服务器,我听过许多老生常谈,说起处理大量连接时, select 是多么低效。我们应该换用 iocp (windows), kqueue(freebsd), 或是 epoll(Linux) 。的确,处理大量的连接的读写,select 是够低效的。因为 kernel 每次都要对 select 传入的一组 socket 号做轮询,那次在上海,以陈榕的说法讲,这叫鬼子进村策略。一遍遍的询问“鬼子进村了吗?”,“鬼子进村了吗?”… 大量的 cpu 时间都耗了进去。(更过分的是在 windows 上,还有个万恶的 64 限制。)

使用 kqueue 这些,变成了派一些个人去站岗,鬼子来了就可以拿到通知,效率自然高了许多。不过最近我在反思,真的需要以这些为基础搭建服务器吗?

刚形成的一个思路是这样的:

我们把处理外部连接和处理游戏逻辑分摊到两个服务器上处理,为了后文容易表述,暂时不太严谨的把前者称为连接服务器,后者叫做逻辑服务器。

连接服务器做的事情可以非常简单,只是把多个连接上的数据汇集到一起。假设同时连接总数不超过 65536 个,我们只需要把每个连接上的数据包加上一个两字节的数据头就可以表识出来。这个连接服务器再通过单个连接和逻辑服务器通讯就够了。

那么连接服务器尽可以用最高效的方式处理数据,它的逻辑却很简单,代码量非常的小。而逻辑服务器只有一个外部连接,无论用什么方式处理都不会慢了。

进一步,我们可以把这个方法扩展开。假定我们逻辑以 10Hz 的频率处理逻辑。我们就让连接服务器以 10Hz 的脉冲把汇总的数据周期性的发送过去,先发一个长度信息再发数据包。即使一个脉冲没有外部数据,也严格保证至少发一个 0 的长度信息。额外的,连接服务器还需要控制每个脉冲的数据总流量,不至于一次发送数据超过逻辑服务器处理的能力。

那么,逻辑服务器甚至可以用阻塞方式调用 recv 收取这些数据,连 select 也省了。至于数据真的是否会被接收方阻塞,就由连接服务器的逻辑保证了。

说到阻塞接收,我跟一个同事讨论的时候,他严重担心这个的可靠性,不希望因为意外把逻辑服务器挂在一个 system call 上。他列举了许多可能发生的意外情况,不过我个人是不太担心的,原因不想在这里多解释。当然我这样设计,主要不是为了节省一个 select 的调用,而是希望方便调试。(当然,如果事实证明这样不可行,修改方案也很容易)

因为阻塞接收可以保证逻辑服务器的严格时序性,当我们把两个服务器中的通讯记录下来,以后可以用这些数据完全重现游戏逻辑的过程,无论怎么调试运行,都可以保证逻辑服务器的行为是可以完全重现的。即,每 0.1s 接受已知的数据包,然后处理它们。

这样做,逻辑服务器对网络层的代码量的需求也大大减少了,可以更专心的构建逻辑。

转自云风blog:http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html

Cloud Wu blog评论:

1.我们现在的游戏古剑OL的后端架构跟云风大大说的一样,甚至逻辑服务器之间通信也要经过这个所谓的“连接服务器”(现在应该都叫gateway了),不过从客户端到服务端,各个组件之间通信都是事件驱动异步的(主程封装了iocp/epoll),其实我觉得后端组件之间绝逼是在一个高速网域内,其间的延时相对于复杂的异步逻辑的代价来讲真的可以忽略不计。

而且现在很多都是分布式架构,单纯依赖单一节点的高性能没太大的意义,不如将节点的代码逻辑变得简单一点,专注于提供系统业务的分布能力和健壮性。

2.我公司也是样这架构的服务器,用libevent写的连接服务器,客户端和逻辑服务端的网络代码是同一套代码,都是用select实现的,现在发现select没必要了,直接阻塞也行的。反正是逻辑服务器单进程的

在学习网络的过程中,参考一些资料和自己的想法,

考虑的方案:

客户端打包发送,不使用SO_RCVBUF选项,

启用TCP_NODELAY选项,Nagle算法禁用。

使用Nagle算法,通过TCP发送的数据不会马上被发送,而是等待一段时间,当数据包大小达到缓冲区的大小再一次发送,这样就可能导致粘包现象。

(如果数据包长时间未达到缓冲区大小,是不是应该有个时间限制吧,猜测而已,不了解Nagle算法)此方法一般试用于每次发送数据比较小而且比较频繁的情况,

使用此方案,需要控制发送包的大小,如果用来做服务器,特别是游戏服务器,一般发包也不会太大,而且交互次数较频繁,效率比较低(使用Nagle算法的优势可以看到),但每个包的大小固定,至少发送数据包的时候,不会造成粘包。

而且不使用接收缓冲区,数据包发送后马上接收,也不会有几个包黏在一起,不用考虑粘包的问题。

一般看到的方案是,使用SO_RCVBUF,TCP_NODELAY不使用,发包的时候带个包头,发包进行打包处理,接包的时候根据包长度进行粘包处理。

IOCP发包重构问题

方案1

安排序列号,在每个ClientContext上两个序号,一个是某个套接字发包的序号(序号1),从接收到第一个包开始计数;另一个是套接字上读包的序号(序号2),这个包是计算当前读包的序列,从此套接字的第一个包开始读,依次递增;

同时,每个包也有一个编号(编号3),在投递WSARecv的时候,将编号1的值赋予编号3。

当读包的时候,对某个套接字上的包的序号(编号2)与将要读的包的序号(编号3)进行比较,

如果相等,则此包就是即将被读取的包;否则,包没有按顺序接收,将包放到ClientContext的未正确接收缓冲区包中,并对未正确接收缓冲区包按编号2进行排序,从未正确接收缓冲区包的第一个包开始读。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/307882.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-28
下一篇2023-04-28

发表评论

登录后才能评论

评论列表(0条)

    保存