I/O复用网络应用场合
当客户处理多个描述字 一个客户同时处理多个套接口 如果一个tcp服务器既要处理监听套接口,又要处理连接套接口 如果一个服务器既要处理TCP,又要处理UDP
select函数作用
这个函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程
select函数什么情况下返回
作为一个例子,我们可以调用函数select并通知内核仅在下列情况发生时才返回: 集合{1,4,5}中的任何描述子准备好读 或 集合{2,7}中的任何描述字准备好写或 集合{1,4}中的任何描述字有异常条件待处理或 已经过了10.2秒 也就是说,通知内核我们对哪些描述字感兴趣 (读、写或异常条件)以及等待多长时间。
select函数
包含头文件<sys/select.h><sys/socket.h> 功能:提供了即时响应多个套接的读写事件 原型: int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *except,const struct timeval *timeout); 参数 maxfdp1:等待最大套接字值加1,(等待套接字的数量) readset:要检查读事件的容器 writeset:要检查写事件的容器 timeout:超时时间 返回值:返回触发套件接字的个数
select函数使用
我们还是模拟CS架构,来使用select
下面是服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h> /* for struct sockaddr_in*/
#include <sys/errno.h>
#include <signal.h>
#include <sys/select.h>
//关于IO复用服务器的socket
void error_exit(char *name)
{
perror(name);
exit(-1);
}
int main(int argc,char *argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
error_exit("create error");
}
//绑定地址(ip和端口号)
struct sockaddr_in svraddr;
memset(&svraddr,0,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr=INADDR_ANY;
//svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");第二种写法
svraddr.sin_port=htons(5555);
int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));
if(ret<0)
{
error_exit("bind error");
}
//设置监听参数back login 半连接数最大
ret=listen(sockfd,1024);
if(ret<0)
{
error_exit("listen error");
}
//添加service到fd
fd_set listen_set,temp_set;
FD_ZERO(&temp_set);
FD_ZERO(&listen_set);
FD_SET(sockfd,&listen_set);
int maxfd=sockfd;
struct sockaddr removeaddr;
int addr_len = sizeof(removeaddr);
//定义一个属于去保存accept fd,FD_SETSIZE函数能够监听文件描述符最大值
int fds[FD_SETSIZE]={0};
int i=0;
for(;i<FD_SETSIZE;i++)
{
fds[i]=-1;
}
char buf[1024]={0};
while(1)
{
temp_set=listen_set;
int nevent=select(maxfd+1,&temp_set,NULL,NULL,NULL);
if(nevent==0)
{
printf("timeout\n");
continue;
}
else if(nevent<0)
{
error_exit("select error");
}
if(FD_ISSET(sockfd,&temp_set))
{
int fd=accept(sockfd,(struct sockaddr *)&removeaddr,&addr_len);
if(fd<0)
{
error_exit("accept error");
}
printf("new connection %d…\n",fd);
//把accept添加到监听集
FD_SET(fd,&listen_set);
//数量改变
maxfd=maxfd>fd?maxfd:fd;
for(i=0;i<FD_SETSIZE;i++)
{
if(fds[i]==-1)
{
fds[i]=fd;
break;
}
}
}
for(i=0;i<FD_SETSIZE;i++)
{
if(fds[i]==-1)
{
continue;
}
if(FD_ISSET(fds[i],&temp_set))
{
memset(buf,0,1024);
int rdsize=read(fds[i],buf,1024);
if(rdsize<=0)
{
printf("close %d\n",fds[i]);
//对方socket关闭,或者出错,把accept,fd移出监听集合
close(fds[i]);
FD_CLR(fds[i],&listen_set);
fds[i]=-1;
}
else
{
printf("read buf =%s,%d\n",buf,fds[i]);
}
}
}
}
return 0;
}
下面是客户端代码,写入数据hello
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h> /* for struct sockaddr_in*/
#include <sys/errno.h>
#include <signal.h>
//关于客户端的socket
void error_exit(char *name)
{
perror(name);
exit(-1);
}
int main(int argc,char *argv[])
{
if(argc<3)
{
printf("run program+ip+port\n");
return-1;
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
error_exit("create error");
}
//连接服务器,设置服务器的地址(ip和端口)
struct sockaddr_in svraddr;
memset(&svraddr,0,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr= inet_addr(argv[1]);
svraddr.sin_port=htons(atoi(argv[2]));
int ret =connect(sockfd,(struct sockaddr *)&svraddr,sizeof(svraddr));
if(ret<0)
{
error_exit("connect error");
}
write(sockfd,"hello",strlen("hello"));
sleep(5);
close(sockfd);
return 0;
}
先执行服务器代码:
等待中......
执行客户端代码:
查看服务端结果如下:
总结:成功读出来数据,证明我们输入成功,并且把fd读出来,解释一下为何从fd为何从4开始,我们知道系统中有0,1,2分别代表标准输入、标准输出和标准错误信息输出,总共3个,所以我们创建的fd从4开始计算