只有趟过各种坑,解决了各种离奇古怪的网络编程异常,才能一步一步的提升,其次再学习新的技术,就不会感觉困惑。前面的程序,我们只是先拿客户端进行了开刀,将其修改成了 IO 多路复用模型,因为它最简单。后面我还看到,即使这样很简单,但是还有很多很多的坑等着我们去填,其中包括批量输入产生的问题,IO 缓冲与 IO 复用混合使用的问题。
在没有系统的学习前,你去趟网络这趟浑水,很可能一不小心你就犯了错误,而且这种隐秘的错误你根本发现不了,乍一看程序运行起来很正常。前辈们为我们总结了这些宝贵的经验,我们一定要汲取,不要等到工作了,写真正的项目代码的时候,再用血的教训来教会我们如何写出正确严谨的程序。
在搞定了客户端后,我们就把之前的多进程并发服务器改成单进程的 IO 复用模型的并发服务器。
1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
如果你已经 clone 过这个代码了,请使用 git pull 更新一下。本节程序所使用的程序路径是 unp/program/echo/multiplexing_select_server
。
2. 使用 select 修改服务器
2.1 修改思路
服务器的修改并没有客户端那么容易,但是也是是很复杂。我们知道服务器主要做两件事情:
- 接收新的连接请求,主要由监听套接字来完成
- 和已连接的客户端交互数据,主要由已连接套接字来完成(accept 函数返回的那个值)
因此,服务器要使用 select 同时管理监听套接和已连接套接字。
我们需要事先准备一个 fd_set 类型的读集合 rfds,该集合保存了所有的监听套接和已连接套接字。另一方面,当 IO 事件产生时,我们需要挨个遍历 rfds 中的每个描述符,但是 rfds 并不像 C++ 中的 set 集合那么方便,它本身不提供任何一种方法来帮助我们遍历所有元素。因此,我们需要单独再使用一个数组 fds[1024] 来保存所有套接字。
2.2 伪代码
即便是伪代码,也很长呐……
上面的代码有几点要说明一下:
第一是 FD_SETSIZE 这个宏,它是 fd_set 集合最大容量,默认大小是 1024,这意味着我们的服务器最多只能并发 1024 - 4 个连接,为什么减 4,因为要除去标准输入、标准输出、标准错误以及 listenfd 这 4 个。
第二是使用 nready 来避免无谓的循环,每次处理完一个 IO 事件,就让 nready 的值减 1,如果 nready 的值为 0 了,说明 IO 事件已经处理完,再进行循环已经没有意义。
3. 程序运行
- flower 主机上启动服务器
- 在 sun 和 moon 上启动客户端
- 运行结果
图1 运行结果
4. 总结
- 掌握使用 IO 复用改写服务器的方法