Linux网络socket多进程编程函数解析

目录

fork()/vfork()系统调用

exec*()执行另外一个程序

wait()与waitpid()

system()与popen()函数


fork()/vfork()系统调用

pid_t fork(void)

功能描述:创建子进程

返回值:>0 父进程运行  =0 子进程运行  <0 函数系统调用失败(系统中有太多进程、该用户的进程总数超过了系统限制)

有两次返回 ①返回给父进程 返回值是子进程的PID ②返回给子进程 返回值是0

PS:父子进程谁先运行没有规定,由操作系统决定。并且fork会将父进程的地址空间完全复制到子进程中

pid_t vfork(void)

PS:用法与fork相同,但是vfork并不将父进程的地址空间复制到子进程中,因为子进程会立即调用exec或exit(),因此不会引用该地址空间。但在子进程调用exec或exit()之前,它会在父进程的空间中进行,但如果子进程想要尝试修改数据域(数据段、堆、栈)都会带来未知的结果,因为它会影响父进程空间的数据可能会导致父进程执行异常。此外,vfork()会保证子进程先运行,在其调用exec或exit()之后父进程才可能被调度运行。如果子进程依赖父进程进一步动作,会导致死锁。

pid_t getpid(void)    pid_t getppid(void)

功能描述:getpid返回当前进程PID,getppid返回其父进程PID

包含的头文件:#include <unistd.h>   #include <sys/types.h>

pid_t 是个宏定义 实质是int 被定义在头文件#include <sys/types.h>

下面以一个程序讲解一下进程的创建过程。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char **argv)
{
	pid_t    pid;

	printf("Parent process PID[%d] start running....\n", getpid() );

	pid = fork();
	if( pid < 0 )
	{
		printf("fork() create child process failure: %s\n", strerror(errno) );
		return -1;
	}
	else if( pid == 0 )
	{
		printf("Child process PID[%d] start running, my parent PID is [%d]\n", getpid(), getppid() );
		return 0;
	}
	else
	{
		printf("Parent process PID[%d] continue running, and child process PID is [%d]\n", getpid(), pid);
		return 0;
	}
}

执行结果如下:

Parent process PID[2676] start running...
Parent process PID[2676] continue running, and child process PID is [2677]
Child process PID[2677] start running, my parent PID is [2676]


exec*()执行另外一个程序

通常情况下,我们创建子进程是让该进程去执行另外一个程序。这时我们会在fork()之后紧接着调用exec*()系列的函数来让子进程去执行另外一个程序。其中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[]);
... ..
以上这些函数我们选择一个实现即可,在这里我主要介绍execl()函数,因为这个函数相对比别的函数参数较为简单。

int execl(const char *path, const char *arg, ...)

功能描述:执行另一个程序。 通常和fork()一起使用,让子进程去执行另一个程序

参数解析:①path字符指针指向要执行的文件路径

                 ②接下来参数代表执行该文件时传递的参数列表(l):argv[0],argv[1]...最后一个参数用空指针NULL作结束

返回值:成功则无返回值,失败返回-1

包含的头文件:#include <unistd.h>   

PS:用execl执行另一个程序,会抛弃父进程的文本段、数据段和堆栈等并加载另一个程序


wait()与waitpid()

当一个进程正常或异常退出时,内核就会向其父进程发送SIGCHLD信号。因为子进程退出是一个异步事件,所以这种信号也 是内核向父进程发送的一个异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即将被执行的函数,父进程可以调用wait()或waitpid()可以用来查看子进程退出的状态。
pid_t wait(int *status)
参数解析:
  • 参数status如果不是一个空指针,则终止进程的终止状态就存放在statloc所指向的单元。
  • 参数status如果是一个空指针,则表示父进程不关心子进程的终止状态
返回值:成功则返回结束子进程的进程号,失败则返回-1
pid_t waitpid(pid_t pid, int *status, int options)
参数解析:①pid是相应的子进程号
                  ②status指针与wait()中参数作用一致
                  ③options是一些内置选项,如:
WNOHANG若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED返回终止子进程信息和因信号停止的子进程信息
WCONTINUED返回收到SIGCONT信号而恢复执行的已停止子进程状态信息
返回值:成功返回结束运行的子进程号,失败则返回-1,没有子进程则返回0
包含的头文件:#include <sys/types.h>   #include <sys/wait.h>
二者的区别:
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项可使调用者不用阻塞。 waitpid并不等待在其调用的之后的 第一个终止进程,他有若干个选项,可以控制他所等待的进程。
僵死进程(zombie):已经终止、但其父进程尚未对其调用wait进行善后处理(获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等)的进程。 ps命令将僵死进程的状态打印为Z
如果子进程已经终止,并且是一个僵死进程,则wait立即返回该子进程的状态。所以,我们在编写多进程程序时,最好调用wait()或waitpid()来解决僵尸进程的问题。
孤儿进程父进程在子进程退出之前退出了,该子进程成为孤儿进程。
PS: Linux内核中所有的子进程在变成孤儿进程之后都会被init进程“领养” ,这也意味着孤
儿进程的父进程最终会变成init进程。

system()与popen()函数

int system(const char *command)

功能描述:Liunx库函数,可以快速创建一个进程来执行相应的命令,在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。

参数解析:command字符指针指向一条命令,如"ping -c 4 -I eth0 4.2.2.2"

返回值:成功则返回0,失败则返回-1

包含头文件:#include <stdlib.h>

FILE *popen(const char *command, const char *type)

功能描述:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令

参数解析:①command字符指针指向一条命令,如"ping -c 4 -I eth0 4.2.2.2" 

                  ②参数type可使用“r”代表读取(对应标准输出),“w”代表写入(对应标准输入)
返回值:若成功则返回文件指针,否则返回NULL,错误原因存于errno中。

包含头文件:#include <stdio.h>

PS:popen()返回一个基于管道(pipe)的文件流,这样我们可以从文件流中一行一行解析

下面是一个程序是关于popen()的使用获取IP地址:

#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 get_ipaddr(char *interface, char *ipaddr, int ipaddr_size);

int main(int argc, char **argv)
{
     char ipaddr[16];
     char *interface="eth0";
     memset(ipaddr, 0, sizeof(ipaddr));
     if( get_ipaddr(interface, ipaddr, sizeof(ipaddr)) < 0 )
     {
         printf("ERROR: get IP address failure\n");
         return -1;
     }
     printf("get network interface %s IP address [%s]\n", interface, ipaddr);
     
     return 0;
}

int get_ipaddr(char *interface, char *ipaddr, int ipaddr_size) {
     char buf[1024];
     char *ptr;
     char *ip_start;
     char *ip_end;
     FILE *fp;
     int len;
     int rv;

     if( !interface || !ipaddr || ipaddr_size<16 )
     {
         printf("Invalid input arguments\n");
         return -1;
     }

     memset(buf, 0, sizeof(buf));
     snprintf(buf, sizeof(buf), "ifconfig %s", interface);
     if( NULL == (fp=popen(buf, "r")) )
     {
         printf("popen() to excute command \"%s\" failure: %s\n", buf, strerror(errno));
         return -2;
     }

     rv = -3; /* Set default return value to -3 means parser failure */
     while( fgets(buf, sizeof(buf), fp) )
     {
         if( strstr(buf, "netmask") )
         {
             ptr=strstr(buf, "inet");
             if( !ptr )
             {
                 break;
             }
         ptr += strlen("inet");

         while( isblank(*ptr) )
             ptr++;
             ip_start = ptr;

         while( !isblank(*ptr) )
             ptr++;
             ip_end = ptr;

         memset(ipaddr, 0, sizeof(ipaddr));
         len = ip_end-ip_start;
         len = len>ipaddr_size ? ipaddr_size : len;
         memcpy(ipaddr, ip_start, len);
         rv = 0; /* Parser IP address OK and set rv to 0 */
         break;
     }
 }
 return rv;
}

结果如下:

get network interface eth0 IP address [192.168.2.17]
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值