一、多进程
1.进程介绍
fork()系统调用会创建一个新的子进程,同时将父进程的文本段、数据段、堆栈都复制一份给子进程,相当于是父进程的副本,但子进程也具有自己独立的空间,其的修改行为并不会对父进程造成影响。父、子进程运行没有固定的先后顺序,具体顺序要看系统的进程调度策略。或可以通过调用wait()、waitpid()及进程间通信的信号量来设置父、子进程运行的先后顺序。
创建进程基本流程:
(1)设置pid值:pid_t pid;
(2)创建新进程:pid = fork();
(3)判断pid值即fork返回值:
①pid <0,即调用fork函数失败,可能是因为系统中已经有太多的进程;或该实际用户ID的进程总数超过了系统限制;
②pid >0,说明此时父进程在运行;
③pid == 0,说明此时子进程在运行。
后续可调用wait()、waitpid()等待子进程终止,确保其被父进程回收,避免变为僵尸进程,浪费系统资源。
2.函数介绍
(1)fork()函数
pid_t pid; //定义进程号
pid = fork(); //创建子进程
①函数功能:创建子进程
②函数返回值:父进程中,函数返回新建子进程的进程ID,在子进程中,函数返回0,失败则返回一个负值,可通过判断其返回值来确定正在运行的进程。
(2)exec*()函数族
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
... ...
①函数功能:让子进程去执行另一个程序
②函数返回值:成功时,函数不会返回,失败返回-1,错误原因存在errno中
exec*()为一系列函数,在fork()后紧接着使用,l代表以列表(list)形式传递要执行程序的命令行,v代表以数组(vector)形式传递要执行程序的命令行。
(3)waitpid()函数
#include <sys.types.h>
#include <wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
①函数功能:等待子进程结束,回收并释放子进程资源
②函数参数:
pid取值不同,则意义也不同
pid > 0时,只等待进程ID为该值的子进程,只要该子进程不结束,就会一直等待下去;
pid = -1时,等待任何一个子进程的退出,此时作用和wait()相同;
pid = 0时,等待同一个进程组中的任何子进程;
pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
status用来保存子进程退出时的状态,为NULL,表示忽略子进程退出时的状态;不为空,waitpid函数会将子进程退出的状态存入status中
option提供一些额外的选项来控制waitpid,目前Linux只支持WNOHANG、WUNTRACED两个选项,可以用‘|’连接起这两个,如都不想使用可使options为0,一般使用WNOHANG代表即使没有子进程退出,它也会立即返回,不会像wait一样一直阻塞下去。
③函数返回值:成功,返回子进程的进程号,如options为WNOHANG,则返回0;失败,返回-1,错误原因存放在errno中。
(4)system()函数
int system(const char *command);
①函数功能:调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行command的命令。此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
②函数参数:command为命令行
③函数返回值:成功,返回进程的状态值;失败,返回-1,当sh不能运行时,返回127
(5)popen()函数、pclose()函数
FILE * popen( const char * command,const char * type);
int pclose(FILE * stream);
①函数功能:popen()会调用fork创建一个子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中,pclose()用来关闭由popen所建立的管道及文件指针。
②函数参数:参数type可使用“r”代表读取,“w”代表写入;参数stream为先前由popen()所返回的文件指针返回值
③函数返回值:popen()成功返回文件指针,失败返回NULL,错误原因存在errno中;pclose()成功返回shell的终止状态(即子进程的终止状态),失败则返回-1,错误原因存于errno中。
3.多进程并发服务器编程
(1)服务端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#define BACKLOG 13
void child_process(int cli_fd);//子进程执行程序
void print_usage(char *progname)
{
printf("%s usage: \n",progname);
printf("-p(--port):sepcify server port.\n");
printf("-h(--Help):print this help information.\n");
return ;
}
int main (int argc, char **argv)
{
int ch;
int port = 0;
int rv = -1;
int cli_fd;
int on = 1;
int listen_fd = -1;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
socklen_t len;
pid_t pid,pid1; //定义进程号,pid为子进程号,pid1为调用waitpid()的返回值
struct option opts[] = {
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while( (ch=getopt_long(argc, argv, "p:h", opts, NULL)) != -1 )
{
switch(ch)
{
case 'p':
port=atoi(optarg);
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if(!port)
{
printf("please input the port!\n");
print_usage(argv[0]);
return 0;
}
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0)
{
printf("create socket failure: %s\n",strerror(errno));
return -1;
}
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//设置套接字描述符的属性
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
rv = bind(listen_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));//绑定监听端口
if(rv < 0)
{
printf("bind socket failure: %s\n",strerror(errno));
return -2;
}
printf("socket[%d] bind on port[%d] successfully!\n",listen_fd,port);
if(listen(listen_fd,BACKLOG) < 0)//监听端口
{
printf("listen failure:%s\n",strerror(errno));
return -3;
}
while(1)
{
cli_fd = accept(listen_fd,(struct sockaddr *)&cliaddr,&len);
if(cli_fd <0 )
{
printf("accept client failure:%s\n",strerror(errno));
return -4;
}
pid = fork();//创建子进程
//判断父、子进程的运行状态
if(pid < 0 )
{
printf("fork() create child process failure:%s\n", strerror(errno));
close(cli_fd);
return -4;
}
else if(pid >0 )
{
close(cli_fd);
continue;
}
else if(0 == pid)//子进程运行
{
child_process(cli_fd);
close(listen_fd);
return 0;
}
pid1 = waitpid(pid,NULL,WNOHANG);//检测子进程的状态
if(pid1 == 0)//子进程仍在运行,WNOHANG表示子进程未结束,则函数立即返回不阻塞
{
printf("the child process[%d] not No finish\n",getpid());
sleep(1);
}
else if(pid1 == pid)//子进程结束并已被回收,避免成为僵尸进程
{
printf("successfully get child PID[%d]\n",getpid());
}
else
{
printf("get error\n");
}
}
close(listen_fd);
return 0;
}
void child_process(int cli_fd)//子进程运行内容
{
char buf[1024];
int rv = -1;
printf("child process PID[%d] start to communicate whit client...\n", getpid());//getpid()获取子进程进程号
while(1)
{
rv = read(cli_fd,buf,sizeof(buf));
if(rv < 0 )
{
printf("read data from client failure:%s\n",strerror(errno));
close(cli_fd);
exit(0);
}
// printf("The temperature('C) is : %s\n",buf); 从客户端获取温度
rv = write(cli_fd,buf,sizeof(buf));
if(rv < 0 )
{
printf("write data failure:%s\n",strerror(errno));
close(cli_fd);
}
close(cli_fd);
exit(0);
}
}