网络编程epoll学习
1、概述
epoll使用的基本步骤
创建epoll文件描述符
调用epoll_create
函数生成epoll实例,返回一个文件描述符(epollfd),该描述符用于后续操作。
注册监控事件
通过epoll_ctl
将需要监控的socket文件描述符添加到epoll实例中,同时指定关注的事件类型(如可读、可写或异常事件)。支持动态增删改监控对象。
等待事件触发
使用epoll_wait
阻塞等待注册的事件发生。该函数返回就绪的文件描述符列表,应用程序可遍历处理这些事件,实现高效I/O多路复用。
可以思考下 select 和 epoll的区别是什么?
1、epoll没有数量限制,select受FD_SETSIZE限制
2、select需轮询所有fd,查找有效fd,epoll_wait直接返回有效的fd
3、epoll内部使用的红黑树结构,select使用的数组或位运算
2、使用epoll编写简单demo代码
#define _GNU_SOURCE#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#define SERVER_IP "192.168.202.224"
#define SERVER_PORT 8082
#define INVALID_HANDLE_VALUE (-1)
#define LISTEN_BACKLOG (1024)int CreateListenfd();
int HandleBind(const int listen_fd, const char* ip, const int port);
int HandleListen(const int listen_fd);
int HandleRecvEvent(int client_fd, int epoll_fd);int main()
{int listen_fd = CreateListenfd();if(-1 == listen_fd){return -1;};if(0 != HandleBind(listen_fd, SERVER_IP, SERVER_PORT)){close(listen_fd);return -1;}if(0 != HandleListen(listen_fd)){close(listen_fd);return -1;}int epoll_fd = epoll_create(1);if(epoll_fd == -1){printf("epoll_create failed errno %s\n", strerror(errno));return -1;}// 把监听socket绑定到epoll上struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = listen_fd;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1){printf("epoll_ctrl faield err:%s\n", strerror(errno));return -1;}while(1){struct epoll_event evs[1024];int n = epoll_wait(epoll_fd, evs, 1024, -1);for(int i = 0; i < n; ++i){if(evs[i].events & EPOLLIN){if(evs[i].data.fd == listen_fd){struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);int client_fd = accept4(listen_fd, (struct sockaddr*)&client_addr, &addrlen, SOCK_NONBLOCK);if(client_fd < 0){if(errno == EAGAIN || errno == EWOULDBLOCK){printf("accept4 %s\n", strerror(errno));}else{printf("accept4 failed %s\n", strerror(errno));}}else{ev.events = EPOLLIN;ev.data.fd = client_fd;if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1){printf("epoll_ctrl client_fd:%d faield err:%s\n", client_fd, strerror(errno));close(client_fd);}}}else{HandleRecvEvent(evs[i].data.fd, epoll_fd);}}}}return 0;
}int CreateListenfd()
{int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if(sockfd == INVALID_HANDLE_VALUE){printf("socket creation failed errno:%s\n", strerror(errno));return -1;}int opt = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));return sockfd;
}int HandleBind(const int listen_fd, const char* ip, const int port)
{// 绑定struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = inet_addr(ip);servAddr.sin_port = htons(port);if(-1 == bind(listen_fd, (struct sockaddr*)&servAddr, sizeof(servAddr))){printf("bind error %s\n", strerror(errno));return -1;}return 0;
}int HandleListen(const int listen_fd)
{// 监听if(-1 == listen(listen_fd, LISTEN_BACKLOG)){printf("listen error %s\n", strerror(errno));return -1;}// 获取监听端口struct sockaddr_in bound_addr;socklen_t len = sizeof(bound_addr);if(getsockname(listen_fd, (struct sockaddr*)&bound_addr, &len) == 0){printf("server bound port:%d\n", ntohs(bound_addr.sin_port));}else{printf("getsockname failed\n");}return 0;
}int HandleRecvEvent(int client_fd, int epoll_fd)
{char buf[1024] = {0};int n = recv(client_fd, buf, sizeof(buf), 0);if( n < 0){// 发生错误,判断errno是否为EAGAIN EWOULDBLOCKif(errno != EAGAIN && errno != EWOULDBLOCK){printf("Client on fd %d disconnected\n", client_fd);}}else if (n == 0){//对端关闭了连接,从epollfd上移除clientfdepoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);close(client_fd);}else{// 收到数据buf[n] = '\0';printf("recv data:%s\n", buf);if(send(client_fd, buf, n, 0) == -1){if(errno == EAGAIN || errno == EWOULDBLOCK){printf("Error %s\n", strerror(errno));}else{//发送失败,从epollfd上移除clientfd,关闭连接epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);close(client_fd);}}}
}
学习链接:https://github.com/0voice