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