本文参考于 analogous_love大神的linux网络专栏,以及徐晓鑫女士的《后台开发》一书,十分感谢两位。
网络I/O模型,在unp中,斯蒂夫先生将它归为五种:
(1)阻塞式I/O
(2)非阻塞式I/O
(3)多路复用式I/O
(4)信号驱动式I/O
(5)异步I/O
而在徐晓鑫女士的《后台开发》一书中,并没有提及信号驱动式。
其中阻塞式是入门级的,就不多讲述了。
(2)非阻塞式,当连接失败,第一时间返回结果,而不会阻塞于进程导致资源的浪费。
一种最常见的方法是用ftcl设置O_NONBLOCK从而设置非阻塞。
int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1)
(3)多路复用式,使用select,poll,epoll等实现。核心思想是将多个描述符加入关心的组,通过遍历组来获知哪个描述符做好被读写的准备,从而进行读写。
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<vector>
#include<string.h>
#include<netinet/in.h>
#include<string.h>
#include<errno.h>
#include<sys/wait.h>
#define default_port 3000
using namespace std;
int main()
{
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(default_port);
ser.sin_addr.s_addr=htonl(INADDR_ANY);
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{ perror("socket");
return -1;
}
else
cout<<"create sockfd successful"<<endl;
if(bind(sockfd,(struct sockaddr *)&ser,sizeof(ser))<0)
{
perror("bind");
return -1;
}
else
cout<<"bind successful"<<endl;
if(listen(sockfd,5)<0)
{
perror("listen....");
return -1;
}
else
cout<<"listening...."<<endl;
char buf[1024];
int clifds[5];
//memset(&clifds,0,sizeof(clifds));
bzero((void*)clifds,sizeof(clifds));
fd_set clifd;
int conn=0,ret=0;
int maxfd=sockfd;
for(;;)
{
FD_ZERO(&clifd);
FD_SET(sockfd,&clifd);
int i;
for(i=0;i<5;i++)
{
if(clifds[i]!=0)
FD_SET(clifds[i],&clifd);
}
struct timeval tv;
tv.tv_sec=30;
tv.tv_usec=0;
ret=select(maxfd+1,&clifd,NULL,NULL,&tv);
if(ret<0)
{
perror("select");
return -1;
}
else if(ret==0)
{
cout<<"timeout!"<<endl;
continue;
}
for(i=0;i<conn;++i)
{ if(FD_ISSET(clifds[i],&clifd))
{
cout<<"recv data from :"<<i<<endl;
ret=recv(clifds[i],buf,1024,0);
if(ret<=0)
{
cout<<"recv data failured"<<endl;
close(clifds[i]);
FD_CLR(clifds[i],&clifd);
clifds[i]=0;
}
else
cout<<"the data is "<<buf<<endl;
}
}
if(FD_ISSET(sockfd,&clifd))
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
int accfd=accept(sockfd,(struct sockaddr *)&cli,&len);
if(accfd<0)
{
perror("accept");
continue;;
}
if(conn<5)
{
clifds[conn++]=accfd;
memset(&buf,0,sizeof(buf));
strcpy(buf,"welcome you ,this is MrSun's server\n");
int slen=send(accfd,buf,strlen(buf),0);
if(slen==strlen(buf))
cout<<"new connection clifds "<<conn<<' '<<inet_ntoa(cli.sin_addr)<<' '<<ntohs(cli.sin_port)<<endl;
else
{
cout<<"send data failured"<<endl;
close(clifds[conn-1]);
clifds[conn-1]=0;
continue;
}
memset(&buf,0,sizeof(buf));
ret=recv(accfd,buf,1024,0);
if(ret<0)
{
cout<<"recv data failured"<<endl;
close(sockfd);
// clifds[conn-1]=0;
return -1;
}
cout<<"recv data: "<<buf<<endl;
if(accfd>maxfd)
maxfd=accfd;
else
{
cout<<"max connection!"<<endl;
break;
}
}
}
}
for(int i=0;i<5;i++)
if(clifds[i]!=0)
close(clifds[i]);
close(sockfd);
return 0;
}
三者的区别在于:select受制于最大连接数1024,以及轮询带来的开销。但其可移植性好而精度较高。
poll较之轮询更快,而其文件数目唯受制于资源。
epoll是集两者之大成,但其实现也较为复杂。尤其值得称道的是,它与前两者不同,与内存映射于同一空间,而省去了copy 描述符的开销。