Linux I/O复用技术---select

1.介绍

select 函数的作用是检测一组 socket 中某个或某几个是否有“事件”就绪,即可读可写

  在Linux平台下的select定义如下:

 /* According to POSIX.1-2001, POSIX.1-2008 */
 #include <sys/select.h>

 /* According to earlier standards */
 #include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

 参数:

  • 参数 nfds,select 函数监听的 fd 中最大 fd 值加 1。

  • 参数 readfds,监听可读事件的 fd 集合。

  • 参数 writefds,监听可写事件的 fd 集合。

  • 参数 exceptfds,监听异常事件 fd 集合。

其中:readfdswritefdsexceptfds 类型都是 fd_set,下面是对fd操作的一系列函数:

将一个 fd 添加到 fd_set 这个集合中

 void FD_SET(int fd, fd_set *set);

从 fd_set 上删除一个 fd

void FD_CLR(int fd, fd_set *set);

 判断某个 fd 是否有事件

int  FD_ISSET(int fd, fd_set *set);

 将 fd_set 中所有的 fd 都清掉

  void FD_ZERO(fd_set *set);
  •  参数 timeout

设定的时间内检测这些 fd,超时select就返回,时长是tv_sectv_usec之和


 //The time structures involved are defined in <sys/time.h> and look like

struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

2.代码实战,单个线程里监听多个客户端连接:

#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>
#include<vector>
#include<string.h>

int main(int argc, char* argv[]){

int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
	printf("create socket failed!, errno=%d\n",errno);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);

int ret;
ret = bind(fd, (struct sockaddr*) &addr, sizeof(addr));
if(ret == -1){
	printf("bind failed!, errno=%d",errno);
	close(fd);
}
ret = listen(fd, SOMAXCONN);
if(ret == -1){
	printf("listen failed!, errno=%d",errno);
}

struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
int maxfd = fd;
std::vector<int> afds;
//开始用select来管理多个连接
	while(true){
		fd_set fdset;
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
		for(int i = 0; i < afds.size(); ++i){
			if(afds[i] != -1)//INVALID_FD
			FD_SET(afds[i], &fdset);
		}
		timeval tm;
		tm.tv_sec = 1;
		tm.tv_usec = 0;
		ret = select(maxfd+1, &fdset, NULL, NULL, &tm);//监听可读事件
		if(ret < 0){
			printf("select error!\n");
			break;
		}
		else if(ret == 0){
			printf("timeout!\n");
			continue;//超时
		}
		else{
			if(FD_ISSET(fd, &fdset)){//fd上有事件
				int afd = accept(fd, (struct sockaddr*) &clientaddr, &clientaddrlen);
				printf("accept afd= %d\n",afd);
		        if(afd == -1){
					break;
			    }
				 printf("A client connected!,ip=%s,port=%d\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
				 afds.push_back(afd);//将客户端连接的socket fd 加入到afds集合
				 maxfd = maxfd > afd ? maxfd : afd;
		   }
			else{

				for(int i = 0; i < afds.size(); ++i){
					if(afds[i] != -1 && FD_ISSET(afds[i], &fdset)){
						char buf[256] = {0};
						int len = recv(afds[i], buf, 256, 0);
					    if(len <= 0){
							printf("afd[%d] close or error\n",i);
							close(afds[i]);
						    afds[i] = -1;
						}
						printf("afd[%d] recv %d bytes:%s\n", i, len, buf);
					}
				}
			}
		
		
		
	}
	
}
	//关闭客户端所以连接
	for(int i = 0; i < afds.size(); ++i){
		if(afds[i] != -1)
		close(afds[i]);
		
	}
	close(fd);//关闭监听socket





}

模拟三个客户端连接:

注意事项:(1)以上代码使用了vector,所以需要g++进行编译

3.总结

(1)select执行后会对三个参数的 fd_set 进行修改,所以每次需要重置。

(2)timeval 的tv也会被修改,所以需要重置。tv为0select会立即返回,为NULL会一直阻塞。

(3)每执行一次select就会fd从用户态拷贝到内核态。

(4)单进程监听socket的数量有限,默认1024,要修改的话,得重新编译内核。

(5)每次处理I/O都需要采用遍历fd(轮询)的方式。

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值