什么是socket在C中Socket编程是一种用于在网络上进行通信的技术它允许不同主机上的应用程序通过TCP/IP协议族进行数据交换。Socket作为应用层与TCP/IP协议族通信的中间软件抽象层提供了一组接口隐藏了协议的复杂性使得开发者只需要关注简单的接口即可实现网络通信。I/OI/O (Input/Output) - 输入输出泛指计算机与外部世界的数据交换socket是网络I/O进行I/O时分为两步一是检测是否符合条件二是进行操作。阻塞条件下不满足条件就会一直检测直到符合条件非阻塞检测一次就会返回。I/O多路复用就是只检测找到符合条件的文件描述符然后只处理这些文件描述符。现在常见的I/O多路复用有三种select、poll、epoll在检测时select、poll是通过轮询进行检测epoll是通过回调进行实现。还有就是select需要每次把关注的fd集合拷贝到内核而epoll只需要第一次把fd拷贝到红黑树中阻塞 I/OBlocking 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/ONon-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 (seqx) | |---------------------------------------| | | | 2. SYN-ACK (seqy, ackx1) | |---------------------------------------| | | | 3. ACK (acky1) | |---------------------------------------| | | | 连接建立开始数据传输 | || 断开连接-四次挥手 Client Server | | | 1. FIN (sequ) | |---------------------------------------| | | | 2. ACK (acku1) | |---------------------------------------| | | | 服务器处理剩余数据 | || | | | 3. FIN (seqv, acku1) | |---------------------------------------| | | | 4. ACK (ackv1) | |---------------------------------------| | 双方关闭连接 |send-recvtcp使用send-recv收发消息发送消息- sendssize_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);接收消息- recvssize_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.txtcmake_minimum_required(VERSION 3.0) project(socketServer) # include_directories(${CMAKE_SOURCE_DIR}/CMAKE_SOURCE_DIRinclude) include_directories(include) add_compile_options(-g -stdc11 -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); } }