这是学习网络编程后写的一个练手的小程序,可以帮助复习socket,I/O复用,非阻塞I/O等知识点。

通过回顾写的过程中遇到的问题的形式记录程序的关键点,最后给出完整程序代码。

0. 功能

编写一个简易群聊程序,程序具备的基本功能:

服务器:支持多个客户端连接,并将每个客户端发过来的消息发给所有其他的客户端

客户端:能够连接服务器,并向服务器发送消息,同时接收服务器发过来的任何消息

 

1. Server I/O模型

采用事件驱动(I/O复用)+ 非阻塞I/O的模型,即Reactor模式。I/O复用采用linux下的epoll机制。

相关API介绍见最后,先梳理几个写程序的时候想到的问题。

1.1  I/O复用为什么搭配非阻塞I/O?(select/epoll返回可读后还用非阻塞是不是没有意义?)

  select/epoll返回了可读,并不一定代表能读,在返回可读到调用read函数之间,是有时间间隙的。内核可能把数据丢失,也可能存在比如多个线程监听该socket,

数据被别人读走的情况。所以这里使用非阻塞I/O是有意义的。

可以参考知乎这个问题  https://www.zhihu.com/question/37271342

1.2 epoll的条件触发LT(水平触发)和边缘触发ET区别,如何正确地处理ET模式下的读操作?

简单讲,以读取数据操作举例。条件触发,只要输入缓冲中还有数据,就会以事件方式再次注册;

而边缘触发中仅在输入缓冲收到数据时注册一次该事件(你没读完也epoll_wait也不再返回了)。

所以如果使用边缘触发发生输入相关事件,需要读取输入缓冲中的全部数据。方法是一直读,直到read返回-1,并且变量errno中的值为EAGAIN,说明没有数据可读

所以在这里再次考虑一下1.1中的问题,epoll如果采用边缘触发,更要使用非阻塞I/O,否则可能就因为无数据可读阻塞整个线程了。

1.3  select与epoll的差别

 一个老生常谈的问题,select函数效率低主要有以下两个原因,首先是每次调用select函数时需要向操作系统传递监视对象信息,其次是调用后针对所有文件描述符的循环语句。

第一点对效率的影响更大。

此外,epoll还支持ET模式,而select只支持LT模式。

但select也有优点,比如兼容性好(大多数操作系统支持),在服务端介入者少的情况下仍然可以考虑使用s