I/O复用--》select

I/O复用技术

    将 n 个文件描述符统一监视,当其中某些文件描述符上有事件发生,则程序只处理有事件发生的文件描述符。

     I/O 复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常,网络程序在下列情况下需要使用 I/O 复用技术:
     1、客户端程序要同时处理多个 socket。比如非阻塞 connect 技术。
     2、客户端程序要同时处理用户输入和网络连接。比如聊天室程序。
     3、TCP 服务器要同时处理监听 socket 和连接 socket。这是 I/O 复用使用最多的场合。
     4、服务器要同时处理 TCP 请求和 UDP 请求。比如回射服务器。
     5、服务器要同时监听多个端口,或者处理多种服务。比如 xinetd 服务器。

     I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。

     Linux 下实现 I/O 复用的系统调用主要有 select、poll 和 epoll(Linux 独有)

select

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

启动监听

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

     maxfd:监听的最大文件描述符的值 +1,检测到最大文件描述符 +1 那里停止检测,提高底层实现效率
     readfds,writefds,exceptfds:分别记录关注的可读,可写,异常三种事件的文件描述符
     readfds,writefds,exceptfds: 内核会在线修改三个结构体,每次调用 select, 都必须重新设置三个值
     timeout:设置 select 一次监听的时间,当时间到达,没有文件描述符就绪,则超时,select 为 0。如果 timeout 为 NULL,则 select 一直阻塞。
返回值:> 0 就绪的文件描述符个数
              = -1 select 调用出错
              = 0 超时

1、如何将用户关注的文件描述符设置到 fd_set 结构变量上

typedef struct
{
	long int fds_bits[32];
}fd_set;

2、select 返回之后如何判断哪些文件描述符有事件发生
在这里插入图片描述
操作 fd_set 结构的四个宏函数
     FD_ZERO(fd_set *fds);              初始化 清空
     FD_SET(int fd, fd_set *fds);       将 fd 设置到 fds 上
     FD_CLR(int fd, fd_set *fds);       清除 fds 上的 fd
     FD_ISSET(int fd, fd_set *fds);    判断 fds 上的 fd 文件描述符是否有事件发生

缺陷

     1、只能关注三种事件类型
     2、select 的三个 fd_set 参数是在线修改的,每次都必须重新设置
          select 每次都会将所有的文件描述符返回,select 返回后必须循环探测哪些是就绪的文件描述符
     3、fd_set 最多记录 1024 个文件描述符,最大值是 1023
     4、仅仅返回了就绪文件描述符个数,用户探测就绪的文件描述符的时间复杂度为 O(n)。
     5、内核使用轮询方式检测就绪文件描述符,内核时间复杂度 O(n)

Sever

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

#define Maxfd 10

typedef struct ClientInfo
{
	int fd;
	struct sockaddr_in addrInfo;
}ClientInfo;

ClientInfo fds[Maxfd];
int maxfd=-1;

void Init()
{
	int i=0;
	for(;i<Maxfd;i++)
	{
		fds[i].fd=-1;
	}
}
                                    
void Insert(int fd,struct sockaddr_in cli)
{
	int i=0;
	for(;i<Maxfd;i++)
	{
		if(fds[i].fd==-1)
		{
			fds[i].fd=fd;
			fds[i].addrInfo=cli;
			break;
		}
	}
}

void Del(int fd)
{
	
	int i=0;
	for(;i<Maxfd;i++)
	{
		if(fds[i].fd==fd)
		{
			fds[i].fd=-1;
			break;
		}
	}
}

void SetFd(fd_set *fdss)
{
	int i=0;
	for(;i<Maxfd;i++)
	{
		if(fds[i].fd!=-1)
		{		
			if(maxfd<fds[i].fd)
			{
				maxfd=fds[i].fd;
			}
			FD_SET(fds[i].fd,fdss);
		}
	}
}
void GetClientLink(int fd)
{
	struct sockaddr_in cli;
	int len=sizeof(cli);
	int c=accept(fd,(struct sockaddr*)&cli,&len);

	if(c<0)
	{
		return;
	}
	printf("accept c=%d\n",c);
	Insert(c,cli);		
}						
	
void DealClientData(int fd,struct sockaddr_in cli)
{
	char buff[128]={0};
	if(recv(fd,buff,127,0)<=0)
	{
		printf("client over\n");
		Del(fd);
		return ;
	}
	else
	{
		printf("read:%s",buff);
		send(fd,"OK",2,0);
	}
}

void DealFinshEvent(int listenfd,fd_set *fdss)
{
	int i=0;
	for(;i<Maxfd;i++)
	{
		int fd=fds[i].fd;
		if(FD_ISSET(fd,fdss))
		{
			//客户链接
			if(fd==listenfd)
			{
				GetClientLink(fd);
			}
			else
			{
				DealClientData(fd,fds[i].addrInfo);
			}
		}
	}
}
int main()
{
	int listenfd=socket(AF_INET,SOCK_STREAM,0);
	assert(-1!=listenfd);

	struct sockaddr_in ser;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	inet_aton("127,0,0,1",(struct in_addr*)&ser.sin_addr);

	int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(-1!=res);

	listen(listenfd,5);

	Init();
	
	Insert(listenfd,ser);

	fd_set read;
	while(1)
	{
		//将 fd 设置到 readfds 上
		SetFd(&read);
		int n=select(maxfd+1,&read,NULL,NULL,NULL);
		if(n==-1)
		{
			perror("error\n");
			continue;
		}
		else if(n==0)
		{
			perror("time out\n");
			continue;
		}
		DealFinshEvent(listenfd,&read);
	}
}

Cli

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>//字节序列转换函数所用
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//地址转换函数所用

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(-1!=sockfd);

	struct sockaddr_in ser;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	inet_aton("127.0.0.1",(struct in_addr*)&ser.sin_addr);

	int res=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
	assert(-1!=res);
	
	while(1)
	{
		printf("please input: ");
		char data[128]={0};
		fgets(data,128,stdin);
		if(strncmp(data,"bye",3)==0)
		{
			close(sockfd);
			break;
		}

		send(sockfd,data,strlen(data)+1,0);
		char buff[128]={0};
		int n=recv(sockfd,buff,127,0);
		if(n<=0)
		{
			printf("error\n");
			close(sockfd);
			break;
		}
		printf("n==%d: %s\n",n,buff);
	}
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值