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

@@ -39,7 +39,7 @@ I/O 复用技术可以解决这个问题。
> 「好像不能同时说话?」
实际上,因为是在进行对话,所以很少发生同时说话的情况。也就是说,上述系统采用的是**「时分复用」**技术。因为说话人频率不同,即使在同时说话也能进行一定程度上的区分(杂音也随之增多)。因此,也可以说是「频分复用技术」。
实际上,因为是在进行对话,所以很少发生同时说话的情况。也就是说,上述系统采用的是**「时分复用」**技术。因为说话人的声音频率不同,即使在同时说话也能进行一定程度上的区分(杂音也随之增多)。因此,也可以说是「频分复用技术」。
#### 12.1.3 复用技术在服务器端的应用
@@ -67,7 +67,7 @@ select 函数是最具代表性的实现复用服务器的方法。在 Windows
> 术语:「事件」。当发生监视项对应情况时,称「发生了事件」。
select 函数的使用方法与一般函数的区别并不大,更准确说,很难使用。但是为了实现 I/O 复用服务器端,我们应该掌握 select 函数并运用于套接字编程当中。认为「select 函数是 I/O 复用的全部内容」也并不为过。select 函数的调用过程如下图所示:
select 函数的使用方法与一般函数的区别并不大,更准确说,很难使用。但是为了实现 I/O 复用服务器端,我们应该掌握 select 函数并运用于套接字编程当中。认为「select 函数是 I/O 复用的全部内容」也并不为过。select 函数的调用过程如下图所示:
![](images/kAtdRs.png)
@@ -116,7 +116,7 @@ timeout: 调用 select 函数后,为防止陷入无限阻塞的状态,传递
1. 文件描述符的监视(检查)范围是?
2. 如何设定 select 函数的超时时间?
第一,文件描述符的监视范围和 select 的第一个参数有关。实际上select 函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在 fd_set 变量中的文件描述符数。但每次新建文件描述符时,其值就会增加 1 ,故只需将最大的文件描述符值加 1 再传递给 select 函数即可。加 1 是因为文件描述符的值是从 0 开始的。
第一,文件描述符的监视范围和 select 的第一个参数有关。实际上select 函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在 fd_set 变量中的文件描述符数。但文件描述符的值通常随创建而增大(已关闭的描述符可能被系统复用),故只需将最大的文件描述符值加 1 再传递给 select 函数即可。加 1 是因为文件描述符的值是从 0 开始的。
第二select 函数的超时时间与 select 函数的最后一个参数有关,其中 timeval 结构体定义如下:
@@ -142,7 +142,7 @@ select 返回正整数时,怎样获知哪些文件描述符发生了变化?
下面是一个 select 函数的例子:
- [select.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch12/select.c)
- [select.c](select.c)
编译运行:
@@ -161,7 +161,7 @@ gcc select.c -o select
下面通过 select 函数实现 I/O 复用服务器端。下面是基于 I/O 复用的回声服务器端。
- [echo_selectserv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch12/echo_selectserv.c)
- [echo_selectserv.c](echo_selectserv.c)
编译运行:
@@ -196,6 +196,11 @@ gcc echo_selectserv.c -o selserv
3. **复用服务器端需要 select 函数。下列关于 select 函数使用方法的描述错误的是?**
① 调用 select 函数前需要集中待监视的文件描述符。
② 调用 select 函数后无需再次注册监视对象。
③ 复用服务器端无法同时向多个客户端提供服务。
④ 基于 select 的复用服务器只需要 1 个进程,可减少创建多进程带来的负担。
答:错误的描述是 2 和 3。说明如下
- 描述 1 正确:调用 select 函数前需要集中 I/O 监视对象的文件描述符。

View File

@@ -59,6 +59,8 @@ int main(int argc, char *argv[])
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
if (clnt_sock == -1)
continue;
FD_SET(clnt_sock, &reads); //注册一个clnt_sock
if (fd_max < clnt_sock)
@@ -68,7 +70,13 @@ int main(int argc, char *argv[])
else //不是服务端套接字时
{
str_len = read(i, buf, BUF_SIZE); //i指的是当前发起请求的客户端
if (str_len == 0)
if (str_len == -1)
{
FD_CLR(i, &reads);
close(i);
printf("closed client(read error): %d \n", i);
}
else if (str_len == 0)
{
FD_CLR(i, &reads);
close(i);

View File

@@ -39,6 +39,11 @@ int main(int argc, char *argv[])
if (FD_ISSET(0, &temps)) //验证发生变化的值是否是标准输入端
{
str_len = read(0, buf, BUF_SIZE);
if (str_len == -1)
{
puts("read error!");
continue;
}
buf[str_len] = 0;
printf("message from console: %s", buf);
}