From 34f485a63ad374e2b14fc68ab1d234bc2e4b0d7a Mon Sep 17 00:00:00 2001 From: riba2534 Date: Mon, 21 Jan 2019 12:01:03 +0800 Subject: [PATCH] =?UTF-8?q?10.4=20=E5=9F=BA=E4=BA=8E=E5=A4=9A=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=9A=84=E5=B9=B6=E5=8F=91=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=20P176?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 218 +++++++++++++++++++++++++++++++++++++++++- ch10/echo_mpserv.c | 88 +++++++++++++++++ ch10/remove_zomebie.c | 56 +++++++++++ ch10/sigaction.c | 29 ++++++ 4 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 ch10/echo_mpserv.c create mode 100644 ch10/remove_zomebie.c create mode 100644 ch10/sigaction.c diff --git a/README.md b/README.md index 66c0202..024b4bc 100644 --- a/README.md +++ b/README.md @@ -1001,7 +1001,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。 #### 4.2.2 进入等待连接请求状态 -已经调用了 bind 函数给他要借资分配地址,接下来就是要通过调用 listen 函数进入等待链接请求状态。只有调用了 listen 函数,客户端才能进入可发出连接请求的状态。换言之,这时客户端才能调用 connect 函数 +已经调用了 bind 函数给套接字分配地址,接下来就是要通过调用 listen 函数进入等待连接请求状态。只有调用了 listen 函数,客户端才能进入可发出连接请求的状态。换言之,这时客户端才能调用 connect 函数 ```c #include @@ -2287,7 +2287,7 @@ gcc fork.c -o fork - 传递参数并调用 exit() 函数 - main 函数中执行 return 语句并返回值 -**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都回传递给操作系统。而操作系统不会销毁子进程,知道把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢? +**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都回传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢? > 应该向创建子进程册父进程传递子进程的 exit 参数值或 return 语句的返回值。 @@ -2612,6 +2612,220 @@ gcc signal.c -o signal #### 10.3.3 利用 sigaction 函数进行信号处理 +前面所学的内容可以防止僵尸进程,还有一个函数,叫做 sigaction 函数,他类似于 signal 函数,而且可以完全代替后者,也更稳定。之所以稳定,是因为: + +> signal 函数在 Unix 系列的不同操作系统可能存在区别,但 sigaction 函数完全相同 + +实际上现在很少用 signal 函数编写程序,他只是为了保持对旧程序的兼容,下面介绍 sigaction 函数,只讲解可以替换 signal 函数的功能。 + +```c +#include + +int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact); +/* +成功时返回 0 ,失败时返回 -1 +act: 对于第一个参数的信号处理函数(信号处理器)信息。 +oldact: 通过此参数获取之前注册的信号处理函数指针,若不需要则传递 0 +*/ +``` + +声明并初始化 sigaction 结构体变量以调用上述函数,该结构体定义如下: + +```c +struct sigaction +{ + void (*sa_handler)(int); + sigset_t sa_mask; + int sa_flags; +}; +``` + +此结构体的成员 sa_handler 保存信号处理的函数指针值(地址值)。sa_mask 和 sa_flags 的所有位初始化 0 即可。这 2 个成员用于指定信号相关的选项和特性,而我们的目的主要是防止产生僵尸进程,故省略。 + +下面的示例是关于 sigaction 函数的使用方法。 + +- [sigaction.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch10/sigaction.c) + +```c +#include +#include +#include + +void timeout(int sig) +{ + if (sig == SIGALRM) + puts("Time out!"); + alarm(2); +} + +int main(int argc, char *argv[]) +{ + int i; + struct sigaction act; + act.sa_handler = timeout; //保存函数指针 + sigemptyset(&act.sa_mask); //将 sa_mask 函数的所有位初始化成0 + act.sa_flags = 0; //sa_flags 同样初始化成 0 + sigaction(SIGALRM, &act, 0); //注册 SIGALRM 信号的处理器。 + + alarm(2); //2 秒后发生 SIGALRM 信号 + + for (int i = 0; i < 3; i++) + { + puts("wait..."); + sleep(100); + } + return 0; +} + +``` + +编译运行: + +```shell +gcc sigaction.c -o sigaction +./sigaction +``` + +结果: + +``` +wait... +Time out! +wait... +Time out! +wait... +Time out! +``` + +可以发现,结果和之前用 signal 函数的结果没有什么区别。以上就是信号处理的相关理论。 + +#### 10.3.4 利用信号处理技术消灭僵尸进程 + +下面利用子进程终止时产生 SIGCHLD 信号这一点,来用信号处理来消灭僵尸进程。看以下代码: + +- [remove_zomebie.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch10/remove_zomebie.c) + +```c +#include +#include +#include +#include +#include + +void read_childproc(int sig) +{ + int status; + pid_t id = waitpid(-1, &status, WNOHANG); + if (WIFEXITED(status)) + { + printf("Removed proc id: %d \n", id); //子进程的 pid + printf("Child send: %d \n", WEXITSTATUS(status)); //子进程的返回值 + } +} + +int main(int argc, char *argv[]) +{ + pid_t pid; + struct sigaction act; + act.sa_handler = read_childproc; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGCHLD, &act, 0); + + pid = fork(); + if (pid == 0) //子进程执行阶段 + { + puts("Hi I'm child process"); + sleep(10); + return 12; + } + else //父进程执行阶段 + { + printf("Child proc id: %d\n", pid); + pid = fork(); + if (pid == 0) + { + puts("Hi! I'm child process"); + sleep(10); + exit(24); + } + else + { + int i; + printf("Child proc id: %d \n", pid); + for (i = 0; i < 5; i++) + { + puts("wait"); + sleep(5); + } + } + } + return 0; +} +``` + +编译运行: + +```shell +gcc remove_zomebie.c -o zombie +./zombie +``` + +结果: + +``` +Child proc id: 11211 +Hi I'm child process +Child proc id: 11212 +wait +Hi! I'm child process + +wait + +wait +Removed proc id: 11211 +Child send: 12 +wait +Removed proc id: 11212 +Child send: 24 +wait +``` + +请自习观察结果,结果中的每一个空行代表间隔了5 秒,程序是先创建了两个子进程,然后子进程 10 秒之后会返回值,第一个 wait 由于子进程在执行,所以直接被唤醒,然后这两个子进程正在睡 10 秒,所以 5 秒之后第二个 wait 开始执行,又过了 5 秒,两个子进程同时被唤醒。所以剩下的 wait 也被唤醒。 + +所以在本程序的过程中,当子进程终止时候,会向系统发送一个信号,然后调用我们提前写好的处理函数,在处理函数中使用 waitpid 来处理僵尸进程,获取子进程返回值。 + +### 10.4 基于多任务的并发服务器 + +#### 10.4.1 基于进程的并发服务器模型 + +之前的回声服务器每次只能同事向 1 个客户端提供服务。因此,需要扩展回声服务器,使其可以同时向多个客户端提供服务。下图是基于多进程的回声服务器的模型。 + +![](https://i.loli.net/2019/01/21/5c453664cde26.png) + +从图中可以看出,每当有客户端请求时(连接请求),回声服务器都创建子进程以提供服务。如果请求的客户端有 5 个,则将创建 5 个子进程来提供服务,为了完成这些任务,需要经过如下过程: + +- 第一阶段:回声服务器端(父进程)通过调用 accept 函数受理连接请求 +- 第二阶段:此时获取的套接字文件描述符创建并传递给子进程 +- 第三阶段:进程利用传递来的文件描述符提供服务 + +#### 10.4.2 实现并发服务器 + +下面是基于多进程实现的并发的回声服务器的服务端,可以结合第四章的 [echo_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch04/echo_client.c) 回声客户端来运行。 + +- [echo_mpserv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch10/echo_mpserv.c) + +编译运行: + +```shell +gcc echo_mpserv.c -o eserver +./eserver +``` + +结果: + + + ## License diff --git a/ch10/echo_mpserv.c b/ch10/echo_mpserv.c new file mode 100644 index 0000000..cd35dcf --- /dev/null +++ b/ch10/echo_mpserv.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 30 +void error_handling(char *message); +void read_childproc(int sig); + +int main(int argc, char *argv[]) +{ + int serv_sock, clnt_sock; + struct sockaddr_in serv_adr, clnt_adr; + + pid_t pid; + struct sigaction act; + socklen_t adr_sz; + int str_len, state; + char buf[BUF_SIZE]; + if (argc != 2) + { + printf("Usgae : %s \n", argv[0]); + exit(1); + } + act.sa_handler = read_childproc; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + state = sigaction(SIGCHLD, &act, 0); //注册信号处理器,把成功的返回值给 state + serv_sock = socket(PF_INET, SOCK_STREAM, 0); //创建服务端套接字 + memset(&serv_adr, 0, sizeof(serv_adr)); + serv_adr.sin_family = AF_INET; + serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_adr.sin_port = htons(atoi(argv[1])); + + if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) //分配IP地址和端口号 + error_handling("bind() error"); + if (listen(serv_sock, 5) == -1) //进入等待连接请求状态 + error_handling("listen() error"); + + while (1) + { + adr_sz = sizeof(clnt_adr); + clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); + if (clnt_sock == -1) + continue; + else + puts("new client connected..."); + pid = fork(); + if (pid == -1) + { + close(clnt_sock); + continue; + } + if (pid == 0) //子进程运行区域 + { + close(serv_sock); + while ((str_len = read(clnt_sock, buf, BUFSIZ)) != 0) + write(clnt_sock, buf, str_len); + + close(clnt_sock); + puts("client disconnected..."); + return 0; + } + else + close(clnt_sock); + } + close(serv_sock); + + return 0; +} + +void error_handling(char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + exit(1); +} +void read_childproc(int sig) +{ + pid_t pid; + int status; + pid = waitpid(-1, &status, WNOHANG); + printf("removed proc id: %d \n", pid); +} diff --git a/ch10/remove_zomebie.c b/ch10/remove_zomebie.c new file mode 100644 index 0000000..40826b5 --- /dev/null +++ b/ch10/remove_zomebie.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +void read_childproc(int sig) +{ + int status; + pid_t id = waitpid(-1, &status, WNOHANG); + if (WIFEXITED(status)) + { + printf("Removed proc id: %d \n", id); //子进程的 pid + printf("Child send: %d \n", WEXITSTATUS(status)); //子进程的返回值 + } +} + +int main(int argc, char *argv[]) +{ + pid_t pid; + struct sigaction act; + act.sa_handler = read_childproc; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGCHLD, &act, 0); + + pid = fork(); + if (pid == 0) //子进程执行阶段 + { + puts("Hi I'm child process"); + sleep(10); + return 12; + } + else //父进程执行阶段 + { + printf("Child proc id: %d\n", pid); + pid = fork(); + if (pid == 0) + { + puts("Hi! I'm child process"); + sleep(10); + exit(24); + } + else + { + int i; + printf("Child proc id: %d \n", pid); + for (i = 0; i < 5; i++) + { + puts("wait"); + sleep(5); + } + } + } + return 0; +} diff --git a/ch10/sigaction.c b/ch10/sigaction.c new file mode 100644 index 0000000..b16204a --- /dev/null +++ b/ch10/sigaction.c @@ -0,0 +1,29 @@ +#include +#include +#include + +void timeout(int sig) +{ + if (sig == SIGALRM) + puts("Time out!"); + alarm(2); +} + +int main(int argc, char *argv[]) +{ + int i; + struct sigaction act; + act.sa_handler = timeout; //保存函数指针 + sigemptyset(&act.sa_mask); //将 sa_mask 函数的所有位初始化成0 + act.sa_flags = 0; //sa_flags 同样初始化成 0 + sigaction(SIGALRM, &act, 0); //注册 SIGALRM 信号的处理器。 + + alarm(2); //2 秒后发生 SIGALRM 信号 + + for (int i = 0; i < 3; i++) + { + puts("wait..."); + sleep(100); + } + return 0; +}