浅谈使用select函数实现IO多路复用

        一个服务器在处理多个客户端连接的情况时,如果为每个客户端创建一个线程来进行连接,显然是不太合适的。比较合适的方法就是使用IO多路复用,本文主要介绍使用select函数方式实现的IO多路复用,poll,epoll等方式后续文章介绍。所谓的IO多路复用模型即为:一个线程,通过记录I/O流的状态来同时管理多个I/O。

       select()函数可以监视多个文件描述符的状态,当所监视的文件描述由阻塞变为非阻塞状态时给出相应的返回值,接下来可以进行读/写/异常操作。

        使用select()函数对文件描述符进行监视时,需要用到FD系列函数:

void FD_CLR(int fd, fd_set *set);   // 清除某一个被监视的文件描述符。
int  FD_ISSET(int fd, fd_set *set); // 测试一个文件描述符是否是集合中的一员
void FD_SET(int fd, fd_set *set);   // 添加一个文件描述符,将set中的某一位设置成1;
void FD_ZERO(fd_set *set);          // 清空集合中的文件描述符,将每一位都设置为0;

        假设此时有两个客户端socket1,socket2同时连接服务器,select()函数使用方法如下:

        1、调用FD_ZERO清空集合fReadds中的文件描述符;

        2、调用DF_SET将文件描述符socket1和socket2添加到集合fReadds中;

        3、调用select()函数,第一个参数设置为socket1和socket2的最大值加1;

        4、如果select()函数返回值大于0,调用FD_ISST判断所监视的文件描述符socket1和socket2是否有数据可读。

        原理说明:

        1、假设取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
​         2、执行FD_ZERO(&set);则set用位表示是0000,0000。
​         3、若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
​         4、若再加入fd=2,fd=1,则set变为0001,0011
​         5、执行select(6,&set,0,0,0),最后一个参数为0,所以阻塞等待
​         6、若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

        根据原理说明可以看出,每次FD_SET和FD_ISSET都是需要根据socket数量来进行循环执行的。也就是需要注意以下几点:

        1、需要通过循环把所有的socket放入集合fReadds;

        2、select之后,再通过循环对所有的socket进行判断是否有数据传输;

        3、所有的socket判断完毕,数据处理完成之后。需要重新从FD_ZERO开始再次执行。

        示例代码:

list listSocket;

fd_set fReadds;
struct timeval timeout;

timeout.tv_sec = 0;
timeout.tv_usec = 200;

listSocket.push_back(socket1);
listSocket.push_back(socket2);

while (1)
{
    // 清空集合
    FD_ZERO(&fReadds);
	int nMaxSocked = 0;
    for (list::iterator itr = listSocket.begin(); itr != listSocket.end(); itr++)
	{
        // 将文件描述符添加到集合
        FD_SET(*itr, &fReadds);
	    if (nMaxSocked < itr->nSocket)
		{
			nMaxSocked = itr->nSocket;
		}
    }

    // 第一个参数为socket的最大值加1,这样才能监视所有的socket
    // 最后一个参数不能为0,否则阻塞模式会导致后续连接不上
    int ret = select(nMaxSocked+1, &fReadds, NULL, NULL, &timeout);
	if (ret == SOCKET_ERROR)
    {
        printf("select error\n");
        continue;
    }
    else if (ret == 0)
    {
        continue;
    }
    else
    {
        for (list::iterator itr = listSocket.begin(); itr != listSocket.end(); itr++)
	    {
            // 判断所有监视的socket是否有数据传输
            if (FD_ISSET(*itr, &fReadds))
            {
                // 接收数据,进行处理
            }
        }
    }
}

你可能感兴趣的:(Windows开发,Linux开发,服务器,网络,c++,c语言)