mirror of
https://github.com/riba2534/TCP-IP-NetworkNote.git
synced 2026-02-02 17:48:55 +08:00
10.4 基于多任务的并发服务器 P176
This commit is contained in:
218
README.md
218
README.md
@@ -1001,7 +1001,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。
|
|||||||
|
|
||||||
#### 4.2.2 进入等待连接请求状态
|
#### 4.2.2 进入等待连接请求状态
|
||||||
|
|
||||||
已经调用了 bind 函数给他要借资分配地址,接下来就是要通过调用 listen 函数进入等待链接请求状态。只有调用了 listen 函数,客户端才能进入可发出连接请求的状态。换言之,这时客户端才能调用 connect 函数
|
已经调用了 bind 函数给套接字分配地址,接下来就是要通过调用 listen 函数进入等待连接请求状态。只有调用了 listen 函数,客户端才能进入可发出连接请求的状态。换言之,这时客户端才能调用 connect 函数
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
@@ -2287,7 +2287,7 @@ gcc fork.c -o fork
|
|||||||
- 传递参数并调用 exit() 函数
|
- 传递参数并调用 exit() 函数
|
||||||
- main 函数中执行 return 语句并返回值
|
- main 函数中执行 return 语句并返回值
|
||||||
|
|
||||||
**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都回传递给操作系统。而操作系统不会销毁子进程,知道把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢?
|
**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都回传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢?
|
||||||
|
|
||||||
> 应该向创建子进程册父进程传递子进程的 exit 参数值或 return 语句的返回值。
|
> 应该向创建子进程册父进程传递子进程的 exit 参数值或 return 语句的返回值。
|
||||||
|
|
||||||
@@ -2612,6 +2612,220 @@ gcc signal.c -o signal
|
|||||||
|
|
||||||
#### 10.3.3 利用 sigaction 函数进行信号处理
|
#### 10.3.3 利用 sigaction 函数进行信号处理
|
||||||
|
|
||||||
|
前面所学的内容可以防止僵尸进程,还有一个函数,叫做 sigaction 函数,他类似于 signal 函数,而且可以完全代替后者,也更稳定。之所以稳定,是因为:
|
||||||
|
|
||||||
|
> signal 函数在 Unix 系列的不同操作系统可能存在区别,但 sigaction 函数完全相同
|
||||||
|
|
||||||
|
实际上现在很少用 signal 函数编写程序,他只是为了保持对旧程序的兼容,下面介绍 sigaction 函数,只讲解可以替换 signal 函数的功能。
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
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 <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
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 个客户端提供服务。因此,需要扩展回声服务器,使其可以同时向多个客户端提供服务。下图是基于多进程的回声服务器的模型。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
从图中可以看出,每当有客户端请求时(连接请求),回声服务器都创建子进程以提供服务。如果请求的客户端有 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
|
## License
|
||||||
|
|||||||
88
ch10/echo_mpserv.c
Normal file
88
ch10/echo_mpserv.c
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#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 <port>\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);
|
||||||
|
}
|
||||||
56
ch10/remove_zomebie.c
Normal file
56
ch10/remove_zomebie.c
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
29
ch10/sigaction.c
Normal file
29
ch10/sigaction.c
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user