IO多路復用通常用于處理單進程高并發,在Linux中,一切皆文件,一個socket連接會對應一個文件描述符,在監聽多個文件描述符的狀態應用中epoll相對于select和poll效率更高
epoll本質是系統在內核維護了一顆紅黑樹,監聽的文件描述符會作為新的節點插入紅黑樹,epoll會等待有狀態變化的節點記錄在鏈表里,然后拷貝到用戶所給的數組里面返回出來
以下是一個獨立的服務端代碼,可以補充業務代碼進行具體使用
sever.h
//
// Created by YEZI on 2024/5/24.
//#ifndef SEVER_H
#define SEVER_H
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sstream>
#define MAX_EVENTS 8
#define PORT 8888
#define BUFFER_SIZE 512
#define BACKLOG_SIZE 16 // 請求隊列最大長度class Sever {
private:uint16_t port;int server_fd = -1;int epoll_fd = -1;sockaddr_in server_addr{}, client_addr{};socklen_t client_addr_len = sizeof(client_addr);epoll_event event{}, events[MAX_EVENTS]{};public:explicit Sever(uint16_t port = PORT): port(port) {// 創建套接字// AF_INET : 表示使用 IPv4 地址 可選參數// SOCK_STREAM 表示使用面向連接的數據傳輸方式,// IPPROTO_TCP 表示使用 TCP 協議server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (server_fd == -1) {std::cerr << "Failed to create socket\n";exit(EXIT_FAILURE);}// 設置服務器地址server_addr.sin_family = AF_INET; // IPv4server_addr.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY:0.0.0.0 表示本機所有IP地址server_addr.sin_port = htons(PORT);// 綁定套接字if (bind(server_fd, (sockaddr *) &server_addr, sizeof(server_addr)) == -1) {std::cerr << "Failed to bind socket\n";exit(EXIT_FAILURE);}// 監聽套接字if (listen(server_fd, BACKLOG_SIZE) == -1) {std::cerr << "Failed to listen on socket\n";exit(EXIT_FAILURE);}// 創建 epoll 實例epoll_fd = epoll_create1(0); // flag設置為0同epoll_create()if (epoll_fd == -1) {std::cerr << "Failed to create epoll instance\n";exit(EXIT_FAILURE);}// 將服務器套接字添加到 epoll 實例中event.events = EPOLLIN | EPOLLET; // 監聽事件類型 EPOLLIN表示有數據可讀 EPOLLET表示邊緣觸發僅在狀態變化時通知event.data.fd = server_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {std::cerr << "Failed to add server socket to epoll\n";exit(EXIT_FAILURE);}std::cout << "Server started. Listening on port " << PORT << "...\n";}void run() {while (true) {// 使用 epoll 等待事件 參數timeout為等待時間,-1等死int num_ready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (num_ready == -1) {std::cerr << "Error in epoll_wait\n";exit(EXIT_FAILURE);}for (int i = 0; i < num_ready; ++i) {if (events[i].data.fd == server_fd) {// 有新的連接請求int client_fd = accept(server_fd, (sockaddr *) &client_addr, &client_addr_len);if (client_fd == -1) {std::cerr << "Failed to accept client connection\n";continue;}std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr)<< ":" << ntohs(client_addr.sin_port) << std::endl;// 將新的客戶端套接字添加到 epoll 實例中event.events = EPOLLIN | EPOLLET;event.data.fd = client_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {std::cerr << "Failed to add client socket to epoll\n";exit(EXIT_FAILURE);}} else {// 有數據到達現有客戶端套接字char buffer[BUFFER_SIZE]{};ssize_t bytes_received = recv(events[i].data.fd, buffer, BUFFER_SIZE, 0);if (bytes_received <= 0) {if (bytes_received == 0) {// 客戶端關閉連接std::cout << "Client disconnected\n";} else {std::cerr << "Error in recv\n";}// 關閉客戶端套接字,并從 epoll 實例中移除close(events[i].data.fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);} else {// 接收到數據,原樣發送回客戶端,此處為業務代碼補充處send(events[i].data.fd, buffer, bytes_received, 0);std::istringstream iss(buffer);std::string data;while (iss >> data) {std::cout << data << ' ';}std::cout<<std::endl;}}}}}~Sever() {// 關閉服務器套接字和 epoll 實例close(server_fd);close(epoll_fd);}
};
#endif //SEVER_H
main.cpp
#include"sever.h"
int main() {Sever sever;sever.run();
}
簡單測試服務端,打開Linux終端,用一下命令連接服務器后即可傳輸數據
telnet localhost 8888