Linux TCP作为服务器连接的单连接、Select、Poll和Epoll方式:C/C++实现高效的服务器通信

简介: 在Linux服务器开发中,TCP(Transmission Control Protocol)作为面向连接的通信方式,为实现可靠的服务器通信提供了强大支持。不同的服务器连接方式,如单连接、Select、Poll和Epoll,各有优势,可以根据连接数和性能需求选择合适的方式。本文将深入探讨这四种方式的实现原理,并给出C/C++代码例子,帮助读者更好地理解和使用这些方式。

1. 单连接方式

单连接方式是最简单的方式,每个客户端连接都创建一个独立的线程或进程来处理数据传输。这种方式适用于连接数较少的情况,代码实现相对简单。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
   
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
   
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
   
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    if (listen(server_socket, 5) == -1) {
   
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    while (1) {
   
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_socket == -1) {
   
            perror("accept");
            continue;
        }

        char buffer[1024];
        int n = recv(client_socket, buffer, sizeof(buffer), 0);
        if (n <= 0) {
   
            perror("recv");
            close(client_socket);
            continue;
        }

        // 处理请求
        char* response = "Hello, I am the server!";
        send(client_socket, response, strlen(response), 0);
        close(client_socket);
    }

    close(server_socket);
    return 0;
}

2. Select方式

Select是最古老的I/O复用技术,它使用fd_set集合来监视文件描述符上的I/O事件。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define MAX_CLIENTS 5

int main() {
   
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
   
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
   
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    if (listen(server_socket, 5) == -1) {
   
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    int client_sockets[MAX_CLIENTS] = {
   0};
    fd_set read_fds;
    int max_fd;

    while (1) {
   
        FD_ZERO(&read_fds);
        FD_SET(server_socket, &read_fds);
        max_fd = server_socket;

        for (int i = 0; i < MAX_CLIENTS; i++) {
   
            if (client_sockets[i] > 0) {
   
                FD_SET(client_sockets[i], &read_fds);
                if (client_sockets[i] > max_fd) {
   
                    max_fd = client_sockets[i];
                }
            }
        }

        select(max_fd + 1, &read_fds, NULL, NULL, NULL);

        if (FD_ISSET(server_socket, &read_fds)) {
   
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);
            int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
            if (client_socket == -1) {
   
                perror("accept");
                continue;
            }

            for (int i = 0; i < MAX_CLIENTS; i++) {
   
                if (client_sockets[i] == 0) {
   
                    client_sockets[i] = client_socket;
                    break;
                }
            }
        }

        for (int i = 0; i < MAX_CLIENTS; i++) {
   
            if (client_sockets[i] > 0 && FD_ISSET(client_sockets[i], &read_fds)) {
   
                char buffer[1024];
                int n = recv(client_sockets[i], buffer, sizeof(buffer), 0);
                if (n <= 0) {
   
                    close(client_sockets[i]);
                    client_sockets[i] = 0;
                } else {
   
                    // 处理请求
                    char* response = "Hello, I am the server!";
                    send(client_sockets[i], response, strlen(response), 0);
                }
            }
        }
    }

    close(server_socket);
    return 0;
}

3. Poll方式

Poll是改进的I/O复用技术,使用pollfd结构体数组来监视文件描述符上的I/O事件。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>

#define MAX_CLIENTS 5

int main() {
   
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
   
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
   
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    if (listen(server_socket, 5) == -1) {
   
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    struct pollfd fds[MAX_CLIENTS + 1];
    memset(fds, 0, sizeof(fds));

    fds[0].fd = server_socket;
    fds[0].events = POLLIN;

    while (1) {
   
        int num_fds = poll(fds, MAX_CLIENTS + 1, -1);
        if (

num_fds == -1) {
   
            perror("poll");
            continue;
        }

        if (fds[0].revents & POLLIN) {
   
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);
            int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
            if (client_socket == -1) {
   
                perror("accept");
                continue;
            }

            for (int i = 1; i < MAX_CLIENTS + 1; i++) {
   
                if (fds[i].fd == 0) {
   
                    fds[i].fd = client_socket;
                    fds[i].events = POLLIN;
                    break;
                }
            }
        }

        for (int i = 1; i < MAX_CLIENTS + 1; i++) {
   
            if (fds[i].fd > 0 && (fds[i].revents & POLLIN)) {
   
                char buffer[1024];
                int n = recv(fds[i].fd, buffer, sizeof(buffer), 0);
                if (n <= 0) {
   
                    close(fds[i].fd);
                    fds[i].fd = 0;
                } else {
   
                    // 处理请求
                    char* response = "Hello, I am the server!";
                    send(fds[i].fd, response, strlen(response), 0);
                }
            }
        }
    }

    close(server_socket);
    return 0;
}

4. Epoll方式

Epoll是Linux特有的高效I/O复用技术,使用事件驱动的方式来监视文件描述符上的I/O事件。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

int main() {
   
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
   
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
   
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    if (listen(server_socket, 5) == -1) {
   
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
   
        perror("epoll_create1");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = server_socket;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
   
        perror("epoll_ctl");
        close(server_socket);
        close(epoll_fd);
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[MAX_EVENTS];

    while (1) {
   
        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (num_events == -1) {
   
            perror("epoll_wait");
            continue;
        }

        for (int i = 0; i < num_events; i++) {
   
            if (events[i].data.fd == server_socket) {
   
                struct sockaddr_in client_addr;
                socklen_t client_addr_len = sizeof(client_addr);
                int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
                if (client_socket == -1) {
   
                    perror("accept");
                    continue;
                }

                event.events = EPOLLIN;
                event.data.fd = client_socket;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
   
                    perror("epoll_ctl");
                    close(client_socket);
                }
            } else {
   
                int client_socket = events[i].data.fd;
                char buffer[1024];
                int n = recv(client_socket, buffer, sizeof(buffer), 0);
                if (n <= 0) {
   
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_socket, NULL);
                    close(client_socket);
                } else {
   
                    // 处理请求
                    char* response = "Hello, I am the server!";
                    send(client_socket, response, strlen(response), 0);
                }
            }
        }
    }

    close(server_socket);
    close(epoll_fd);
    return 0;
}

5.选择适合的服务器连接方式

单连接方式适用于连接数较少的情况,服务器性能要求较低。
Select方式适用于连接数少于1000个的情况,服务器性能要求中等。
Poll方式适用于连接数在1000-10000个的情况,服务器性能要求较高。
Epoll方式适用于连接数超过10000个的情况,服务器性能要求非常高。

6. 结论

TCP作为服务器连接方式在Linux服务器开发中得到广泛应用。不同的连接方式,如单连接、Select、Poll和Epoll,各有优势,可以根据连接数和性能需求选择合适的方式。本文给出了C/C++代码例子,帮助读者更好地理解和使用这些方式。在实际的服务器开发中,选择合适的连接方式可以提高服务器的性能和可扩展性,确保服务器通信的稳定运行。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
3月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
105 0
|
25天前
|
缓存 网络协议 Java
【JavaEE】——TCP回显服务器(万字长文超详细)
ServerSocket类,Socket类,PrintWriter缓冲区问题,Socket文件释放问题,多线程问题
|
2月前
|
存储 JSON Java
细谈 Linux 中的多路复用epoll
大家好,我是 V 哥。`epoll` 是 Linux 中的一种高效多路复用机制,用于处理大量文件描述符(FD)事件。相比 `select` 和 `poll`,`epoll` 具有更高的性能和可扩展性,特别适用于高并发服务器。`epoll` 通过红黑树管理和就绪队列分离事件,实现高效的事件处理。本文介绍了 `epoll` 的核心数据结构、操作接口、触发模式以及优缺点,并通过 Java NIO 的 `Selector` 类展示了如何在高并发场景中使用多路复用。希望对大家有所帮助,欢迎关注威哥爱编程,一起学习进步。
|
3月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
68 0
Linux C/C++之IO多路复用(poll,epoll)
|
3月前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
84 2
|
3月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
93 1
|
3月前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
547 0
vsCode远程执行c和c++代码并操控linux服务器完整教程
|
3月前
|
网络协议 Python
Python创建一个TCP服务器
Python创建一个TCP服务器
34 0
|
11天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
51 18
|
11天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
38 13

热门文章

最新文章