IO复用的本质是不在任何地方阻塞!
总结:非阻塞情况下,要配合循环使用。
阻塞&非阻塞IO
-
阻塞:在进/线程中,发起一个调用时,在调用返回之前,进/线程会被阻塞等待,等待中的进/线让出CPU的使用权。
-
非阻塞:在进/线程中,发起一个调用时,会立即返回。
-
会阻塞的四个函数:connect()、accept()、send()、recv()。
connect函数看起好像不会阻塞,但是会经历三次握手是阻塞的,如果connect一个不存在的地址,10s后会返回失败,说明是阻塞的。
阻塞&非阻塞IO的应用场景
-
在传统的网络服务端程序中(每连接每线/进程),采用阻塞IO。
-
在I0复用的模型中,事件循环不能被阻塞在任何环节,所以,应该采用非阻塞IO。
事件循环是不能阻塞的,如果阻塞了就不能响应其他的客户端请求了!上一节epoll服务端代码的send函数是存在问题的,如果发送数据量足够大,或者网络带宽不够,那么send就会阻塞,无法响应其他客户端的请求。
1.非阻塞lO-connect()
-
对非阻塞的IO调用connect()函数,不管是否能连接成功,connect()都会立即返回失败,设置错误代码errno==EINPROGRESS。 需要仔细思考...有点绕的,这个特殊,跟后面三个不一致。
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
if (errno!=EINPROGRESS) // 说明失败
{
printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd); return -1;
}
}
代码是为了理解。
-
判断是否连接成功:对非阻塞的IO调用connect()函数后,如果socket的状态是可写的,证明连接是成功的,否则是失败的。
在程序中使用IO复用的函数处理一下,使用poll(因为更简单)监视socket的写事件,如果是可写的说明是成功的,如果是不可写就是失败的。
client1.cpp中部分代码:
//把socket设置成非阻塞。
int setnonblocking(int fd)
{
int flags;
if((flags = fcntl(fd,F_GETFL,0))==-1)
flags=0;
return fcntl(fd,F_SETFL,flags|O_NONBLOCK);
}
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("socket() failed.\n"); return -1; }
setnonblocking(sockfd);
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(atoi(argv[2]));
servaddr.sin_addr.s_addr=inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
//非阻塞的socket的connect都会返回失败,errno设置为EINPROGRESS。
if(errno!=EINPROGRESS)
{
//errno不等于EINPROGRESS一定失败,等于不一定成功,也可能会失败。
printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd); return -1;
}
}
pollfd fds;
fds.fd = sockfd;
fds.events = POLLOUT;
poll(&fds,1,-1);
if(fds.revents==POLLOUT)
printf("connect ok.\n");
else
printf("connect faild.\n");
}
客户端采用,服务端很少使用,知道就行了,面试的时候问到了能回答即可。
2.非阻塞IO-accept()
-
对非阻塞的IO调用accept(),如果已连接队列中没有socket,函数立即返回失败,errno==EAGAIN;如果已连接队列中有socket,accept返回socket。需要仔细思考...有点绕的。
tcpepoll1.cpp部分代码:
// 初始化服务端的监听端口。
int initserver(int port);
//把socket设置成非阻塞。
int setnonblocking(int fd)
{
int flags;
if((flags = fcntl(fd,F_GETFL,0))==-1)
flags=0;
return fcntl(fd,F_SETFL,flags|O_NONBLOCK);
}
int main(int argc,char *argv[])
{
if (argc != 2) { printf("usage: ./tcpepoll port\n"); return -1; }
// 初始化服务端用于监听的socket。
int listensock = initserver(atoi(argv[1]));
printf("listensock=%d\n",listensock);
setnonblocking(listensock);
while(true)
{
if(accept(listensock,0,0)==-1)
{
if(errno!=EAGAIN)
{
perror("accept:");return -1;
}
}
else
break;
}
printf("客户端已连接。\n");
return 0;
}
真实项目中不会这么写,以上代码为了测试,看一下就行,理解非阻塞。
3.非阻塞IO-recv()
对非阻塞的IO调用recv(),如果没数据可读函数(接收缓冲区为空)立即返回失败,errno==EAGAIN。
🔴重点:读取不到数据的时候返回-1;客户端断开的时候读取到0!别搞反了。
4.非阻塞lO-send()
对非阻塞的I0调用send(),如果socket不可写(发送缓冲区已满),函数立即返回失败,errno==EAGAIN。