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);
	}

猜你喜欢

转载自blog.csdn.net/qq_43313035/article/details/89345340