I/O函数复用 -- select

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

select API

select函数原型如下:

# include<stdio.h>
int select(int nfds, fd_set* readfds, fd_set* wtitefds, fd_set* exceptfds, struct timeval* timeout);

//nfd参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中最大的值加一,因为文件描述符是从0开始的。
//reafds、writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符结合。
//应用程序调用select时,通过这3个参数传入自己感兴趣的文件描述符。select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
//这3个参数时fd_set结构体指针类型。fd_set结构体仅包含一个数组,该数组的每个元素的每一位标记一个文件描述符。
//fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。
//timeout参数用来设置select函数的超时时间。

由于位操作过于繁琐,我们通常使用下面的一系列宏来访问fd_set结构体中的位:

# include<sys/select.h>
FD_ZERO(fd_set* fdset);//清楚fd_set中所有位
FD_SET(int fd, fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd, fd_set *fdset);//清楚fdset的位fd
int FD_ISSET(int fd, fd_set *fdset);//测试fdset的位fd是否被设置

文件描述符就绪的条件

哪些情况下文件描述符可以被认为是可读、可写或出现异常,对于select的使用非常关键。在网络编程中:

下列情况下socket可读:

  • socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
  • socket通信的对方关闭连接。此时队该socket的读操作将返回0。
  • 监听套接字上有新的连接请求。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

下列情况下socket可写:

  • socke内核发送缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回地字节数大于0。
  • socket的写操作被关闭。对写操作被关闭的soacket执行写操作将触发一个SIGPIPE信号。
  • socket使用非阻塞connect连接成功或者失败之后。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清楚该错误。

代码清单

# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<unistd.h>
# include<arpa/inet.h>
# include<sys/socket.h>
# include<string.h>
# include<arpa/inet.h>
# include<sys/select.h>
# define MAXFD 10

//初始化fds数组 
void fds_init(int *fds)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		fds[i] = -1;
	}
}
//添加套接字描述符到fds数组 
void fds_add(int *fds, int fd)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		if(fds[i] == -1)
		{
			fds[i] = fd;
			break;
		}
	}
}
//删除指定套接字描述符 
void fds_del(int *fds, int fd)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;
			break;
		}
	}
}
int main()
{
	//创建套接字 
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in saddr, caddr;
	memset(&saddr, 0, sizeof(saddr));

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	//命名套接字 
	int res = bind(sockfd, (struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
	//监听套接字 
	listen(sockfd, 5);
	//定义数组 
	int fds[MAXFD];
	fds_init(fds);
	//将定义好的套接字描述符添加到数组中 
	fds_add(fds, sockfd);
	
	while(1)
	{
		fd_set fdset;
		FD_ZERO(&fdset);
		
		int maxfd = -1;

		int i = 0;
		for(; i < MAXFD; i++)
		{
			if(fds[i] == -1)
			{
				continue;
			}

			FD_SET(fds[i], &fdset);
			if(fds[i] > maxfd)
			{
				maxfd = fds[i];
			}
		}

		struct timeval tv = {5, 0};
		int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
		if(n == -1)
		{
			printf("select error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < MAXFD; i++)
			{
				if(fds[i] == -1)
				{
					continue;
				}
				if(FD_ISSET(fds[i], &fdset))
				{
					if(fds[i] == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd,(struct sockaddr*)&caddr, &len);
						if(c < 0)
						{
							continue;
						}
						printf("accept c = %d\n", c);
						fds_add(fds, c);
					}
					else
					{
						char buff[128] = {0};
						int num = recv(fds[i], buff, 127, 0);
						if(num <= 0)
						{
							close(fds[i]);
							fds_del(fds,fds[i]);
							printf("one client close\n");
						}
						else
						{
							printf("recv(%d)=%s\n", fds[i], buff);
							send(fds[i], "ok", 2, 0);
						}
					}
				}
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_41727218/article/details/83536722
今日推荐