Linux epoll与socket

epoll的简介

epoll 是 Linux 提供的一种高效的 I/O 多路复用接口,它是 select 和 poll 的增强版,用于同时监视多个文件描述符(FDs),以确定哪些 FDs 已经准备好进行 I/O 操作。epoll 相较于 select 和 poll 更加高效,因为它不需要在每次调用时重复传递和检查整个文件描述符集合,而是通过事件回调机制来通知进程哪些 FDs 已经就绪。

epoll 的核心优势

  1. 高效的事件通知机制:epoll 通过在内核中维护一个事件表,当 socket 状态发生变化时,epoll 能够立即通知应用程序,而不需要应用程序不断轮询检查。
  2. 支持大量并发连接:epoll 没有像 select 那样的文件描述符数量限制,因此能够处理大量的并发连接,这对于高并发的网络服务来说非常重要。
  3. 资源消耗低:epoll 通过使用内存映射(mmap)技术,减少了用户态和内核态之间的数据交换,从而降低了资源消耗。
  4. 两种触发模式:epoll 支持水平触发(LT)和边缘触发(ET)两种模式。LT 模式下,只要 socket 状态就绪,epoll 就会不断通知应用程序;而 ET 模式下,socket 状态变化时 epoll 只通知一次,这有助于减少不必要的通知,提高效率。

epoll的使用方法

  1. 创建 epoll 实例:使用 epoll_create 函数创建一个 epoll 实例,它返回一个文件描述符,用于后续的 epoll 操作。
  2. 添加监控的 socket:使用 epoll_ctl 函数将需要监控的 socket 添加到 epoll 实例中。
  3. 等待事件通知:使用 epoll_wait 函数等待事件发生,当有 socket 就绪时,epoll_wait 会返回,应用程序可以处理这些就绪的 socket。
  4. 处理就绪的 socket:应用程序根据 epoll_wait 返回的事件,进行相应的读写操作。

epoll与socket的关系

epoll 与 socket 的关系在于,socket 是网络通信的端点,而 epoll 是用来高效地管理多个 socket 连接的机制。在网络服务器中,通常会创建多个 socket 来处理客户端的连接请求,而 epoll 则用于高效地监控这些 socket 的状态,以便及时响应。

epoll监听socket的示例

server.cpp
#include <iostream>
#include <vector>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>

const int MAX_EVENTS = 10;
const int LISTEN_PORT = 8080; // 监听端口
const char* LISTEN_IP = "127.0.0.1"; // 监听IP

// 创建并绑定套接字
int create_and_bind() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        std::cerr << "Failed to create socket" << std::endl;
        return -1;
    }

    int opt = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
        std::cerr << "setsockopt(SO_REUSEADDR) failed" << std::endl;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(LISTEN_IP);
    addr.sin_port = htons(LISTEN_PORT);

    if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        std::cerr << "Failed to bind" << std::endl;
        close(listen_fd);
        return -1;
    }

    return listen_fd;
}

int main() {
    // 创建并绑定套接字
    int listen_fd = create_and_bind();
    if (listen_fd == -1) {
        return -1;
    }

    // 监听套接字
    if (listen(listen_fd, SOMAXCONN) < 0) {
        std::cerr << "Failed to listen" << std::endl;
        close(listen_fd);
        return -1;
    }

    // 创建epoll实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        std::cerr << "Failed to create epoll instance" << std::endl;
        close(listen_fd);
        return -1;
    }

    // 将监听套接字添加到epoll
    struct epoll_event event;
    event.data.fd = listen_fd;
    event.events = EPOLLIN;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {
        std::cerr << "Failed to add listen_fd to epoll" << std::endl;
        close(listen_fd);
        close(epoll_fd);
        return -1;
    }

    // 定义事件数组
    struct epoll_event events[MAX_EVENTS];

    // 等待并处理事件
    while (true) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            std::cerr << "epoll_wait error" << std::endl;
            break;
        }

        for (int i = 0; i < nfds; ++i) {
            if (events[i].events & EPOLLIN) {
                if (events[i].data.fd == listen_fd) {
                    // 接受新的连接
                    struct sockaddr_in client_addr;
                    socklen_t client_addr_len = sizeof(client_addr);
                    int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);
                    if (client_fd < 0) {
                        std::cerr << "Failed to accept connection" << std::endl;
                        continue;
                    }

                    const char* message = "from, server!";
                    if (send(client_fd, message, strlen(message), 0) < 0) {
                        std::cerr << "Failed to send message to the client" << std::endl;
                        close(client_fd);
                        return -1;
                    }

                    std::cout << "Accepted new connection on FD " << client_fd << std::endl;

                    // 添加新的客户端连接到epoll
                    event.data.fd = client_fd;
                    event.events = EPOLLIN;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                        std::cerr << "Failed to add client_fd to epoll" << std::endl;
                        close(client_fd);
                    }
                } else {
                    // 读取数据
                    char buffer[1024];
                    ssize_t count = read(events[i].data.fd, buffer, sizeof(buffer));
                    if (count < 0) {
                        std::cerr << "Error reading from socket" << std::endl;
                    } else if (count == 0) {
                        std::cout << "Socket " << events[i].data.fd << " disconnected" << std::endl;
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                        close(events[i].data.fd);
                    } else {
                        std::cout << "Data from socket " << events[i].data.fd << ": " << buffer << std::endl;
                    }
                }
            }
        }
    }

    // 清理
    close(listen_fd);
    close(epoll_fd);
    return 0;
}
client.cpp
#include <iostream>
#include <vector>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
const int MAX_EVENTS = 10;

// 函数用于创建并连接到服务器的套接字
int create_and_connect(const char* server_ip, int server_port) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "Failed to create socket" << std::endl;
        return -1;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

    if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Failed to connect to the server" << std::endl;
        close(sock);
        return -1;
    }

    std::cout << "Connected to server at " << server_ip << ":" << server_port << std::endl;

    // 设置非阻塞模式
    fcntl(sock, F_SETFL, O_NONBLOCK);

    return sock;
}

int main() {
    const char* server_ip = "127.0.0.1";
    const int server_port = 8080;
    const int num_clients = 5; // 假设我们有5个客户端连接

    // 创建epoll实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        std::cerr << "Failed to create epoll instance" << std::endl;
        return -1;
    }

    // 创建多个客户端连接并添加到epoll
    std::vector<int> client_sockets;
    for (int i = 0; i < num_clients; ++i) {
        int sock = create_and_connect(server_ip, server_port);
        if (sock < 0) {
            continue; // 错误处理可以更精细
        }

        client_sockets.push_back(sock);

        struct epoll_event event;
        event.data.fd = sock;
        event.events = EPOLLIN; // 我们对读事件感兴趣
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event) < 0) {
            std::cerr << "Failed to add socket to epoll" << std::endl;
            // 错误处理
        }
    }

    // 定义事件数组
    struct epoll_event events[MAX_EVENTS];

    // 等待并处理事件
    while (true) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            std::cerr << "epoll_wait error" << std::endl;
            break;
        }

        for (int i = 0; i < nfds; ++i) {
            if (events[i].events & EPOLLIN) {
                int sock = events[i].data.fd;
                char buffer[1024];
                ssize_t count = read(sock, buffer, sizeof(buffer));
                if (count < 0) {
                    std::cerr << "Error reading from socket" << std::endl;
                } else if (count == 0) {
                    std::cout << "Socket " << sock << " disconnected" << std::endl;
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
                    close(sock);
                } else {
                    std::cout << "Data from socket " << sock << ": " << buffer << std::endl;
                }
            }
        }
    }

    // 清理
    close(epoll_fd);
    return 0;
}

client与server通信的结果
$./server 
Accepted new connection on FD 5
Accepted new connection on FD 6
Accepted new connection on FD 7
Accepted new connection on FD 8
Accepted new connection on FD 9
$./client
Connected to server at 127.0.0.1:8080
Connected to server at 127.0.0.1:8080
Connected to server at 127.0.0.1:8080
Connected to server at 127.0.0.1:8080
Connected to server at 127.0.0.1:8080
Data from socket 4: from, server!
Data from socket 5: from, server!
Data from socket 6: from, server!

疑问

  1. 为什么服务端接收者的client端的fd为5,6,7,8,9. client端显示生成的fd为4,5,6,7,8?

在网络编程中,当客户端和服务端建立连接时,每个端点都会获得一个文件描述符(fd),用于标识和管理该连接。服务端接收到客户端的连接请求后,会调用 accept 函数来接受连接,并返回一个新的文件描述符,用于后续的读写操作。

服务端接收到的客户端文件描述符(例如5, 6, 7, 8, 9)和客户端生成的文件描述符(例如4, 5, 6, 7, 8)之间的关系如下:

  • 客户端文件描述符:当客户端调用 socket 函数创建一个套接字时,它会获得一个文件描述符,这个文件描述符用于标识客户端的套接字。客户端使用这个文件描述符来执行 connect 调用,尝试与服务端建立连接。
  • 服务端文件描述符:服务端监听某个端口,当 accept 被调用时,它会阻塞等待客户端的连接请求。一旦有客户端连接,accept 会返回一个新的文件描述符,这个文件描述符是服务端为这次连接创建的新套接字,专门用于与该客户端通信。
  • 文件描述符的编号:文件描述符是一个整数,用于索引进程的文件描述符表。表中的每个条目都包含了一个打开文件或套接字的状态信息。文件描述符的编号是操作系统动态分配的,它不一定需要在客户端和服务端之间保持一致。换句话说,客户端的文件描述符编号(如4, 5, 6, 7, 8)和服务端接收到的文件描述符编号(如5, 6, 7, 8, 9)可以是不同的,它们只是在各自进程的文件描述符表中的索引。
  • 文件描述符的分配:文件描述符的分配是由操作系统管理的,当一个新的文件或套接字被打开时,操作系统会分配一个当前未使用的最小文件描述符。因此,客户端和服务端的文件描述符编号可能会不同,这取决于各自的文件描述符表中当前已分配的情况。
  • 显示的文件描述符:客户端显示的文件描述符是它自己用于 connect 调用的套接字的文件描述符。而服务端显示的文件描述符是 accept 返回的新套接字的文件描述符,用于与特定的客户端通信。

总结来说,客户端和服务端的文件描述符编号不需要匹配,它们只是在各自的上下文中用于标识套接字的整数。服务端的 accept 调用返回的文件描述符是专门用于与已连接的客户端通信的,而客户端的文件描述符是它最初创建套接字时获得的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值