传统的Server/Client实现是基于Thread per request,即服务器为每个客户端请求建立一个线程处理,单独负责处理一个客户的请求。

大多数的网络游戏的服务器都会选择非阻塞select这种结构,为什么呢?因为网络游戏的服务器需要处理的连接非常之多,并且大部分会选择在Linux/Unix下运行,那么为每个用户开一个线程实际上是很不划算的,一方面因为在Linux/Unix下的线程是用进程这么一个概念模拟出来的,比较消耗系统资源,另外除了I/O之外,每个线程基本上没有什么多余的需要并行的任务,而且网络游戏是互交性非常强的,所以线程间的同步会成为很麻烦的问题。由此一来,对于这种含有大量网络连接的单线程服务器,用阻塞显然是不现实的。
iocp,在linux下使用epoll


关于线程是这样的,肯定不可能一个用户一个线程的,没见过那么做的,通常我们是这样的,我们创建几个线程分别用于发送和接收网络消息,当然数量也不是太多,通常是CPU个数的2倍,然后另外建立一个逻辑线程,所有的网络线程接收到的数据都会打入这个逻辑线程,以保证逻辑处理中的顺序处理.  不知道你是否理解.   另外一个小提示,QQGAME可不是一个进程就500个连接,通常他一个进程都会达到20000左右的连接数.

NIO服务器最核心的一点就是反应器模式:当有感兴趣的事件发生的,就通知对应的事件处理器去处理这个事件,如果没有,则不处理。所以使用一个线程做轮询就可以了。当然这里这是个例子,如果要获得更高性能,可以使用少量的线程,一个负责接收请求,其他的负责处理请求,特别是对于多CPU时效率会更高。


JDK 7,WEB服务器 Tomcat、Jetty等,在Windows下,Java将可以使用IOCP,而不是现在nio所用的select,网络并发性能将会得到大幅度提升。在Linux下则应该改变不多,毕竟linux现在并发最好性能的网络I/O EPOLL,JDK 6.0 nio包含5.0的后续版本的缺省实现就是epoll。
-------------------
关于C++中完成端口的介绍:

完成端口在我理解看来也就是用来管理异步过程调用的,呵呵!在Wait等待事件函数中存在数量限制,而完成端口不存在这一限制。  
  只给完成端口启动2倍CPU的线程,这是因为线程也耗系统资源,在一个时间片内一个CPU上只会处理一个线程,大量的线程上下文切换也很耗时间,设定2倍的线程,是因为线程处理例程可能会处理比较耗时的操作,这时如果完成端口又来了请求,系统就会唤醒线程池中的空闲线程及时响应,所以设定为2倍。说得够明白了吧!^-^

两个线程是处理绝对的并发请求,假如真的会有1000个并发请求,两个线程同时处理也是非常快的,估计1-2秒就差不多了.  
  完成端口并不是为每个请求开一个线程,否则会把cpu累坏的.楼主没真正理解.

 当你的线程在诸如做数据库调用的时候或者读写文件,如果处于等待状态,IOCP会激活另外一个线程。所以不用太担心。
完成端口的出发点之一就是减少性能在线程切换时的损失吧,好像有的例子里面工作线程的个数是CPU*2+2,据说是实际测试出来的结果这样的设置效率最高


完成端口
 
 一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关联。

 步骤:
 1、创建一个空的完成端口;
 2、得到本地机器的CPU个数;
 3、开启CPU*2个工作线程(又名线程池),全部都在等待完成端口的完成包;
 4、创建TCP的监听socket,使用事件邦定,创建监听线程;
 5、当有人连接进入的时候,将Client socket保存到一个我们自己定义的关键键,
    并把它与我们创建的完成端口关联;
 6、使用WSARecv和WSASend函数投递一些请求,这是使用重叠I/O的方式;
 7、重复5~6;

 注:1、重叠I/O的方式中,接收与发送数据包的时候,一定要进行投递请求这是它们这个体系结构的特点 当然,在完成端口方式中,不是直接使用的WSARecv和WSASend函数进行请求的投递的。而是使用的ReadFile,Write的方式
  2、完成端口使用了系统内部的一些模型,所以我们只要按照一定的顺序调用就可以完成了。
  3、完成端口是使用在这样的情况下,有成千上万的用户连接的时候,它能够保证性能不会降低。

 
引文来源   Java与完成端口IOCP--流子
Logo

更多推荐