随着internet的发展,更高效率、更多并发连接数的服务已经成为当前迫切的需要。我们虽然可以采用UDP这样的无连接协议来解除连接限制(事实上这个限制也同样是存在的,尤其是当一个UDP处理需要占用不少CPU处理时间的情况下其上限也就越小),但是这样的方案并不能解决所有问题(例如从服务端发出给连接客户端的及时消息)。
解决这个问题的一种途径是线程池(Thread Pool),并在各个线程中运行单独的select()以实现对网络事件的多路分离。select其实的内部机制依赖于fd_set,说白了就是一个数组,select通过遍历这个数组中的所有socket handle,通过ioctl判断该handle是否被激活的socket事件。因此,我们可以对select得出两个结论:
1)select可以在多线程中分离使用,但是我们需要实现一个机制,以便在多个线程之间平均的划分socket handles,从而在各个线程之间平均分摊负载。
2)由于需要不停的(循环的)遍历一个fd_set数组,当这个数组变得很大的时候select就会显得效率低下(简直难以忍受,这就是为什么在windows中FD_SETSIZE的缺省大小为64的原因)
从以上的结论我们看到,采用select和线程池的方式解决以上问题应该是一种方法。但是我想这个问题还没完,因为这还不是我看来最有高效的解决方案。为什么这么说呢,首先select的选择能力有限,假设我们使用最大的FD_SETSIZE=1024,这样,我们需要10个左右不停循环的线程,由于每个线程每次遍历fd_set所花费的时间较长,10个不停循环的线程将很有可能使服务进程阻塞,占用大量的CPU资源,由于我们不是单纯的保持这些TCP连接就万事大吉了,我们还得对这些连接的请求进行处理,而服务器恐怕此时已经难以承担(当然,采用提高硬件配置、多CPU的方式可以有效解决这一问题,但这不是我们做软件的应该带给系统的其他成员的。如果采用FD_SETSIZE=64的方案呢?这样我们将在线程池中运行大约16*10=160个线程,考虑线程间切换和同步开销,该方案仍然是比较难忍受的。
如何解决这个问题?从目前的情况看,问题主要还是存在于select这一古老socket api 函数的瓶颈上。(毕竟线程间切换和同步开销问题是无法解决的)。于是我们考虑是否可以用poll或epoll多路分离来解决这个瓶颈问题。poll和select其实是采用的同种机制,因此其效率不会比select高多少。epoll提供Edge Triggered ( ET ) 和Level Triggered ( LT )两种方式的多路事件分离方法,使用LT方式时,其效果与select和poll相似,顶多可以看成是一个更快的poll;但在使用ET方式时其效率在处理具有大量idle connection的时候明显高于select和poll,在处理always busy connection等情况时,效率与select和poll没有多大区别。(关于这些效能的比较可以参考[1]中的Fig5和Fig6)当然,epoll比起poll和select来说缺省的最大FD大小更大,不过这其实不是问题,因为我们都可以修改其水平边界的大小的。补充一点,由于epoll、poll和select都使用文件描述符fd作为数组的索引,而在普通系统中一个FD的定义是integer,也就是说它是存在上限的,在C中一个int通常是16位的,也就是说最大文件句柄数32768(不是65536哦;-)构成了单个fd_set的上限。见[2]
实际情况下,很少有服务的连接是always busy connection的,也就是说客户端一但建立和与服务端的TCP连接后,该连接在很多时侯是处于idle connection状态的,它只是在有数据需要传送的时候才处于busy connection状态的。换句话说,在实际服务情况下,使用ET模式的epoll(以下的epoll都是指在ET模式下的)比使用poll和select具有更高的效率。这样看来,使用epoll比使用select或poll在单线程中具有更高的处理效率和更大的处理连接数。它仅使用单个线程就已经能处理前面问题中所提到的并发10K连接数问题。
至此,前面提出的10K连接数问题已经基本解决。但是我认为这还只是个开始,我们常见一些服务能够支持1M以上的同时连接数。为达到这个目的,我们需要将线程池和epoll结合起来,即,在多个线程中使用epoll_wait[3]等待多个socket events的到达。这带来了另外一些深层次的问题:
1)socket descript的限制,在大多数系统中SOCKET对应的是integer,其连接数是有上限的;
2)线程间同步及流量分配问题,需要为每个线程合理的分配连接以达到负载平衡;
3)服务质量问题,防止出现饿死现象。
1、修改用户进程可打开文件数限制
在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,