随笔 - 119  文章 - 290  trackbacks - 0

博客搬家了哦,请移步
叫我abc

常用链接

留言簿(12)

随笔分类

我的博客

搜索

  •  

积分与排名

  • 积分 - 302262
  • 排名 - 84

最新评论

阅读排行榜

我一直坚信,如果不是处理大规模客户端连接,是不需要使用epoll和IOCP的。我倾向于简单的东西,所以我一直用着select。
一直以来,我的网络程序结构就是在每一帧的开始select,有什么消息就处理一下,然后跑程序的主逻辑。我觉得这个结构挺好,单线程,简单、明了、优雅。
不过最近有头儿告诉我,这个事情虽然可以,但是感觉上不太对头,网络组件的工作应该是独立的,不可以占用主逻辑的时间。
好吧,我改成多线程就是了。一个主线程,负责处理客户端消息和运算主逻辑;一个网络线程,负责从网络上读取数据和将数据发送到网络上,基本上就是select,recv,send三调用。

这里出现了第一个问题:主线程和网络线程之间如何进行数据交换?主线程中待发送的数据需要交给网络线程做实际的发送,网络线程接收到的数据需要交给主线程处理。
基于尽可能短的lock-time这一原则,我给主线程和网络线程各分配了一个完全一样的容器,这个容器由各线程独自享有,容纳发送和接收的数据。
然后在特定的时候,lock主其中一个容器,进行数据拷贝即可——将欲发送的数据从主线程容器拷贝到网络线程的容器,将收到的数据从网络线程的容器拷贝到主线程的容器。
这一lock只有拷贝工作,时间上应该是十分短暂的。

第二个问题:由谁负责这个拷贝,主线程还是网络线程?负责拷贝的线程,必然去lock另一个线程的容器。
我选择了主线程负责拷贝操作。在每帧的开始,锁住网络线程的容器,将它收到的数据拷出来,将要发送的数据拷进去,解锁,然后处理收到的消息。网络线程则需要在操作自己的容器的时候加锁。
好处是,主线程的 send_packet 操作不需要加锁,并且收到的数据是拷出来就消耗掉。
顺便也想想网络线程负责拷贝的情形,在select之前,锁住主线程的容器,将欲发送的数据拷进,解锁;然后是select,recv,send;然后再次锁住主线程的容器,将收到的数据拷出。相应的,主线程需要在操作自己容器的时候加锁。
看起来我并不想主线程在一帧内有太多次的加锁解锁操作,因此就选择了第一个方案。

至此,程序跑起来了。不过出现了一个单线程所没有的新问题——CPU占用率太高了。
原因应该是,select能挂起程序,所以单线程的时候,程序多多少少总会有挂起的机会;但是多线程以后,主线程就跟while ( true )差不多,浪费了太多的资源。
因此,让主线程在每帧也睡一会就好了。游戏的主逻辑是限帧的,一般每秒25帧,称逻辑帧。但是处理网络消息不是限帧的,而是希望能尽可能快的处理他们,因此处理网络消息是在实际帧中进行的。
通常游戏主逻辑的一次tick并不能完全消耗掉一个逻辑帧的时间,因此让主线程在逻辑帧剩下的时间里睡上一觉就好。

第三个问题是:如何让主线程在剩下的逻辑帧时间里挂起,并在有网络消息的时候立即激活?
信号/EVENT——主线程在进行容器的数据拷贝之前,如果自己没有欲发送的数据,则等待信号,等待的时间是上一个逻辑帧所剩余的时间。相应的网络线程中,如果收到新的数据,则激活这个信号,那么主线程会被立即唤醒。
等待超时或者被唤醒后,就会执行数据拷贝和消息处理。这样,既实现了sleep,又兼顾了即时反应能力。

编译运行,程序看起来挺稳定,CPU占用率为0.。。。。。。新项目,逻辑上几乎啥都没有呢。
posted on 2009-01-03 16:54 LOGOS 阅读(7934) 评论(8)  编辑 收藏 引用

FeedBack:
# re: 多线程下的select网络程序结构[未登录] 2009-01-03 17:21 关中刀客
我的做法就是:
底层select线程不停的接受数据,插入到缓冲中,上层的单逻辑线程一桢取一次数据,然后全部的处理。  回复  更多评论
  
# re: 多线程下的select网络程序结构[未登录] 2009-01-03 17:30 关中刀客
对了,在说以下,LZ可以把发送数据,在每一桢里面都写入到逻辑主线程里的一个内存块,在每一桢结束的时候使用逻辑主线程来发送,不需要再交给底层的select线程了。底层就只需要管理接受九可以了。  回复  更多评论
  
# re: 多线程下的select网络程序结构 2009-01-03 20:57 LOGOS
@关中刀客
回复1就是我想表达的
回复2,由主线程在每帧结束的时候执行真正的发送——socket对象是被包装过的,sendbuf并不是可重入的。基本上就是,我所使用的socketlib无法便利的做到这点  回复  更多评论
  
# re: 多线程下的select网络程序结构[未登录] 2009-01-03 21:40 true
我也在做多线程select,不同的是,接受数据也是多线程的select,因为单个select的连接数受限,所以,是一个单独的select线程,accept连接,然后交个多个select线程处理接收数据,至于发送是和接收分开处理的,也是一个select+一个队列的发送模式。多个接收线程将收到的数据放到同一个队列,这里当然少不了lock,队列的数据如果交给 上层逻辑,则比较灵活,可以一次复制整个队列,也可以是一次一个消息处理。  回复  更多评论
  
# re: 多线程下的select网络程序结构[未登录] 2009-01-03 21:45 true
另外,粗略看过glibc,及内核的epoll的源代码,epoll内部实现比较复杂,而且加锁次数,及加锁层次较多,与select的简单特性相比,感觉优势不大  回复  更多评论
  
# re: 多线程下的select网络程序结构 2009-01-04 09:44 zuhd
epoll的优势是轮询的效率高,会忽略闲置的fd,select不会。不管什么网络模型,感觉网络事件和逻辑处理都是两个线程的,而且只需两个线程。我比较同意1楼的想法,感觉很成熟。  回复  更多评论
  
# re: 多线程下的select网络程序结构 2009-01-04 14:04 LOGOS
@true
我觉得如果多个线程做select,连接的数量规模较大的话,还是使用epoll或者IOCP好一点
如zuhd所说,内核处理的代码总是相当稳定和有效率的  回复  更多评论
  
# re: 多线程下的select网络程序结构 2009-01-05 09:18 honghui
我一般是用pthread_cond_t 配合线程锁 来解决的,当没有数据等待处理时,主线程释放锁并且在pthread_cond_t上睡眠,直到子线程拿到数据并且将数据加入到数据结构中后,释放锁并唤醒主线程  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理