C++后端开发--网络编程基础

目录

一、网络编程基础概念

1.1 网络协议

1.2 IP地址和端口号

1.3 Socket

1.4 TCP协议的三次握手和四次挥手

TCP的三次握手

TCP的四次挥手

整个流程更通俗易懂

TCP 三次握手流程图

TCP 四次挥手流程图

1.5 详细介绍一下http协议

HTTP协议的主要特点

HTTP请求

HTTP请求方法

HTTP响应

常见的HTTP状态码

HTTP/1.1 vs HTTP/2 vs HTTP/3

HTTP/1.1

HTTP/2

HTTP/3

HTTP安全

二、Socket编程

2.1 创建Socket

2.2 绑定地址和端口

2.3 监听和接受连接

2.4 发送和接收数据

2.5 关闭Socket

三、简单的服务器示例

四、结语

五、深入探讨 select 和 epoll,以及多客户端编程

5.1 select 的工作原理及使用

5.1.1 select 基本概念

5.1.2 select 函数原型

5.1.3 使用 select 的步骤

5.1.4 select 示例

5.2 epoll 的工作原理及使用

5.2.1 epoll 基本概念

5.2.2 epoll 函数

5.2.3 使用 epoll 的步骤

5.2.4 epoll 示例

5.3、select 和 epoll 的比较

5.3.1 select 的优缺点

5.3.2 epoll 的优缺点

5.4、结语


网络编程是后端开发中不可或缺的一部分,尤其是在构建需要与其他系统或设备通信的应用程序时。本文将从基础概念开始,逐步深入网络编程的各个方面,帮助读者建立全面的网络编程知识体系。

一、网络编程基础概念

1.1 网络协议

网络协议是计算机网络中进行数据交换的规则。常见的网络协议包括:

  • TCP/IP:传输控制协议/互联网协议,是互联网的核心协议。TCP提供可靠的、面向连接的通信,而IP负责数据包的路由和传输。
  • UDP:用户数据报协议,是一种无连接的协议,适用于实时应用,如视频流和在线游戏。
  • HTTP/HTTPS:超文本传输协议/安全超文本传输协议,用于浏览器和服务器之间的通信。
  • FTP:文件传输协议,用于文件的上传和下载。

1.2 IP地址和端口号

  • IP地址:标识网络中的每个设备,如192.168.1.1
  • 端口号:用于区分同一设备上的不同服务,范围为065535

1.3 Socket

Socket是网络编程的基石,提供了应用层与TCP/IP协议栈通信的接口。主要类型有:

  • 流式套接字(Stream Socket):基于TCP,提供可靠的数据传输。
  • 数据报套接字(Datagram Socket):基于UDP,适用于无连接的数据传输。

1.4 TCP协议的三次握手和四次挥手

TCP的三次握手

三次握手(Three-way Handshake)是TCP建立连接的过程,确保双方能够正确接收和发送数据。三次握手的步骤如下:

  1. 第一次握手(SYN)

    • 客户端向服务器发送一个SYN(Synchronize)报文,表示请求建立连接。
    • 报文头中的SYN标志位被置为1,同时生成一个初始序列号(Sequence Number),假设为x。
    客户端 -> 服务器:SYN=1, Seq=x
  2. 第二次握手(SYN-ACK)

    • 服务器收到客户端的SYN报文后,确认连接请求,并向客户端发送一个SYN-ACK(Synchronize-Acknowledgment)报文。
    • 报文头中的SYN和ACK标志位都被置为1,ACK号为x+1(确认已收到客户端的SYN),并生成一个自己的初始序列号(假设为y)。
    服务器 -> 客户端:SYN=1, ACK=1, Seq=y, Ack=x+1
  3. 第三次握手(ACK)

    • 客户端收到服务器的SYN-ACK报文后,向服务器发送一个ACK(Acknowledgment)报文,表示确认连接建立。
    • 报文头中的ACK标志位被置为1,ACK号为y+1(确认已收到服务器的SYN),序列号为x+1。
    客户端 -> 服务器:ACK=1, Seq=x+1, Ack=y+1

完成三次握手后,客户端和服务器之间的TCP连接正式建立,可以开始数据传输。

TCP的四次挥手

四次挥手(Four-way Handshake)是TCP断开连接的过程,确保双方都能正常关闭连接。四次挥手的步骤如下:

  1. 第一次挥手(FIN)

    • 一方(通常是客户端)向另一方发送一个FIN(Finish)报文,表示希望关闭连接。
    • 报文头中的FIN标志位被置为1,序列号为u。
    客户端 -> 服务器:FIN=1, Seq=u
  2. 第二次挥手(ACK)

    • 另一方(通常是服务器)收到FIN报文后,向发送方发送一个ACK报文,确认已经收到关闭连接的请求。
    • 报文头中的ACK标志位被置为1,ACK号为u+1。
    服务器 -> 客户端:ACK=1, Seq=v, Ack=u+1
  3. 第三次挥手(FIN)

    • 服务器也向客户端发送一个FIN报文,表示同意关闭连接。
    • 报文头中的FIN标志位被置为1,序列号为w。
    服务器 -> 客户端:FIN=1, Seq=w
  4. 第四次挥手(ACK)

    • 客户端收到服务器的FIN报文后,向服务器发送一个ACK报文,确认已经收到关闭连接的请求。
    • 报文头中的ACK标志位被置为1,ACK号为w+1。
    客户端 -> 服务器:ACK=1, Seq=u+1, Ack=w+1

完成四次挥手后,客户端和服务器之间的TCP连接正式关闭。

通过三次握手和四次挥手机制,TCP协议能够确保可靠地建立和关闭连接,使得数据传输变得可靠和有序。这是网络编程中非常重要的部分,理解和掌握这些概念对于实现高效和稳定的网络通信至关重要。

整个流程更通俗易懂
TCP 三次握手流程图
客户端                              服务器
  |                                    |
  | --------- SYN, Seq=x ------------> |
  |                                    |
  | <------ SYN, ACK, Seq=y, Ack=x+1 --|
  |                                    |
  | --------- ACK, Seq=x+1, Ack=y+1 -->|
  |                                    |
  • 解释:
  1. 第一次握手:客户端发送SYN

    • 客户端向服务器发送一个SYN(同步)包,表明客户端想要建立连接,并且包含一个初始序列号(Seq=x)。
  2. 第二次握手:服务器回应SYN-ACK

    • 服务器收到SYN包后,回应一个SYN-ACK包,表示同意连接。这个包包含服务器的初始序列号(Seq=y)和对客户端SYN包的确认(Ack=x+1)。
  3. 第三次握手:客户端发送ACK

    • 客户端收到SYN-ACK包后,回应一个ACK包,确认服务器的SYN包(Ack=y+1),此时连接建立,双方可以开始传输数据。
TCP 四次挥手流程图
客户端                              服务器
  |                                    |
  | --------- FIN, Seq=u ------------> |
  |                                    |
  | <--------- ACK, Seq=v, Ack=u+1 ----|
  |                                    |
  | <--------- FIN, Seq=w ------------ |
  |                                    |
  | --------- ACK, Seq=u+1, Ack=w+1 -->|
  |                                    |
  • 解释:
  1. 第一次挥手:客户端发送FIN

    • 客户端发送一个FIN(终止)包,表示客户端要关闭连接,并且包含当前序列号(Seq=u)。
  2. 第二次挥手:服务器回应ACK

    • 服务器收到FIN包后,回应一个ACK包,确认客户端的FIN包(Ack=u+1),此时客户端到服务器的连接关闭,但服务器到客户端的连接仍然存在。
  3. 第三次挥手:服务器发送FIN

    • 服务器也发送一个FIN包,表示服务器也要关闭连接,并且包含当前序列号(Seq=w)。
  4. 第四次挥手:客户端回应ACK

    • 客户端收到服务器的FIN包后,回应一个ACK包,确认服务器的FIN包(Ack=w+1),此时整个连接正式关闭。

这些流程图展示了TCP协议在建立和关闭连接时的具体步骤,帮助理解TCP三次握手和四次挥手的机制。这样,你可以更直观地看到每一步是如何进行的,以及每一步的作用。

1.5 详细介绍一下http协议

HTTP(HyperText Transfer Protocol,超文本传输协议)是用于分布式、协作和超媒体信息系统的应用层协议,是万维网数据通信的基础。HTTP起初由蒂姆·伯纳斯-李(Tim Berners-Lee)为万维网设计,现由互联网工程任务组(IETF)和万维网联盟(W3C)共同维护。HTTP协议定义了浏览器(客户端)与Web服务器之间的通信规则。

HTTP协议的主要特点
  1. 简单快速

    • 客户端向服务器请求服务时,只需传送请求方法和路径。
    • 由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  2. 灵活

    • HTTP允许传输任意类型的数据对象。通过Content-Type头,可以表示具体的数据类型。
  3. 无连接

    • 无连接的意思是限制每次连接只处理一个请求。服务器处理完客户端的请求并收到客户端的应答后,就断开连接。但这种方式能节省传输时间。
  4. 无状态

    • HTTP协议是无状态协议,即对事务处理没有记忆能力。每次请求都是独立的,服务器不保留任何会话信息。
HTTP请求

一个HTTP请求由以下部分组成:

  1. 请求行:包括请求方法、请求URL和HTTP版本。

    • 示例:GET /index.html HTTP/1.1
  2. 请求头:包括各种头部信息,用于客户端向服务器传递附加信息。

    • 示例:
      Host: www.example.com
      User-Agent: Mozilla/5.0
      Accept: text/html
  3. 空行:用于分隔请求头和请求体。

  4. 请求体:包含客户端发送给服务器的数据(仅在POST、PUT等请求方法中使用)。

HTTP请求方法

常见的HTTP请求方法包括:

  • GET:请求获取指定资源。常用于请求数据。
  • POST:向指定资源提交数据。常用于提交表单或上传文件。
  • PUT:上传指定资源的最新内容。
  • DELETE:删除指定资源。
  • HEAD:类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头。
  • OPTIONS:返回服务器支持的HTTP请求方法。
  • PATCH:对资源进行部分修改。
HTTP响应

一个HTTP响应由以下部分组成:

  1. 状态行:包括HTTP版本、状态码和状态描述。

    • 示例:HTTP/1.1 200 OK
  2. 响应头:包括各种头部信息,用于服务器向客户端传递附加信息。

    • 示例:
      Content-Type: text/html
      Content-Length: 1234
  3. 空行:用于分隔响应头和响应体。

  4. 响应体:包含服务器返回给客户端的数据。

常见的HTTP状态码
  • 1xx(信息性状态码):表示请求已被接收,继续处理。

    • 100 Continue
    • 101 Switching Protocols
  • 2xx(成功状态码):表示请求已成功被服务器接收、理解并接受。

    • 200 OK
    • 201 Created
  • 3xx(重定向状态码):表示需要客户端采取进一步的操作以完成请求。

    • 301 Moved Permanently
    • 302 Found
    • 304 Not Modified
  • 4xx(客户端错误状态码):表示请求包含错误或无法完成。

    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
  • 5xx(服务器错误状态码):表示服务器在处理请求的过程中发生了错误。

    • 500 Internal Server Error
    • 502 Bad Gateway
    • 503 Service Unavailable
HTTP/1.1 vs HTTP/2 vs HTTP/3
HTTP/1.1
  • 长连接:默认使用长连接,允许多个请求/响应在同一连接上进行,减少了连接的建立和关闭次数,提高了传输效率。
  • 管道化:允许客户端在收到HTTP响应之前发送多个HTTP请求,但服务器仍然会按顺序响应。
HTTP/2
  • 二进制分帧:使用二进制格式传输数据,更高效和更容易解析。
  • 多路复用:允许同时通过单一的HTTP/2连接发送多个请求和响应。
  • 头部压缩:使用HPACK算法对头部信息进行压缩,减少带宽占用。
  • 服务器推送:服务器可以主动向客户端推送资源,而不需要客户端明确请求。
HTTP/3
  • 基于QUIC:HTTP/3基于QUIC协议,使用UDP传输,减少了连接建立的延迟。
  • 更快的连接建立:由于QUIC在单个数据包内完成握手,大大减少了连接建立时间。
  • 改进的多路复用:避免了HTTP/2中的队头阻塞问题,更高效。
HTTP安全

HTTP本身是明文传输的,容易被窃听和篡改。为了提高安全性,通常使用HTTPS(HTTP Secure),即HTTP over TLS(或SSL),加密HTTP通信,保证数据的机密性和完整性。

  • HTTPS:通过使用TLS(传输层安全性)来加密数据传输,确保数据在传输过程中不被窃听和篡改。
  • SSL/TLS:安全套接字层/传输层安全性,是一种加密协议,用于保护互联网通信安全。

总结来说,HTTP协议是万维网的基石,通过明确的请求和响应机制,客户端和服务器可以高效地进行通信。随着技术的发展,HTTP协议也在不断演进,以提高性能、安全性和用户体验。

二、Socket编程

2.1 创建Socket

在C++中,可以使用socket()函数创建Socket。语法如下:

int socket(int domain, int type, int protocol);
  • domain:地址族,如AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:Socket类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。
  • protocol:协议,一般设为0,由系统自动选择。

示例:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket creation failed");
    exit(EXIT_FAILURE);
}

2.2 绑定地址和端口

使用bind()函数将Socket绑定到特定的IP地址和端口。语法如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

示例:

struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(8080);

if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
    perror("socket bind failed");
    exit(EXIT_FAILURE);
}

2.3 监听和接受连接

使用listen()函数使Socket进入监听状态,并使用accept()函数接受客户端连接。

int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

示例:

if (listen(sockfd, 5) != 0) {
    perror("Listen failed");
    exit(EXIT_FAILURE);
}

int connfd = accept(sockfd, (struct sockaddr *)&cli, &len);
if (connfd < 0) {
    perror("server accept failed");
    exit(EXIT_FAILURE);
}

2.4 发送和接收数据

使用send()recv()函数在服务器和客户端之间传输数据。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

示例:

char buffer[1024] = {0};
recv(connfd, buffer, sizeof(buffer), 0);
send(connfd, "Hello from server", strlen("Hello from server"), 0);

2.5 关闭Socket

使用close()函数关闭Socket。

close(sockfd);

三、简单的服务器示例

以下是一个简单的C++服务器示例,使用TCP协议,监听8080端口,并向每个连接的客户端发送一条欢迎消息。

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int sockfd, connfd;
    struct sockaddr_in servaddr, cli;

    // 创建Socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        std::cerr << "Socket creation failed\n";
        exit(EXIT_FAILURE);
    }

    // 绑定IP和端口
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(8080);

    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
        std::cerr << "Socket bind failed\n";
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(sockfd, 5) != 0) {
        std::cerr << "Listen failed\n";
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    socklen_t len = sizeof(cli);
    connfd = accept(sockfd, (struct sockaddr *)&cli, &len);
    if (connfd < 0) {
        std::cerr << "Server accept failed\n";
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 发送欢迎消息
    const char *message = "Hello from server";
    send(connfd, message, strlen(message), 0);

    // 关闭连接
    close(connfd);
    close(sockfd);

    return 0;
}

四、结语

网络编程是后端开发中的重要技能,掌握基础概念和Socket编程方法是深入学习和实践的第一步。本文介绍了网络协议、IP地址、端口号以及Socket编程的基本操作。希望通过这篇文章,读者能对网络编程有一个初步的了解,并能够编写简单的网络应用程序。

在后续的文章中,我们将深入探讨高级网络编程技术,包括多线程服务器、异步I/O、网络安全等内容,敬请期待。

五、深入探讨 selectepoll,以及多客户端编程

在网络编程中,处理多个客户端连接是一项常见且重要的任务。为了有效地管理多个连接,操作系统提供了多种I/O多路复用机制,如selectpollepoll。本文将详细讲解selectepoll,并展示如何使用它们实现多客户端编程。

5.1 select 的工作原理及使用

5.1.1 select 基本概念

select 是一种I/O多路复用机制,用于监视多个文件描述符(如Socket)上的事件,如数据可读、可写或异常状态。当任何一个文件描述符上的事件发生时,select 返回,程序可以对这些事件进行处理。

5.1.2 select 函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:所有文件描述符中最大值加一。
  • readfds:可读事件的文件描述符集合。
  • writefds:可写事件的文件描述符集合。
  • exceptfds:异常事件的文件描述符集合。
  • timeout:超时时间,NULL表示永不超时。
5.1.3 使用 select 的步骤
  1. 初始化文件描述符集合。
  2. 将需要监视的文件描述符添加到集合中。
  3. 调用 select 函数,等待事件发生。
  4. 遍历文件描述符集合,处理已发生的事件。
5.1.4 select 示例

下面是一个使用 select 实现的简单多客户端服务器示例:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

#define PORT 8080
#define MAX_CLIENTS 30

int main() {
    int server_fd, new_socket, client_socket[MAX_CLIENTS], max_clients = MAX_CLIENTS, activity, i, valread, sd;
    int max_sd;
    struct sockaddr_in address;
    char buffer[1025];
    fd_set readfds;

    // 初始化所有客户端socket为0
    for (i = 0; i < max_clients; i++) {
        client_socket[i] = 0;
    }

    // 创建服务器socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    int addrlen = sizeof(address);
    std::cout << "Listening on port " << PORT << std::endl;

    while (true) {
        // 清空文件描述符集合
        FD_ZERO(&readfds);

        // 添加服务器socket到集合
        FD_SET(server_fd, &readfds);
        max_sd = server_fd;

        // 添加客户端socket到集合
        for (i = 0; i < max_clients; i++) {
            sd = client_socket[i];
            if (sd > 0)
                FD_SET(sd, &readfds);
            if (sd > max_sd)
                max_sd = sd;
        }

        // 等待活动事件
        activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
        if ((activity < 0) && (errno != EINTR)) {
            std::cerr << "select error" << std::endl;
        }

        // 处理新连接
        if (FD_ISSET(server_fd, &readfds)) {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            std::cout << "New connection, socket fd is " << new_socket << ", ip is: " << inet_ntoa(address.sin_addr) << ", port: " << ntohs(address.sin_port) << std::endl;

            // 添加新socket到客户端数组
            for (i = 0; i < max_clients; i++) {
                if (client_socket[i] == 0) {
                    client_socket[i] = new_socket;
                    std::cout << "Adding to list of sockets as " << i << std::endl;
                    break;
                }
            }
        }

        // 处理已连接的客户端的IO操作
        for (i = 0; i < max_clients; i++) {
            sd = client_socket[i];
            if (FD_ISSET(sd, &readfds)) {
                if ((valread = read(sd, buffer, 1024)) == 0) {
                    // 某客户端断开连接
                    getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    std::cout << "Host disconnected, ip: " << inet_ntoa(address.sin_addr) << ", port: " << ntohs(address.sin_port) << std::endl;
                    close(sd);
                    client_socket[i] = 0;
                } else {
                    buffer[valread] = '\0';
                    std::cout << "Received message: " << buffer << std::endl;
                    send(sd, buffer, strlen(buffer), 0);
                }
            }
        }
    }

    return 0;
}

5.2 epoll 的工作原理及使用

5.2.1 epoll 基本概念

epoll 是Linux特有的一种I/O多路复用机制,提供比 selectpoll 更高效的事件通知。epoll 使用事件驱动模型,通过内核维护一个事件表,减少用户空间和内核空间之间的拷贝开销。

5.2.2 epoll 函数

epoll 主要包含以下三个函数:

  1. epoll_create1:创建一个 epoll 实例。
  2. epoll_ctl:控制 epoll 实例,添加、删除或修改事件。
  3. epoll_wait:等待事件发生,并返回已触发的事件。
5.2.3 使用 epoll 的步骤
  1. 创建 epoll 实例。
  2. 使用 epoll_ctl 函数将文件描述符添加到 epoll 实例中。
  3. 调用 epoll_wait 函数等待事件发生。
  4. 处理已触发的事件。
5.2.4 epoll 示例

下面是一个使用 epoll 实现的简单多客户端服务器示例:

#include <iostream>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

#define PORT 8080
#define MAX_EVENTS 10
#define MAX_CLIENTS 30

int main() {
    int server_fd, new_socket, epoll_fd, event_count, i, valread;
    struct sockaddr_in address;
    struct epoll_event ev, events[MAX_EVENTS];
    char buffer[1025];

    // 创建服务器socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置socket为非阻塞
    fcntl(server_fd, F_SETFL, O_NONBLOCK);

    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 创建epoll实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 添加服务器socket到epoll实例
    ev.events = EPOLLIN;
    ev.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
        perror("epoll_ctl: server_fd");
        close(server_fd);
        close(epoll_fd);
        exit(EXIT_FAILURE);
    }

    std::cout << "Listening on port " << PORT << std::endl;

    while (true) {
        // 等待事件发生
        event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if

 (event_count == -1) {
            perror("epoll_wait");
            close(server_fd);
            close(epoll_fd);
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < event_count; i++) {
            if (events[i].data.fd == server_fd) {
                // 处理新连接
                new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
                if (new_socket == -1) {
                    perror("accept");
                    continue;
                }
                std::cout << "New connection, socket fd is " << new_socket << ", ip is: " << inet_ntoa(address.sin_addr) << ", port: " << ntohs(address.sin_port) << std::endl;

                // 设置新socket为非阻塞
                fcntl(new_socket, F_SETFL, O_NONBLOCK);

                // 添加新socket到epoll实例
                ev.events = EPOLLIN;
                ev.data.fd = new_socket;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &ev) == -1) {
                    perror("epoll_ctl: new_socket");
                    close(new_socket);
                }
            } else {
                // 处理客户端IO操作
                valread = read(events[i].data.fd, buffer, 1024);
                if (valread == -1) {
                    perror("read");
                    close(events[i].data.fd);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                } else if (valread == 0) {
                    // 客户端断开连接
                    getpeername(events[i].data.fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    std::cout << "Host disconnected, ip: " << inet_ntoa(address.sin_addr) << ", port: " << ntohs(address.sin_port) << std::endl;
                    close(events[i].data.fd);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                } else {
                    buffer[valread] = '\0';
                    std::cout << "Received message: " << buffer << std::endl;
                    send(events[i].data.fd, buffer, strlen(buffer), 0);
                }
            }
        }
    }

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

5.3、selectepoll 的比较

5.3.1 select 的优缺点

优点:

  • 跨平台,几乎在所有操作系统上都支持。
  • 简单易用,适合小规模应用。

缺点:

  • 每次调用 select 都需要重新设置文件描述符集合,效率较低。
  • 支持的文件描述符数量有限(一般为1024)。
5.3.2 epoll 的优缺点

优点:

  • 高效,支持大量并发连接,适用于高并发场景。
  • 事件驱动模型,减少了无效的系统调用。

缺点:

  • 仅支持Linux,跨平台性较差。
  • 相对复杂,需要更多的代码来管理事件。

5.4、结语

上面详细介绍了 selectepoll 的工作原理及其使用方法,并通过示例展示了如何实现多客户端编程。对于小规模的网络应用,可以选择简单易用的 select;而对于高并发、高性能的应用,epoll 则是更好的选择。

通过学习和实践这些I/O多路复用技术,读者可以更好地理解和掌握网络编程中的并发处理,为构建高效稳定的网络应用奠定坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/771375.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构 1.1 数据结构的基本概念

本章总览&#xff1a; 一.什么是数据 1.数据 数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程 序识别和处理的符号的集合。数据是计算机程序加工的原料。 早期计算机只能处理纯数值的问题&#xff0c;如世界第一题计算机ENI…

【京存】AI人工智能时代的分布式存储

如今&#xff0c;AI人工智能的浪潮席卷全球&#xff0c;数据以前所未有的速度增长与积累。如何高效存储、管理和利用海量数据&#xff0c;成为推动AI发展的关键。 今日&#xff0c;我们将为您深度剖析AI人工智能分布式存储方案&#xff0c;伴随AI技术在图像识别、自然语言处理…

金融(基金)行业信创国产化特点及统一身份认证解决方案

金融业在政策支持及自主驱动下&#xff0c;金融信创取得快速发展。从2020年开始&#xff0c;三期试点已扩容至5000余家&#xff0c;进入全面推广阶段。而基金行业信创建设与银行、证券、保险这些试点行业相比&#xff0c;进展较为缓慢。 基金行业信创当前面临的问题 与多家基…

百度出品_文心快码Comate提升程序猿效率

1.文心快码 文心快码包含指令、插件 和 知识三种功能&#xff0c; 1&#xff09;指令包含Base64编码、Base64解码、JSON转TS类型、JSON转YAML、JWT解码喂JSON。 2&#xff09;插件包含 3&#xff09;指令包含如下功能&#xff1a; 官网链接

SQL Server 2022的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 SQL Server 2022主要由4部分组成&#xff0c;分别是数据库引擎、分析服务、集成服务和报表服务。本节将详细介绍这些内容。 1.2.1 SQL Server 2022的数据库引擎 SQL Server 2022的…

盘点几款国产AI高效神器!打工人赶紧码住

在这个AI技术飞速发展的时代&#xff0c;国产AI工具正成为提升工作效率的得力助手。作为AI工具测评博主&#xff0c;米兔有幸体验了多款国产AI工具&#xff0c;今天要向大家介绍几款超级好用的AI工具。这些工具不仅功能强大&#xff0c;而且操作简便&#xff0c;是职场人士不可…

贵的智能猫砂盆值得买吗?包含百元、千元的高性价比品牌推荐!

对于养猫的上班族来说&#xff0c;智能猫砂盆真的是越早买越好&#xff0c;普通猫砂盆用这么久下来能把我们这些上班的都累死&#xff0c;每次一回到家就能闻到猫屎的臭味&#xff0c;一看就收获猫砂盆里满满当当的猫屎&#xff0c;在外面要上班&#xff0c;在家里也要给猫上班…

API-正则表达式

学习目标&#xff1a; 掌握正则表达式 学习内容&#xff1a; 什么是正则表达式语法元字符修饰符 什么是正则表达式&#xff1a; 正则表达式是用于匹配字符串中字符组合的模式。在JavaScript中&#xff0c;正则表达式也是对象。 通常用来查找、替换那些符合正则表达式的文本&a…

二叉树的链式访问 与 二叉树专题

目录 二叉树的前、中、后序遍历求二叉树第K层节点的个数二叉树查找值为x的节点leetcode相同的树对称二叉树二叉树的前序遍历另一棵子树牛客 二叉树的遍历 二叉树的前、中、后序遍历 1.前序遍历&#xff1a;先访问根节点&#xff0c;再访问左子树&#xff0c;最后访问右子树 根…

# windows 安装 mysql 显示 no packages found 解决方法

windows 安装 mysql 显示 no packages found 解决方法 一、问题描述&#xff1a; 安装 mysql 时&#xff0c;出现 no packages found 不能进行下一步安装了&#xff0c; 如下图&#xff1a; 二、解决方法&#xff1a; 1、路径问题&#xff0c;系统不能识别你的安装包路径&…

MTK6769芯片性能参数_MT6769规格书_datasheet

联发科MT6769处理器采用了台积电12nm工艺。它具有8核CPU&#xff0c;采用2Cortex A75 2.0GHz 6Cortex A55 1.7GHz的构架。该处理器搭载了Mali-G52 MC2 GPU&#xff0c;运行速度高达820MHz&#xff0c;能够提供出色的图形处理性能。此外&#xff0c;MT6769还提供高达8GB的快速L…

无法启动此程序,因为计算机中丢失 api-ms-win-crt-string-11-1-0.dl。尝试重新安装该程序以解决此问题。

在windows server2012系统中利用WinSW部署jar包时&#xff0c;报错&#xff1a;无法启动此程序&#xff0c;因为计算机中丢失 api-ms-win-crt-string-11-1-0.dl。尝试重新安装该程序以解决此问题。 原因&#xff1a; 缺少Microsoft Visual C 2015运行库或者已安装低版本运行库…

DevExpress WPF中文教程:Grid - 如何显示摘要(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

conda中创建环境并安装tensorflow1版本

conda中创建环境并安装tensorflow1版本 一、背景二、命令三、验证一下 一、背景 最近需要使用tensorflow1版本的&#xff0c;发个记录&#xff01; 二、命令 conda create -n tf python3.6 #创建tensorflow虚拟环境 activate tf #激活环境&#xff0c;每次使用的时候都…

机器学习与AI大数据的融合:开启智能新时代

在当今这个信息爆炸的时代&#xff0c;大数据和人工智能&#xff08;AI&#xff09;已经成为推动社会进步的强大引擎。作为AI核心技术之一的机器学习&#xff08;Machine Learning, ML&#xff09;&#xff0c;与大数据的深度融合正引领着一场前所未有的科技革命&#xff0c;不…

太速科技-FMC209-基于FMC的4路125MAD输入、2路1GDA输出子卡

FMC209-基于FMC的4路125MAD输入、2路1GDA输出子卡 一、板卡概述 本子卡基于FMC连接器实现4路125M采样率AD输出&#xff0c;两路1G采样率DA输出子卡&#xff0c;板卡默认由FMC连接器12V供电&#xff0c;支持外参考时钟&#xff0c;外输入时钟&#xff0c;外触发。 …

PE文件学习

一、介绍 PE文件&#xff0c;即Portable Executable文件&#xff0c;是一种标准的文件格式&#xff0c;主要用于微软的Windows操作系统上。这种格式被用来创建可执行程序&#xff08;如.exe文件&#xff09;、动态链接库&#xff08;.DLL文件&#xff09;、设备驱动&#xff0…

汽车信息安全--数据安全:图像脱敏

General 随着车联网的发展&#xff0c;汽车越来越智能化&#xff0c;就像是一部“装着四个轮子的手机”。 有人说&#xff0c;智能手机就如同一部窃听器&#xff0c;无论你开机或者关机&#xff0c;它都会无时不刻地监听着用户的一举一动。 可想而知&#xff0c;智能车辆上…

马工程刑法期末复习笔记重点2

马工程刑法期末复习笔记重点2

8人团队历时半年打造开源版GPT-4o,零延迟演示引爆全网!人人可免费使用!

目录 01 Moshi 02 背后技术揭秘 GPT-4o可能要等到今年秋季才会公开。 然而&#xff0c;由法国8人团队开发的原生多模态Moshi&#xff0c;已经达到了接近GPT-4o的水平&#xff0c;现场演示几乎没有延迟&#xff0c;吸引了大量AI专家的关注。 令人惊讶的是&#xff0c;开源版的…