服务器模式:阻塞+多线程

DinS          Written on 2017/9/14

本文介绍服务器模式之一:阻塞+多线程。请确保阅读过《socket概述》以及《Windows多线程编程 – 基础》。

既然服务器问题出在等待客户端和响应客户端不可同时进行,那么使用多线程来处理是很自然的思路,于是我们可以这样设计:在主线程中开一个while不断accept,如果没有客户端消息就阻塞。一旦接收到客户端,就开辟一个新线程,利用该线程响应客户端,而主线程因为while再次accept阻塞住。
这样就实现了持续监听同时能够响应客户端。

 

图片来自http://blog.csdn.net/hguisu/article/details/7453390

(强烈推荐读者去看看这一篇博客,收获绝对大大的)

按照这个思路,使用windows提供的API写一个简单的程序。
为了突出重点,代码中不进行错误处理。

整体介绍一下思路,其实就是刚刚论述的阻塞+多线程模式的细化。
建立socket略过。
主线程在while中不断accept,一旦成功就得到了连接socket,此时把其压入向量,然后开辟线程。为什么需要这一步?直接向线程传入这个socket参数不行吗?
不行,这是因为windowsAPI中线程只能传入void*,试想我们传入了这个参数,实际上指向的是while中的sockClient。如果在等待客户端回应时一个新的客户端连接进来,那么while中的sockClient的内容就变化了,因此在刚刚的线程中通信的那个socket就不是原来的了,这样肯定不行。
解决办法就是把已连接的socket记录下来,每个线程单独用,互不影响。
当然通信结束后连接socket是需要删除的,更好的方法是使用hashtable,只需要O(1)。这里仅仅是演示就用vector了,而且也不清除。

其他注意事项:如果客户端发送了200长度,而服务端一次只接收100长度,则剩下的100会留在缓存区,一旦下次服务端再次recv,会把剩下的100读入。显然这次接收的目的应该不是剩余信息,统一约定一次通信长度,可以避免许多问题。

由于编号是全局变量,且存在多线程,所以需要使用原子操作。想深入理解的可见《Windows多线程编程 – 互斥问题》。

线程中没有什么太多可说的,就是一来一去,为了突出不同所以内容是客户端自定义的。

下面看看客户端代码:

客户端代码都差不多,只不过增加了一个接受自定义内容的流程。

运行结果:

这个是在10.0.16.76机器上看到的结果,这个机器的服务器环境已经搭建好了,所以可以收到socket。

这是在本地机器上的画面,开了4个客户端。
首先开了2个客户端,然后第一个客户端说了句话。
然后又开了2个客户端,第四个客户端说了句话。

从服务端的结果看,我们做的阻塞+多线程模式成功了,各个客户端对应正确。
在2和3号客户端未结束通信前,服务器同样可以响应4号客户端。也就是说,各个客户端与服务器的通信与其他客户端没有关系。

小结:

阻塞+多线程模式是一种解决方法,有利有弊。
优点是思路简单,而且利用线程确实做到了连接与响应分离。
缺点是在大并发量的情况下不能够达到需求。。
这是因为每一个客户端都对应一个线程,如果同时连接的有1万+(对于目前环境而言并不算多),那么同时开辟1万个线程肯定不是什么好事。操作系统不能够做出及时响应,客户端也会出现卡顿情况。为了缓解压力,可以做缓冲池之类的东西,但是仍然不能从根本上解决大并发问题。

如何处理大并发情况,可参考其他文章《服务器模式:非阻塞+轮询》和《服务器模式:多路复用》。