news 2026/7/3 5:44:30

【C++】网络编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++】网络编程

什么是socket

在C++中,Socket编程是一种用于在网络上进行通信的技术,它允许不同主机上的应用程序通过TCP/IP协议族进行数据交换。Socket作为应用层与TCP/IP协议族通信的中间软件抽象层,提供了一组接口,隐藏了协议的复杂性,使得开发者只需要关注简单的接口即可实现网络通信。

I/O

I/O (Input/Output) - 输入输出:泛指计算机与外部世界的数据交换,socket是网络I/O;

进行I/O时分为两步:一是检测是否符合条件,二是进行操作。阻塞条件下不满足条件就会一直检测直到符合条件;非阻塞检测一次就会返回。

I/O多路复用就是只检测,找到符合条件的文件描述符,然后只处理这些文件描述符。现在常见的I/O多路复用有三种select、poll、epoll,在检测时select、poll是通过轮询进行检测,epoll是通过回调进行实现。还有就是select需要每次把关注的fd集合拷贝到内核,而epoll只需要第一次把fd拷贝到红黑树中;

阻塞 I/O(Blocking I/O):满足发送/接收条件才输入输出

  • 当进程执行I/O操作时,如果条件不满足,进程会被操作系统挂起(睡眠),直到条件满足才被唤醒继续执行。
应用程序 内核 网络 | | | | send() | | |--------------->| | | | wait buffer space | | |<----------------> | | | copy data | | | start send | | |------------------>| | return size | | |<---------------| | 应用程序 内核 网络 | | | | recv() | | |--------------->| | | |check recv buffer| | | if null->wait | | |<--------------->| | | else recv | | |<----------------| | | copy data to | | return size | user space | |<---------------| |

非阻塞 I/O(Non-blocking I/O):

  • 当进程执行I/O操作时,如果条件不满足,系统调用立即返回一个错误,而不是让进程进入睡眠状态。进程可以继续执行其他任务,稍后再重试。
  • 非阻塞 I/O 的关键特征
    • 立即返回:无论I/O是否完成,立即返回控制权
    • 错误码指示:用特殊错误码表示"需要等待"
    • 主动轮询:需要程序主动检查I/O状态
    • 并行处理:可以在等待I/O时做其他事情
模型特点适用场景
阻塞 I/O条件不满足时进程挂起简单应用,连接数少
非阻塞 I/O立即返回,需要轮询需要实时响应的应用
I/O 多路复用一个线程管理多个 I/O高并发服务器

TCP-UDP

特性TCPUDP
连接面向连接无连接
可靠性可靠,自动重传不可靠,可能丢包
顺序性保证数据顺序不保证顺序
流量控制有滑动窗口
头部开销20-60字节8字节

连接:tcp三次握手,四次挥手

  • SYN(Synchronize):同步序号,用于建立连接
  • ACK(Acknowledgment):确认标志
  • FIN(Finish):结束标志,用于关闭连接
  • RST(Reset):重置连接
  • PSH(Push):推送数据
  • URG(Urgent):紧急指针有效
连接-三次握手 Client Server | | | 1. SYN (seq=x) | |--------------------------------------->| | | | 2. SYN-ACK (seq=y, ack=x+1) | |<---------------------------------------| | | | 3. ACK (ack=y+1) | |--------------------------------------->| | | | 连接建立,开始数据传输 | |<======================================>| 断开连接-四次挥手 Client Server | | | 1. FIN (seq=u) | |--------------------------------------->| | | | 2. ACK (ack=u+1) | |<---------------------------------------| | | | 服务器处理剩余数据 | |<======================================>| | | | 3. FIN (seq=v, ack=u+1) | |<---------------------------------------| | | | 4. ACK (ack=v+1) | |--------------------------------------->| | 双方关闭连接 |

send-recv

tcp使用send-recv收发消息;

  • 发送消息- send
ssize_t send(int sockfd, // 目标socket文件描述符 const void *buf, // 要发送的数据缓冲区 size_t len, // 要发送的数据长度 int flags); // 发送标志(通常为0) // 0 - 默认行为,阻塞发送 send(sockfd, buf, len, 0); // MSG_DONTWAIT - 非阻塞发送 send(sockfd, buf, len, MSG_DONTWAIT); // MSG_NOSIGNAL - 不生成SIGPIPE信号 send(sockfd, buf, len, MSG_NOSIGNAL); // MSG_OOB - 发送带外数据(紧急数据) send(sockfd, buf, len, MSG_OOB); // MSG_MORE - 提示有更多数据要发送(用于UDP) send(sockfd, buf, len, MSG_MORE); // 可以组合使用 send(sockfd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL);
  • 接收消息- recv
ssize_t recv(int sockfd, // socket文件描述符 void *buf, // 接收数据缓冲区 size_t len, // 缓冲区长度 int flags); // 接收标志(通常为0) // 常用的flags值: // 0 - 默认行为,阻塞接收 recv(sockfd, buf, len, 0); // MSG_DONTWAIT - 非阻塞接收 recv(sockfd, buf, len, MSG_DONTWAIT); // MSG_PEEK - 查看数据但不从缓冲区移除 recv(sockfd, buf, len, MSG_PEEK); // MSG_OOB - 接收带外数据(紧急数据) recv(sockfd, buf, len, MSG_OOB); // MSG_WAITALL - 等待接收所有请求的字节 recv(sockfd, buf, len, MSG_WAITALL); // MSG_TRUNC - 即使数据被截断也返回数据包长度(原始长度) recv(sockfd, buf, len, MSG_TRUNC); // 可以组合使用(某些组合可能无效) recv(sockfd, buf, len, MSG_DONTWAIT | MSG_PEEK);

TCP实例

简单实例

实现一个简单的单线程、单个连接的阻塞I/O的客户端-服务器程序

server
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd; struct sockaddr_in address, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[BUFFER_SIZE]; // 创建socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); return 1; } int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); close(server_fd); return 1; } // 设置地址 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"); return 1; } // 监听 if (listen(server_fd, 3) < 0) { perror("listen"); return 1; } printf("Server started on port %d\n", PORT); // 接受连接 client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); return 1; } printf("Client connected\n"); while (1) { // 读取数据 int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; printf("Received: %s\n", buffer); // 发送响应 const char* response = "Hello from server!\n"; write(client_fd, response, strlen(response)); } else if (bytes_read == 0) { printf("Client disconnected\n"); break; } else { perror("Read failed"); break; } } // 关闭连接 close(client_fd); return 0; }
  • CMakeLists.txt
cmake_minimum_required(VERSION 3.0) project(socketServer) # include_directories(${CMAKE_SOURCE_DIR}/CMAKE_SOURCE_DIRinclude) include_directories(include) add_compile_options(-g -std=c++11 -o2 -Wall) set(CMAKE_BUILD_TYPE Debug) # 设置可执行文件输出目录(在 build/bin/) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) # 设置库文件输出目录(在 build/lib/) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) add_executable(server ./src/server.cpp) target_link_libraries(server PRIVATE pthread)
client
#include <iostream> #include <string> #include <cstring> #include <cerrno> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int sock = 0; struct sockaddr_in serv_addr; char buffer[BUFFER_SIZE] = {0}; // 创建socket if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { std::cerr << "Socket creation error: " << strerror(errno) << std::endl; return EXIT_FAILURE; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // 转换IP地址 if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) { std::cerr << "Invalid address/Address not supported: " << strerror(errno) << std::endl; return EXIT_FAILURE; } // 连接服务器 if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { std::cerr << "Connection failed: " << strerror(errno) << std::endl; return EXIT_FAILURE; } std::cout << "Connected to server at " << SERVER_IP << ":" << PORT << std::endl; std::cout << "Type your messages (type 'exit' to quit):" << std::endl; while (true) { // 获取用户输入 std::string input; //不使用do-while 如果直接按enter,程序会向下走,send发送为空,但是服务端接收不到,就会在read中等待 //客户端也会在read中等待 do{ std::cout << "> "; std::getline(std::cin, input); }while(input.empty()); // 检查退出命令 if (input == "exit") { break; } // 发送消息到服务器 if (send(sock, input.c_str(), input.length(), 0) < 0) { std::cerr << "Send failed: " << strerror(errno) << std::endl; break; } std::cout << "Message sent to server" << std::endl; // 接收服务器响应 memset(buffer, 0, BUFFER_SIZE); int valread = read(sock, buffer, BUFFER_SIZE - 1); if (valread < 0) { std::cerr << "Read error: " << strerror(errno) << std::endl; break; } else if (valread == 0) { std::cout << "Server closed the connection" << std::endl; break; } else { buffer[valread] = '\0'; std::cout << "Server response:\n" << buffer << std::endl; } } close(sock); std::cout << "Connection closed" << std::endl; return 1; }
非阻塞方式
// 获取当前文件状态标志 int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { perror("fcntl F_GETFL"); return -1; } // 添加非阻塞标志 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { perror("fcntl F_SETFL"); return -1; }
int on = 1; // 1表示启用非阻塞 if (ioctl(fd, FIONBIO, &on) < 0) { perror("ioctl FIONBIO"); return -1; }

服务端接收多个客户端

  • 多进程、多线程、select、poll、epoll
    • 少量连接:多进程/多线程
    • 中等并发:select/poll
    • 高并发:epoll
多进程 fork()
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd; struct sockaddr_in address, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[BUFFER_SIZE]; // 创建socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); return 1; } int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); close(server_fd); return 1; } // 设置地址 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"); return 1; } // 监听 if (listen(server_fd, 3) < 0) { perror("listen"); return 1; } signal(SIGCHLD, SIG_IGN); printf("Server started on port %d\n", PORT); // 只需要修改主循环部分 while (true) { // 接受连接 client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept faild!"); continue; // 继续等待下一个连接 } printf("Client connected\n"); // 创建子进程处理客户端 pid_t pid = fork(); if (pid < 0) { perror("fork failed"); close(client_fd); continue; } if (pid == 0) { // 子进程 close(server_fd); // 子进程不需要监听socket // 处理客户端连接(使用你现有的while循环) while (true) { int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; printf("PID %d Received: %s\n", getpid(), buffer); const char* response = "server received!\n"; write(client_fd, response, strlen(response)); } else if (bytes_read == 0) { printf("PID %d Client disconnected\n", getpid()); break; } else { perror("Read failed"); break; } } close(client_fd); exit(0); // 子进程退出 } else { // 父进程 close(client_fd); // 父进程关闭客户端socket,继续监听 } } // 关闭连接 close(client_fd); return 0; }
多线程 std::thread
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <thread> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <signal.h> #include <iostream> #define PORT 8080 #define BUFFER_SIZE 1024 // 客户端处理线程函数 void handle_client(int client_fd) { char buffer[BUFFER_SIZE]; std::cout << "Thread " << std::this_thread::get_id() << ": Client connected" << std::endl; while (1) { memset(buffer, 0, BUFFER_SIZE); int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; const char * response = "receive over!"; std::cout<< "received:" << buffer << std::endl; write(client_fd, response, strlen(response)); } else if (bytes_read == 0) { printf("Thread %lu: Client disconnected\n", pthread_self()); break; } else { perror("Read failed"); break; } } close(client_fd); return; } int main() { int server_fd, client_fd; struct sockaddr_in address, client_addr; socklen_t client_len = sizeof(client_addr); // 创建socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); return 1; } int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); close(server_fd); return 1; } // 设置地址 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"); return 1; } // 监听 if (listen(server_fd, 3) < 0) { perror("listen"); return 1; } signal(SIGCHLD, SIG_IGN); printf("Server started on port %d\n", PORT); while (true) { // 接受连接 client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept faild!"); continue; // 继续等待下一个连接 } char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip)); int client_port = ntohs(client_addr.sin_port); std::cout << "New client connected from " << client_ip << ":" << client_port << std::endl; try { std::thread client_thread(handle_client, client_fd); client_thread.detach(); // 分离线程,让它在后台运行 } catch (const std::exception& e) { std::cerr << "Failed to create thread: " << e.what() << std::endl; close(client_fd); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 8:25:33

AI Agent平台架构设计与实现:从核心概念到工程落地

你好&#xff0c;我是CSDN的一名技术博主。最近在准备技术分享和复盘项目时&#xff0c;我深入研究了AI Agent平台的设计与实现&#xff0c;发现网上资料虽多&#xff0c;但大多停留在概念层面&#xff0c;缺乏从架构设计到代码落地的系统性拆解。恰好&#xff0c;这类问题也是…

作者头像 李华
网站建设 2026/7/2 5:34:33

基于51/STM32单片机智能浇花 土壤湿度计控制系统 自动灌溉 大棚1(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_

基于51/STM32单片机智能浇花 土壤湿度计控制系统 自动灌溉 大棚1(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_ 功能说明&#xff1a; 土壤湿度水泵自动手动声光报警 版本一/二十一&#xff1a; 51/32单片机进行数据处理LCD1602液晶显示土壤湿…

作者头像 李华
网站建设 2026/7/2 5:59:34

白发增多是衰老的信号吗?高仕星维生素b从营养干预时间窗口谈起

「我今年 52 岁&#xff0c;这两年白头发长得特别快&#xff0c;几乎每两个月就能看出新的一批。朋友说这是正常的衰老表现&#xff0c;不用管。可我还是想知道&#xff0c;有没有什么办法能慢一点&#xff1f;」随着年龄的增长&#xff0c;白发增多确实是一个普遍现象。流行病…

作者头像 李华
网站建设 2026/7/2 9:59:37

工业场景视觉测距实战:AGV 导航与避障

工业场景视觉测距实战&#xff1a;AGV 导航与避障 1. AGV 视觉导航需求 AGV 视觉导航功能&#xff1a; ├── 定位&#xff1a;视觉 SLAM / 二维码定位 ├── 测距&#xff1a;障碍物距离检测 ├── 避障&#xff1a;实时障碍物规避 ├── 导航&#xff1a;路径规划与跟踪…

作者头像 李华
网站建设 2026/7/2 17:33:45

Element Plus 级联选择器实战:仿学科网教材多级选择的完整方案

基于 Element Plus 2.10 的 el-cascader&#xff0c;实现一个「前两级单选 后续层级同父多选」的教材选择器&#xff0c;涵盖 props 配置、选中逻辑、UI 定制和数据提交。一、需求背景 在资源上传场景中&#xff0c;教材数据是一个多级树结构&#xff1a; 出版社&#xff08;第…

作者头像 李华
网站建设 2026/7/2 23:40:25

施工动画全解析:从技术选型到标杆案例,如何让复杂施工方案“看得懂、说得清、能预演”

2026年&#xff0c;施工动画已成为大型基建项目投标、方案评审和技术交底的标准配置。它不再是“效果图的动态版”&#xff0c;而是一份可预演、可验证的“可视化施工方案”。与一般的产品宣传动画不同&#xff0c;施工动画的核心价值不在于视觉有多华丽&#xff0c;而在于能否…

作者头像 李华