我们之前实现了一个服务器端可以依次和多个客户端进行通信,但是如果能同时向多个客户端提供服务会提高CPU的利用率,下面列出代表性的并发服务器端实现模型与方法。
我们先讲解多进程服务器。
进程的定义:占用内存空间的正在运行的程序。注意到进程强调正在运行的概念,一段代码可以称为程序,而加载到主内存并进入运行状态才叫做进程。
无论进程是如何创建的,所有进程都会从操作系统分配到ID,使用ps au
进行查看。第二行PID就是进程的ID
下面我们来创建子进程。fork()函数可以复制正在运行的、调用fork函数的进程。在fork函数返回后,都将执行fork函数调用后的语句,但因为通过同一进程、复制相同的内存空间,所以之后的程序流根据fork()函数的返回值加以区分。对于父进程来说,调用fork()函数返回子进程ID,而子进程fork函数返回0;
#include
pid_t fork(void);
我们通过fork()函数返回值的不同使父进程子进程执行不同的内容
#include
#include
int main(int argc, char *argv[])
{
pid_t pid;
int lval = 20;
int gval = 10;
pid = fork();
if (pid == 0) // if Child Process
gval += 2, lval += 2;
else // if Parent Process
gval -= 2, lval -= 2;
if (pid == 0)
printf("Child Proc: [%d, %d] \n", gval, lval);
else
printf("Parent Proc: [%d, %d] \n", gval, lval);
return 0;
}
Child Proc: [12, 22]
Parent Proc: [8, 18]
调用fork函数后子进程的终止方式有两种
以上两种方式的返回值都将传递给操作系统,而操作系统不会主动销毁子进程,这时子进程就变为僵尸进程(暂时未被回收)。直到父进程主动发起请求(函数调用)操作系统才会传递该值给父进程,子进程才会被回收。
下面我们通过让父进程sleep30秒,使子进程变为僵尸进程,在控制台观察子进程的状态。
#include
#include
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid == 0) // if Child Process
{
puts("Hi I'am a child process");
}
else
{
printf("Child Process ID: %d \n", pid);
sleep(30); // Sleep 30 sec.
}
if (pid == 0)
puts("End child process");
else
puts("End parent process");
return 0;
}
下图可以看出父进程还没执行,而子进程已经执行完毕。
使用ps au
查看,Z+表示僵尸进程
30秒过后,父进程结束,回收子进程
此外可以让程序后台运行,使用一个终端实现:./zombie &
wait()函数与waitpid()函数:父进程主动请求获取子进程返回值
#include
pid_t wait(int *statloc);
我们需要用宏查看是否正常返回以及子进程的返回值
eg
if (WIFEXITED(status))
{
puts("Normal termination!");
printf("Child pass num: %d \n", WEXITSTATUS(status)); //返回值是多少
}
创建两个子进程,并用wait()函数回收
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int status;
pid_t pid = fork();
if (pid == 0)
{
return 3;
}
else
{
printf("Child PID: %d \n", pid);
pid = fork();
if (pid == 0)
{
exit(7);
}
else
{
printf("Child PID: %d \n", pid);
wait(&status);
if (WIFEXITED(status))
printf("Child send one: %d \n", WEXITSTATUS(status));
wait(&status);
if (WIFEXITED(status))
printf("Child send two: %d \n", WEXITSTATUS(status));
sleep(30); // Sleep 30 sec.
}
}
return 0;
}
创建两个子进程分别返回3,7。控制台就找不到僵尸进程,说明已经被回收
如果我们在子进程中加入sleep(20)
,那么调用wait函数时,没有子进程终止,父进程将会阻塞直到有子进程终止。
wait()函数会引起程序阻塞,而使用waitpid()函数不会阻塞
#include
pid_t waitpid(pid_t pid,int *statloc,int options);
验证waitpid不会阻塞:子进程执行15s最后返回24,而父进程每3s调用一次waitpid,直到回收子进程。
#include
#include
#include
int main(int argc, char *argv[])
{
int status;
pid_t pid = fork();
if (pid == 0)
{
sleep(15);
return 24;
}
else
{
while (!waitpid(-1, &status, WNOHANG))
{
sleep(3);
puts("sleep 3sec.");
}
if (WIFEXITED(status))
printf("Child send %d \n", WEXITSTATUS(status));
}
return 0;
}