阻塞/非阻塞IO
阻塞:程序在调用结果返回之前,会等待,得到结果之后才会返回
非阻塞:不管调用是否得到结果,程序都不会等待,立即返回
缺省阻塞的函数:connect(), send(), revc(), accept()
epoll模型:
epoll只在有linux下才有,windows中没有,包含sys/epoll.h头文件
水平触发&边缘触发
select和poll采用水平触发
epoll有水平触发和边缘触发两种机制
水平触发:
如果接收缓冲区不为空,表示有数据可读,如果数据没有被读取完,再次调用epoll_wait的时候,读事件一直触发
如果发送缓冲区没有满,表示可以写入数据,只要缓冲区没有写满,再次调用epoll_wait的时候,写事件一直触发
读:
有客户端连接上来,会触发读事件,如果一直不处理客户端连接(注释此语句块代码),就会一直触发
如果客户端有报文过来,会触发读事件,不处理(注释此语句块代码), 一直触发
写:
如果关注客户端的写事件,可以向客户端缓冲发送报文的话,一直触发写事件,直到缓冲区写满
每个连接的客户端都有发送缓冲区,各自填满为止
边缘触发:
socket加入epoll后,如果接收缓冲区不为空,触发可读事件,如果有新的数据到达,再次触发可读事件
边缘触发(如果有客户端连接请求未处理,只触发一次)
epoll触发可读事件后,不管程序有没有处理可读的事件,epoll都不会再触发,只有当新的数据到达时,才再次触发可读事件
可写事件:
socket加入epoll后,如果发送缓冲区不为空,触发可写事件,如果发送缓冲区由满变成有空时,再次触发可写事件
网上有文章说边缘触发模式下,只要发送缓冲区有变化,就会触发写事件,是不对的。
IO复用的场景要使用非阻塞IO
1.当数据达到socket缓冲区的时候,可能会因为某些原因被内核丢弃,比如校验和错误,这时候,如果采用了阻塞的IO
唤醒的程序读到不到数据,accept和recv函数就会阻塞
2.达到缓冲区的数据有可能被别人取走,比如多个进程accept同一个socket时引发的惊群现象,只有一个连接到来,
但是所有的监听进程都会被唤醒,最终只有一个进程可以accept到这个请求,其他进程accept会被阻塞
3.ET边缘触发模式必须要使用非阻塞IO,因为程序中需要循环读和写,直到EAGAN出现,如果使用阻塞IO容易被阻塞住
读的方法:
如果接收缓冲区中有事件没有处理或有数据没有读完
水平触发:epoll_wait会重复报告,不必担心遗漏事件
边缘触发:epoll_wait不会重复报告,程序要用一个循环处理全部的事件或读取全部的数据
写的方法:
想写就直接写,出现EAGAIN就别写了
水平触发:epoll_wait会重复报告,如果不想写了,可以注销事件,否则一直触发
边缘触发:epoll_wait不会重复报告,不想写了就不写,不必注销事件
百度为什么IO多路复用要搭配非阻塞IO
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <sys/epoll.h>
int initserver(int port);
int main(int argc,char *argv[])
{
if (argc != 2) { printf("usage: ./tcpepoll port\n"); return -1; }
int listensock = initserver(atoi(argv[1]));
printf("listensock=%d\n",listensock);
if (listensock < 0) { printf("initserver() failed.\n"); return -1; }
int epollfd=epoll_create(1);
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=listensock;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);
struct epoll_event evs[10];
while (true)
{
int infds=epoll_wait(epollfd,evs,10,-1);
if (infds < 0)
{
perror("epoll() failed"); break;
}
if (infds == 0)
{
printf("epoll() timeout.\n"); continue;
}
for (int ii=0;ii<infds;ii++)
{
printf("events=%d,data.fd=%d\n",evs[ii].events,evs[ii].data.fd);
if (evs[ii].data.fd==listensock)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
printf ("accept client(socket=%d) ok.\n",clientsock);
ev.data.fd=clientsock;
ev.events=EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);
}
else
{
char buffer[1024];
memset(buffer,0,sizeof(buffer));
if (recv(evs[ii].data.fd,buffer,sizeof(buffer),0)<=0)
{
printf("client(eventfd=%d) disconnected.\n",evs[ii].data.fd);
close(evs[ii].data.fd);
}
else
{
printf("recv(eventfd=%d):%s\n",evs[ii].data.fd,buffer);
send(evs[ii].data.fd,buffer,strlen(buffer),0);
}
}
}
}
return 0;
}
int initserver(int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
perror("socket() failed"); return -1;
}
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
{
perror("bind() failed"); close(sock); return -1;
}
if (listen(sock,5) != 0 )
{
perror("listen() failed"); close(sock); return -1;
}
return sock;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <sys/epoll.h>
int initserver(int port);
int main(int argc,char *argv[])
{
if (argc != 2) { printf("usage: ./tcpepoll port\n"); return -1; }
int listensock = initserver(atoi(argv[1]));
printf("listensock=%d\n",listensock);
if (listensock < 0) { printf("initserver() failed.\n"); return -1; }
int epollfd=epoll_create(1);
struct epoll_event ev;
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=listensock;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);
struct epoll_event evs[10];
while (true)
{
int infds=epoll_wait(epollfd,evs,10,-1);
if (infds < 0)
{
perror("epoll() failed"); break;
}
if (infds == 0)
{
printf("epoll() timeout.\n"); continue;
}
for (int ii=0;ii<infds;ii++)
{
printf("events=%d,data.fd=%d\n",evs[ii].events,evs[ii].data.fd);
if (evs[ii].data.fd==listensock)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
printf ("accept client(socket=%d) ok.\n",clientsock);
fcntl(clientsock, F_SETFL, O_NONBLOCK);
ev.data.fd=clientsock;
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);
}
else
{
printf("可以向客户端写数据。\n");
for (int uu=0;uu<10000;uu++)
{
if (send(evs[ii].data.fd,"dssssssssssssssssssssdddddddddddddddd",30,0)<0)
if (errno==EAGAIN) break;
}
printf("写完。\n");
}
}
}
return 0;
}
int initserver(int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
perror("socket() failed"); return -1;
}
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
{
perror("bind() failed"); close(sock); return -1;
}
if (listen(sock,5) != 0 )
{
perror("listen() failed"); close(sock); return -1;
}
return sock;
}