mirror of
https://github.com/riba2534/TCP-IP-NetworkNote.git
synced 2026-06-29 17:36:05 +08:00
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:
@@ -1,8 +1,8 @@
|
||||
## 第 7 章 优雅的断开套接字的连接
|
||||
## 第 7 章 优雅地断开套接字的连接
|
||||
|
||||
本章代码,在[TCP-IP-NetworkNote](https://github.com/riba2534/TCP-IP-NetworkNote)中可以找到。
|
||||
|
||||
本章讨论如何优雅的断开套接字的连接,之前用的方法不够优雅是因为,我们是调用 close 函数或 closesocket 函数单方面断开连接的。
|
||||
本章讨论如何优雅地断开套接字的连接,之前用的方法不够优雅是因为,我们是调用 close 函数或 closesocket 函数单方面断开连接的。
|
||||
|
||||
### 7.1 基于 TCP 的半关闭
|
||||
|
||||
@@ -14,9 +14,9 @@ Linux 的 close 函数和 Windows 的 closesocket 函数意味着完全断开连
|
||||
|
||||

|
||||
|
||||
图中描述的是 2 台主机正在进行双向通信,主机 A 发送完最后的数据后,调用 close 函数断开了最后的连接,之后主机 A 无法再接受主机 B 传输的数据。实际上,是完全无法调用与接受数据相关的函数。最终,由主机 B 传输的、主机 A 必须要接受的数据也销毁了。
|
||||
图中描述的是 2 台主机正在进行双向通信,主机 A 发送完最后的数据后,调用 close 函数断开了最后的连接,之后主机 A 无法再接收主机 B 传输的数据。实际上,是完全无法调用与接收数据相关的函数。最终,由主机 B 传输的、主机 A 必须要接收的数据也销毁了。
|
||||
|
||||
为了解决这类问题,「只关闭一部分数据交换中使用的流」的方法应运而生。断开一部分连接是指,可以传输数据但是无法接收,或可以接受数据但无法传输。顾名思义就是只关闭流的一半。
|
||||
为了解决这类问题,「只关闭一部分数据交换中使用的流」的方法应运而生。断开一部分连接是指,可以传输数据但是无法接收,或可以接收数据但无法传输。顾名思义就是只关闭流的一半。
|
||||
|
||||
#### 7.1.2 套接字和流(Stream)
|
||||
|
||||
@@ -37,7 +37,7 @@ shutdown 用来关闭其中一个流:
|
||||
int shutdown(int sock, int howto);
|
||||
/*
|
||||
成功时返回 0 ,失败时返回 -1
|
||||
sock: 需要断开套接字文件描述符
|
||||
sock: 需要断开的套接字文件描述符
|
||||
howto: 传递断开方式信息
|
||||
*/
|
||||
```
|
||||
@@ -48,7 +48,7 @@ howto: 传递断开方式信息
|
||||
- `SHUT_WR` : 断开输出流
|
||||
- `SHUT_RDWR` : 同时断开 I/O 流
|
||||
|
||||
若向 shutdown 的第二个参数传递`SHUT_RD`,则断开输入流,套接字无法接收数据。即使输入缓冲收到数据也会抹去,而且无法调用相关函数。如果向 shutdown 的第二个参数传递`SHUT_WR`,则中断输出流,也就无法传输数据。若如果输出缓冲中还有未传输的数据,则将传递给目标主机。最后,若传递关键字`SHUT_RDWR`,则同时中断 I/O 流。这相当于分 2 次调用 shutdown ,其中一次以`SHUT_RD`为参数,另一次以`SHUT_WR`为参数。
|
||||
若向 shutdown 的第二个参数传递`SHUT_RD`,则断开输入流,套接字无法接收数据。即使输入缓冲收到数据也会抹去,而且无法调用相关函数。如果向 shutdown 的第二个参数传递`SHUT_WR`,则中断输出流,也就无法传输数据。如果输出缓冲中还有未传输的数据,则将传递给目标主机。最后,若传递关键字`SHUT_RDWR`,则同时中断 I/O 流。这相当于分 2 次调用 shutdown ,其中一次以`SHUT_RD`为参数,另一次以`SHUT_WR`为参数。
|
||||
|
||||
#### 7.1.4 为何要半关闭
|
||||
|
||||
@@ -56,15 +56,15 @@ howto: 传递断开方式信息
|
||||
|
||||
> 一旦客户端连接到服务器,服务器将约定的文件传输给客户端,客户端收到后发送字符串「Thank you」给服务器端。
|
||||
|
||||
此处「Thank you」的传递是多余的,这只是用来模拟客户端断开连接前还有数据要传输的情况。此时程序的编写难度并不小,因为传输文件的服务器端只需连续传输文件数据即可,而客户端无法知道需要接收数据到何时。客户端也没办法无休止的调用输入函数,因为这有可能导致程序**阻塞**。
|
||||
此处「Thank you」的传递是多余的,这只是用来模拟客户端断开连接前还有数据要传输的情况。此时程序的编写难度并不小,因为传输文件的服务器端只需连续传输文件数据即可,而客户端无法知道需要接收数据到何时。客户端也没办法无休止地调用输入函数,因为这有可能导致程序**阻塞**。
|
||||
|
||||
> 是否可以让服务器和客户端约定一个代表文件尾的字符?
|
||||
|
||||
这种方式也有问题,因为这意味这文件中不能有与约定字符相同的内容。为了解决该问题,服务端应最后向客户端传递 EOF 表示文件传输结束。客户端通过函数返回值接受 EOF ,这样可以避免与文件内容冲突。那么问题来了,服务端如何传递 EOF ?
|
||||
这种方式也有问题,因为这意味着文件中不能有与约定字符相同的内容。为了解决该问题,服务端应最后向客户端传递 EOF 表示文件传输结束。客户端通过函数返回值接收 EOF ,这样可以避免与文件内容冲突。那么问题来了,服务端如何传递 EOF ?
|
||||
|
||||
> 断开输出流时向主机传输 EOF。
|
||||
|
||||
当然,调用 close 函数的同时关闭 I/O 流,这样也会向对方发送 EOF 。但此时无法再接受对方传输的数据。换言之,若调用 close 函数关闭流,就无法接受客户端最后发送的字符串「Thank you」。这时需要调用 shutdown 函数,只关闭服务器的输出流。这样既可以发送 EOF ,同时又保留了输入流。下面实现收发文件的服务器端/客户端。
|
||||
当然,调用 close 函数的同时关闭 I/O 流,这样也会向对方发送 EOF 。但此时无法再接收对方传输的数据。换言之,若调用 close 函数关闭流,就无法接收客户端最后发送的字符串「Thank you」。这时需要调用 shutdown 函数,只关闭服务器的输出流。这样既可以发送 EOF ,同时又保留了输入流。下面实现收发文件的服务器端/客户端。
|
||||
|
||||
#### 7.1.5 基于半关闭的文件传输程序
|
||||
|
||||
@@ -74,8 +74,8 @@ howto: 传递断开方式信息
|
||||
|
||||
下面的代码为编程简便,省略了大量错误处理代码。
|
||||
|
||||
- [file_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch07/file_client.c)
|
||||
- [file_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch07/file_server.c)
|
||||
- [file_client.c](file_client.c)
|
||||
- [file_server.c](file_server.c)
|
||||
|
||||
编译运行:
|
||||
|
||||
@@ -90,7 +90,7 @@ gcc file_server.c -o fserver
|
||||
|
||||

|
||||
|
||||
客户端接受完成后,服务器会接收到来自客户端的感谢信息。
|
||||
客户端接收完成后,服务器会接收到来自客户端的感谢信息。
|
||||
|
||||
### 7.2 基于 Windows 的实现
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ int main(int argc, char *argv[])
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fp = fopen("receive.cpp", "wb");
|
||||
fp = fopen("receive.dat", "wb");
|
||||
sd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
|
||||
memset(&serv_adr, 0, sizeof(serv_adr));
|
||||
|
||||
Reference in New Issue
Block a user