目录
一、概述
二、使用
三、API
3.1 epoll_create(int size)
3.2 epoll_ctl(int epfd,int op, int fd. struct epoll_event *event)
3.3 epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout)
3.4 *ssize_t read(int fd, void buf, size_t count);
3.5 *ssize_t write(int fd, const void buf, size_t count);
3.6writev与readv
四、通过一个对一个错误的程序进行修改学习epoll
一般学习某种技术可以白嫖的最全的途径就是百度
epoll_百度百科
不过百度的东西都比较浓缩,有时候 ”不是人话“(调侃罢了,确实全但是不好理解),
下面我来学习一下,虽然以前学过,可惜没好好学。下图是他们三个的工作流程
epoll的调用过程
下面文章参考了以下博文:
EPOLL高并发服务器_epoll服务器_OwnResponsibility的博客-CSDN博客
IO多路复用之epoll_陈子青 - See的博客-CSDN博客_epoll多路复用
epoll详解_每天进步一奈奈的博客-CSDN博客_epoll
深入了解epoll模型(特别详细) - 知乎
一、概述
Linux内核为处理大批量文件描述符而作了改进的poll
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
二、使用
epoll有两种工作状态:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
三、API
3.1 epoll_create(int size)
---------------------------------------------------------------------------------------------------------------------------------
作用:创建一个红黑树模型的实例,用于管理待检测的文件描述符的集合。
size: 监听数目
监听文件描述符的个数,与内存大小有关。 Linux 内核 2.6.8 版本以后,这个参数是被忽略的,只需要指定一个大于 0 的数值就可以了。
函数返回值:
失败:返回 - 1
成功:返回一个有效的文件描述符,通过这个文件描述符就可以访问创建的 epoll 实例了
---------------------------------------------------------------------------------------------------------------------------------
3.2 epoll_ctl(int epfd,int op, int fd. struct epoll_event *event)
---------------------------------------------------------------------------------------------------------------------------------
epfd:
为epoll_creat的句柄 --具体是那一课epoll树
epoll_create () 函数的返回值,通过这个参数找到 epoll 实例
op:
这是一个枚举值,控制通过该函数执行什么操作。
表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd)
EPOLL_CTL_MOD (修改已经注册的fd的监听事件)
EPOLL_CTL_DEL (从epfd删除一个fd)
fd:
文件描述符,即要添加 / 修改 / 删除的文件描述符
evnet:
epoll 事件,用来修饰第三个参数对应的文件描述符的,指定检测这个文件描述符的什么事件。
events:
委托 epoll 检测的事件
EPOLLIN:读事件,接收数据,检测读缓冲区,如果有数据该文件描述符就绪
(包括对端socket 正常关闭)
EPOLLOUT:写事件,发送数据,检测写缓冲区,如果可写该文件描述符就绪
EPOLLERR:异常事件(
表示对应的文件描述符发生错误)
data:
用户数据变量,这是一个联合体类型,通常情况下使用里边的 fd 成员,用于存储待检测的文件描述符的值,在调用 epoll_wait() 函数的时候这个值会被传出。
函数返回值:
失败:返回 - 1
成功:返回 0
---------------------------------------------------------------------------------------------------------------------------------
struct epoll_event{
_uint32_t events; /*Epoll events*/
epoll_data_t data; /*User data variable*/
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
3.3 epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout)
---------------------------------------------------------------------------------------------------------------------------------
events:
用来存内核得到事件的集合,传出参数,这是一个结构体数组的地址,里边存储了已就绪的文件描述符的信息。
maxevents:
告知内核这个events有多大, 这个maxevents的值不能大于创建epoll_create() 时的size,
修饰第二个参数,结构体数组的容量(元素个数)
timeout:
如果检测的 epoll 实例中没有已就绪的文件描述符,该函数阻塞的时长,单位 ms 毫秒
-1: 函数一直阻塞,直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞
0:函数不阻塞,不管 epoll 实例中有没有就绪的文件描述符,函数被调用后都直接返回
大于 0:如果 epoll 实例中没有已就绪的文件描述符,函数阻塞对应的毫秒数再返回
返回值:
成功返回有多少文件描述符就绪, 事件到时返回0. 出错时返回-1;
等于 0:函数是阻塞被强制解除了,没有检测到满足条件的文件描述符
大于 0:检测到的已就绪的文件描述符的总个数
失败:返回 - 1
epfd:epoll_create () 函数的返回值,通过这个参数找到 epoll 实例
---------------------------------------------------------------------------------------------------------------------------------
3.4 *ssize_t read(int fd, void buf, size_t count);
---------------------------------------------------------------------------------------------------------------------------------
参数:
fd:文件描述符
buf: 读取数据的缓冲区
count: 缓冲区大小
返回值:
返回值>0; 实际读到的字节数 buf=1024
两种情况:
返回值==buf;
返回值<buf;
返回值==0;
数据读完(读到文件, 管道,socket 末尾—对端关闭)
返回值==-1; 异常
1. errno==EINTR 被信号中断, 重启/quit
2. errno==EAGAIN;(EWOULLDBLOCK) 非阻塞方式读取, 并没有数据
3. errno==ECONNRESET; 说明连接被重置,需要close(),移除监听队列
4. 其他值,出现错误. ------perror_exit
---------------------------------------------------------------------------------------------------------------------------------
3.5 *ssize_t write(int fd, const void buf, size_t count);
---------------------------------------------------------------------------------------------------------------------------------
参数:
fd:文件描述符
buf: 待写数据的缓冲区
count: 缓冲区大小
返回值:
成功: 写入字节数
失败: -1, 设置
返回值==buf;
返回值<buf;
相比read和write一次只能下发一个IO请求,并将数据读写到一个指定的缓冲区,readv和writev可以一次性指定多个缓冲区进行读写。
---------------------------------------------------------------------------------------------------------------------------------
3.6writev与readv
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
其中 const struct iovec *iov 设置多个缓冲区
struct iovec{
void *iov_base; /*starting address of buffer*/
size_t iov_len; //size of buffer;
}
writev和readv 都是以 iov[0],iov[1],…,iov[n-1] 的順序输出或者读入的, 返回值是输出|读入的字节总数长度之和.
四、通过一个对一个错误的程序进行修改学习epoll
客户端
#include "net.h"
void usage(char *s)
{
printf("%s addr port\n",s);
printf("addr is dotted address\n");
printf("port should above 5000\n");
}
int main(int argc,char *argv[])
{
//0 Check input parameter.
if(argc!=3)
{
usage(argv[0]);
exit(1);
}
int port=-1;
port=atoi(argv[2]);
if(port<5000)
{
usage(argv[0]);
exit(1);
}
//1 Socket.
int cfd=-1;
cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd<0)
{
perror("socket");
exit(1);
}
//2 Connect.
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(port);
if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr)!=1)
{
perror("inet_pton");
exit(1);
}
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
perror("connect");
exit(1);
}
printf("Client(%s:%d):Connect!\n",argv[1],port);
//3 Write/read.
char buf[BUFSIZ];
int ret=-1;
while(1)
{
#if 1
bzero(buf,BUFSIZ);
printf("2");
do{
ret=read(0,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
printf("2");
if(!ret)
{
continue;
}
if(ret<0)
{
perror("read");
continue;
}
if(write(cfd,buf,strlen(buf))<0)
{
perror("write to socket");
continue;
}
if(!strncmp(buf,QUIT,strlen(buf)))
{
printf("Quit!\n");
break;
}
#endif
bzero(buf,BUFSIZ);
do{
ret=read(cfd,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
if(!ret)
{
continue;
}
if(ret<0)
{
perror("read from socket");
continue;
}
printf("server said:%s\n",buf);
if(!strncmp(buf+strlen(SERVER_STR),QUIT,strlen(QUIT)))
{
printf("Quit!\n");
break;
}
}
//4 Close.
close(cfd);
}
服务器:
#include "net.h"
int main()
{
//1 Socket.
int lfd=-1;
lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd<0)
{
printf("perror");
exit(1);
}
//2 Bind.
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(SERV_PORT);
sin.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(lfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
perror("bind");
exit(1);
}
//3 Listen.
if(listen(lfd,BACKLOG)<0)
{
perror("listen");
exit(1);
}
//4 Create epoll.
int epfd=epoll_create(1024);
if(epfd==-1)
{
perror("epoll_create");
exit(0);
}
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
struct epoll_event evs[1024];
int size=sizeof(evs)/sizeof(evs[0]);
//5 Detect.
while(1)
{
int num=epoll_wait(epfd,evs,size,-1);
printf("num=%d\n",num);
for(int i=0;i<num;++i)
{
int fd=evs[i].data.fd;
if(fd==lfd)//fd for listening
{
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
int cfd=-1;
cfd=accept(lfd,(struct sockaddr*)&cin,&addrlen);
if(cfd<0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
if(inet_ntop(AF_INET,(void*)&cin.sin_addr,ipv4_addr,sizeof(cin))<0)
{
perror("inet_ntop");
exit(1);
}
printf("The client is:%s:%d\n",ipv4_addr,ntohs(cin.sin_port));
//struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=cfd;
epoll_ctl(cfd,EPOLL_CTL_ADD,cfd,&ev);
// continue;
}
else//fd for communitcation
{
while(1)
{
char buf[BUFSIZ];
char resp_buf[BUFSIZ+10];
int ret=-1;
bzero(buf,BUFSIZ);
do
{
ret=read(fd,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
if(ret<0)
{
perror("read");
continue;
}
if(!ret)//Client closed.
{
printf("Client is unconnected.\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
break;
}
printf("read:%s\n",buf);
if(!strncasecmp(buf,QUIT,strlen(QUIT)))
{
printf("Quit.\n");
break;
}
int re=-1;
bzero(resp_buf,BUFSIZ+10);
strncpy(resp_buf,SERVER_STR,strlen(SERVER_STR));
strcat(resp_buf,buf);
do
{
re=write(fd,resp_buf,strlen(resp_buf));
}while(re<0 && EINTR==errno);
}
}
}
}
return 0;
}
共用头文件
#ifndef _NET_H_
#define _NET_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/epoll.h>
#define SERV_PORT 5002
#define SERV_ADDR "192.168.112.130"
#define BACKLOG 5
#define QUIT "quit"
#define SERVER_STR "SERVER:"
#endif
可以连接成功但是不能发消息
发现一个问题,没有回车就打印不到屏幕上
缓冲区应该是出现了问题,不加回车不能打印,这个现象 在连接后才产生,初步判断是服务器有问题。
服务器退出了客户端居然没断开,这就更神奇了。
bind:address already in use的深刻教训以及解决办法_程序猿小泽的博客-CSDN博客
客户端现在是对的了,之前一定会阻塞在读取服务器程序那里注释掉就没问题了。这个退出程序也有问题,我改了一下现在没问题了
#include "net.h"
void usage(char *s)
{
printf("%s addr port\n",s);
printf("addr is dotted address\n");
printf("port should above 5000\n");
}
int main(int argc,char *argv[])
{
//0 Check input parameter.
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
int port = -1;
port = atoi(argv[2]);
if(port < 5000)
{
usage(argv[0]);
exit(1);
}
//1 Socket.
int cfd=-1;
cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd<0)
{
perror("socket");
exit(1);
}
//2 Connect.
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family=AF_INET;//TCP or UDP
sin.sin_port = htons(port);
if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr)!=1)
{
perror("inet_pton");
exit(1);
}
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
perror("connect");
exit(1);
}
printf("Client(%s:%d):Connect!\n",argv[1],port);
//3 Write/read.
char buf[BUFSIZ];
int ret=-1;
fd_set rset;
while(1)
{
#if 1
bzero(buf,BUFSIZ);
do{
ret=read(0,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
if(!ret)
{
continue;
}
if(ret<0)
{
perror("read");
continue;
}
if(write(cfd,buf,strlen(buf))<0)
{
perror("write to socket");
continue;
}
if (!strncasecmp (buf, "quit", strlen ("quit"))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
#if 0
if(!strncmp(buf,"quit",strlen(buf)))
{
printf("Quit!\n");
break;
}
#endif
#endif
#if 0
bzero(buf,BUFSIZ);
do{
ret=read(cfd,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
if(!ret)
{
continue;
}
if(ret<0)
{
perror("read from socket");
continue;
}
printf("server said:%s\n",buf);
if(!strncmp(buf+strlen(SERVER_STR),QUIT,strlen(QUIT)))
{
printf("Quit!\n");
break;
}
#endif
}
//4 Close.
close(cfd);
}
好吧最后老师出马,创建事件的文件描述符写错了。(呜呜呜呜)
客户端程序没有问题,我测试用的服务器没有回传才会卡,服务器程序把文件描述符换了,然后去掉那个死循环:
#include "net.h"
int main()
{
//1 Socket.
int lfd=-1;
lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd<0)
{
printf("perror");
exit(1);
}
//2 Bind.
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(SERV_PORT);
sin.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(lfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
perror("bind");
exit(1);
}
//3 Listen.
if(listen(lfd,BACKLOG)<0)
{
perror("listen");
exit(1);
}
//4 Create epoll.
int epfd = epoll_create(1);
if(epfd == -1)
{
perror("epoll_create");
exit(0);
}
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
struct epoll_event evs[1024];
int size = sizeof(evs)/sizeof(evs[0]);
//5 Detect.
while(1)
{
int num = epoll_wait(epfd,evs,size,-1);
if(num == -1){
perror("epoll_wait");
exit(0);
}
printf("num=%d\n",num);
for(int i = 0;i < num;++i)
{
if(!(evs[i].events & EPOLLIN))
continue;
int fd = evs[i].data.fd;
if(fd == lfd)//fd for listening
{
#if 1
printf("1\n");
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
int cfd=-1;
cfd = accept(lfd,(struct sockaddr*)&cin,&addrlen);
if(cfd<0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
if(inet_ntop(AF_INET,(void*)&cin.sin_addr,ipv4_addr,sizeof(cin))<0)
{
perror("inet_ntop");
exit(1);
}
printf("The client is:%s:%d\n",ipv4_addr,ntohs(cin.sin_port));
//struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
#else
cfd = accept(events[i].data.fd,(struct sockaddr *)&clientAddr, &addrlen);
printf("new client %s,port=%d \n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
event.events = EPOLLIN;
event.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
// continue;
#endif
}
else//fd for communitcation
{
//while(1)
//{
printf("2\n");
char buf[BUFSIZ];
char resp_buf[BUFSIZ+10];
int ret=-1;
bzero(buf,BUFSIZ);
do
{
ret=read(fd,buf,BUFSIZ-1);
}while(ret<0 && EINTR==errno);
if(ret<0)
{
perror("read");
continue;
}
if(!ret)//Client closed.
{
printf("Client is unconnected.\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
break;
}
printf("read:%s\n",buf);
if(!strncasecmp(buf,QUIT,strlen(QUIT)))
{
printf("Quit.\n");
break;
}
int re=-1;
bzero(resp_buf,BUFSIZ+10);
strncpy(resp_buf,SERVER_STR,strlen(SERVER_STR));
strcat(resp_buf,buf);
do
{
re=write(fd,resp_buf,strlen(resp_buf));
}while(re<0 && EINTR==errno);
//}
}
#if 0
else{ //否则,是连接的客户端事件,读取数据
rdlen = read(events[i].data.fd,buf,BUFLEN);
if(rdlen>0){
printf("read buf=%s\n",buf);
}else if (rdlen==0){//客户连接中断,删除epoll监听的客户端文件描述符
event.events = EPOLLIN;
event.data.fd = events[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
}
#endif
}
}
return 0;
}
我终于成为某人不可或缺的人了,人逢喜事精神爽呀。正好拓展坞到了,开整。