mirror of
https://github.com/riba2534/TCP-IP-NetworkNote.git
synced 2026-06-30 09:56:04 +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:
@@ -17,7 +17,7 @@
|
||||
|
||||
只有一个 CPU 的系统是将时间分成多个微小的块后分配给了多个进程。为了分时使用 CPU ,需要「上下文切换」的过程。「上下文切换」是指运行程序前需要将相应进程信息读入内存,如果运行进程 A 后紧接着需要运行进程 B ,就应该将进程 A 相关信息移出内存(或保存到寄存器),并读入进程 B 相关信息。这就是上下文切换。上下文切换需要保存和恢复进程的上下文信息(寄存器、程序计数器、栈指针等),这个过程会带来一定的开销,即使通过优化加快速度,也会存在一定的局限。
|
||||
|
||||
为了保持多进程的优点,同时在一定程度上克服其缺点,人们引入的线程(Thread)的概念。这是为了将进程的各种劣势降至最低程度(不是直接消除)而设立的一种「轻量级进程」。线程比进程具有如下优点:
|
||||
为了保持多进程的优点,同时在一定程度上克服其缺点,人们引入了线程(Thread)的概念。这是为了将进程的各种劣势降至最低程度(不是直接消除)而设立的一种「轻量级进程」。线程比进程具有如下优点:
|
||||
|
||||
- 线程的创建和上下文切换比进程的创建和上下文切换更快
|
||||
- 线程间交换数据无需特殊技术
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
线程是为了解决:为了得到多条代码执行流而复制整个内存区域的负担太重。
|
||||
|
||||
每个进程的内存空间都由保存全局变量的「数据区」、向 malloc 等函数动态分配提供空间的堆(Heap)、函数运行时间使用的栈(Stack)构成。每个进程都有独立的这种空间,多个进程的内存结构如图所示:
|
||||
每个进程的内存空间都由保存全局变量的「数据区」、向 malloc 等函数动态分配提供空间的堆(Heap)、函数运行时使用的栈(Stack)构成。每个进程都有独立的这种空间,多个进程的内存结构如图所示:
|
||||
|
||||

|
||||
|
||||
@@ -80,7 +80,7 @@ arg : 通过第三个参数传递的调用函数时包含传递参数信息的
|
||||
|
||||
下面通过简单示例了解该函数功能:
|
||||
|
||||
- [thread1.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch18/thread1.c)
|
||||
- [thread1.c](thread1.c)
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@@ -210,13 +210,13 @@ gcc thread2.c -o tr2 -lpthread
|
||||
|
||||
#### 18.2.2 可在临界区内调用的函数
|
||||
|
||||
在同步的程序设计中,临界区块(Critical section)指的是一个访问共享资源(例如:共享设备或是共享存储器)的程序片段,而这些共享资源有无法同时被多个线程访问的特性。
|
||||
在同步的程序设计中,临界区(Critical section)指的是一个访问共享资源(例如:共享设备或是共享存储器)的程序片段,而这些共享资源有无法同时被多个线程访问的特性。
|
||||
|
||||
当有线程进入临界区块时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区块的进入点与离开点实现,以确保这些共享资源是被异或的使用,例如:semaphore。
|
||||
当有线程进入临界区块时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区块的进入点与离开点实现,以确保这些共享资源被互斥地使用,例如:信号量(semaphore)。
|
||||
|
||||
只能被单一线程访问的设备,例如:打印机。
|
||||
|
||||
一个最简单的实现方法就是当线程(Thread)进入临界区块时,禁止改变处理器;在uni-processor系统上,可以用“禁止中断(CLI)”来完成,避免发生系统调用(System Call)导致的上下文交换(Context switching);当离开临界区块时,处理器恢复原先的状态。
|
||||
一个最简单的实现方法就是当线程(Thread)进入临界区块时,禁止改变处理器;在uni-processor系统上,可以用“禁止中断(CLI)”来完成,避免发生系统调用(System Call)导致的上下文切换(Context switching);当离开临界区块时,处理器恢复原先的状态。
|
||||
|
||||
根据临界区是否引起问题,函数可以分为以下 2 类:
|
||||
|
||||
@@ -305,7 +305,7 @@ gcc thread3.c -D_REENTRANT -o tr3 -lpthread
|
||||
|
||||

|
||||
|
||||
可以看出计算结果正确,两个线程都用了全局变量 sum ,证明了 2 个线程共享保存全局变量的数据区。
|
||||
可以看出计算结果正确,两个线程都用了全局变量 sum,证明了 2 个线程共享保存全局变量的数据区。
|
||||
|
||||
但是本例子本身存在问题。存在临界区相关问题,可以从下面的代码看出,下面的代码和上面的代码相似,只是增加了发生临界区错误的可能性,即使在高配置系统环境下也容易产生的错误:
|
||||
|
||||
@@ -438,7 +438,7 @@ void *thread_des(void *arg)
|
||||
|
||||
#### 18.4.2 互斥量
|
||||
|
||||
互斥锁(英语:Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全域变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
|
||||
互斥锁(英语:Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
|
||||
|
||||
通俗地说,互斥量就是一把锁,当临界区被占据的时候就上锁,等占用完毕然后再放开。
|
||||
|
||||
@@ -481,7 +481,7 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
||||
*/
|
||||
```
|
||||
|
||||
函数本身含有 lock unlock 等词汇,很容易理解其含义。进入临界区前调用的函数就是 pthread_mutex_lock。调用该函数时,发现有其他线程已经进入临界区,则 pthread_mutex_lock 函数不会返回,直到里面的线程调用 pthread_mutex_unlock 函数退出临界区位置。也就是说,其他线程让出临界区之前,当前线程一直处于阻塞状态。接下来整理一下代码的编写方式:
|
||||
函数本身含有 lock unlock 等词汇,很容易理解其含义。进入临界区前调用的函数就是 pthread_mutex_lock。调用该函数时,发现有其他线程已经进入临界区,则 pthread_mutex_lock 函数不会返回,直到里面的线程调用 pthread_mutex_unlock 函数退出临界区。也就是说,其他线程让出临界区之前,当前线程一直处于阻塞状态。接下来整理一下代码的编写方式:
|
||||
|
||||
```c
|
||||
pthread_mutex_lock(&mutex);
|
||||
@@ -491,7 +491,7 @@ pthread_mutex_lock(&mutex);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
```
|
||||
|
||||
简言之,就是利用 lock 和 unlock 函数围住临界区的两端。此时互斥量相当于一把锁,阻止多个线程同时访问,还有一点要注意,线程退出临界区时,如果忘了调用 pthread_mutex_unlock 函数,那么其他为了进入临界区而调用 pthread_mutex_lock 的函数无法摆脱阻塞状态。这种情况称为「死锁」。需要格外注意,下面是利用互斥量解决示例 [thread4.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch18/thread4.c) 中遇到的问题代码:
|
||||
简言之,就是利用 lock 和 unlock 函数围住临界区的两端。此时互斥量相当于一把锁,阻止多个线程同时访问,还有一点要注意,线程退出临界区时,如果忘了调用 pthread_mutex_unlock 函数,那么其他为了进入临界区而调用 pthread_mutex_lock 的线程无法摆脱阻塞状态。这种情况称为「死锁」。需要格外注意,下面是利用互斥量解决示例 [thread4.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch18/thread4.c) 中遇到的问题代码:
|
||||
|
||||
- [mutex.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch18/mutex.c)
|
||||
|
||||
@@ -583,11 +583,11 @@ void *thread_inc(void *arg)
|
||||
|
||||
#### 18.4.3 信号量
|
||||
|
||||
信号量(英语:Semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.
|
||||
信号量(英语:Semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于 0,为 signaled 状态;计数值等于 0,为 nonsignaled 状态。
|
||||
|
||||
semaphore对象适用于控制一个仅支持有限个用户的共享资源,是一种不需要使用忙碌等待(busy waiting)的方法。
|
||||
|
||||
信号量的概念是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程当前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)。在linux系统中,二进制信号量(binary semaphore)又称互斥锁(Mutex)。
|
||||
信号量的概念是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程当前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)。在 Linux 系统中,二进制信号量(binary semaphore)与互斥锁(Mutex)功能相似,但二者并不完全等同——互斥锁具有所有权语义,必须由加锁的线程解锁;信号量没有所有权限制。
|
||||
|
||||
下面介绍信号量,在互斥量的基础上,很容易理解信号量。此处只涉及利用「二进制信号量」(只用 0 和 1)完成「控制线程顺序」为中心的同步方法。下面是信号量的创建及销毁方法:
|
||||
|
||||
@@ -615,7 +615,7 @@ sem : 传递保存信号量读取值的变量地址值,传递给 sem_post 的
|
||||
*/
|
||||
```
|
||||
|
||||
调用 sem_init 函数时,操作系统将创建信号量对象,此对象中记录着「信号量值」(Semaphore Value)整数。该值在调用 sem_post 函数时增加 1,调用 sem_wait 函数时减 1。但信号量的值不能小于 0,因此,在信号量为 0 的情况下调用 sem_wait 函数时,调用的线程将进入阻塞状态(因为函数未返回)。当然,此时如果有其他线程调用 sem_post 函数,信号量的值将变为 1,而原本阻塞的线程可以将该信号重新减为 0 并跳出阻塞状态。实际上就是通过这种特性完成临界区的同步操作,可以通过如下形式同步临界区(假设信号量的初始值为 1):
|
||||
调用 sem_init 函数时,操作系统将创建信号量对象,此对象中记录着「信号量值」(Semaphore Value)整数。该值在调用 sem_post 函数时增加 1,调用 sem_wait 函数时减 1。但信号量的值不能小于 0,因此,在信号量为 0 的情况下调用 sem_wait 函数时,调用的线程将进入阻塞状态(因为函数未返回)。当然,此时如果有其他线程调用 sem_post 函数,信号量的值将变为 1,而原本阻塞的线程可以将该信号量重新减为 0 并跳出阻塞状态。实际上就是通过这种特性完成临界区的同步操作,可以通过如下形式同步临界区(假设信号量的初始值为 1):
|
||||
|
||||
```c
|
||||
sem_wait(&sem);//信号量变为 0...
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define BUF_SIZE 100
|
||||
@@ -73,7 +74,7 @@ void *recv_msg(void *arg) // 读取消息
|
||||
while (1)
|
||||
{
|
||||
str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
|
||||
if (str_len == -1)
|
||||
if (str_len <= 0)
|
||||
return (void *)-1;
|
||||
name_msg[str_len] = 0;
|
||||
fputs(name_msg, stdout);
|
||||
|
||||
@@ -22,7 +22,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
int serv_sock, clnt_sock;
|
||||
struct sockaddr_in serv_adr, clnt_adr;
|
||||
int clnt_adr_sz;
|
||||
socklen_t clnt_adr_sz;
|
||||
pthread_t t_id;
|
||||
if (argc != 2)
|
||||
{
|
||||
@@ -52,7 +52,10 @@ int main(int argc, char *argv[])
|
||||
clnt_socks[clnt_cnt++] = clnt_sock; //写入新连接
|
||||
pthread_mutex_unlock(&mutx); //解锁
|
||||
|
||||
pthread_create(&t_id, NULL, handle_clnt, (void *)&clnt_sock); //创建线程为新客户端服务,并且把clnt_sock作为参数传递
|
||||
int *clnt_sock_ptr = malloc(sizeof(int));
|
||||
*clnt_sock_ptr = clnt_sock;
|
||||
pthread_create(&t_id, NULL, handle_clnt, (void *)clnt_sock_ptr);
|
||||
// handle_clnt 中读取后需 free(arg) //创建线程为新客户端服务,并且把clnt_sock作为参数传递
|
||||
pthread_detach(t_id); //引导线程销毁,不阻塞
|
||||
printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr)); //客户端连接的ip地址
|
||||
}
|
||||
@@ -63,6 +66,7 @@ int main(int argc, char *argv[])
|
||||
void *handle_clnt(void *arg)
|
||||
{
|
||||
int clnt_sock = *((int *)arg);
|
||||
free(arg);
|
||||
int str_len = 0, i;
|
||||
char msg[BUF_SIZE];
|
||||
|
||||
@@ -74,8 +78,11 @@ void *handle_clnt(void *arg)
|
||||
{
|
||||
if (clnt_sock == clnt_socks[i])
|
||||
{
|
||||
while (i++ < clnt_cnt - 1)
|
||||
while (i < clnt_cnt - 1)
|
||||
{
|
||||
clnt_socks[i] = clnt_socks[i + 1];
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
void error_handling(char* message);
|
||||
|
||||
@@ -61,7 +61,10 @@ void * handle_clnt(void * arg)
|
||||
pthread_mutex_lock(&mutx);
|
||||
str_len = read(clnt_sock, buf, sizeof(buf));
|
||||
if(str_len <= 0)
|
||||
{
|
||||
pthread_mutex_unlock(&mutx);
|
||||
break;
|
||||
}
|
||||
else
|
||||
write(clnt_sock, buf, str_len);
|
||||
pthread_mutex_unlock(&mutx);
|
||||
|
||||
@@ -19,7 +19,6 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
void *thread_main(void *arg) //传入的参数是 pthread_create 的第四个
|
||||
{
|
||||
int i;
|
||||
int cnt = *((int *)arg);
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
void *thread_main(void *arg);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
@@ -30,7 +31,7 @@ void *thread_main(void *arg) //传入的参数是 pthread_create 的第四个
|
||||
int i;
|
||||
int cnt = *((int *)arg);
|
||||
char *msg = (char *)malloc(sizeof(char) * 50);
|
||||
strcpy(msg, "Hello,I'am thread~ \n");
|
||||
strcpy(msg, "Hello, I'm thread~ \n");
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
sleep(1);
|
||||
|
||||
@@ -13,7 +13,7 @@ int main(int argc, char *argv[])
|
||||
pthread_t thread_id[NUM_THREAD];
|
||||
int i;
|
||||
|
||||
printf("sizeof long long: %d \n", sizeof(long long));
|
||||
printf("sizeof long long: %zu \n", sizeof(long long));
|
||||
for (i = 0; i < NUM_THREAD; i++)
|
||||
{
|
||||
if (i % 2)
|
||||
|
||||
Reference in New Issue
Block a user