Linux多进程编程fork和vfork系统调用

版权声明:转载请附带原博主的网址 https://blog.csdn.net/qq_43260665/article/details/88823011

服务器按处理方式可以分为迭代服务器和并发服务器两类。平常用C写的简单Socket客户端服务器通信,服务器每次只能处理 一个客户的请求,它实现简单但效率很低,通常这种服务器被称为迭代服务器。 然而在实际应用中,不可能让一个服务器长时间 地为一个客户服务,而需要其具有同时处理 多个客户请求的能力,这种同时可以处理多个客户请求的服务器称为并发服务器,其 效率很 高却实现复杂。

在Linux的服务器编程中,多进程的使用是必不可少的,在Linux的socket编程中,如果服务器端不使用并发,一个服务器程序同时处理一个客户端的连接,这样当有多个客户连接时,服务器处理客户端的效率是非常低的。但在现实中,试想一下,如果12306使用这种迭代的服务器来处理连接的话,结果可能是不能想象的。。。

这个时候多进程就能解决大部分问题了,(当然12306肯定不是用的多进程哈哈)

什么是一个进程?
在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进 程。站在程序员的角度来看,我们使用vim编辑生成的C文件叫做源码,源码给程序员来看的但机器不识别,这时我们需要使用 编译器gcc编译生成CPU可识别的二进制可执行程序并保存在存储介质上,这时编译生成的可执行程序只能叫做程序而不能叫进 程。而一旦我们通过命令(./a.out)开始运行时,那正在运行的这个程序及其占用的资源就叫做进程了。进程这个概念是针对系统 而不是针对用户的,对用户来说,他面对的概念是程序。很显然,一个程序可以执行多次,这也意味着多个进程可以执行同一个 程序。
在深入理解Linux下多进程编程之前,我们首先要了解Linux下进程在运行时的内存布局。Linux 进程内存管理的对象都是虚拟 内存,每个进程先天就有 0-4G 的各自互不干涉的虚拟内存空间,0—3G 是用户空间执行用户自己的代码, 高 1GB 的空间是内核 空间执行 Linu x 系统调用,这里存放在整个内核的代码和所有的内核模块,用户所看到和接触的都是该虚拟地址,并不是实际 的物理内存地址。 Linux下一个进程在内存里有三部分的数据,就是”代码段”、”堆栈段”和”数据段”。其实学过汇编语言 的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分是构成一个完整的执行序列的必要的部 分。”代码段”,顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相
同的代码段。”堆栈段”存放的就是子程 序的返回地址、子程序的参数以及程序的局部变量和malloc()动态申请内存的地址。而 数据段则存放程序的全局变量,静态变量及常量的内存空间。

在这里插入图片描述

栈内存由编译器在程序编译阶段完成,进程的栈空间位于进程用户空间的顶部并且是向下增长,每个函数的每次调用 都会在栈空间中开辟自己的栈空间,函数参数、局部变量、函数返回地址等都会按照先入者为栈顶的顺序压入函数栈中, 函数返回后该函数的栈空间消失,所以函数中返回局部变量的地址都是非法的。

堆内存是在程序执行过程中分配的,用于存放进程运行中被动态分配的的变量,大小并不固定,堆位于非初始化数据 段和栈之间,并且使用过程中是向栈空间靠近的。当进程调用 malloc 等函数分配内存时,新分配的内存并不是该函数的 栈帧中,而是被动态添加到堆上,此时堆就向高地址扩张;当利用 free 等函数释放内存时,被释放的内存从堆中被踢 出,堆就会缩减。因为动态分配的内存并不在函数栈帧中,所以即使函数返回这段内存也是不会消失。
非初始化数据段
通常将此段称为 bss 段,用来存放未初始化的全局变量和 static 静态变量。并且在程序开始执行之前, 就是在 main()之前,内核会将此段中的数据初始化为 0 或空指针。
初始化数据段
用来保已初始化的全局变量和 static 静态变量。
文本段也称代码段
这是可执行文件中由 CPU 执行的机器指令部分。正文段常常是只读的,以防止程序由于意外而修改 其自身的执行。

Linux 内存管理的基本思想就是只有在真正访问一个地址的时候才建立这个地址的物理映射,Linux C/C++语言的分配方式共有 3 种方式。
(1)从静态存储区域分配。就是数据段的内存分配,这段内存在程序编译阶段就已经分配好,在程序的整个运行期间都存在, 例如全局变量,static 变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。 栈内存分配运算内置于处理器的指令集中,效率很高,但是系统栈中分配的内存容量有限,比如大额数组就会把栈空间撑爆导致
段错误。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。此 区域内存分配称之为动态内存分配。动态内存的生存期由我们决定,使用非常灵活,但问题也 多,比如指向某个内存块的指针取值发生了变化又没有其他指针指向 这块内存,这块内存就无法访问,发生内存泄露。

Linux内核在启动的后阶段会创建init进程来执行程序/sbin/init,该进程是系统运行的第一个进程,进程号为 1,称为 Linux 系统的初始化进程,该进程会创建其他子进程来启动不同写系统服务,而每个服务又可能创建不同的子进程来执行不同的 程序。所以init进程是所有其他进程的“祖先”,并且它是由Linux内核创建并以root的权限运行,并不能被杀死。Linux 中维护 着一个数据结构叫做 进程表,保存当前加载在内存中的所有进程的有关信息,其中包括进程的 PID(Process ID)、进程的状态、 命令字符串等,操作系统通过进程的 PID 对它们进行管理,这些 PID 是进程表的索引。

Linux下有两个基本的系统调用可以用于创建子进程:fork()和vfork()。fork在英文中是"分叉"的意思。为什么取这个名字呢? 因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就”分叉”了,所以这个名字取得很形象。在我们编 程的过程中,一个函数调用只有一次返回(return),但由于fork()系统调用会创建一个新的进程,这时它会有两次返回。一次返回 是给父进程,其返回值是子进程的PID(Process ID),第二次返回是给子进程,其返回值为0。所以我们在调用fork()后,需要通 过其返回值来判断当前的代码是在父进程还是子进程运行,如果返回值是0说明现在是子进程在运行,如果返回值>0说明是父进 程在运行,而如果返回值<0的话,说明fork()系统调用出错。fork 函数调用失败的原因主要有两个:

  1. 系统中已经有太多的进 程;
  2. 该实际用户 ID 的进程总数超过了系统限制。
    每个子进程只有一个父进程,并且每个进程都可以通过getpid()获取自己的进程PID,也可以通过getppid()获取父进程的 PID,这样在fork()时返回0给子进程是可取的。一个进程可以创建多个子进程,这样对于父进程而言,他并没有一个API函数可 以获取其子进程的进程ID,所以父进程在通过fork()创建子进程的时候,必须通过返回值的形式告诉父进程其创建的子进程PID。 这也是fork()系统调用两次返回值设计的原因。

接下来在代码中理解fork的实现:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/wait.h>
int main(int argc ,char **argv)
{
    pid_t           pid,cpid;
    pid=fork();
    if(pid<0)
    {
        printf("fork error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    else if(0==pid)
    {
        printf("Child process is running with pid [%d],his father pid[%d]\n",getpid(),getppid());
        cpid=fork();
        if(cpid<0)
        {
            printf("fork error:%d:%s\n",errno,strerror(errno));
            return -1;
        }
        else if(0==cpid)
        {
            printf("Child process's child process is running with pid[%d],and his father pid[%d]\n",getpid(),getppid());
            return 0;
        }
        else
        {
            sleep(1);//let child process excute first
            printf("child process's father process is running with pid[%d],and his child pid[%d]\n",getpid(),cpid);
            wait(NULL);
            return 0;
        }
    }
    else
    {
        sleep(1);//let child process excute first
        printf("father process is running with pid [%d], and his child pid[%d]\n",getpid(),pid);
        wait(NULL);
        return 0;
    }
}

zhanghang@Ubuntu-14:~/fork$ gcc fork.c 
zhanghang@Ubuntu-14:~/fork$ ./a.out    
Child process is running with pid [4280],his father pid[4279]
Child process's child process is running with pid[4281],and his father pid[4280]
father process is running with pid [4279], and his child pid[4280]
child process's father process is running with pid[4280],and his child pid[4281]

fork()系统调用会创建一个新的子进程,这个子进程是父进程的一个副本。这也意味着,系统在创建新的子进程成功后,会将 父进程的文本段、数据段、堆栈都复制一份给子进程,但子进程有自己独立的空间,子进程对这些内存的修改并不会影响父进程 空间的相应内存。这时系统中出现两个基本完全相同的进程(父、子进程),这两个进程执行没有固定的先后顺序,哪个进程先执 行要看系统的进程调度策略。如果需要确保让父进程或子进程先执行,则需要程序员在代码中通过进程间通信的机制来自己实 现。

由子进程自父进程继承到:
进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs)) 环境(environment)变量 堆栈 内存 打开文件的描述符(注意对应的文件的位置由父子进程共享, 这会引起含糊情况) 执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描 述符设置,POSIX.1要求所有目录 流都必须在exec函数调用时关闭。更详细说明, 参见《APUE》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编 程》), 3.13节和8.9节) 信号(signal)控制设定 nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级, 数值越小,优先级越高) 进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级, 根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优 先执行) 进程组号 对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期 (session)ID, 一个对话期包括一个或多 个进程组, 更详细说明参见《APUE》 9.5节) 当前工作目录 根目录 (根目录不一定是“/”,它可由chroot函数改变) 文件方式创建屏蔽字(file mode creation mask (umask)) 资源限制 控制终端
子进程所独有:
进程号
不同的父进程号(译者注: 即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到) 自己的文件描述符和目录流的拷贝(译者注: 目录流由opendir函数创建,因其为顺序读取,顾称“目录流”) 子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks) (译者注:锁定内存指被锁定的虚拟内存 页,锁定后, 不允许内核将其在必要时换出(page out), 详细说明参见《The GNU C Library Reference Manual》 2.2 版, 1999, 3.4.2节) 在tms结构中的系统时间(译者注:tms结构可由times函数获得, 它保存四个数据用于记录进程使用中央处理器 (CPU: Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间) 资源使用(resource utilizations)设定为0 阻塞信号集初始化为空集(译者注:原文此处不明确, 译文根据fork函数手册页稍做修改) 不继承由timer_create函数创建的计时器 不继承异步输入和输出 父进程设置的锁(因为如果是排他锁,被继承的话就矛盾了)

这里不得不提的就是引入多进程后,会出现的问题,这里只说常见的一个,代码:

#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main(int argc,char **argv)
{
	pid_t			pid;
	int			fd;
	int 			i=0;
	pid=fork();
	if(pid<0)
	{
		printf("fork error\n");
		return -1;
	}
	else if(pid==0)
	{
		fd=open("test_fork.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
		while(i<=4)
		{
			sleep(1);
			printf("Child process is running...\n");
			lseek(fd,0,SEEK_END);
			write(fd,"hello\n",strlen("hello\n"));
			i++;
		}
		close(fd);
		return 0;
	}
	else
	{
		fd=open("test_fork.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
		while(i<=4)
		{
			sleep(2);
			printf("Father process is running...\n");
			lseek(fd,0,SEEK_END);
			write(fd,"?????\n",strlen("?????\n"));
			i++;
		}
		close(fd);
	}
	wait(NULL);
	return 0;
}

运行结果

iot@Public_RPi:~/zhanghang/fork $ gcc test_fork.c 
iot@Public_RPi:~/zhanghang/fork $ ./a.out 
Child process is running...
Father process is running...
Child process is running...
Child process is running...
Father process is running...
Child process is running...
Child process is running...
Father process is running...
Father process is running...
Father process is running...
iot@Public_RPi:~/zhanghang/fork $ tail test_fork.txt 
hello
?????
hello
hello
?????
hello
hello
?????
?????
?????

多进程操作同一个文件时,会出现这种结果,双方都向该文件写入,会使文件内容紊乱。
解决方法:文件锁,具体使用方法:百度,哈哈,反正我也是百度的
修改代码如下:

#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/file.h>
int main(int argc,char **argv)
{
	pid_t			pid;
	int			fd;
	int 			i=0;
	pid=fork();
	if(pid<0)
	{
		printf("fork error\n");
		return -1;
	}
	else if(pid==0)
	{
		fd=open("test_fork.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
		flock(fd,LOCK_EX);
		while(i<=4)
		{
			sleep(1);
			printf("Child process is running...\n");
			lseek(fd,0,SEEK_END);
			write(fd,"hello\n",strlen("hello\n"));
			i++;
		}
		flock(fd,LOCK_UN);
		close(fd);
		return 0;
	}
	else
	{
		fd=open("test_fork.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
		flock(fd,LOCK_EX);
		while(i<=4)
		{
			sleep(2);
			printf("Father process is running...\n");
			lseek(fd,0,SEEK_END);
			write(fd,"?????\n",strlen("?????\n"));
			i++;
		}
		flock(fd,LOCK_UN);
		close(fd);
	}
	wait(NULL);
	return 0;
}

iot@Public_RPi:~/zhanghang/fork $ gcc test_fork.c            
iot@Public_RPi:~/zhanghang/fork $ ./a.out 
Father process is running...
Father process is running...
Father process is running...
Father process is running...
Father process is running...
Child process is running...
Child process is running...
Child process is running...
Child process is running...
Child process is running...
iot@Public_RPi:~/zhanghang/fork $ tail test_fork.txt 
?????
?????
?????
?????
?????
hello
hello
hello
hello
hello

再给处一个示例,让父进程先运行,如不加wait()等待等待子进程,则会出现的情况:

#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/file.h>
int main(int argc,char **argv)
{
	pid_t			pid;
	int			fd;
	int 			i=0;
	pid=fork();
	if(pid<0)
	{
		printf("fork error\n");
		return -1;
	}
	else if(pid==0)
	{
		sleep(1);
		fd=open("test_fork.txt",O_RDWR|O_CREAT,0644);
		flock(fd,LOCK_EX);
		lseek(fd,0,SEEK_END);
		while(i<=4)
		{
			sleep(1);
			printf("Child process is running...\n");
//			lseek(fd,0,SEEK_END);
			write(fd,"hello\n",strlen("hello\n"));
			i++;
		}
		flock(fd,LOCK_UN);
		close(fd);
		return 0;
	}
	else
	{
		fd=open("test_fork.txt",O_RDWR|O_CREAT,0644);
		flock(fd,LOCK_EX);
		lseek(fd,0,SEEK_END);
		while(i<=4)
		{
			sleep(1);
			printf("Father process is running...\n");
//			lseek(fd,0,SEEK_END);
			write(fd,"?????\n",strlen("?????\n"));
			i++;
		}
		flock(fd,LOCK_UN);
		close(fd);
	}
	//wait(NULL);
	return 0;
}

compile and run:

iot@Public_RPi:~/zhanghang/fork $ gcc test_fork.c
iot@Public_RPi:~/zhanghang/fork $ ./a.out 
Father process is running...
Father process is running...
Father process is running...
Father process is running...
Father process is running...
iot@Public_RPi:~/zhanghang/fork $ Child process is running...
Child process is running...
Child process is running...
Child process is running...
Child process is running...

加入文件所后,只需对光标进行重一次i定位
同时注意使用多进程对同一文件描述符操作时,要对光标进行重定位lseek和fseek(当open产生的文件描述符在fork外时,多进程共享光标,如果多进程一边写,另一边读时,需要重定位光标lseek(fd,0,SEEK_SET);才能读到数据,在fork内打开同一文件时时,各自有自己的光标,此时,后写入者写入会覆盖前者写入,须在每次写入时对光标重定位到文件尾,如上文所示
给出一个示例:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
int main(int  argc,char **argv)
{
	pid_t 			pid;
	int		 	fd;
	char 			*ptr=NULL;
	char 			*str=NULL;
	char 			buf[2048];
	FILE 			*fp=NULL;
	char 			ipaddr[16]={0};
	char			broadcast[16]={0};
	char			netmask[16]={0};
	if((fd=open("test.txt",O_CREAT|O_RDWR|O_TRUNC,0644))<0)
	{
		printf("open test.txt error\n");
		return -1;
	}
	memset(buf,0,sizeof(buf));
	pid=fork();
	if(pid<0)
	{
		printf("fork() error:%s\n",strerror(errno));
		return -1;
	}
	else if(pid==0)
	{
		
		printf("Child process is running...\n");
		dup2(fd,STDOUT_FILENO);
		execl("/sbin/ifconfig","ifconfig","wlan0",NULL);
		printf("This message will not be printed,if printed,this program is wrong\n");
		return -1;
	}
	else
	{
		sleep(2);
		printf("father process is running...\n");
	}
	printf("Before lseek\n");
	while(read(fd,buf,sizeof(buf))!=0)
	{
		printf("%s\n",buf);
	}
	lseek(fd,0,SEEK_SET);
	memset(buf,0,sizeof(buf));
	printf("After lseek\n");
	while(read(fd,buf,sizeof(buf))!=0)
	{
		printf("%s\n",buf);
	}
	//fd=lseek(fd,0,SEEK_SET);
	fp=fdopen(fd,"r");
	fseek(fp,0,SEEK_SET);
	memset(buf,0,sizeof(buf));
	while(fgets(buf,sizeof(buf),fp))
	{
		if(strstr(buf,"inet")&&strstr(buf,"netmask")&&strstr(buf,"broadcast"))
		{
			printf("find it!!!\n");
			ptr=strstr(buf,"inet");
			ptr+=strlen("inet");
			while(isblank(*ptr))
			{
				ptr++;
			}
			str=ptr;
			while(!isblank(*str))
			{
				str++;
			}
			memcpy(ipaddr,ptr,str-ptr);


			ptr=strstr(buf,"netmask");
			ptr+=strlen("netmask");
			while(isblank(*ptr))
			{
				ptr++;
			}
			str=ptr;
			while(!isblank(*str))
			{
				str++;
			}
			memcpy(netmask,ptr,str-ptr);


			ptr=strstr(buf,"broadcast");
			ptr+=strlen("broadcast");
			while(isblank(*ptr))
			{
				ptr++;
			}
			str=ptr;
			while(!isblank(*str)&&((*str)!='\n'))
			{
				str++;
			}
			memcpy(broadcast,ptr,str-ptr);
			break;
		}
	}
	printf("ipaddr:%s\nbroadcast:%s\nnetmask:%s\n",ipaddr,broadcast,netmask);
	return 0;
}

iot@Public_RPi:~/zhanghang/fork $ gcc fork.c    
iot@Public_RPi:~/zhanghang/fork $ ./a.out 
Child process is running...
father process is running...
Before lseek
After lseek
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.16  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::ba27:ebff:fefb:c317  prefixlen 64  scopeid 0x20<link>
        ether b8:27:eb:fb:c3:17  txqueuelen 1000  (Ethernet)
        RX packets 865719  bytes 64300020 (61.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2574781  bytes 2874910568 (2.6 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


find it!!!
ipaddr:192.168.0.16
broadcast:192.168.0.255
netmask:255.255.255.0

execl(…)函数是让子进程开始执行带参数的ifconfig命令: ifconfig eth0

execl()会导致子进程彻底丢掉父进程的文本段、数据段,并加载/sbin/ifconfig这个程序的文本段、数据段 重新建立进程内存空间。 execl()函数的第一个参数是所要执行程序的路径,ifconfig命令(程序)的路径是/sbin/ifconfig; 接下来的参数是命令及其相关选项、参数,每个命令、选项、参数都用双引号("")扩起来,并以NULL结束。 ifconfig eth0命令在执行时会将命令的执行的结果输出到标准输出上,而这时子进程已经重定向标准输出到 文件中去了,所以ifconfig 命令的打印结果会输出到文件中去,这样父进程就会从该文件里读到子进程执行该命令的结果;

在上面的例子中我们可以看到,在fork()之后常会紧跟着调用exec来执行另外一个程序,而exec会抛弃父进程的文本段、数据 段和堆栈等并加载另外一个程序,所以现在的很多fork()实现并不执行一个父进程数据段、堆和栈的完全副本拷贝。作为替代, 使用了写时复制(CopyOnWrite)技术: 这些数据区域由父子进程共享,内核将他们的访问权限改成只读,如果父进程和子进程 中的任何一个试图修改这些区域的时候,内核再为修改区域的那块内存制作一个副本。
vfork()是另外一个可以用来创建进程的函数,他与fork()的用法相同,也用于创建一个新进程。 但vfork()并不将父进程的地址 空间完全复制到子进程中,因为子进程会立即调用exec或exit(),于是也就不会引用该地址空间了。不过子进程再调用exec()或 exit()之前,他将在父进程的空间中运行,但如果子进程想尝试修改数据域(数据段、堆、栈)都会带来未知的结果,因为他会影响 了父进程空间的数据可能会导致父进程的执行异常。此外,vfork()会保证子进程先运行,在他调用了exec或exit()之后父进程才 可能被调度运行。如果子进程依赖于父进程的进一步动作,则会导致死锁。

vfork()的函数原型和 fork原型一样:
#include <unistd.h> 
#include <sys/types.h>
pid_t fork(void); 
pid_t vfork(void);

在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项可使调用者不用阻塞。 waitpid并不等待在其调用的之后的 第一个终止进程,他有若干个选项,可以控制他所等待的进程。 如果一个已经终止、但其父进程尚未对其调用wait进行善后处理 (获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等)的进程被称僵死进程(zombie),ps命令将僵死 进程的状态打印为Z。如果子进程已经终止,并且是一个僵死进程,则wait立即返回该子进程的状态。所以,我们在编写多进程 程序时,好调用wait()或waitpid()来解决僵尸进程的问题。

pid_t wait(int *status);      
pid_t waitpid(pid_t pid, int *status, int options);    
 

相关函数popen可代替fork+execl这里不做说明

此时,已经可以将多进程的服务器端通过多进程优化了:
给出伪代码:

#include<>
int main()
{
listen_fd=socket();
bind(listen_fd);
listen(listen_fd);
while(1)
{
fd=accept(listen_fd);//阻塞
pid=fork();
if(pid==0)
{
close(listen_fd);
while(1)
{
read(fd);write(fd);
}
close(fd);
return 0;
}
else if(pid>0)
{
close(fd);
}
}
close(listen_fd);
return 0;
}

关于多进程编程只讲这么多,如果有不对之处还请指出,谢谢!!!

猜你喜欢

转载自blog.csdn.net/qq_43260665/article/details/88823011