IO复用—select系统调用

I/O复用

I/O复用的作用:使得程序能同时监听多个文件描述符,提高程序的性能

I/O复用的应用场景

  • cli程序需要处理多个socket.
  • cli程序要同时处理用户连接和网络连接。
  • TCP服务器要同时处理监听socket和连接socket.
  • 服务器要同时处理TCP请求和UDP请求
  • 服务器要同时监听多个端口,处理多种服务。

select系统调用

作用:在一段指定的时间内,监听用户感兴趣的文件描述符上的就绪事件。

就绪条件

  • 读就绪

  • socket内核接收缓冲区中的字节数>=其低水位标记SO_RCVLOWAT,此时该socket刻度,并且读操作返回的字节数>0

  • socket通信的对方关闭连接。socket的读操作返回的字节数=0

  • 监听socket上由新的请求

  • socket上由未处理的错误。可调用getsockopt来读取和清除该错误。

  • 写就绪

  • socket内核发送缓冲区中的可用字节数>=其低水位标记SO_SNDLOWAT,该socket可写,写操作返回的字节数>0

  • socket的写操作关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号

  • socket使用非阻塞connect连接成功或失败(超时)后

  • socket由未处理的错误。此时可以使用getsockopt来读取和清除该错误。

select系统调用API

select函数原型

#include<sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
  • nfds参数指定被监听的文件描述符的总数。值为select监听的文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
  • readfds、writefds、exceptfds参数分别指向可读、可写和异常事件对应的文件描述符集合。select返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
  • fd_set结构体
#include<typesizes.h>
#define _FD_SETSIZE 1024

#include<sys/select.h>
#define FD_SETSIZE   _FD_SETSIZE
typedef long int  _fd_mask;
#undef  _NFDBITS
#define  _NFDBITS  (8*(int) sizeof(_fd_mask))

typedef struct
{
	#ifdef  _USE_XOPEN
	     _fd_mask   fds_bits[_FD_SETSIZE/ _NFDBITS];   //32个元素,每个元素是long int 类型
	 #define  _FDS_BITS(set)   ((set)->fds_bits)
	 #else
	 	_fd_mask   _fds_bits[_FD_SETSIZE/ _NFDBITS];
	 	#define  _FDS_BITS(set)    ((set)->_fds_bits)
	 #endif
}fd_set;

fd_set结构体包含一个整型数组,数组中每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限制了能同时处理的文件描述符的总量。

使用下述宏来访问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_set *fdset);  //测试fdset的位fd是否被设置

timout参数用来设置select函数的超时时间。他是一个timeval 结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。但调用失败时timeout值是不确定的。

timeval 结构体

struct  timeval
{
	long tv_sec;    //秒数
	long  tv_userc;   //微秒数
}

如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则select将立即返回。如果给timeout变量传递NULL,则select将一直阻塞,直到某个文件描述符就绪。

select成功时返回就绪文件描述符的个数。如果在指定时间内没有任何文件描述符就绪,select将返回0

select应用举例

ser

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/time.h>

#define MAXFD 10

void fds_add(int fds[],int fd)//将文件描述符添加进fds数组中
{
	int i=0;
	for(;i<MAXFD;++i)
	{
		if(fds[i]==-1)
		{
	      fds[i]=fd;
		  break;
		}
	}
}


int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);
     printf("sockfd=%d\n",sockfd);
	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);
   
    fd_set fdset;//定义fdset集合

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

   fds_add(fds,sockfd);

   while(1)
   {
	 FD_ZERO(&fdset);//清0

	 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);//select系统调用,在这里我们只关注读事件
	 if(n==-1)//失败
	 {
		perror("select error");
	 }
	 else if(n==0)//没有任何文件描述符返回
	 {
		printf("time out\n");
	 }
	 else//有就绪事件产生
	 {
	 /*
       由于我们只能通过select的返回值知道就绪事件的个数,而无法知道具体是哪些事件就绪
       因此,需要遍历每一个文件描述符进行判断
      */
		for(i=0;i<MAXFD;++i)
		{
			if(fds[i]==-1)
			{
			   continue;
			}
			if(FD_ISSET(fds[i],&fdset))//该文件描述符对应的事件就绪
			{
			   
			   //对文件描述符分两种情况进行判断
			   
				if(fds[i]==sockfd)//文件描述符是套接字,表示有新的客户端连接
				{
					//accept
					struct sockaddr_in caddr;
					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   //已经存在的客户端发送数据
				{
					//recv
					char buff[128]={0};
					int res=recv(fds[i],buff,127,0);//接收数据
					if(res<=0)
					{
						close(fds[i]);
						fds[i]=-1;
						printf("one client over\n");
					}
					else
					{
						printf("recv(%d)=%s\n",fds[i],buff);
						send(fds[i],"OK",2,0);//回复
					}

				}
			}
		}
	 }
   }

}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

cli

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>

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=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	while(1)
	{
		printf("input:\n");
		char buff[128]={0};
		fgets(buff,128,stdin);

		if(strncmp(buff,"end",3)==0)
		{
			break;
		}

       send(sockfd,buff,strlen(buff),0);
	   memset(buff,0,128);

	   recv(sockfd,buff,127,0);
	   printf("buff=%s\n",buff);
	}
	close(sockfd);
	}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多路 I/O 复用是一种在系统编程中常用的技术,它允许一个进程同时监视多个 I/O 事件,以提高程序的效率和响应能力。在 C 语言中,一个常用的多路 I/O 复用函数是 `select`。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:监视的文件描述符集合中最大的文件描述符值加1。 - `readfds`:读事件的文件描述符集合。 - `writefds`:写事件的文件描述符集合。 - `exceptfds`:异常事件的文件描述符集合。 - `timeout`:超时时间,如果设置为 NULL,则阻塞直到有事件发生;如果设置为零,立即返回;如果设置为一个指定时间,超过该时间还没有事件发生,则返回。 `select` 函数的工作原理是将进程阻塞,直到监视的文件描述符集合中的任意一个文件描述符就绪(可读、可写或出现异常),或者超过指定的超时时间。 使用 `select` 函数进行多路 I/O 复用的一般步骤如下: 1. 创建并初始化文件描述符集合。 2. 将需要监视的文件描述符添加到相应的集合中。 3. 调用 `select` 函数进行阻塞等待。 4. 检查哪些文件描述符已经就绪。 5. 处理就绪的文件描述符。 需要注意的是,`select` 函数在每次调用时都会修改传入的文件描述符集合,因此在每次调用前需要重新初始化。 除了 `select`,还有其他的多路 I/O 复用函数,如 `poll` 和 `epoll`,它们在不同的操作系统中有不同的实现方式和特性,可以根据具体需求选择合适的函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值