网络基础与套接字编程详解
2026/8/18 12:34:23 网站建设 项目流程

摘要:本文系统介绍了网络编程的基础知识,涵盖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_unstruct sockaddr_in6struct 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。
  • 网络字节序 → 主机字节序
    • uint16_t ntohs(uint16_t val);
    • uint32_t ntohl(uint32_t val);

10. IP 地址相关转换接口

IP 本质:4 字节无符号整数,可以用 ntohl,htonl 进行转换。

日常使用点分十进制字符串表示 IP,因此需要专门接口:

  1. struct in_addr inet_addr(const char *ipstr);
    • 将字符串点分十进制 IP 地址,直接转换为网络字节序数字 IP 地址
  2. const char* inet_ntoa(struct in_addr addr);
    • 将网络字节序数字 IP 地址,转换为字符串点分十进制 IP 地址。
    • 注意:inet_ntoa 接口是非线程安全的。
  3. const char* inet_ntop(int domain, void* src, char* dst, size_t size);
    • 功能:将网络字节序整数 IP 地址转换为字符串 IP 地址。
    • domain:AF_INETAF_INET6,描述要转换的地址类型。
    • src:网络字节序的数据地址。
    • dst:转换后的字符串存放空间地址。
    • size:dst 空间的大小。
  4. int inet_pton(int domain, const char *src, void *dst);
    • 功能:将字符串的 IP 地址,转换为网络字节序 IP 地址。

11. UDP 通信程序编写流程

通常客户端程序中写死了服务器 IP 地址,客户端必然知道服务端的地址。

UDP 客户端流程

  1. 创建套接字socket()
  2. 绑定地址信息(通常客户端不主动绑定地址)操作系统检测套接字未绑定地址时,自动从闲置端口池分配端口 + IP 完成绑定
  3. 发送数据sendto()(发送数据自带完整五元组:源端地址由 bind 确定;对端地址由 sendto 传入)
  4. 接收响应recvfrom()
  5. 关闭套接字close()

UDP 服务端流程

  1. 创建套接字socket()
  2. 为套接字绑定地址bind()
  3. 接收数据recvfrom()
  4. 向客户端响应数据sendto()
  5. 关闭套接字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 &lt; 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-&amp;gt;second; } ret = send(sockfd, resp.c_str(), resp.size(), 0); if (ret != resp.size()) { if (ret &amp;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*)&amp;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*)&amp;peerAddr, &amp;addrlen); if (newfd &lt; 0) { perror("获取新连接出错"); continue; } char peer_ip[24] = {0}; inet_ntop(AF_INET, &amp;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() 流程

  1. 信号处理:忽略 SIGCHLD 信号,避免僵尸进程
  2. 创建套接字:使用socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)创建 TCP 套接字
  3. 绑定地址:构造sockaddr_in结构,绑定到 192.168.14.128:9001
  4. 开始监听:调用listen()设置最大连接数为 1024
  5. 接受连接:循环调用accept()接受客户端连接
  6. 多进程处理:为每个连接创建子进程处理

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 &amp;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*)&amp;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*)&amp;peerAddr, &amp;addrlen); if (newfd &lt; 0) { perror("获取新链接出错"); continue; } char peer_ip[24] = {0}; inet_ntop(AF_INET, &amp;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() 流程

  1. 创建套接字:使用socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)创建 TCP 套接字
  2. 绑定地址:构造sockaddr_in结构,绑定到 192.168.14.128:9001
  3. 开始监听:调用listen()设置最大连接队列长度为 1024
  4. 接受连接:循环调用accept()接受客户端连接
  5. 多线程处理:为每个连接创建独立线程处理

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 密集型任务

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询