TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1

TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1

  • 1.并发服务器的实现方法
  • 2.进程的概念
    • (1)理解进程
    • (2)使用fork()函数创建进程
  • 3.僵尸进程
    • (1)产生原因
    • (2)使用wait()函数销毁僵尸进程
    • (3)使用waitpid()函数销毁僵尸进程

1.并发服务器的实现方法

我们之前实现了一个服务器端可以依次和多个客户端进行通信,但是如果能同时向多个客户端提供服务会提高CPU的利用率,下面列出代表性的并发服务器端实现模型与方法。

  • 多进程服务器 :通过创建多个进程提供服务
  • 多路复用服务器:通过捆绑并统一管理I/O对象提供服务
  • 多线程服务器:通过生成与客户端等量的线程提供服务

我们先讲解多进程服务器。

2.进程的概念

(1)理解进程

进程的定义:占用内存空间的正在运行的程序。注意到进程强调正在运行的概念,一段代码可以称为程序,而加载到主内存并进入运行状态才叫做进程。
无论进程是如何创建的,所有进程都会从操作系统分配到ID,使用ps au进行查看。第二行PID就是进程的ID
TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1_第1张图片

(2)使用fork()函数创建进程

下面我们来创建子进程。fork()函数可以复制正在运行的、调用fork函数的进程。在fork函数返回后,都将执行fork函数调用后的语句,但因为通过同一进程、复制相同的内存空间,所以之后的程序流根据fork()函数的返回值加以区分。对于父进程来说,调用fork()函数返回子进程ID,而子进程fork函数返回0;

#include
pid_t fork(void);
  • 成功返回进程ID,失败时返回-1

我们通过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]

3.僵尸进程

(1)产生原因

调用fork函数后子进程的终止方式有两种

  • 传递参数并调用exit函数
  • main函数中return语句并返回

以上两种方式的返回值都将传递给操作系统,而操作系统不会主动销毁子进程,这时子进程就变为僵尸进程(暂时未被回收)。直到父进程主动发起请求(函数调用)操作系统才会传递该值给父进程,子进程才会被回收。
下面我们通过让父进程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秒过后,父进程结束,回收子进程
TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1_第2张图片
此外可以让程序后台运行,使用一个终端实现:./zombie &
TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1_第3张图片

(2)使用wait()函数销毁僵尸进程

wait()函数与waitpid()函数:父进程主动请求获取子进程返回值

#include
pid_t wait(int *statloc);
  • 成功返回子进程ID,失败返回-1
  • 子进程终止时传递的返回值将保存到statloc指向的内存空间

我们需要用宏查看是否正常返回以及子进程的返回值

  • WIFEXITED子进程正常终止时返回真(true)
  • WEXITSTATUS返回子进程的返回值

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。控制台就找不到僵尸进程,说明已经被回收
TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1_第4张图片
在这里插入图片描述

如果我们在子进程中加入sleep(20),那么调用wait函数时,没有子进程终止,父进程将会阻塞直到有子进程终止。
在这里插入图片描述

(3)使用waitpid()函数销毁僵尸进程

wait()函数会引起程序阻塞,而使用waitpid()函数不会阻塞

#include
pid_t waitpid(pid_t pid,int *statloc,int options);
  • 成功返回终止的子进程ID(或0),失败返回-1
  • pid是目标子进程ID,-1则与wait函数相同,可以等待任意子进程终止
  • statloc是子进程终止时的返回值保存到的内存空间
  • options是WNOHANG时没有子进程终止时返回0并退出,不会出现阻塞

验证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;
}

结果如下,由于waitpid调用5次说明waitpid函数不会阻塞
TCP/IP网络编程笔记Chapter I -7进程与僵尸进程1_第5张图片

你可能感兴趣的:(TCP/IP网络编程,linux,多进程,c++)