本例要实现一个简单聊天室的服务端程序,能并发处理多个客户端的请求。具体要实现验证客户端登录,转发客户端发送的消息的功能。在此之前应该对网络编程有一定了解,熟悉socket通信的基本用法,熟悉客户端和服务端收发功能的实现方法。
问题分析
传统的socket网络编程中有很多阻塞的函数,比如accept()
和read()
。这些函数在阻塞的时候,就不能执行其他任务,这对于服务端程序肯定是不适用的,因为一个服务端不能只服务一个客户端。我们要解决的问题就是在不同的任务到达是服务端程序可以做出不同的响应。这就是IO多路复用,Linux系统中有三种IO复用,分别是select
,poll
和epoll
。本例中选用select。
select函数
select
函数可以同时监视多个套接字的可读可写状态,当状态发生变化时(例如客户端发来消息,对应套接字变成可读状态),select从阻塞状态中返回,这时程序可以根据select的返回值处理套接字。
select原型
头文件:#include <sys/select.h>
select
函数的原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数 | 含义 |
---|---|
nfds | 集合中所有文件描述符的范围即所有文件描述符的最大值加1 |
readfds | (可选)指针,指向一组等待可读性检查的套接口 |
writefds | (可选)指针,指向一组等待可写性检查的套接口 |
exceptfds | (可选)指针,指向一组等待错误检查的套接口 |
timeout | select()最多等待时间,对阻塞操作则为NULL |
其中阻塞时间传入的timeval结构体如下,tv_sec设置秒,tv_usec设置微秒。
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
第一个参数我们传入select
监视的socket描述字的最大值+1;因为服务端需要监视socket可读状态发生变化,所以我们需要选择传入第二个参数readfds;select的第五个参数可以设置阻塞时间,有三种状态超时阻塞,永久阻塞和不阻塞。
参数 | 状态 |
---|---|
timeval结构体 | 超时阻塞,超时时长为结构体设定的时长,超时或监视的socket活动时select返回 |
0 | 不阻塞,timeval的值设定为0秒0毫秒,也就是不阻塞 |
NULL | 永久阻塞,等监视的socket状态改变时select才会返回 |
select原理
select会监视一个fd_set类型的数据结构,这个空间可以类比成一个数组(实际是位操作),内核通过fd_set和任意打开的描述字建立联系(网络socket描述字或文件描述字都可以),类似数组的下标和数组的元素之间的关系,数组中元素的值代表了其下标对应的描述字的状态。通过数组中指定位置为0或者1判断对应socket的活动是否发生。