网络IO 多路IO复用
select
poll
epoll
epoll用于多路io复用,具体是如何实现的?
内部使用
epoll_create()
epoll_ctl()
epoll_wait()
3个函数来实现 多路io复用
epoll模型的底层是用红黑树来处理的
其中的水平触发和边沿触发的区别?
ET 是recvbuf 从没有数据到有数据,就会触发
LT 是 recvbuf里面有数据 就会一直触发
大块数据 用 LT
小块数据 用 ET
以recvbuf为界限的
epoll的实现流程
1、创建sockfd
socket(AF_INET,SOCK_STREAM,0)
2、初始化addr 属性 struct sockaddr_in (sa_handler ,sa_port,sa_addr.s_addr)
3、bind sockfd
4、listen 设置最大连接数
5、创建 epoll的句柄,epollfd,初始化专门用于增、删、改的事件的ev属性,第一个ev 是用于连接的,用水平触发的方式,同时创建events事件数组 ,struct epoll_event
6、开始使用epoll_wait阻塞等待事件的发生,参数是epollfdepoll句柄,作为传出参数的event,epoll的大小 ,以及时间 ,返回值为 nready,实际的触发事件数
7、遍历实际触发的事件数,判断是触发的是sockfd链接,还是客户端的数据通信 事件,
若是sockfd的连接事件,则定义好客户端的数据结构和数据长度,作为传出参数,传入到 accept中,返回客户端的fd,将fd加入到epoll 红黑树中
若是客户端的fd发送数据的事件,则使用read/recv函数读取该fd上的数据,并进行数据处理,
判断数据通信的时候服务器 使用 recv/read 读取数据,放入到buff中,
如果函数返回值是 <0 ,则需要判断,错误号若为 EAGAIN || EWOULDBLOCK -- 被信号打断了,未读取到数据,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd
如果函数返回值是 == 0,说明客户端断开连接了,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd
如果函数返回值是 > 0,说明正常读取到数据,开始对数据进行处理
若处理完毕后,将该客户端的fd移出epoll红黑树。
最后 关闭sockfd 套接字 close(sockfd)
编码
写一个简单的demo,来感受一下epoll的使用
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <pthread.h> #include <errno.h> #include <fcntl.h> #include <sys/epoll.h> #include <sys/poll.h> #include <unistd.h> #define BUFFER_LENGTH 1024 #define EPOLL_LENGTH 1024 int main(int argc,char * argv[]) { //端口从命令行输入 如 : ./server 8888 if(argc < 2) { perror("please input port nunber"); return -1; } //建立tcp套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("socket"); return -1; } //初始化属性 struct sockaddr_in seraddr; memset(&seraddr,0,sizeof(seraddr)); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(atoi(argv[1])); seraddr.sin_addr.s_addr = INADDR_ANY; //bind int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in)); if(ret < 0){ perror("bind"); return -1; } //设置最大连接数 ret = listen(sockfd,10); if(ret < 0){ perror("listen"); return -1; } //epoll //创建epoll fd int epollfd = epoll_create(EPOLL_LENGTH); //这个参数 只有 0 和大于0 的区别,作用不大 struct epoll_event ev,event[EPOLL_LENGTH]={0}; ev.events = EPOLLIN; //用于连接的,最好使用水平触发,因为水平触发的时候,有数据的时候会一直触发 ev.data.fd = sockfd; epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&ev); //开始监听 while(1){ int nready = epoll_wait(epollfd,event,EPOLL_LENGTH,-1); if (nready == -1) { printf("epoll_wait error\n"); break; } for(int i = 0;i<nready;i++){ //如果是发起连接的 if(event[i].data.fd == sockfd ){ //准备好客户端的数据结构 struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_len = sizeof(client_addr); int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if (clientfd <= 0) continue; char str[INET_ADDRSTRLEN] = {0}; printf("recvived from %s at port %d, sockfd:%d, clientfd:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port), sockfd, clientfd); //将客户端的fd加入到event中 ev.events = EPOLLIN | EPOLLET;//通信的时候,可以选择水平或者是边沿触发,大块数据用边沿触发,小块数据用水平触发 ev.data.fd = clientfd; epoll_ctl(epollfd,EPOLL_CTL_ADD,clientfd,&ev); } //如果是进行数据通信的 else{ int clientfd = event[i].data.fd; char buffer[BUFFER_LENGTH] = {0}; int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); //可能是因为信号打断,因此要判断一下错误号 if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("read all data"); } close(clientfd); //将该fd 从 epoll中删除 ev.events = EPOLLIN | EPOLLET; ev.data.fd = clientfd; epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev); //客户端关闭连接 } else if (ret == 0) { printf(" disconnect %d\n", clientfd); close(clientfd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = clientfd; epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev); break; } else { printf("Recv: %s, %d Bytes\n", buffer, ret); } } } } close(sockfd); return 0; }
编译:
gcc -o server_epoll server_epoll.c