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

Linux网络socket多进程编程函数解析,第1张

目录

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   #include

pid_t 是个宏定义 实质是int 被定义在头文件#include

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


#include 
#include 
#include 
#include 

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   

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    #include 二者的区别: 在一个子进程终止前,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

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

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

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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]

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/563228.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-02
下一篇 2022-04-02

发表评论

登录后才能评论

评论列表(0条)

保存