docs: 全面校对全部章节文档与示例代码

通过多智能体工作流对 19 章笔记(README.md)与 96 个 .c 示例代码做深度
审查与对抗性验证,修复 317 处确认问题,涵盖:

技术正确性:
- 修复缓冲区溢出:echo_mpserv.c / echo_storeserv.c 等的 read(buf, BUFSIZ)
  改为 BUF_SIZE(buf 仅 30 字节,BUFSIZ 远大于此)
- 修复 open() 缺少 mode 参数:low_open.c / fd_seri.c / desto.c 等
  O_CREAT 调用补 0644(原导致 low_read 链路失败)
- 修复 feof 循环 off-by-one:news_sender.c / echo_stdserv.c 改用 fgets
  返回值判断
- 修复线程竞态:chat_server.c / webserv_linux.c 的 &clnt_sock 栈地址
  传子线程改为 malloc 分配 + free
- 修复索引混淆:char_EPLTserv.c 错用 clnt_sock 查找改为 ep_events[i].data.fd
- 修复格式化符:thread4.c 的 sizeof 用 %d 改为 %zu
- 修正习题答案:ch01 fd 序号、ch13 MSG_OOB 加粗项、ch09 Nagle 等

文档规范:
- 统一术语:IPv4/IPv6、接收(receive)/连接(connection)
- 修正错别字:occured→occurred、cooffee→coffee、Usgae→Usage、
  eerror→error、proess→process 等
- 修复病句、补全习题答案解释
- GitHub 绝对 URL 改为相对路径,统一项目引用规范
- 同步根 README.md(前言 + 19 章合并)

另:重命名 ch10/remove_zomebie.c → remove_zombie.c(修正拼写)

所有 .c 文件经 gcc 编译验证通过(ch17 epoll 文件因 macOS 无 sys/epoll.h
跳过,已人工复核)。
This commit is contained in:
riba2534
2026-06-28 12:47:46 +08:00
parent a9ef4b6dc4
commit 5625eea472
76 changed files with 707 additions and 629 deletions

View File

@@ -24,7 +24,7 @@ TCP 与 UDP 的区别很大一部分来源于流控制。也就是说 TCP 的生
#### 6.1.3 UDP 的高效使用
UDP 也具有一定的可靠性。对于通过网络实时传递视频或音频时情况有所不同。对于多媒体数据而言,丢失一部分数据也没有太大问题,这只是会暂时引起画面抖动,或者出现细微的杂音。但是要提供实时服务,速度就成为了一个很重要的因素。因此流控制就显得有一点多余,这时就要考虑使用 UDP 。TCP 比 UDP 慢的原因主要有以下两点:
UDP 也具有一定的可靠性。通过网络实时传递视频或音频时情况有所不同。对于多媒体数据而言,丢失一部分数据也没有太大问题,这只是会暂时引起画面抖动,或者出现细微的杂音。但是要提供实时服务,速度就成为了一个很重要的因素。因此流控制就显得有一点多余,这时就要考虑使用 UDP 。TCP 比 UDP 慢的原因主要有以下两点:
- 收发数据前后进行的连接设置及清除过程。
- 收发过程中为保证可靠性而添加的流控制。
@@ -64,7 +64,7 @@ addrlen: 传递给参数 to 的地址值结构体变量长度
*/
```
上述函数与之前的 TCP 输出函数最大的区别在于,此函数需要向它传递目标地址信息。接下来介绍接收 UDP 数据的函数。UDP 数据的发送并不固定,因此该函数定义为可接发送端信息的形式,也就是将同时返回 UDP 数据包中的发送端信息。
上述函数与之前的 TCP 输出函数最大的区别在于,此函数需要向它传递目标地址信息。接下来介绍接收 UDP 数据的函数。UDP 数据的发送并不固定,因此该函数定义为可接发送端信息的形式,也就是将同时返回 UDP 数据包中的发送端信息。
```c
#include <sys/socket.h>
@@ -89,7 +89,7 @@ addrlen: 保存参数 from 的结构体变量长度的变量地址值。
代码:
- [uecho_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_client.c)
- [uecho_client.c](uecho_client.c)
- [uecho_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_server.c)
编译运行:
@@ -97,7 +97,7 @@ addrlen: 保存参数 from 的结构体变量长度的变量地址值。
```shell
gcc uecho_client.c -o uclient
gcc uecho_server.c -o userver
./server 9190
./userver 9190
./uclient 127.0.0.1 9190
```
@@ -119,7 +119,7 @@ UDP 程序中,调用 sendto 函数传输数据前应该完成对套接字的
#### 6.3.1 存在数据边界的 UDP 套接字
前面说 TCP 数据传输中不存在数据边界,这表示「数据传输过程中调用 I/O 函数的次数不具有任何意义」
前面说 TCP 数据传输中不存在数据边界,这表示「数据传输过程中调用 I/O 函数的次数不具有任何意义」
相反UDP 是具有数据边界的协议,传输中调用 I/O 函数的次数非常重要。因此,输入函数的调用次数和输出函数的调用次数应该完全一致,这样才能保证接收全部已经发送的数据。例如,调用 3 次输出函数发送的数据必须通过调用 3 次输入函数才能接收完。通过一个例子来进行验证:
@@ -145,13 +145,13 @@ host1 是服务端host2 是客户端host2 一次性把数据发给服务
#### 6.3.2 已连接connectUDP 套接字与未连接unconnectedUDP 套接字
TCP 套接字中需注册待传输数据的目标IP和端口号而在 UDP 中无需注册。因此通过 sendto 函数传输数据的过程大概可以分为以下 3 个阶段:
TCP 套接字中需注册待传输数据的目标IP和端口号而在 UDP 中无需注册。因此通过 sendto 函数传输数据的过程大概可以分为以下 3 个阶段:
- 第 1 阶段:向 UDP 套接字注册目标 IP 和端口号
- 第 2 阶段:传输数据
- 第 3 阶段:删除 UDP 套接字中注册的目标地址信息。
每次调用 sendto 函数时重复上述过程。每次都变更目标地址,因此可以重复利用同一 UDP 套接字向不同目标传递数据。这种未注册目标地址信息的套接字称为未连接套接字,反之,注册了目标地址的套接字称为连接 connected 套接字。显然UDP 套接字默认属于未连接套接字。当一台主机向另一台主机传输很多信息时,上述的三个阶段中,第一个阶段和第三个阶段占整个通信过程中近三分之一的时间,缩短这部分的时间将会大大提高整体性能。
每次调用 sendto 函数时重复上述过程。每次都变更目标地址,因此可以重复利用同一 UDP 套接字向不同目标传递数据。这种未注册目标地址信息的套接字称为未连接套接字,反之,注册了目标地址的套接字称为连接connected套接字。显然UDP 套接字默认属于未连接套接字。当一台主机向另一台主机传输很多信息时,上述的三个阶段中,第一个阶段和第三个阶段占整个通信过程中近三分之一的时间,缩短这部分的时间将会大大提高整体性能。
#### 6.3.3 创建已连接 UDP 套接字
@@ -170,7 +170,7 @@ connect(sock, (struct sockaddr *)&adr, sizeof(adr));
之后就与 TCP 套接字一致,每次调用 sendto 函数时只需传递信息数据。因为已经指定了收发对象,所以不仅可以使用 sendto、recvfrom 函数,还可以使用 write、read 函数进行通信。
下面的例子把之前的 [uecho_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_client.c) 程序改成了基于已连接 UDP 的套接字的程序,因此可以结合 [uecho_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_server.c) 程序运行。代码如下:
下面的例子把之前的 [uecho_client.c](uecho_client.c) 程序改成了基于已连接 UDP 的套接字的程序,因此可以结合 [uecho_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_server.c) 程序运行。代码如下:
- [uecho_con_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/uecho_con_client.c)
@@ -188,7 +188,7 @@ connect(sock, (struct sockaddr *)&adr, sizeof(adr));
1. **UDP 为什么比 TCP 快?为什么 TCP 传输可靠而 UDP 传输不可靠?**
答:为了提供可靠的数据传输服务TCP 在不可靠的IP进行流控制,而 UDP 缺少这种流控制。所以 UDP 是不可靠的传输方式。
答:UDP 比 TCP 快主要有两点原因:一是 UDP 不需要收发数据前后的连接建立与拆除过程;二是 UDP 不进行为保证可靠性而添加的流控制。而 TCP 传输可靠,是因为它在不可靠的 IP 层之上进行流控制等可靠性保障UDP 缺少这些保障,所以是不可靠的传输方式。
2. **下面不属于 UDP 特点的是?**
@@ -200,7 +200,7 @@ connect(sock, (struct sockaddr *)&adr, sizeof(adr));
4. **UDP 套接字和 TCP 套接字可以共存。若需要,可以同时在同一主机进行 TCP 和 UDP 数据传输。**
5. 针对 UDP 函数也可以调用 connect 函数,此时 UDP 套接字跟 TCP 套接字相同,也需要经过 3 次握手阶段。
第2句和第5句不属于 UDP 的特点(即这句话是错误的。第2句错误是因为 UDP 只需一个套接字就可以向多个目标传输数据第5句错误是因为 UDP 调用 connect 函数只是注册目标地址信息,不会进行 TCP 那样的三次握手过程。
第2句、第3句和第5句不属于 UDP 的特点(即这句话是错误的。第2句错误是因为 UDP 只需一个套接字就可以向多个目标传输数据;第3句错误是因为 TCP 和 UDP 的端口空间相互独立UDP 套接字可以使用与 TCP 相同的端口号;第5句错误是因为 UDP 调用 connect 函数只是注册目标地址信息,不会进行 TCP 那样的三次握手过程。
3. **UDP 数据报向对方主机的 UDP 套接字传递过程中IP 和 UDP 分别负责哪些部分?**

View File

@@ -38,6 +38,8 @@ int main(int argc, char *argv[])
adr_sz = sizeof(your_adr);
str_len = recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr *)&your_adr, &adr_sz);
if (str_len == -1)
error_handling("recvfrom() error");
printf("Message %d: %s \n", i + 1, message);
}
close(sock);

View File

@@ -16,7 +16,6 @@ int main(int argc, char *argv[])
char msg3[] = "Nice to meet you";
struct sockaddr_in your_adr;
socklen_t your_adr_sz;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);

View File

@@ -32,12 +32,12 @@ int main(int argc, char* argv[])
while(1)
{
fputs("Inset message(q to Quit): ", stdout);
fputs("Insert message(q to Quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;
sendto(sock, message, BUF_SIZE, 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
adr_sz = sizeof(from_adr);
str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);

View File

@@ -41,7 +41,7 @@ int main(int argc, char *argv[])
sendto(sock, message, strlen(message), 0,
(struct sockaddr *)&serv_adr, sizeof(serv_adr));
adr_sz = sizeof(from_adr);
str_len = recvfrom(sock, message, BUF_SIZE, 0,
str_len = recvfrom(sock, message, BUF_SIZE - 1, 0,
(struct sockaddr *)&from_adr, &adr_sz);
message[str_len] = 0;
printf("Message from server: %s", message);

View File

@@ -13,9 +13,7 @@ int main(int argc, char *argv[])
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t adr_sz; //多余变量
struct sockaddr_in serv_adr, from_adr; //不需要 from_adr
struct sockaddr_in serv_adr;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);

View File

@@ -24,7 +24,7 @@ int main(int argc, char *argv[])
//创建 UDP 套接字后,向 socket 的第二个参数传递 SOCK_DGRAM
serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if (serv_sock == -1)
error_handling("UDP socket creation eerror");
error_handling("UDP socket creation error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;