《UNIX 网络编程-卷1》阅读笔记05: 基本 TCP 套接字编程
第四章是UNP全书的真正编程起点。前一章学习了套接字地址结构和字节序转换这些基础构件,本章将这些概念落到实处——开始编写真正的TCP客户/服务器程序。本章的使命是完整介绍编写一个TCP客户/服务器程序所需要的全部基本套接字函数,包括socket、connect、bind、listen、accept、close和fork等。学完本章,你将能够独立编写一个简单的TCP回射(echo)客户/服务器程序。
作者: andylin02
学习章节: 第四章 基本TCP套接字编程
关键词: TCP套接字, socket, connect, bind, listen, accept, fork, 并发服务器, 迭代服务器, 字节流, 套接字描述符
一、章节概述
1.1 本章焦点
第四章是UNP全书的真正编程起点。前一章学习了套接字地址结构和字节序转换这些基础构件,本章将这些概念落到实处——开始编写真正的TCP客户/服务器程序。
本章的使命是完整介绍编写一个TCP客户/服务器程序所需要的全部基本套接字函数,包括socket、connect、bind、listen、accept、close和fork等。学完本章,你将能够独立编写一个简单的TCP回射(echo)客户/服务器程序。
1.2 本章内容结构
| 节号 | 标题 | 核心内容 |
|---|---|---|
| 4.2 | socket函数 | 创建套接字描述符,协议族与套接字类型的有效组合 |
| 4.3 | connect函数 | TCP客户端连接服务器,三次握手触发点,错误处理 |
| 4.4 | bind函数 | 将本地协议地址绑定到套接字,服务器端绑定知名端口 |
| 4.5 | listen函数 | 将主动套接字转换为被动套接字,内核连接队列机制 |
| 4.6 | accept函数 | 从已完成连接队列返回下一个已完成连接 |
| 4.7 | fork和exec函数 | 进程创建,并发服务器的基石 |
| 4.8 | 并发服务器 | 使用fork实现每客户一个子进程的并发模型 |
| 4.9 | close函数 | 关闭套接字,TCP连接终止的起点 |
| 4.10 | getsockname和getpeername | 获取本地和对端协议地址 |
💡 本章核心价值:读完第四章,你应该能够——
- 掌握TCP客户/服务器程序的完整函数调用流程
- 理解socket、connect、bind、listen、accept每个函数的作用和调用时机
- 写出一个能工作的TCP回射服务器和对应的客户端
- 理解fork并发模型的基本原理
- 知道如何正确关闭套接字避免资源泄漏
二、TCP套接字编程核心函数
2.1 函数调用全景图
// 服务器端调用流程
socket() → 创建监听套接字
↓
bind() → 绑定知名端口
↓
listen() → 转换为被动监听套接字
↓
accept() → 阻塞等待客户连接
↓
read()/write() → 与特定客户端通信
↓
close() → 关闭连接
// 客户端调用流程
socket() → 创建套接字
↓
connect() → 主动发起连接(触发三次握手)
↓
write()/read() → 与服务器通信
↓
close() → 关闭连接
2.2 socket函数——一切从这里开始
进程进行网络I/O的第一步就是调用socket函数,指定期望的通信协议类型。
#include <sys/socket.h>
int socket(int family, int type, int protocol);
// 返回:成功则为非负描述符(套接字描述符),若出错则为-1
参数详解:
| 参数 | 常用值 | 含义 |
|---|---|---|
| family | AF_INET |
IPv4协议 |
AF_INET6 |
IPv6协议 | |
AF_LOCAL/AF_UNIX |
Unix域协议 | |
AF_ROUTE |
路由套接字 | |
AF_KEY |
密钥套接字 | |
| type | SOCK_STREAM |
字节流套接字(TCP使用) |
SOCK_DGRAM |
数据报套接字(UDP使用) | |
SOCK_SEQPACKET |
有序分组套接字(SCTP使用) | |
SOCK_RAW |
原始套接字 | |
| protocol | IPPROTO_TCP |
TCP传输协议 |
IPPROTO_UDP |
UDP传输协议 | |
IPPROTO_SCTP |
SCTP传输协议 | |
0 |
系统默认协议 |
💡 关键理解:并不是所有family和type的组合都有效。例如,
AF_INET与SOCK_STREAM的组合默认使用TCP,AF_INET与SOCK_DGRAM的组合默认使用UDP。
AF_XXX与PF_XXX的关系:
AF_前缀表示地址族(Address Family),PF_前缀表示协议族(Protocol Family)。历史上曾设想单个协议族可支持多个地址族,PF_值用于创建套接字,AF_值用于套接字地址结构。但实践中从未实现过,且PF_值与AF_值总是相等的。为与现存代码保持一致,本书仅使用AF_常值。
socket创建后的状态:
socket函数调用成功后,得到的套接字是一个主动套接字,处于CLOSED状态。它还不能用于接收连接请求,需要后续函数将其转换为所需状态。
2.3 connect函数——客户端发起连接
TCP客户端使用connect函数建立与TCP服务器的连接。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
// 返回:若成功则为0,若出错则为-1
参数说明:
sockfd:由socket函数返回的套接字描述符servaddr:指向服务器套接字地址结构的指针(必须包含服务器的IP地址和端口号)addrlen:套接字地址结构的大小
connect前的bind问题:
客户端在调用connect之前不必调用bind函数。如果需要,内核会为该套接字自动选择一个临时端口作为源端口,并选择一个本地IP地址。这正是典型的客户端行为——不关心使用哪个端口和哪个IP地址。
connect触发的三次握手:
调用connect函数将触发TCP的三次握手过程,且仅在连接建立成功或出错时才返回。
三种常见出错情况:
| 错误类型 | 原因 | 处理方式 |
|---|---|---|
ETIMEDOUT |
客户端没有收到SYN分节的响应(超时) | 检查服务器是否可达,网络是否正常 |
ECONNREFUSED |
对客户端的SYN响应是RST(复位) | 服务器指定端口没有进程在监听 |
EHOSTUNREACH/ENETUNREACH |
中间路由器引发ICMP错误 | 检查路由和网络连通性 |
⚠️ 重要:若connect失败,该套接字描述符不再可用,必须关闭它,然后重新调用socket创建新套接字。
connect触发的状态转换:
调用connect使套接字从CLOSED状态转换到SYN_SENT状态;若成功,再转换到ESTABLISHED状态。
2.4 bind函数——服务器绑定地址
bind函数把一个本地协议地址赋予一个套接字。对于TCP服务器,这通常用于绑定一个知名端口。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
// 返回:若成功则为0,若出错则为-1
bind的两大作用:
| 角色 | bind的作用 |
|---|---|
| TCP服务器 | 绑定知名端口,限定套接字只接收目的地为该IP地址的客户连接 |
| TCP客户端 | 可选。如果调用bind,则指定发送IP数据报的源IP地址(通常不需要) |
常见用法:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配地址,监听所有网络接口
servaddr.sin_port = htons(SERV_PORT); // 知名端口
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
关于INADDR_ANY:
INADDR_ANY的值定义为0,表示“任意地址”或“所有地址”。当服务器绑定INADDR_ANY时,内核在处理连接时,不会进行目的地址匹配——即套接字可以接收发往本机任何网络接口IP地址的客户连接。这是绝大多数服务器程序的标准做法,使服务器能处理来自任何网络接口的连接请求。
⚠️ 常见错误:bind返回
EADDRINUSE表示地址已在使用(通常是端口被占用或处于TIME_WAIT状态)。
2.5 listen函数——转换为被动套接字
listen函数仅由TCP服务器调用,完成两个关键任务:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
// 返回:若成功则为0,若出错则为-1
listen的两大功能:
-
状态转换:将socket创建的主动套接字转换为被动套接字(监听套接字),指示内核应接受指向该套接字的连接请求。
-
队列初始化:规定内核为该套接字排队的最大连接个数。
listen的时机:通常在socket和bind之后,accept之前调用。
内核的两个连接队列:
┌─────────────────────────────────────────────────────────────┐
│ 监听套接字的两个队列 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端SYN到达 │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ 未完成连接队列 │ │
│ │ (incomplete connection)│ ← 处于SYN_RCVD状态 │
│ │ - 正在进行三次握手的连接 │ │
│ └────────────┬────────────┘ │
│ │ 三次握手完成 │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ 已完成连接队列 │ │
│ │ (completed connection) │ ← 处于ESTABLISHED状态 │
│ │ - 等待accept取走的连接 │ │
│ └────────────┬────────────┘ │
│ │ │
│ ▼ │
│ accept()取走 │
└─────────────────────────────────────────────────────────────┘
- 未完成连接队列:每个这样的SYN分节对应其中一项,套接字处于SYN_RCVD状态,正在等待TCP三次握手完成。
- 已完成连接队列:每个已完成TCP三次握手的客户对应其中一项,套接字处于ESTABLISHED状态,等待accept取走。
backlog参数的含义:
历史上,backlog指定的是两个队列之和的最大值。但在现代Linux实现中,backlog指定的是已完成连接队列的最大长度,未完成连接队列长度则由系统参数控制。
实际队列长度取min(backlog, core.sysctl_somaxconn)。在Linux中,/proc/sys/net/core/somaxconn定义了系统级的上限。
💡 注意:当连接请求到达而队列已满时,TCP会忽略该SYN分节(不发送RST),客户端会重传SYN,期待未来队列有空位。
2.6 accept函数——接受连接
accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
// 返回:若成功则为非负描述符(已连接套接字描述符),若出错则为-1
参数说明:
sockfd:监听套接字描述符(由socket创建,bind和listen使用的那个)cliaddr:用于返回已连接的对端进程(客户)的协议地址(可为NULL)addrlen:值-结果参数。调用时传入cliaddr结构的大小,返回时内核告知实际存储的字节数
💡 关键区分:在讨论accept函数时,第一个参数称为监听套接字(listening socket),返回值称为已连接套接字描述符(connected socket descriptor)。
accept的工作流程:
- 检查已完成连接队列是否为空
- 若队列为空,则进程被投入睡眠(阻塞)直到有连接放入队列
- 若队列非空,取出队列中的第一个已完成连接
- 由内核自动生成一个全新的套接字描述符(已连接套接字)
- 该新套接字具有与监听套接字相同的属性,但不再处于监听状态
- 返回该新套接字描述符
💡 设计思想:为何不直接复用监听套接字?因为监听套接字需要持续监听新的连接请求。若将其用于与单个客户通信,将无法同时接受其他客户。通过创建新套接字,监听套接字得以保留,可以继续接受更多连接。这也是并发服务器的基础。
2.7 fork和exec函数——创建新进程
#include <unistd.h>
pid_t fork(void);
// 返回:子进程中返回0,父进程中返回子进程PID,出错返回-1
fork的特点:
- fork是Unix中创建新进程的唯一方法
- 子进程获得父进程数据空间、堆和栈的副本
- 父进程和子进程共享文件描述符,但引用计数增加
- fork后的两个进程独立执行
// fork的典型使用模式
if ((pid = fork()) == 0) {
// 子进程代码
do_child_work();
exit(0);
} else if (pid > 0) {
// 父进程代码
do_parent_work();
}
2.8 close函数——关闭套接字
#include <unistd.h>
int close(int sockfd);
// 返回:若成功则为0,若出错则为-1
close的工作原理:
close将套接字描述符的引用计数减1。若引用计数变为0,则启动TCP连接终止过程(发送FIN)。若引用计数仍大于0,则仅减少计数,不真正关闭连接。
💡 引用计数的含义:每个进程独立持有文件描述符。父进程fork子进程后,父子进程共享同一个套接字,引用计数变为2。若父进程close了套接字,引用计数降为1,但套接字仍然打开,直到子进程也close它,才真正开始终止过程。
2.9 getsockname和getpeername——获取地址信息
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
// 返回:若成功则为0,若出错则为-1
应用场景:
- getsockname:获取与套接字关联的本地协议地址(如客户端想知道内核自动分配的临时端口)
- getpeername:获取与套接字关联的对端协议地址(如exec后的子进程需要知道客户地址)
2.10 套接字函数时序图
TCP服务器 TCP客户端
│ │
│ socket() │ socket()
│ bind() │
│ listen() │
│ accept() ←────阻塞等待───── │
│ │ connect()
│ ←───TCP三次握手────→ │
│ │ (connect返回)
│ (accept返回) │
│ │
│ read() │ write()
│ ←───数据请求─────────────────── │
│ 处理请求 │
│ write() │ read()
│ ───→数据响应──────────────────→ │
│ │
│ read() ←─返回0(对端FIN)─ │ close()
│ close() │
│ │
▼ ▼
三、两种服务器模式详解
3.1 迭代服务器(Iterative Server)
迭代服务器是最简单的服务器模型:一次只处理一个客户端,在处理完当前客户的所有请求之前,不接受新的连接。
// 迭代服务器核心框架
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
while (1) {
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
// 处理当前客户——其他客户必须等待
do_service(connfd);
Close(connfd);
}
迭代服务器的局限性:
- 若当前客户处理时间较长,所有后续客户都会长时间等待
- 不适合实际生产环境(但适合学习和测试)
3.2 并发服务器(Concurrent Server)——核心知识点
并发服务器是Unix网络编程中的经典范式,通过在accept后fork子进程来处理每个客户,实现真正的并发处理。
// 并发服务器核心框架
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ((childpid = Fork()) == 0) {
/* 子进程 */
Close(listenfd); // 子进程关闭监听套接字(不需要)
str_echo(connfd); // 处理客户请求
Close(connfd);
exit(0);
}
/* 父进程 */
Close(connfd); // 父进程关闭已连接套接字(不需要)
}
💡 关键理解:父进程和子进程共享所有打开的描述符。子进程不需要监听套接字,父进程不需要已连接套接字。如果不主动关闭,引用计数不会减少,套接字资源无法释放,最终会导致可用文件描述符耗尽。
并发服务器的执行流程:
父进程
│
│ accept()返回connfd
▼
fork()执行
│
├──────────────────────────┐
│ (子进程) │ (父进程)
│ │
│ Close(listenfd) │ Close(connfd)
│ (不需要监听) │ (不需要连接)
│ │
│ 处理客户请求 │ 继续accept()新连接
│ ... │ ...
│ Close(connfd) │
│ exit(0) │
│ │
▼ ▼
为什么父子进程都要close不用的套接字?
因为文件和套接字有引用计数。当父进程fork后,监听套接字的引用计数为2,已连接套接字的引用计数也为2。若不主动关闭不需要的套接字,则:
- 子进程持有监听套接字→若父进程崩溃,监听套接字仍被占用→端口无法释放
- 父进程持有已连接套接字→已连接套接字引用计数不会降为0→TCP连接不会终止
四、完整源代码分析
4.1 时间获取服务器(迭代服务器版本)
这是书中第4章的完整示例,一个显示客户IP地址和端口号的时间获取服务器程序。
#include "unp.h"
#include <time.h>
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t len;
struct sockaddr_in servaddr, cliaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime服务器端口 */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &len);
/* 打印客户IP地址和端口号 */
printf("connection from %s, port %d\n",
Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
4.2 TCP回射服务器(并发服务器版本)
这是第5章的完整代码,体现了并发服务器的核心设计模式:
#include "unp.h"
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ((childpid = Fork()) == 0) {
/* 子进程 */
Close(listenfd); /* 子进程关闭监听套接字 */
str_echo(connfd); /* 处理客户请求的回射函数 */
Close(connfd);
exit(0);
}
/* 父进程 */
Close(connfd); /* 父进程关闭已连接套接字 */
}
}
4.3 str_echo回射处理函数
#include "unp.h"
void str_echo(int sockfd)
{
ssize_t n;
char line[MAXLINE];
while ((n = Readline(sockfd, line, MAXLINE)) > 0) {
Writen(sockfd, line, n); /* 将接收到的数据原样返回 */
}
}
4.4 TCP回射客户端(str_cli)
#include "unp.h"
void str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Writen(sockfd, sendline, strlen(sendline));
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
}
五、关键注意事项与常见错误
5.1 字节流协议的理解误区
关键点:TCP是字节流协议,没有记录边界。read和write调用传输的字节数可能比请求的数量少,这不是出错,而是因为内核中用于套接字的缓冲区可能已达到极限。
这意味着:
- 不能假设一次read就能读完整条消息
- 发送端调用一次write,接收端可能需要多次read才能读完
- 发送端多次write,接收端可能一次read就读到所有数据
💡 解决方案:使用第三章介绍的
readn、writen和readline等包裹函数,确保读写完整数量的字节或完整的一行数据。
5.2 并发服务器的fork陷阱
父子进程的套接字引用计数问题:
- fork后父子进程共享文件描述符,引用计数变为2
- 父进程必须close已连接套接字(子进程也需要close监听套接字)
- 若不close,引用计数不会降为0,连接无法终止,文件描述符会耗尽
5.3 accept的阻塞行为
- 若已完成连接队列为空,accept会阻塞进程直到有连接可用
- 阻塞模式下,accept会一直等待,不会主动返回
5.4 connect失败的套接字重用
重要规则:若connect失败,该套接字描述符不再可用,必须关闭它,然后重新调用socket创建新套接字。
5.5 服务器需要bind的原因
如果TCP服务器不bind一个地址,在listen的时候内核会随机分配端口。这种操作对服务器不合适,因为服务器需要运行在一个知名端口上,这样客户端才知道连接到哪里。
5.6 并发服务器的信号处理(提前预览)
当子进程终止时,内核会向父进程发送SIGCHLD信号。若不处理这个信号,子进程会成为僵尸进程(zombie)。第5章将详细讲解如何使用signal函数和waitpid来处理这个问题。
六、TCP状态转换回顾
/* 回顾第二章的TCP状态转换,在套接字函数调用中如何体现 */
socket() → 套接字处于 CLOSED 状态
connect() 调用 → CLOSED → SYN_SENT
connect() 成功返回 → SYN_SENT → ESTABLISHED
bind() → 不改变套接字状态
listen() 调用 → CLOSED → LISTEN
accept() 成功返回 → 监听套接字状态不变,返回新套接字处于 ESTABLISHED
七、本章习题解答
习题4.1
问题:在并发服务器中,父子进程为什么要关闭自己不使用的套接字描述符?
答案:因为文件和套接字是引用计数的。fork后父子进程共享套接字,引用计数变为2。父进程必须close已连接套接字,否则:
- 父进程不关闭,引用计数不会降为0,子进程close后仍为1,TCP连接不会真正终止(不会发送FIN)
- 长时间运行会耗尽文件描述符
子进程必须close监听套接字,否则父进程即使close了监听套接字,引用计数仍为1(被子进程持有),端口无法释放。
习题4.2
问题:在迭代服务器中,如果客户在处理过程中突然终止,会发生什么?
答案:当客户进程终止时,所有打开的描述符被自动关闭。这将导致向服务器发送FIN,服务器的read将返回0(EOF),服务器会关闭连接并继续接受下一个客户。这是正常的连接终止流程。
习题4.3
问题:backlog参数如何影响服务器处理大量并发连接的能力?
答案:backlog限制了已完成连接队列的最大长度。当大量客户同时连接而服务器来不及调用accept时,队列会填满。队列满后,新到达的SYN会被忽略(不发送RST),客户端会重传SYN。因此,在高并发场景下设置足够大的backlog值很重要。但实际值受系统参数net.core.somaxconn限制。
习题4.4
问题:write返回成功是否意味着对方已经收到数据?
答案:不是。write返回成功只表示数据已从应用程序缓冲区复制到TCP发送缓冲区,并不表示数据已被对方接收。TCP的可靠传输是异步的,确认过程在后台进行。
八、本章小结
8.1 核心知识点回顾
| 函数 | 角色 | 关键点 |
|---|---|---|
socket |
客户+服务器 | 创建主动套接字(CLOSED状态) |
connect |
客户 | 触发三次握手,阻塞等待完成 |
bind |
服务器 | 绑定知名端口,INADDR_ANY监听所有接口 |
listen |
服务器 | 转换为被动套接字,初始化连接队列 |
accept |
服务器 | 从已完成队列取连接,返回新套接字 |
fork |
服务器 | 创建子进程处理每个客户 |
close |
客户+服务器 | 减少引用计数,0时触发FIN |
getsockname/getpeername |
工具函数 | 获取本地/对端地址 |
8.2 TCP套接字编程思维导图
第四章 基本TCP套接字编程
├── 核心函数调用顺序
│ ├── 服务器: socket → bind → listen → accept → read/write → close
│ └── 客户: socket → connect → write/read → close
├── socket函数
│ ├── family: AF_INET/AF_INET6/AF_LOCAL
│ ├── type: SOCK_STREAM/SOCK_DGRAM/SOCK_RAW
│ └── protocol: 0表示默认
├── connect函数
│ ├── 触发三次握手
│ ├── 阻塞到完成
│ └── 失败必须重新socket
├── bind函数
│ ├── 服务器绑定知名端口
│ ├── INADDR_ANY: 监听所有接口
│ └── 常见错误: EADDRINUSE
├── listen函数
│ ├── 主动→被动套接字
│ └── 维护两个连接队列
├── accept函数
│ ├── 监听套接字 vs 已连接套接字
│ └── 队列空时阻塞
├── 并发服务器
│ ├── fork创建子进程
│ └── 父子进程各close不用的套接字
└── close函数
├── 引用计数机制
└── 触发FIN的条件
九、下一章预告
📌 下一篇:《UNIX网络编程》读书笔记(五):第五章 TCP客户/服务器程序示例
第五章将详细讲解:
- 完整的TCP回射服务器程序:包括main函数、
str_echo、str_cli的完整实现 readline函数的优化:为什么标准readline效率低,如何改进SIGCHLD信号处理:如何处理僵尸进程,为什么wait不够,必须使用waitpid- 连接中断的各种情况分析:
- 服务器进程终止时客户端的反应
- 服务器主机崩溃时会发生什么
- 服务器主机崩溃后重启的处理
- 服务器主动关闭连接的影响
- 数据格式问题:网络传输中的二进制数据与文本数据的考虑
- TCP紧急数据(带外数据):
MSG_OOB标志的使用
学习目标:学完第五章后,你将能够——
- 写出一个健壮的、能处理各种异常情况的TCP回射服务器
- 正确处理SIGCHLD信号,避免僵尸进程
- 理解TCP连接在各种故障场景下的行为
- 设计可靠的应用层协议
敬请期待!
参考资料
- W. Richard Stevens, Bill Fenner, Andrew M. Rudoff. 《UNIX网络编程 卷1:套接字联网API(第3版)》. 北京:人民邮电出版社
- UNIX网络编程卷一 学习笔记 第四章 基本TCP套接字编程,CSDN,https://blog.csdn.net/tus00000/article/details/107283780
- 《UNIX网络编程》读书笔记——第四章(基本TCP套接字编程),CSDN,https://blog.csdn.net/u012103747/article/details/40951395
- UNP学习笔记(第四章 基本TCP套接字编程),CSDN,https://blog.csdn.net/Runnyu/article/details/47450461
- unp第四章:基本套接字编程,博客园,https://www.cnblogs.com/ronnieos/p/15974327.html
- UNIX网络编程(UNP) 第四章学习笔记,www.e-com-net.com,https://www.e-com-net.com/article/1666979089786953728.htm
- UNP学习笔记二–简单的并发服务器(concurrent servers),CSDN,https://blog.csdn.net/aaaaa851766403/article/details/101404004
- unix网络编程第四章----基于TCP套接字编程,博客园,https://www.cnblogs.com/zengyiwen/p/5755213.html
- UNP——第四章,TCP套接字编程,博客园,https://www.cnblogs.com/yangxinrui/p/12219815.html
- UNP读书笔记第4章 基本套接字编程,CSDN,https://blog.csdn.net/weixin_41217899/article/details/97620326
- 绑定特殊 IP 之 0.0.0.0 的内部工作原理,腾讯云开发者社区,https://cloud.tencent.com.cn/developer/article/1969217
- listen linux,腾讯云开发者社区,https://cloud.tencent.com.cn/developer/information/listen%20linux
- unix网络编程之listen()详解,阿里云开发者社区,https://developer.aliyun.com/article/67762
- accept(2) — manpages-zh,https://manpages.debian.org/accept.2.zh.html
- accept()返回的套接字绑定哪个端口 新旧套接字的联系,腾讯云开发者社区,https://cloud.tencent.cn/developer/article/1361071
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)