Posted on 2011-11-20 22:35
冷锋 阅读(2927)
评论(11) 编辑 收藏 引用 所属分类:
linux
最近想写个多线程模型的服务器,但是一直纠结要选哪种方式,参考了memcached,但是觉得不完美.
备选方案,当然这些都是NIO
1.一个IO线程,专门处理连接读写数据,一个逻辑线程,专门处理数据。
2.一个IO线程,一个线程池,可任意配置线程池的数量。
先考虑1,假设连接层用epoll的ET模式实现,当IO线程发生可读事件时,必须把接收缓冲区的数据全部收完,一直read直到发生EAGAIN错误,否则就必须自己在上层维护一个可读队列。
方法a:每收到一个协议包,就转发给逻辑处理.处理完再接着收取剩下的包。但是TCP是无边界的,有可能收到0.5个或者2.5个包,这时你得为每个连接准备个buf,并且每收到个包都要跟逻辑线程同步加锁一次,还有个很大的弊端就是,连接层已经跟逻辑协议相关了,这似乎不是很好。
方法b:IO线程把所有数据都收完再通知逻辑线程。当然这样也无法避免收到半个协议包的情况,所以我是想维护一个recv到的数据队列,IO线程把每次收到的包都丢到这个队列。
a,b2种方法都面临着send的麻烦,现在send也有以下方案.
方法a.每次需要write的时候就直接send发不完的就加到发送队列中去。在发送前必须要先判断一下队列是不是空,如果不为空就必须先处理队列,剩余数据待可读事件发生时再处理, 于是脑子中又出现一大堆锁了。有2个线程可能对socket进行写操作。
方法b.每次需要send的时候都直接把数据丢到发送队列去,等到可读事件到来时再尽量把队列都发送完。但是响应可能没那么迅速了。
这两种方法都必须为每个数据包增加一个标识TCP连接的字段,因为socket fd是可以重复使用的,比如用户A连接分配的socket是100,逻辑线程正在为A处理数据,但是用户A断开连接了,同时立即有另外个用户连接进来并且分配的socket是100,这时悲剧就发生了。
再考虑下线程池的2种情况.
a.主线程负责IO,包括处理连接,读写,把读到的数据加入recv队列,子线程把需要写的加入send队列.
这个方案有个很大的缺陷:无法保证先到的请求先返回,这是很致命的,只能通过客户端来确保收到前一个请求的结果以后再发送下一个请求。
b.主线程只负责监听连接,收到连接到来事件后,通知线程池去accept连接,因此每个连接的数据都会由同一个线程处理,也就保证了顺序,但是这样的话子线程就必须承担起IO的任务了,这样好像就有些分工不清了,这个是memcached中用的方案.
PS:用epoll的ET模式需要一直recv,这样有可能是使得活跃的连接占用了全部带宽,因此需要在上层对连接进行限速,因此也就需要维护可读事件了。
纠结好几天了,第一次写多线程服务器,一直为选方案纠结啊,我本人倾向于线程池,毕竟可以利用多核的优势啊。不知道大家实际上用的都是什么方案呢,非常想知道,请指教。
再PS:做了个小测试,nginx+memcached,下载一个700多K的文件,开100个线程,每个线程下载100次,全部命中跟全部不命中的情况几乎没有差别,这是为什么呢?难道linux系统本身就已经有cached了吗?