IO复用使得程序能够同时监听多个文件描述符,可以大大提高程序的性能。

select 系统调用

select系统调用的用途是:在一段指定的时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件

select API

#include <sys/select.h>

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval* timeout);
/* * 成功时返回就绪文件描述符总数 * 如果没有任何文件描述符就绪,select返回0 * 失败返回-1 */
  • nfds参数指定被监听的文件描述符的总数,一般设置为select监听的所有文件描述符最大值+1,因为文件描述符从0开始。
  • readfds、writefds、exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。
    应用程序调用select时,通过这三个参数传入自己感兴趣的文件描述符,select调用返回时,内核将修改它们来通知应用程序哪些文件描述符 已就绪。
    因为内核会对其进行修改,所以就需要进行保存
    这三个参数对应的fd_set其实就是一个整形数组该数组的每个元素的每一位标记一个文件描述符

    -timeout设置select的超时时间。

修改fd_set结构体

#include <sys/select.h>

FD_ZERO(fd_set* fdset);					//清除fdset所有位
FD_SET(int fd, fd_set* fdset);			//设置fdset的位fd
FD_CLR(int fd, fd_set* fdset);			//清除fdset的位fd
int FD_ISSET(int fd, fd_ste* fdset);	//测试fdset的位fd是否被设置

文件描述符就绪条件

select编程实例

select编程流程

/* * 1.创建socket-->bind-->listen * 2.创建一个链表或数组存储socketfd 并清空select的标志数组 * 3.先将servicefd放入链表 * 4.循环:① 先清空select标志数组 * ② 获取链表中最大文件描述符值,并将链表中存储的sockfd一一设置在select中的数组 * ③ 进入第五步:处理数组 * 5.处理数组:① 如果是发生事件的标记为 == servicefd ,则是有新的连接将连接放入链表 * ② 如果不是servicefd,则代表是客户端收发数据,如果收到断开请求,则从链表中删除socketfd并关闭该socket */

头文件 tcp_socket.h


#include<iostream>
#include<assert.h>
#include<string.h>

#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include<unistd.h>

#define BUFF_SIZE 128

using namespace std;
class Socket
{
   
public:
	Socket()
	{
   
		sockfd_ = socket(PF_INET, SOCK_STREAM, 0);
		assert(sockfd_ >= 0);
	}

	int Get_socket()
	{
   
		return sockfd_;
	}

	~Socket()
	{
   
		close(sockfd_);
	}
protected:
	int sockfd_;
};

class Socket_Ser :public Socket
{
   
public:
	Socket_Ser(const char* ip, int port = 6000, int backlog = 5)
	{
   
		struct sockaddr_in address;
		memset(&address, 0, sizeof(address));

		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = inet_addr(ip);

		int ret = bind(sockfd_, (struct sockaddr*)&address, sizeof(address));
		assert(ret != -1);

		ret = listen(sockfd_, backlog);
		assert(ret != -1);
	}

	int Accept()
	{
   
		struct sockaddr_in client;
		socklen_t len = sizeof(client);
		int connfd = accept(sockfd_, (struct sockaddr*)&client, &len);
		return connfd;
	}

	int Recv(int fd, char* buffer, int size)
	{
   
		int ret = recv(fd, buffer, size - 1, 0);
		if (ret == -1 || (strncmp(buffer, "end", 3) == 0))
		{
   
			return -1;
		}
	}

	void Send(int fd, const char* buffer, int size)
	{
   
		send(fd, buffer, size, 0);
	}
};

class Socket_Cli :public Socket
{
   
public:
	Socket_Cli(const char* ip, int port = 6000)
	{
   
		struct sockaddr_in address;
		memset(&address, 0, sizeof(address));

		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = inet_addr(ip);

		int ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address));
		assert(ret != -1);
	}

	int Send(char* buffer, int size)
	{
   
		send(sockfd_, buffer, size, 0);
	}
};

头文件tcp_select.h


#include "tcp_socket.h"
#include <list>

class Select
{
   
public:
	Select(const char* ip, int port = 6000, int backlog = 5) :ser(ip, port, backlog)
	{
   
		cli_list.push_front(ser.Get_socket());
		FD_ZERO(&readfds_);
		FD_ZERO(&exceptfds_);
		FD_ZERO(&writefds_);
	}

	void Deal()
	{
   
		Using_Select();
		Deal_connect();
	}

	void Delete_fd(int fd)
	{
   
		cli_list.remove(fd);
		close(fd);
	}
public:
	void Join_Read(int connfd)
	{
   
		FD_SET(connfd, &readfds_);
	}
	void Join_Write(int connfd)
	{
   
		FD_SET(connfd, &writefds_);
	}
	void Join_Except(int connfd)
	{
   
		FD_SET(connfd, &exceptfds_);
	}
public:
	int Judge_Read(int connfd)
	{
   
		return FD_ISSET(connfd, &readfds_);
	}
	int Judge_Write(int connfd)
	{
   
		return FD_ISSET(connfd, &writefds_);
	}
	int Judge_Except(int connfd)
	{
   
		return FD_ISSET(connfd, &exceptfds_);
	}
public:
	void Zero()
	{
   
		FD_ZERO(&readfds_);
		FD_ZERO(&exceptfds_);
		FD_ZERO(&writefds_);
	}

private:
	int Accept()
	{
   
		int connfd = ser.Accept();
		cli_list.push_front(connfd);
		return connfd;
	}

	void Using_Select()
	{
   
		int serfd = Ret_Max();
		int ret = select(serfd + 1, &readfds_, NULL, NULL, NULL);
		if (ret < 0)
		{
   
			cout << "selcet failure" << endl;
		}
	}

	int Ret_Max()
	{
   
		Zero();
		auto it = cli_list.begin();
		int max = *it;
		for (; it != cli_list.end(); it++)	//获取最大值,并重置select数组
		{
   
			Join_Read(*it);
			if (*it > max)
			{
   
				max = *it;
			}
		}
		return max;
	}

	void Deal_connect()
	{
   
		int sockfd = ser.Get_socket();

		for (auto it = cli_list.begin(); it != cli_list.end(); )
		{
   
			if (Judge_Read(*it))		//当发生读事件
			{
   
				if (*it == sockfd)	//服务器接受新连接
				{
   
					int connfd = Accept();
					cout << "new client:" << connfd << " connet!" << endl;
				}
				else				//客户端发送消息
				{
   
					char buffer[BUFF_SIZE] = {
    0 };
					int ret = ser.Recv(*it, buffer, BUFF_SIZE);
					if (ret == -1)
					{
      
						int fd = *it++;		//删除操作注意!
						Delete_fd(fd);
						continue;
					}

					cout << "recv form " << *it << ":" << buffer << endl;
					ser.Send(*it, "ok", 2);
				}
			}
			++it;
		}
	}
private:
	Socket_Ser ser;
	list<int> cli_list;			//存储套接字

	fd_set readfds_;
	fd_set writefds_;
	fd_set exceptfds_;
};

主文件tcp_select.cpp

#include "tcp_select.h"
#include <list>

int main(int argc, char* argv[])
{
   
	if (argc <= 1)
	{
   
		cout << "error! please input again" << endl;
		return 1;
	}
	
	Select sel(argv[1]);

	while (1)	//不断的去在select上注册事件
	{
   
		sel.Deal();
	}
	return 0;
}

参考文献

[1]游双.Linux高性能服务器编程.机械工业出版社,2043.5.