mirror of
https://github.com/riba2534/TCP-IP-NetworkNote.git
synced 2026-06-30 01:46:15 +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:
@@ -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 已连接(connect)UDP 套接字与未连接(unconnected)UDP 套接字
|
||||
|
||||
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 分别负责哪些部分?**
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user