IOCP-Tips
這個問題是這樣,如果在接收端(WSARecv)投遞了一個200字節的WSABUF,那麽你的工作器線程處理完成通知的時候,首先收到第一個200字節,此時你可以繼續投遞接收下來的200個字節(注意了,是得到上一個200字節通知并保存處理了才投遞下一個)這樣才能保證你的接收和處理1000個字節不亂。如果你一開始就投遞(而不是投遞一個之後,在得到通知處理之後依次投遞)了5個WSARecv,對應5個單IO操作數據,會有5個通知在隊列,當然在隊列裏這5個完成包順序是正确的。問題在于,你有多個工作器線程來分别處理這些通知,那麽雖然隊列裏是正确的,但你的工作器線程在處理通知和保存數據的時候,保存這些數據有可能出現亂序。根源在于投遞WSARecv的方式。解決方案雖然有,就是對發送的包加上編号header,在工作器線程中保存200字節的時候根據編号來處理.
1。IOCP一般只用于“响应socket的recv消息”,发送数据时,其线程载体未必是IOCP线程,建议使用其它线程池的线程(IOCP本质上就是1个线程池,速度稍微快一点而已)来处理业务逻辑;
2。IOCP照样可用于“长连接socket”,无论是“大数量的并发访问”还是“长时间的事务处理”,只要在收到数据之后,把数据投递到另外1个线程池里处理,而IOCP就立即返回了,不会造成任何socket层面的延迟和阻塞(即:转换处理事务的线程载体)。本人在win2k下测试过最高4000个并发连接(P2-350,28M);
3。我在项目里写的服务器,用3个线程池:第1线程池是IOCP,处理recv消息;第2线程池是普通线程池,处理程序事务;第3线程池是普通线程池,用来send网络数据;
--------------
我一般把你第三个线程池和第一个重合起来,这样效率会更高。
我来个总结:
1,我会创建cpu*4的线程数处理,res,and send.
2.我会用cpu*8的线程数处理所有事务。
同时post cpu*4个accept,这样同时最大的并行accept就会是cpu*4;
对于每个accpte的clinet post 1 read
接收的情况:
当clienta收到一个完成通知的时候,处理buff,把结果(list)放到client结点中,post event(注意给clienta ->addref())给2,2派出一个idle thread to 处理clienta ,clienta->release().
N个client的时候,情况类是。
发送情况。
在2中业务和暴风雨一样复杂,而对于每个结点来说,都是single thread的,如果2没有idle的线程,对于和结点clienta一样的其他结点来说,他们的包结果来不级处理的时候,会list到自己的结点上。直到2有idle的线程来处理。但1中的线程还在快乐的接收的自己数据。
当2中某个thread的接受到一个命的时候,比如要发送一个数据,2 post event to 1,1从clienta中的list取出一个待发包,post write.这样可以避免多个线程对同一个socket post wirte,大家都知道如果一个socket 在pending状态时,不能对同一个overlay发起两个post write.我们之所以用一个Overlay而不用多个,是为了程序着想,不需要太复杂。因为对于同一个包,有可能需要post 多次。这样,我们可以通过结点的记录上次发送了多少byte.从而正确的从上次的offsize post.
业务处理上:
我一般不会把分析包的fuction放在io处理线程中,也就是iocp中,我会暴路一个接口类,比如一个pure class.这样做,可以很好的把类分离,并且可以应用到不同的应用中。
从上面的分析可以看出,真正的执行者肯定在2中,当2有一个包分析完成的时候,我会post一个event给2,也就是自己发事件给自己。让2分配出另一个线程来处理业务,而原来的线程可以继续分析包。2->2的情况,也不是一概而论,看具体的业务。但这样做,就可以比较高效了。
我现在的处理方式上面,与楼上的有点不一样.线程数量上面我不会固定,也就是不受CPU的多少而限制.但是会给出一个同步运行线程数参考值.比如CPU*2.但是实际运作过程当中会由于有多个网络事件到来,而可能这个值会被耗光,也就是在某个时间里没有线程处于GetQueuedCompletionStatue阻赛来处理下一个网络事件,此时我就会考虑再添加一个例外线程,来做等待.由于多出了一个例外线程,所以可能已经超过允许同时运行的线程数量参考值,则哪一个线程完成任务后先做值检测,若超出则看是处于阻塞状态的线程数是否达到两个,若是则自行结束(这时候会带一个麻烦事情就是,该线程的退出会引发由其发出的I/O请求被取消,也会有一个网络事件).
不过我发送时是直接使用WSASend拉交,也就是说可能会有两个线程同时工作于一个连接之上.只是当有执行WSASend时,Sending计数加1,而下一轮需要发送时,先检测Sending计数.如果有Sending,则直接把数据包附加到Sending队列,如此也可以保证数据的顺序性,同时不需要PostQueuedCompletionStatus,少一个切换过程,并且实现真正意义上的重叠IO,对于处理发送完成事件的线程,则可以一直发送WSASend到没有待发数据为止,每一次完成通知则扣减一个完成计数,每提交一次WSASend则增加一个计数.
由于有多个线程引用一个连接的上下文信息,如果在删除时直接删除,则会报错,所以在需要设置删除前,先置删除标识,所有线程遇到该标识将不再发送WSASend/WSARecv,另由一个检测线程来对没有(发送和接收)计数的数据进行清理(记得closesocket先,以阻止所有后来的网络事件,在下一次检测过程当中清理为其分配的资源,并删除).
检测线程主要有两项工作,一是清理垃圾,二是针对不良连接,也就是在一段时间内没有任何I/O操作的连接进行清理,也就是说如果是自己的客户端则最好是在没有数据交互的情况下发送时脉信息,对于部分需要验证的服务,也可以在此对超时未进行身份验证的连接清理掉.
从我个人做过的项目中讲,一个连接就应该同时只会有1个r + 1个s 被投递,否则出现工作线程恰逢时间片切换(多核CPU上尤其小心),网络粘包半包时,逻辑协议包的解析上极为困难。
所谓广播消息,往往只是部分广播(例如区域同步),这个由逻辑层决定需要对哪些session进行消息发送就行了,socket层要做的只是给指定socket发送消息。
无论r/s,都应该有自己的缓冲队列,r的用于处理粘包半包,s的用于流量缓冲控制。
此外,在我自己的应用中,网络模块(独立进程)收到完整数据(能成功解析出逻辑包),再使用namedpipe(也用IOCP)转发给具体工作进程,反向流程依然