摘要:本文系统介绍了网络编程的基础知识,涵盖IP地址、端口号、网络通信协议、网络字节序、TCP/IP五层模型等核心概念,并详细讲解了套接字编程的原理与实践。文章从网络通信的基本要素出发,逐步深入到协议分层、数据封装解封装过程,最后重点阐述了Socket API接口的使用方法,包括创建套接字、绑定地址信息、连接服务器等关键操作,为读者构建了完整的网络编程知识体系。
1. IP地址
本质:
- IPv4版本:4字节无符号整数
- IPv6版本:16字节的数据
作用:在网络中唯一标识一台主机。
IP地址不够用的解决方案:
- DHCP(动态主机配置协议):动态地址分配
- NAT/NAPT(网络地址转换/网络地址端口转换):通过地址转换技术复用IP
核心要素:网络通信中的每条数据中,都会包含源端IP和对端IP。
2. 端口号
本质:2字节无符号整数。
作用:在一台主机上,唯一标识一个网络通信程序。网络通信程序通过端口号,告诉操作系统,网卡收到了发往哪个端口的数据,应该交给我来进行处理。
核心要素:网络通信中的每条数据中,都会包含源端口和对端端口。
重要规则:一个进程可以占用多个端口号,但是一个端口号只能被一个进程占用。
3. 网络通信协议
本质:网络通信中,通信两端的数据格式约定。
基于网络通信环境的复杂性,对网络通信协议进行分层(不同的层次使用不同的协议,解决不同的问题)。
OSI七层模型:应用层、表示层、会话层、传输层、网络层、链路层、物理层。
TCP/IP五层模型:应用层、传输层、网络层、链路层、物理层。
数据封装与解封装过程:
- 发送端(自上而下封装):
- 应用层:你好 → qq 你好
- 传输层:tcp qq 你好
- 网络层:ip tcp qq 你好
- 链路层:eth ip tcp qq 你好
- 经过网络传输
- 接收端(自下而上解封装):
- 链路层:剥掉eth头部 → ip tcp qq 你好
- 网络层:剥掉ip头部 → tcp qq 你好
- 传输层:剥掉tcp头部 → qq 你好
- 应用层:得到“你好”
网络通信层次中的设备:
- 物理层:集线器等
- 链路层:交换机(负责相邻设备的选择与转发)
- 网络层:路由器(负责起点到终点的路径选择)
- 传输层:PC主机
五元组:源端IP + 源端端口 + 对端IP + 对端端口 + 通信协议。五元组完整地描述了一条网络通信。
4. 网络字节序
字节序:CPU在内存中进行数据存取的顺序。
- 二进制存在高位和低位之分:左边是高位,右边是低位,例如 0x0102。
- 内存存储存在高地址和低地址之分:一块空间起始处总是低地址。
- 一个存储单元大于1字节的数据,就会存在字节序的问题。
字节序类型:
- 大端字节序:低地址存高位。例如 0x01020304 → 内存依次为 01 02 03 04。
- 小端字节序:低地址存低位。例如 0x01020304 → 内存依次为 04 03 02 01。
字节序的决定性因素:CPU架构(例如 X86 为小端,MIPS 为大端)。
字节序对网络通信的影响:非常重要。如果通信双方字节序不一致,直接传输原始数据会导致解析错误。
网络字节序:实际上就是网络通信中数据的字节序标准。它约定在网络通信中,所有数据都应该转换为大端字节序后进行传输。
有了这个约定,数据接收方就可以根据自己的主机字节序,决定如何对收到的数据进行字节序转换。
5. TCP/IP 五层模型各层功能与协议
应用层:负责应用程序之间的数据沟通。
- 例如QQ与QQ程序之间沟通有自己的协议(数据格式约定),约定源QQ号、对端QQ号、时间、字体、消息类型等。
- 应用层在进行通信时,先要对数据进行描述,要素多了就需要约定如何组织。
- 典型协议:HTTP、FTP、DNS。
传输层:负责两个进程之间的数据传输。
- 传输层约定的关键信息:源端口号 + 对端端口号。
- 典型协议:TCP、UDP。
- TCP:可靠传输协议,安全但传输性能略低于UDP。
- UDP:不可靠传输协议,不安全,但传输性能高。
网络层:负责两个主机之间的数据传输。
- 网络层约定的关键信息:源端IP + 对端IP。
- 典型协议:IP协议。
链路层:负责相邻设备之间的数据传输。
- 链路层约定的关键信息:源端MAC地址 + 对端MAC地址。
- MAC地址:网卡的物理硬件地址,出厂时设定。
- 典型协议:以太网协议。
6. 套接字编程概述
套接字编程做什么:编写网络通信程序,实现两个主机上的进程之间的数据传输。
网络通信也是进程间通信(IPC)方式的一种,相较于以前的进程间通信,它可以跨主机。
通信两端的角色:
- 客户端:通常指的是部署在客户机上,发起业务请求的一端。
- 服务端:通常指的是部署在服务提供商的服务器上,向客户端提供服务的一端。
通信架构:
- C/S(客户端/服务器模式):程序员既要实现服务端程序,也要实现客户端程序。谁安装了客户端,谁才能与服务器通信。
- B/S(浏览器/服务器模式):程序员通常只需要实现服务端程序,客户端不需要实现(客户端就是浏览器)。
我们学习的网络编程,称为套接字编程(Socket 编程)。
学习内容:基于 Socket 套接字相关的 API 接口实现网络通信的客户端与服务端程序。
Socket 套接字:一套网络通信 API 接口。
7. 通信程序编写流程与 Socket API
主要关心的是传输层通信程序的编写。传输层有两个典型协议:TCP 和 UDP。
7.1 Socket API 接口
1. 创建套接字
int socket(int domain, int type, int protocol);- domain(地址域):描述通信地址类型。
<pre><ul> AF_UNIX:域间通信(本地通信),使用struct sockaddr_un。AF_INET:IPv4 通信,使用struct sockaddr_in。AF_INET6:IPv6 通信,使用struct sockaddr_in6。
- type(套接字类型):
SOCK_STREAM:流式套接字,有序的、可靠的、双向的,基于字节流(对应TCP)。SOCK_DGRAM:数据报套接字,无连接的、不可靠的,有最大长度限制(对应UDP)。SOCK_RAW:提供更底层原始的数据包捕获。
- protocol(协议类型):选择要使用的协议;默认值 0。
SOCK_STREAM默认 TCP。SOCK_DGRAM默认 UDP。- 也可显式指定:
IPPROTO_TCP(TCP协议)、IPPROTO_UDP(UDP协议)。
- 返回值:成功则返回操作句柄(套接字描述符);失败返回 -1。
2. 为套接字绑定地址信息
int bind(int sockfd, struct sockaddr* addr, socklen_t len);- 作用:
- 设置发送数据时的源端地址信息(当前设备网卡IP地址+端口号)
- 告知操作系统,网卡收到的哪些数据应该交给当前套接字进行处理
- 参数:
sockfd:套接字描述符,socket()返回的描述符。addr:要绑定的地址信息,可用结构体:struct sockaddr_un、struct sockaddr_in6、struct sockaddr_in。len:地址信息长度。
- 地址结构体示例(IPv4):
struct sockaddr_in { ushort sin_family, // 地址域类型,填充 AF_INET ushort sin_port, // 要绑定的端口号 struct in_addr { uint32 s_addr; } sin_addr; // 要绑定的 IP 地址 }; - 端口号规则:0~1024 是预留端口(需要管理员权限)
- 返回值:成功返回 0;失败返回 -1。
3. 连接服务器(用于 TCP 协议客户端)
int connect(int sockfd, struct sockaddr* addr, socklen_t len);- 参数:
sockfd:套接字描述符。addr:要连接的服务器地址信息。len:地址信息长度。
- 返回值:成功返回 0;失败返回 -1。
4. 开始监听(用于 TCP 协议的服务端)
int listen(int sockfd, int backlog);- 目的:告诉操作系统,可以开始接受客户端的连接请求了。
- backlog:同一时间能够接受最大客户端连接请求数量。限制同一时间最多有多少个客户端能够连接服务器。
- 返回值:成功返回 0,失败返回 -1。
5. 获取就绪连接(用于 TCP 协议的服务端)
int accept(int sockfd, struct sockaddr* peer_addr, socklen_t* len);- 作用:获取与指定客户端进行通信的操作句柄(描述符)。
- peer_addr:获取对端地址信息。
- len:既要设置要获取多长的地址,并且还要返回实际的地址信息长度。
- 函数的参数有三种分类:输入型参数,输出型参数,输入输出型参数。
- 返回值:成功则返回指定客户端通信句柄描述符;失败返回 -1。
6. 接收数据
接收数据的接口是一个阻塞接口(recv 的时候没有数据则等待)。
TCP 接收
ssize_t recv(int sockfd, void* buf, size_t len, int flag);- sockfd:对于客户端来说就是 socket 的返回值;对于服务端来说是 accept 的返回值。
- flag:
MSG_DONTWAIT:本次 recv 能拿到数据则直接返回,没有数据则报错返回。MSG_PEEK:接收数据,但是不从缓冲区删除数据。
- 返回值:
- 成功则返回实际读取到的数据长度。
- 出错则返回 -1。
- TCP 通信连接断开,返回 0。
UDP 接收接口
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flag, struct sockaddr* peer_addr, socklen_t* addrlen);- peer_addr:输出参数,返回对端的地址信息,表示该数据是谁发过来的。
7. 发送数据
发送数据相关的接口也是阻塞接口(发送过快,数据将 socket 发送缓冲区塞满了就会阻塞)。
ssize_t send(int sockfd, char* data, size_t len, int flag);ssize_t sendto(int sockfd, char* data, size_t len, int flag, struct sockaddr* toaddr, socklen_t addrlen);- toaddr:描述数据要发送给谁。
- 返回值:实际发送的数据长度;出错则返回 -1。
8. 关闭套接字
int close(int fd);9. 字节序转换接口
字节序转换针对的数据类型:short, int, long, float, double, 无符号。
- 主机字节序 → 网络字节序
- 2 字节数据转换:
uint16_t htons(uint16_t val); - 4 字节数据转换:
uint32_t htonl(uint32_t val); - 注意:短整型数据的转换不能使用 htonl。
- 2 字节数据转换:
- 网络字节序 → 主机字节序
uint16_t ntohs(uint16_t val);uint32_t ntohl(uint32_t val);
10. IP 地址相关转换接口
IP 本质:4 字节无符号整数,可以用 ntohl,htonl 进行转换。
日常使用点分十进制字符串表示 IP,因此需要专门接口:
struct in_addr inet_addr(const char *ipstr);- 将字符串点分十进制 IP 地址,直接转换为网络字节序数字 IP 地址。
const char* inet_ntoa(struct in_addr addr);- 将网络字节序数字 IP 地址,转换为字符串点分十进制 IP 地址。
- 注意:inet_ntoa 接口是非线程安全的。
const char* inet_ntop(int domain, void* src, char* dst, size_t size);- 功能:将网络字节序整数 IP 地址转换为字符串 IP 地址。
- domain:
AF_INET、AF_INET6,描述要转换的地址类型。 - src:网络字节序的数据地址。
- dst:转换后的字符串存放空间地址。
- size:dst 空间的大小。
int inet_pton(int domain, const char *src, void *dst);- 功能:将字符串的 IP 地址,转换为网络字节序 IP 地址。
11. UDP 通信程序编写流程
通常客户端程序中写死了服务器 IP 地址,客户端必然知道服务端的地址。
UDP 客户端流程
- 创建套接字
socket() - 绑定地址信息(通常客户端不主动绑定地址)操作系统检测套接字未绑定地址时,自动从闲置端口池分配端口 + IP 完成绑定
- 发送数据
sendto()(发送数据自带完整五元组:源端地址由 bind 确定;对端地址由 sendto 传入) - 接收响应
recvfrom() - 关闭套接字
close()
UDP 服务端流程
- 创建套接字
socket() - 为套接字绑定地址
bind() - 接收数据
recvfrom() - 向客户端响应数据
sendto() - 关闭套接字
close()
7.2 套接字编程本质与TCP/IP四层模型
套接字编程本质:学习套接字 socket API 相关的网络编程接口。
网络通信程序的角色之分:
- 客户端:主动向服务器发送请求,获取服务的一端,通常部署在客户机上。
- 服务端:通常部署在网络服务提供商的服务器上,接收客户端请求提供服务。
TCP/IP四层模型:
- 应用层:负责应用程序的沟通;典型知名协议:HTTP、DNS。
- 传输层:负责进程之间的数据传输;典型知名协议:TCP、UDP;运行于PC机。
- 网络层:负责主机之间的数据传输;典型知名协议:IP;运行于路由器。
- 链路层:负责相邻设备之间的数据传输;典型知名协议:ETH、ARP协议。
8. TCP 多进程服务器实战示例
下面是一个完整的 C++ TCP 多进程服务器示例,演示了如何使用 Socket API 创建一个支持多客户端连接的简单字典翻译服务器。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> // close #include <sys/socket.h> // socket api #include <netinet/in.h> // struct sockaddr_in #include <arpa/inet.h> // 字节序转换 htons #include <signal.h> #include <string> #include <unordered_map> // hash Map--键值对 #define CHECK_RESULT(ret, msg) if((ret) < 0) { perror(msg); return -1; } std::unordered_map<std::string,std::string> dict = { {"hello", "你好"}, {"leihou", "你好"}, {"eat", "吃饭"} }; // 实现一个多进程版本的服务器: 创建进程,在子进程中与客户端进行通信 void worker(int sockfd) { pid_t pid = fork(); if (pid < 0) { perror("创建子进程失败"); return ; } if (pid > 0) { return; } while(1) { char buffer[1024] = {0}; int ret = recv(sockfd, buffer, 1023, 0); if (ret < 0) { perror("接收数据出错"); continue; }else if (ret == 0) { printf("与客户端连接断开\n"); break; } printf("客户端say: %s\n", buffer); std::string resp; auto it = dict.find(buffer); if (it == dict.end()) { resp = "当前字典中没有该内容"; }else { resp = it-&gt;second; } ret = send(sockfd, resp.c_str(), resp.size(), 0); if (ret != resp.size()) { if (ret &lt; 0) { perror("发送数据出错"); close(sockfd); continue; } printf("数据发送不完整\n"); } } close(sockfd); exit(0); // 当前在子进程中,这里退出的是子进程 } int main(){ // 主动忽略 SIGCHLD 信号--告诉系统我不关注子进程状态 // 显式的告诉系统,子进程退出后,直接释放所有资源 signal(SIGCHLD, SIG_IGN); // 1. 创建套接字 // int socket(int domain, int type, int protocol) int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); CHECK_RESULT(listen_fd, "创建套接字失败"); // 2. 构造监听地址结构对象,为套接字绑定地址信息 // int bind(int fd, struct sockaddr* addr, socklen_t len) struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(9001); addr.sin_addr.s_addr = inet_addr("192.168.14.128"); socklen_t addrlen = sizeof(struct sockaddr_in); int ret = bind(listen_fd, (struct sockaddr*)&addr, addrlen); CHECK_RESULT(ret, "绑定地址失败"); // 3. 开始监听 // int listen(int sockfd, int backlog) ret = listen(listen_fd, 1024); CHECK_RESULT(ret, "监听失败"); while(1){ struct sockaddr_in peerAddr; int newfd = accept(listen_fd, (struct sockaddr*)&peerAddr, &addrlen); if (newfd < 0) { perror("获取新连接出错"); continue; } char peer_ip[24] = {0}; inet_ntop(AF_INET, &amp;peerAddr.sin_addr, peer_ip, 23); printf("新的客户端连接建立成功: [%s:%d]\n", peer_ip, ntohs(peerAddr.sin_port)); worker(newfd); close(newfd); // 父进程中并没有用到这个描述符 } return 0; }8.1 代码解析
1. 头文件说明
<stdio.h>、<string.h>、<stdlib.h>:标准 C 库函数<unistd.h>:提供 close、fork 等系统调用<sys/socket.h>:Socket API 相关函数和数据结构<netinet/in.h>:网络地址结构定义<arpa/inet.h>:字节序转换函数(htons、inet_addr 等)<signal.h>:信号处理相关<string>、<unordered_map>:C++ 标准库容器
2. 核心功能模块
字典数据结构:
std::unordered_map<std::string,std::string> dict = { {"hello", "你好"}, {"leihou", "你好"}, {"eat", "吃饭"} };使用哈希表存储简单的英汉翻译字典,客户端发送英文单词,服务器返回对应的中文翻译。
3. 多进程处理函数 worker()
- 通过
fork()创建子进程处理客户端连接 - 父进程立即返回,继续监听新连接
- 子进程进入循环,接收客户端数据并响应
- 使用
recv()接收数据,send()发送响应 - 连接断开时子进程退出
4. 主函数 main() 流程
- 信号处理:忽略 SIGCHLD 信号,避免僵尸进程
- 创建套接字:使用
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)创建 TCP 套接字 - 绑定地址:构造
sockaddr_in结构,绑定到 192.168.14.128:9001 - 开始监听:调用
listen()设置最大连接数为 1024 - 接受连接:循环调用
accept()接受客户端连接 - 多进程处理:为每个连接创建子进程处理
8.2 关键 Socket API 使用
1. socket() 创建套接字
int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);AF_INET:IPv4 地址族SOCK_STREAM:流式套接字(TCP)IPPROTO_TCP:TCP 协议
2. bind() 绑定地址
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(9001); addr.sin_addr.s_addr = inet_addr("192.168.14.128");htons():将主机字节序的端口号转换为网络字节序inet_addr():将点分十进制 IP 字符串转换为网络字节序整数
3. listen() 开始监听
ret = listen(listen_fd, 1024);设置最大连接队列长度为 1024,超过此数量的连接请求将被拒绝。
4. accept() 接受连接
int newfd = accept(listen_fd, (struct sockaddr*)&peerAddr, &addrlen);- 返回新的套接字描述符用于与客户端通信
peerAddr保存客户端地址信息inet_ntop()将网络字节序 IP 转换为可读字符串
5. 多进程模型
- 主进程负责接受连接(
accept()) - 为每个连接创建子进程(
fork()) - 子进程处理具体业务逻辑(
recv()/send()) - 使用
signal(SIGCHLD, SIG_IGN)避免僵尸进程
8.3 编译与运行
编译命令:
g++ -std=c++11 -o tcp_server tcp_server.cpp运行服务器:
./tcp_server客户端测试(使用 telnet 或 nc):
telnet 192.168.14.128 9001 # 或 nc 192.168.14.128 9001连接成功后,输入 "hello"、"leihou" 或 "eat" 等字典中的单词,服务器会返回对应的中文翻译。
8.4 注意事项
- 实际部署时需要将 IP 地址改为服务器的实际 IP
- 多进程模型适合连接数不多的场景,连接数多时建议使用多线程或 I/O 多路复用
- 代码中使用了简单的错误处理,实际生产环境需要更完善的错误处理机制
- 字典数据可以扩展为从文件或数据库加载
- 注意资源管理,确保所有套接字在适当的时候关闭
9. TCP 多线程服务器实战示例
下面是一个完整的 C++ TCP 多线程服务器示例,演示了如何使用 Socket API 和 pthread 库创建一个支持多客户端连接的简单聊天服务器。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> // close #include <sys/socket.h> // socket api #include <netinet/in.h> // struct sockaddr_in #include <arpa/inet.h> // 字节序转换 htons #include <pthread.h> #define CHECK_RESULT(ret, msg) if((ret) < 0) { perror(msg); return -1; } void* entry(void *arg) { int sockfd = (int)arg; while(1) { char buffer[1024] = {0}; int ret = recv(sockfd, buffer, 1023, 0); if (ret < 0) { perror("接收数据出错"); continue; }else if (ret == 0) { printf("与客户端连接断开\n"); break; } printf("客户端say: %s\n", buffer); // 捕获键盘输入,向客户端发送数据 memset(buffer, 0x00, 1024); printf("输入数据:"); fflush(stdout); scanf("%s", buffer); ret = send(sockfd, buffer, strlen(buffer), 0); if (ret != strlen(buffer)) { if (ret &lt; 0) { perror("发送数据出错"); close(sockfd); continue; } printf("数据发送不完整\n"); } } close(sockfd); } // 创建一个线程,负责通过当前的描述符与客户端进行通信 void worker(int sockfd) { pthread_t tid; int ret = pthread_create(&tid, NULL, entry, (void*)sockfd); if (ret != 0) { perror("创建工作线程失败"); return; } pthread_detach(tid); // 分离线程,让线程退出后自动释放所有资源,不需要被等待 } int main(){ // 1. 创建套接字 // int socket(int domain, int type, int protocol) int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); CHECK_RESULT(listen_fd, "创建套接字失败"); // 2. 构造监听地址结构对象,为套接字绑定地址信息 // int bind(int fd,struct sockaddr* addr, socklen_t len) struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(9001); addr.sin_addr.s_addr = inet_addr("192.168.14.128"); socklen_t addrlen = sizeof(struct sockaddr_in); int ret = bind(listen_fd, (struct sockaddr*)&addr, addrlen); CHECK_RESULT(ret, "绑定地址失败"); // 3. 开始监听 // int listen(int sockfd, int backlog) ret = listen(listen_fd, 1024); CHECK_RESULT(ret, "监听失败"); while(1){ struct sockaddr_in peerAddr; int newfd = accept(listen_fd, (struct sockaddr*)&peerAddr, &addrlen); if (newfd < 0) { perror("获取新链接出错"); continue; } char peer_ip[24] = {0}; inet_ntop(AF_INET, &amp;peerAddr.sin_addr, peer_ip, 23); printf("新的客户端连接建立成功: [%s:%d]\n",peer_ip, ntohs(peerAddr.sin_port)); worker(newfd); // close(newfd); 线程间资源共享,这里关闭会导致工作线程无法使用 } return 0; }9.1 代码解析
1. 头文件说明
<stdio.h>、<string.h>、<stdlib.h>:标准 C 库函数<unistd.h>:提供 close 等系统调用<sys/socket.h>:Socket API 相关函数和数据结构<netinet/in.h>:网络地址结构定义<arpa/inet.h>:字节序转换函数(htons、inet_addr、inet_ntop 等)<pthread.h>:POSIX 线程库,用于多线程编程
2. 线程处理函数 entry()
- 参数:接收客户端套接字描述符作为参数
- 功能:循环接收客户端数据并响应
- 接收数据:使用
recv()接收客户端发送的数据 - 发送数据:从标准输入读取用户输入,通过
send()发送给客户端 - 连接管理:检测连接断开(
recv()返回 0)时退出循环
3. 线程创建函数 worker()
- 使用
pthread_create()创建新线程处理客户端连接 - 线程分离:调用
pthread_detach()分离线程,线程退出后自动释放资源 - 线程参数:将客户端套接字描述符传递给线程处理函数
4. 主函数 main() 流程
- 创建套接字:使用
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)创建 TCP 套接字 - 绑定地址:构造
sockaddr_in结构,绑定到 192.168.14.128:9001 - 开始监听:调用
listen()设置最大连接队列长度为 1024 - 接受连接:循环调用
accept()接受客户端连接 - 多线程处理:为每个连接创建独立线程处理
9.2 关键 Socket API 使用
1. socket() 创建套接字
int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);AF_INET:IPv4 地址族SOCK_STREAM:流式套接字(TCP)IPPROTO_TCP:TCP 协议
2. bind() 绑定地址
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(9001); addr.sin_addr.s_addr = inet_addr("192.168.14.128");htons():将主机字节序的端口号转换为网络字节序inet_addr():将点分十进制 IP 字符串转换为网络字节序整数
3. listen() 开始监听
ret = listen(listen_fd, 1024);设置最大连接队列长度为 1024,超过此数量的连接请求将被拒绝。
4. accept() 接受连接
int newfd = accept(listen_fd, (struct sockaddr*)&peerAddr, &addrlen);- 返回新的套接字描述符用于与客户端通信
peerAddr保存客户端地址信息inet_ntop()将网络字节序 IP 转换为可读字符串
5. 多线程模型
- 主线程负责接受连接(
accept()) - 为每个连接创建工作线程(
pthread_create()) - 工作线程处理具体业务逻辑(
recv()/send()) - 使用
pthread_detach()避免需要等待线程结束
9.3 编译与运行
编译命令:
gcc -o tcp_thread_server tcp_thread_server.c -lpthread运行服务器:
./tcp_thread_server客户端测试(使用 telnet 或 nc):
telnet 192.168.14.128 9001 # 或 nc 192.168.14.128 9001连接成功后,服务器会显示客户端发送的消息,并等待用户输入回复消息。
9.4 注意事项
- 实际部署时需要将 IP 地址改为服务器的实际 IP
- 多线程模型相比多进程模型,资源开销更小,适合高并发场景
- 线程间共享进程资源,需要注意线程安全问题
- 代码中使用了简单的错误处理,实际生产环境需要更完善的错误处理机制
- 注意资源管理,确保所有套接字在适当的时候关闭
- 编译时需要链接 pthread 库(
-lpthread) - 线程分离后不需要调用
pthread_join()等待线程结束
9.5 与多进程模型的对比
- 资源开销:线程比进程更轻量,创建和销毁开销更小
- 资源共享:线程共享进程的地址空间,进程有独立的地址空间
- 通信方式:线程间通信更简单(共享内存),进程间通信需要 IPC 机制
- 稳定性:一个线程崩溃可能影响整个进程,进程间相互隔离更安全
- 适用场景:多线程适合 I/O 密集型任务,多进程适合 CPU 密集型任务