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;// 创建Socketsockfd = 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 30int 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为0for (i = 0; i < max_clients; i++) {client_socket[i] = 0;}// 创建服务器socketserver_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 30int 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];// 创建服务器socketserver_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.mzph.cn/news/865993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis 五大数据类型底层原理

0、前言 本文涉及的主题&#xff1a; redis 对象存储 底层数据结构&#xff1a;int、embstr、raw、ziplist、listpack、quicklist、skiplist、intset、hashtable redis 数据类型&#xff1a;string、list、set、zset、hash 1、对象存储、底层编码、数据类型 1.1 对象存储…

14-23 深度神经网络的主要架构(RNN/LSTM/CNN)

神经网络架构 神经网络的架构决定了这些网络如何运行&#xff0c;这是执行各种任务和扩展神经网络应用的关键因素&#xff0c;主要有两种方法&#xff1a;前馈神经网络和反馈神经网络。在本文中&#xff0c;在彻底分析每种方法之后&#xff0c;我们将对这两种架构进行深入比较…

规则·理解·成长:与自闭症儿童共绘记忆蓝图

在星贝育园&#xff0c;作为专注于自闭症儿童康复的专业教育者&#xff0c;我们常常遇到家长的疑惑&#xff1a;“为什么我的孩子总是记不清楚规则&#xff1f;”这个问题触及了自闭症谱系障碍&#xff08;ASD&#xff09;儿童在理解与遵守规则方面面临的独特挑战。下面&#x…

每日一题——Python实现PAT乙级1005 继续(3n+1)猜想(举一反三+思想解读+逐步优化)五千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 代码逻辑概述 时间复杂度分析 空间复杂度分析 总结 我要更强 代码优化点…

【MySQL基础篇】函数及约束

1、函数 函数是指一段可以直接被另一段程序程序调用的程序或代码。 函数 - 字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的几个如下&#xff1a; 函数功能CONCAT(S1,S2,...,Sn)字符串拼接&#xff0c;将S1,S2,...,Sn拼接成一个字符串LOWER(str)将字符串str全部…

【热部署】✈️Springboot 项目的热部署实现方式

目录 &#x1f378;前言 &#x1f37b;一、热部署和手动重启 &#x1f37a;二、热部署的实现 2.1 手动启动热部署 2.2 自动检测热部署 2.3 关闭热部署 &#x1f49e;️三、章末 &#x1f378;前言 小伙伴们大家好&#xff0c;书接上文&#xff0c;通过Springboot 中的 actu…

提升TK直播体验:使用美国直播网络的六大优势

国内有许多公司想在TikTok上进行美国直播&#xff0c;但由于TikTok的政策限制&#xff0c;在国内直接访问存在困难。然而&#xff0c;通过使用Ogcloud的美国直播网络&#xff0c;这一问题得以解决。那么&#xff0c;TikTok海外直播使用美国直播网络具体有哪些优势呢&#xff1f…

【懒删除堆 优先队列】1172. 餐盘栈

本文涉及知识点 懒删除堆 优先队列 LeetCode1172. 餐盘栈 我们把无限数量 ∞ 的栈排成一行&#xff0c;按从左到右的次序从 0 开始编号。每个栈的的最大容量 capacity 都相同。 实现一个叫「餐盘」的类 DinnerPlates&#xff1a; DinnerPlates(int capacity) - 给出栈的最大…

stm32中IIC通讯协议

参考资料&#xff1a;大部分均引用b站江协科技课程、GPT及网络资料 什么是IIC&#xff08;i2C&#xff09;通讯协议&#xff1f; 关键字&#xff1a;SCL、SDA、半双工、同步、串行。 IIC&#xff08;Inter-Integrated Circuit&#xff09;&#xff0c;也称为I2C&#xff08;In…

不同的llm推理框架

vLLM适用于大批量Prompt输入&#xff0c;并对推理速度要求比较高的场景。 实际应用场景中&#xff0c;TensorRT-LLM通常与Triton Inference Server结合起来使用&#xff0c;NVIDIA官方能够提供更适合NVIDIA GPU运行的高效Kernel。 LightLLM比较轻量、易于扩展、易于上手&…

Linux—KVM虚拟化中使用基本命令管理虚拟机(纯实例)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f468;‍&#x1f4bb;Linux高级管理专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年7月2日11点11分 &#x1f004;️文章质量&#xff1a;94分 文章目录 前言 1.查看命令帮助 2.查看KVM 的…

c/c++语言的一种日志的编写办法

今日分享一下&#xff0c;从某源码中看到这种日志编写方式&#xff0c;很强。可以借鉴。 这个函数调用的日志函数是不一样的&#xff0c;仔细观看&#xff1a; 这几种日志输出函数&#xff0c;背后都调用了相同的调用。 与之对应的区别就是&#xff0c;函数名称的差异取决于…

mpeg格式怎么转换成mp4?这四种转换方法非常好用!

mpeg格式怎么转换成mp4&#xff1f;在数字视频领域中&#xff0c;MPEG格式算是相对冷门的一种选择&#xff0c;然而&#xff0c;选择这种格式却不是没有代价的&#xff0c;首先&#xff0c;MPEG采用了有损压缩技术&#xff0c;这意味着在视频处理过程中&#xff0c;会丢失一些细…

单个企业年收入达24.7亿!银发游学市场按下“加速键”,兴趣教育平台抢滩布局

干货抢先看 1. 银发游学市场渐趋火热&#xff0c;美国最大的银发游学机构Road Scholar最新披露的财报显示&#xff0c;上个财年公司收入达3.395亿美元&#xff08;约合人民币24.7亿&#xff09;。 2. 在国内&#xff0c;一批专注银发兴趣教育的品牌纷纷入局&#xff0c;拓展游…

MYSQL8.0配置

1、下载安装包 2、解压软件包 将MySQL软件包解压在没有中文和空格的目录下 3、设置配置文件my.ini my.ini中加入以下内容 [client] #客户端设置&#xff0c;即客户端默认的连接参数 # 设置mysql客户端连接服务端时默认使用的端口 port3306#默认编码 default-character-set…

一个使用 g++ 模块化编译的 hello world 示例( Ubuntu 20.04 )

1. 确认 ubuntu 版本&#xff1a; 2. 文件夹结构&#xff1a; 3. 各个文件内容&#xff1a; 3.1. myadd.cpp&#xff1a; #include<iostream> using namespace std; int add_xxx( int a,int b ){int result a b;cout << a << " " << …

【C语言】return 关键字

在C语言中&#xff0c;return是一个关键字&#xff0c;用于从函数中返回值或者结束函数的执行。它是函数的重要组成部分&#xff0c;负责将函数的计算结果返回给调用者&#xff0c;并可以提前终止函数的执行。 主要用途和原理&#xff1a; 返回值给调用者&#xff1a; 当函数执…

技术成神之路:设计模式(二)建造者模式

1.定义 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它允许你分步骤创建复杂对象&#xff0c;而不必直接调用构造函数。建造者模式特别适合那些包含多个组成部分并且构造过程复杂的对象。 2. 结构 建造者模式的主要组成部分包括&#…

朗新天霁eHR GetFunc_code.asmx SQL注入致RCE漏洞复现

0x01 产品简介 朗新天霁人力资源管理系统(LongShine eHR)是一款由北京朗新天霁软件技术有限公司研发的人力资源管理系统,该产品融合了国外先进的人力资源管理理念和国内大量人力资源管理实践经验,是国内功能较为全面、性价比较高的人力资源管理系统之一,系统凭借其集成化…

中国农业会计编辑部中国农业会计杂志社2024年第10期目录

人物风采 为民服务守初心 平凡岗位担使命——记云南省漾濞县畜牧兽医管理服务中心高级畜牧师徐健春 2 会计研究 管理会计在企业全面管理中的应用——以D公司为例 蒯浠语; 3-5,《中国农业会计》投稿&#xff1a;cnqikantg126.com AI时代企业会计数据集成管理策略研究…