并发流式套接字服务器编程
1.实验环境要求及特性
Linux是一个多任务操作系统,可以允许多个程序同时进行,每个正在运行的陈旭构成了一个进程。可以利用Linux系统的多任务特性,通过创建子进程系统调用,让新产生的子进程对客户端请求进行后续处理,而主进程返回继续接收其他客户端发来的请求,这样就实现了同时对多个客户端请求的并行处理模式。
2.多进程并发服务器特点
多进程并发服务器解决了之前一个服务器只能处理一个客户端的请求的问题,只有当服务器结束了该客户端的请求,断开与它的连接的时候,才能对下一个客户端的请求进行处理,无法对多个客户端同时服务,造成了服务器资源的浪费。多进程并发服务器可同时为多个客户端服务。
3.创建子进程
(1)调用fork()函数创建子进程
#include<unistd.h>
pid_t fork(void);
fork用于普通进程的创建,采用的是Copy in Write(写实拷贝),函数调用失败会返回-1;调用成功时父进程返回新的子进程的ID,子进程返回0。
fork()函数的一个特性是所有由父进程打开的描述符都复制到子进程中。父子进程每个相同的描述符共享一个文件表项。如果父子进程写到同一个描述符文件,但又没有任何形式的同步,那么它们的输出就会相互融合。
(2)调用vfork()函数创建子进程
#include<sys/types.h>
#include<unistd.h>
pid_vfork(void);
其返回值同fork()。vfork采用完全共享的创建,新老进程共享同样的资源,完全没有拷贝。
4.子进程创建成功后父子进程的操作
父进程只负责接收客户请求,关闭自己的连接套接字文件描述符connfd。
由于fork()函数返回之后,与监听和已连接描述符相关联的文件表项的访问均加1。当父进程调用close关闭已连接描述符时,只是将访问计数值减一,而描述符只在访问计数为0时才真正关闭,所以为了正确地关闭连接,当调用fork()函数之后,父进程将不需要的已连接描述符关闭,而子进程关闭不需要的监听描述符。
fork返回后双方的状态
父、子进程关闭相应的套接字后双方的状态
5.子进程的善后
(1)父进程在子进程之前终止,由父进程创建的子进程全部变成孤儿进程,并把子进程的父进程改为init进程,其ID号为1。
(2)子进程先于父进程终止
子进程终止后,系统内核会向其父进程发送SIGCHILD信号,默认情况下,进程忽略该信号,或者提供一个该信号发生时即被调用的函数wait()或waitpid()用于释放子进程所占用的资源。
wait()函数:
#include<sys/wait.h>
pid_t wait(int *statloc);
参数statloc返回子进程的终止状态,返回值是终止子进程的ID号。如果有一个进程已经终止,则释放子进程的所有资源,并立即返回。如果当前没有终止的子进程,但有正在执行的子进程,则wait将阻塞直到有子进程终止时才返回。如果当前既没有终止的子进程,也没有正在执行的子进程,则返回错误-1。
但wait()存在问题,当多个SIGCHILD信号同时来时,wait()没有排队机制,它只调用一次释放一个进程的资源,其余的进程将继续处于“僵尸进程”。
waitpid()函数:
#include<sys/wait.h>
pid_t waitpid(pid_t,int *statloc,int option);
当pid取-1,option取0时,该函数等同于wait()函数,waitpid函数比wait函数更常用。
当pid取-1,要求知道任何一个子进程的终止状态;
当pid取值大于0时,要求知道进程号为pid的子进程的终止状态;
当pid取值小于0时,要求知道进程号为pid绝对值的子进程的终止状态;
option最常用的选项是WON_HANG,作用是当没有已终止子进程时不要阻塞。
6.终止进程exit()
#include<stdlib.h>
void exit(int status);
此函数关闭所有子进程打开的描述符,向父进程发送SIGCHILD信号,并返回状态,随后父进程即可通过调用wait或waitpid函数获得终止子进程的状态了。