当前位置: 代码迷 >> 综合 >> LinuxC++:网络编程(三)多进程
  详细解决方案

LinuxC++:网络编程(三)多进程

热度:60   发布时间:2023-10-30 15:19:45.0

前言:学习编程一定要敲,接着测试,然后查资料,最后总结!!!

一、进程的概念

什么是进程?进程这个概念是针对系统而不是针对程序员的,对程序员来说,我们面对的概念是程序,当输入指令执行一个程序的时候,对系统而言,它将启动一个进程。

进程就是正在内存中运行中的程序,Linux下一个进程在内存里有三部分的数据,就是“代码段”、”堆栈段”和”数据段”。
”代码段”:顾名思义,就是存放了程序代码。
“堆栈段”:存放的就是程序的返回地址、程序的参数以及程序的局部变量。
“数据段”:则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用new函数分配的空间)。

对于堆栈段和数据段我们可以统称为数据段,这痒便于我们区分代码段和数据段。

系统如果同时运行多个相同的程序,它们的“代码段”是相同的,“堆栈段”和“数据段”是不同的(相同的程序,处理的数据不同)。

二、进程的编号

1. 查看进程
可自行查看我的博客:Linux&C++ command中目录为“任务和进程相关操作”的相关内容。

2. getpid库函数
获取本程序运行时进程的编号。
函数声明:

pid_t getpid();

函数没有参数,
返回值是进程的编号,
pid_t就是typedef int pid_t。
示例程序:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{
    printf("本程序的进程编号是:%d\n",getpid());sleep(30);    // 是为了方便查看进程在shell下用ps -ef|grep book251查看本进程的编号。
}

注意:
1)进程的编号是系统动态分配的,相同的程序在不同的时间执行,进程的编号是不同的。
2)进程的编号会循环使用,但是,在同一时间,进程的编号是唯一的,也就是说,不管任何时间,系统不可能存在两个编号相同的进程。

三、多进程概念及简单实现

1. 多进程用来干什么

充分利用CPU(多CPU、多核)资源。以及实现并发。

2. 利用fork()函数产生新的进程
fork在英文中是“分叉”的意思。

函数声明:

pid_t fork();

fork函数用于产生一个新的进程,
函数返回值:
pid_t是一个整数,
在父进程中,返回值是子进程编号,
在子进程中,返回值是0。

示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{
    printf("本程序的进程编号是:%d\n",getpid());int ipid=fork();sleep(1);       // sleep等待进程的生成。printf("pid=%d\n",ipid);if (ipid!=0) printf("父进程编号是:%d\n",getpid());else printf("子进程编号是:%d\n",getpid());sleep(30);    // 是为了方便查看进程在shell下用ps -ef|grep book252查看本进程的编号。
}

注意:
fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一模一样。子进程和父进程使用相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它复制了父进程的一切数据,然后各自运行,相互之间没有影响。更加要注意的就是子进程的作用域从fork()函数开始一直持续到子进程结束,也就是说和父进程一样会一直执行到main函数结束。

四、多进程日常的使用举例

如下图linux中terminal中多进程示例:
我在terminal中后台运行sleep 50,那么终端会开启一个子进程去执行这个命令,这个子线程ID也就是下面的3016,而终端的主进程ID为3005.
在这里插入图片描述

五、解决多进程中的僵尸进程问题

1. 什么是僵尸进程

僵尸进程是指一个已经终止、但是其父进程尚未对其进行善后处理获取终止进程的有关信息的进程,这个进程被称为“僵尸进程”(zombie)。

2. 怎样产生僵尸进程

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit, 它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。

在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位 置,记载该进程的退出状态等信息供其他进程收集。除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD 信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了, 那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是 为什么系统中有时会有很多的僵尸进程。

3. 怎么查看僵尸进程
利用命令ps,可以看到有父进程ID为1的进程是孤儿进程;s(state)状态为Z的是僵尸进程。

注意:孤儿进程(orphan process)是尚未终止但已停止(相当于前台挂起)的进程,但其父进程已经终止,由init收养;而僵尸进程则是已终止的进程,其父进程未终止。

4. 网络编程中如何避免僵尸进程
只需要在main函数后的第一行增加忽略子进程退出的信号即可,如下:

signal(SIGCHLD,SIG_IGN);//忽略子进程退出信号,避免产生僵尸进程

六、多进程网络服务端关闭多余socket

在多进程网络服务端中由于代码段和数据段在每次fork()后都会创建副本;
而父进程用来监听,那么应close掉clientsocket(也就是accept后的socket)。
子进程用来和客户端通信,那么应该关闭子进程的listensocket。
避免高并发时的资源浪费。

七、多进程网络服务端资源释放

一般的C++程序资源释放是写在析构函数里的;
多进程网络服务端是在后台运行的,我要就要用信号的方式来管比服务端程序并释放服务端占用的资源。
注意:在接收信号退出时,子进程和父进程使用到的信号函数肯定是不一样的。

八、多进程程序调试问题

参照标题为:GDB调试多进程