下面是epoll的实现图
epoll 是一个 Linux 下用于高效 I/O 多路复用的机制,它提供了一组系统调用函数来管理事件和文件描述符。以下是 epoll 的主要函数:
1.epoll_create:创建 epoll 实例
1.epoll_create:创建 epoll 实例
int epoll_create(int size);
创建一个新的 epoll 实例,返回一个文件描述符。
size 参数指定了内核应该为该实例预分配的文件描述符表的大小。这个参数在实际使用中通常可以设置为一个较大的值,表示能够监视的文件描述符的最大数量。
2.epoll_ctl:注册、修改或删除事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd 是 epoll 实例的文件描述符。
op 指定操作类型,可以是 EPOLL_CTL_ADD(添加新的文件描述符)、EPOLL_CTL_MOD(修改已注册的文件描述符的事件)或 EPOLL_CTL_DEL(从 epoll 实例中删除文件描述符)。
fd 是要操作的目标文件描述符。
event 是一个结构体,用于描述文件描述符上的事件,包括事件类型和关联的用户数据。
3.struct epoll_event 结构体
原文链接:https://blog.csdn.net/festaw/article/details/135173172
struct epoll_event { uint32_t events; // 表示注册的事件类型 epoll_data_t data; // 用户数据,通常是文件描述符或其他标识符 };
- events 字段表示注册的事件类型,可以是 EPOLLIN(可读事件)、EPOLLOUT(可写事件)、EPOLLERR(错误事件)等。
- data 字段包含与文件描述符关联的用户数据。
4.epoll_wait:等待事件的发生
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd 是 epoll 实例的文件描述符。
events 是用于存储事件的数组。
maxevents 指定了 events 数组的大小,表示最多等待多少个事件。
timeout 指定等待的超时时间,单位是毫秒。如果设置为 -1,表示无限等待,直到有事件发生;如果设置为 0,表示非阻塞模式,立即返回。
返回就绪的文件描述符数量,如果超时则返回 0,如果出错返回 -1。
这些函数共同构成了 epoll 的基本操作。通过这些函数,应用程序可以实现对大量文件描述符的高效管理和事件处理。
这里有个坏习惯,代码没有封装。。。看着逻辑不是很清晰
下一期更新粘包、分包问题
server:(改端口和ip)
#include <iostream> #include <string> #include <sys/epoll.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <map> #include <arpa/inet.h> using namespace std; //最大连接数 const int MAX_CONN = 1024; //保存客户端的信息 struct Client { int sockfd; string name;//名字 }; int main() { // 创建epoll实例 int epld = epoll_create1(0); if (epld < 0) { perror("epoll create error\n"); return -1; } //创建监听的socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket error\n"); return -1; } //绑定本地ip和端口 struct sockaddr_in addr; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_family = AF_INET; addr.sin_port = htons(9999); int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); if (sockfd < 0) { perror("bind error\n"); return -1; } //监听客户端 ret = listen(sockfd, 1024); if (ret < 0) { perror("listen error\n"); return -1; } //将监听的socket加入epoll struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = sockfd; ret = epoll_ctl(epld, EPOLL_CTL_ADD, sockfd, &ev); if (ret < 0) { perror("epoll_ctl error\n"); return -1; } //保存客户端信息 map<int, Client> clients; //循环监听 while (1) { struct epoll_event evs[MAX_CONN]; int n = epoll_wait(epld, evs, MAX_CONN, -1); if (n < 0) { perror("epoll_wait error\n"); return -1; } for (int i = 0; i < n; i++) { int fd = evs->data.fd; //如果是监听的fd收到消息,那么表示有客服端进行链接了 if (fd == sockfd) { struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addr_len); if (client_sockfd < 0) { perror("accept error\n"); continue; } //将客户端的socket加入epoll struct epoll_event ev_client; ev_client.events = EPOLLIN;//监测客户端有没有消息过来 ev_client.data.fd = client_sockfd; ret = epoll_ctl(epld,EPOLL_CTL_ADD, client_sockfd,&ev_client); if (ret < 0) { perror("epoll_ctl error\n"); break; } char ip_str[16] = ""; inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip_str,16); printf("%s正在链接...\n", ip_str); //保存该客服端的信息 Client client; client.sockfd = client_sockfd; client.name = ""; clients[client_sockfd] = client; } else//如果是客户端消息 { char buffer[1024]; size_t n = read(fd, buffer, 1024); if (n < 0) { //处理错误 break; } else if (n == 0) { //客服端断开连接 close(fd); epoll_ctl(epld, EPOLL_CTL_ADD, fd, 0); clients.erase(fd); } else { string msg(buffer, n); //如果该客户端name为空,说明该消息为这个客户端的用户名 if (clients[fd].name == "") { clients[fd].name = msg; } else//否则是聊天消息 { string name = clients[fd].name; for (auto& c : clients) { if (c.first != fd) { write(c.first, ('[' + name + ']' + ": " + msg).c_str(), msg.size() + name.size() + 4); } } } } } } } //关闭epoll实例 close(epld); close(sockfd); }
client:(改端口和ip)
#include <stdio.h> #include <WinSock2.h> #include <Windows.h> #include <WS2tcpip.h> #pragma comment(lib,"Ws2_32.lib") #define BUF_SIZE 1024 char szMsg[BUF_SIZE]; unsigned SendMsg(void* arg) { SOCKET sock = *((SOCKET*)arg); while (1) { scanf_s("%s", szMsg,999); if (!strcmp(szMsg, "QUIT\n") || !strcmp(szMsg, "quit\n")) { closesocket(sock); exit(0); } send(sock, szMsg, strlen(szMsg), 0); } return 0; } unsigned RecvMsg(void* arg) { SOCKET sock = *((SOCKET*)arg); char msg[BUF_SIZE]; while (1) { int len = recv(sock, msg, sizeof(msg) - 1, 0); if (len == -1) { return -1; } msg[len] = '\0'; printf("%s\n", msg); } return 0; } int main() { //初始化socket环境 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return -1; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return -1; } //创建socket SOCKET hSock; //SOCKADDR_IN servAdr; hSock = socket(AF_INET, SOCK_STREAM, 0); //绑定端口 SOCKADDR_IN servAdr; memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_port = htons(9999); inet_pton(AF_INET, "**********", &servAdr.sin_addr); //连接服务器 if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) { printf("connect error: %d", GetLastError()); return -1; } else { printf("欢迎来到py交易私人聊天室,请输入你的聊天用户名:"); } //循环发消息 HANDLE hSendHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void*)&hSock, 0, NULL); //循环收消息 HANDLE hRecvHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL); //等待线程结束 WaitForSingleObject(hSendHand, INFINITE); WaitForSingleObject(hRecvHand, INFINITE); closesocket(hSock); WSACleanup(); }