Linux C编程 SelectIO复用
I/O复用简述
概念
解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用
使用一个函数去检测多个句柄的IO
使用场合
- 当客户端处理多个描述符(通常是交互式输入、网路套接字)时,使用IO复用
- tcp服务器既要处理监听套接字,又要处理已连接套接字,一般要使用I/O复用。
- 如果一个服务器既要处理tcp又要处理udp,一般要使用I/O复用。
- 如果一个服务器要处理多个服务或多个服务时,一般要使用I/O复用。
IO复用常用函数
select 、poll
epoll
select编程
select函数简介
- select 函数
#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);
//功能:轮询扫描多个描述符中的任一描述符是否发生响应,发生相应就立即返回,
//没发生响应,等待timeout时间,返回
- 参数:
参数 | 名称 | 说明 |
---|---|---|
nfds | 指定要检测描述符的范围 | 所检测描述符最大值+1 |
readfds | 可读描述符集 | 监测该集合中的任意描述符是否有数据可读 |
writefds | 可写描述符集 | 监测该集合中的任意描述符是否有数据可写 |
exceptfds | 异常描述符集 | 监测该集合中的任意描述符是否发生异常 |
timeout | 超过规定时间后唤醒 | 超过规定时间后唤醒 |
- 返回值
0:超时
-1:出错
大于0:准备好的文件描述符数量 - 结构体
//超时结构体:
struct timeval{
long tv_sec;//秒
long tv_usec;//微秒
};
//比如等待10.2秒
struct timeval timeout;
timeoout.tv_sec = 10;
timeoout.tv_usec = 200000;
//将select函数的timeout参数设置为NULL则永远等待
- 集合操作函数
描述符集合的操作:
select()函数能对多个文件描述符进行监测,如果一个参数对应一个描述符,那么select函数的4个参数最多能监测4个文件描述,那他如何实现对多个文件描述符的监测的呢?
大家想一想文件描述符基本具有3种特性(读、写、异常),如果我们统一将监测可读的描述符放入可读集合(readset),监测可写的描述符放入可写集合(writeset),监测异常的描述符放入异常集合(exceptset)。然后将这3个集合传给select函数,是不是就可监测多个描述符呢.
//将一个文件描述符从集合中删除
void FD_CLR(int fd, fd_set *set);
//检测该描述符在那个集合中的事件是否发生
int FD_ISSET(int fd, fd_set *set);
//将文件描述符加入一个集合
void FD_SET(int fd, fd_set *set);
//初始化集合
void FD_ZERO(fd_set *set);
tips
每次selcet之后必须重新初始化以及添加文件描述符,和集合,否则会发生错误
使用示例:集合操作函数必须放在while循环里。
while(1)
{
fd_set rset;//创建一个描述符集rset
FD_ZERO(&rset);//对描述符集rset清零
FD_SET(0, &rset);//将描述符0加入到描述符集rset中
FD_SET(4, &rset);//将描述符4加入到描述符集rset中
FD_SET(5, &rset);//将描述符5加入到描述符集rset中
if(select(5+1, &rset, NULL, NULL, NULL) > 0)
{
if(FD_ISSET(0, &rset))
{
//描述符0可读及相应的处理代码
}
if(FD_ISSET(4, &rset))
{
//描述符4可读及相应的处理代码
}
if(FD_ISSET(5, &rset))
{
//描述符5可读及相应的处理代码
}
}
}
select编程示例
//tcp_select_server.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUFF 1024
static void usaAge(char * proc)
{
printf("Please enter: %s [local_ip] [local_port] [listnum]\n",proc);
}
int main(int argc,char *argv[])
{
int socked,new_fd;
int listnum;
socklen_t sender_len;
int sendBuf_len=0;
int recvBUf_len=0;
struct sockaddr_in myaddr,sender;
fd_set rfds;
struct timeval tv;
int retval,maxfd=1;
char sendBuf[MAXBUFF+1];
sender_len=sizeof(sender);
if(argc==1)
{
usaAge(argv[0]);
}
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family=AF_INET;
if(argv[1])
{
myaddr.sin_addr.s_addr=inet_addr(argv[1]);
}
else
{
myaddr.sin_addr.s_addr=htonl(INADDR_ANY);
}
if(argv[2])
{
myaddr.sin_port=htons(atoi(argv[2]));
}
else
{
myaddr.sin_port=htons(8888);
}
if(argv[3])
{
listnum=atoi(argv[3]);
}
else
{
listnum=5;
}
socked=socket(AF_INET,SOCK_STREAM,0);
if(socked==-1)
{
perror("socket failed");
}
if(bind(socked,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("bind error");
}
if (listen(socked, listnum) == -1)
{
perror("listen error");
}
else
{
printf("listen ok\n");
}
while(1)
{
new_fd=accept(socked,(struct sockaddr*)&sender,&sender_len);
if(new_fd<0)
{
perror("accept error");
}
else
{
printf("Get connectls,ip is :%s port is %d\n",inet_ntoa(sender.sin_addr),ntohs(sender.sin_port));
}
while(1)
{
FD_ZERO(&rfds);
FD_SET(0,&rfds);
FD_SET(new_fd,&rfds);
maxfd=new_fd;
tv.tv_sec=1;
tv.tv_usec=0;
retval=select(maxfd+1,&rfds,NULL,NULL,&tv);
if(retval==-1)
{
perror("select failed");
exit(EXIT_FAILURE);
}
else if(retval==0)
{
printf("Timeout\n");
}
else
{
if(FD_ISSET(0,&rfds))
{
printf("You can sendMessage\n");
memset(sendBuf,0,MAXBUFF+1);
fgets(sendBuf,MAXBUFF,stdin);
if(!strncasecmp(sendBuf,"quit",4))
{
printf("I will quit\n");
break;
}
sendBuf_len=send(new_fd,sendBuf,strlen(sendBuf)-1,0);
if (sendBuf_len > 0)
printf ("send successful,%d byte send!\n",sendBuf_len);
else {
printf("send failure!");
break;
}
}
if(FD_ISSET(new_fd,&rfds))
{
printf("Receive Message\n");
bzero(sendBuf, MAXBUFF + 1);
recvBUf_len=recv(new_fd,sendBuf,MAXBUFF,0);
if (recvBUf_len > 0)
printf ("recv success :'%s',%dbyte recv\n", sendBuf, recvBUf_len);
else
{
printf("receive failure!");
break;
}
}
}
}
close(new_fd);
printf("need other connect\n");
fflush(stdout);
bzero(sendBuf,MAXBUFF+1);
fgets(sendBuf, MAXBUFF, stdin);
if (!strncasecmp(sendBuf, "no", 2))
{
printf("quit!\n");
break;
}
}
close(socked);
return 0;
}
//tcp_select_cilent.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUFF 1024
static void usaAge(char * proc)
{
printf("Please enter: %s [send_ip] [send_port\n",proc);
}
int main(int argc,char *argv[])
{
int socked,new_fd;
socklen_t sender_len;
int sendBuf_len=0;
int recvBUf_len=0;
struct sockaddr_in sender;
fd_set rfds;
struct timeval tv;
int retval,maxfd=1;
char sendBuf[MAXBUFF+1];
sender_len=sizeof(sender);
if(argc==1)
{
usaAge(argv[0]);
}
bzero(&sender,sizeof(sender));
sender.sin_family=AF_INET;
if(argv[1])
{
sender.sin_addr.s_addr=inet_addr(argv[1]);
}
else
{
sender.sin_addr.s_addr=htonl(INADDR_ANY);
}
if(argv[2])
{
sender.sin_port=htons(atoi(argv[2]));
}
else
{
sender.sin_port=htons(8888);
}
socked=socket(AF_INET,SOCK_STREAM, 0);
if(socked<0)
{
perror("socket failed");
}
if (connect(socked, (struct sockaddr *) &sender, sizeof(sender)) != 0)
{
perror("Connect ");
exit(EXIT_FAILURE);
}
else
{
printf("Get connect,ip is :%s port is %d\n",inet_ntoa(sender.sin_addr),ntohs(sender.sin_port));
}
printf("\nget ready pls chat\n");
while(1)
{
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(socked, &rfds);
maxfd = socked;
tv.tv_sec = 1;
tv.tv_usec = 0;
retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
if(retval==-1)
{
perror("select failed");
exit(EXIT_FAILURE);
}
else if(retval==0)
{
printf("Timeout\n");
}
else
{
if(FD_ISSET(0,&rfds))
{
printf("You can sendMessage\n");
memset(sendBuf,0,MAXBUFF+1);
fgets(sendBuf,MAXBUFF,stdin);
if(!strncasecmp(sendBuf,"quit",4))
{
printf("I will quit\n");
break;
}
sendBuf_len=send(socked,sendBuf,strlen(sendBuf)-1,0);
if (sendBuf_len > 0)
printf ("send successful,%d byte send!\n",sendBuf_len);
else
{
printf("send failure!");
break;
}
}
if(FD_ISSET(socked,&rfds))
{
printf("Receive Message\n");
bzero(sendBuf, MAXBUFF + 1);
recvBUf_len=recv(socked,sendBuf,MAXBUFF,0);
if (recvBUf_len > 0)
printf ("recv success :'%s',%dbyte recv\n", sendBuf, recvBUf_len);
else
{
printf("receive failure!");
break;
}
}
}
}
close(socked);
return 0;
}
tips
程序调试的时候我发现了bug,就是你即使开始不传入argv的值,除了argv[1]=0x00,以后agrv[2]以后还是会有值。
导致在使用默认参数传参时,开启的端口不是8888,bug自行修改,或者直接传入参数运行