TCP——多进程编程(1)_tcp多进程-程序员宅基地

1、什么是进程
在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进 程。我们使用vim编辑生成的C文件叫做源码,源码给程序员来看的但机器不识别,这时我们需要使用 编译器gcc编译生成CPU可识别的二进制可执行程序并保存在存储介质上,这时编译生成的可执行程序只能叫做程序而不能叫进 程。而一旦我们通过命令(./a.out)开始运行时,那正在运行的这个程序及其占用的资源就叫做进程了。很显然,一个程序可以执行多次,这也意味着多个进程可以执行同一个 程序。
2.fork()系统调用
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()系统调用两次返回值设计的原因。
    简单示例:
  1 #include <stdio.h>  
  2 #include <unistd.h>  
  3 #include <string.h>  
  4 #include <errno.h>  
  5  
  6 int main(int argc, char **argv)  
  7 {
      
  8         pid_t          pid;  
  9 
  10         
  printf("Parent process PID[%d] start running...\n", getpid() ); 
  11 
  12         pid = fork(); 
  13         if(pid < 0) 
  14         {
     
  15                 printf("fork() create child process failure: %s\n", strerror(errno)); 
  16                 return -1; 
  17         } 
  18         else if( pid == 0 ) 
  19         {
    
  20                 printf("Child process PID[%d] start running, my parent PID is [%d]\n", getpid(), getppid()); 
  21                 return 0; 
  22         }
  23         else // if( pid > 0 ) 
  24         {
     
  25                 printf("Parent process PID[%d] continue running, and child process PID is [%d]\n", getpid(), pid); 
  26                 return 0; 
  27         } 
  28 }

运行结果:

Parent process PID[26765] start running... 
Parent process PID[26765] continue running, and child process PID is [26766]
Child process PID[26766] start running, my parent PID is [1](此处由于父进程比子进程先退出,所以子进程变成孤儿进程,子进程将被init进程认领,所以它的父进程号变成init进程的进程号【1)

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

接下来我们再写一个例子深入了解一下创建子进程的过程

   #include <stdio.h>  
   #include <errno.h>  
   #include <unistd.h>  
   #include <string.h>  
    
   int  g_var = 6;  
   char g_buf[]="A string write to stdout.\n";  
   int main (int argc, char **argv)  
   {
     
           int     var = 88; 
           pid_t   pid;
           
           printf("Befor fork\n"); 
   
           if( (pid=fork()) < 0) 
           {
     
                   printf("fork() error: %s\n", strerror(errno)); 
                   return -2; 
           } 
           else if( 0 == pid) 
           {
     
                   printf("Child process  PID[%d] running...\n", getpid());          
                   g_var ++; 
                   var ++; 
           } 
           else 
           {
     
                   printf("Parent process PID[%d] waiting...\n", getpid()); 
                   sleep(1); 
           } 
   
           printf("PID=%ld, g_var=%d, var=%d\n", (long) getpid(), g_var, var); 
           return 0; 
   }

运行结果:

 Befor fork 
 Parent process PID[27642] waiting... 
 Child process  PID[27643] running... 
 PID=27643, 
 g_var=7, 
 var=89 PID=27642, 
 g_var=6, var=88

在上面的编译运行过程我们可以看到,父进程在代码第21行创建了子进程后,系统会将父进程的文本段、数据段、堆栈都拷贝 一份给子进程,这样子进程也就继承了父进程数据段中的的全局变量g_var和局部变量var的值。

  1. 因为进程创建之后究竟是父进程还是子进程先运行没有规定,所以父进程在第35行调用了sleep(1)的目的是希望让子进程 先运行,但这个机制是不能100%确定能让子进程先执行,如果系统负载较大时1秒的时间内操作系统可能还没调度到子进 程运行,所以sleep()这个机制并不可靠,这时候我们需要使用到今后学习的进程间通信机制来实现这种父子进程之间的同 步问题;
  2. 程序中38行的printf()被执行了两次,这是因为fork()之后,子进程会复制父进程的代码段,这样38行的代码也被复制给子 进程了。而子进程在运行到第30行后并没有调用return()或exit()函数让进程退出,所以程序会继续执行到38行至39行调 用return 0退出子进程;同理父进程也是执行38行至39行才让父进程退出,所以38行的printf()分别被父子进程执行了两 次。
  3. 子进程在第29行和30行改变了这两个变量的值,这个改变只影响子进程的空间的值,并不会影响父进程的内存空间,所 以子进程里g_var和var分别变成了7和89,而父进程的g_var和var都没改变;

子进程继承父进程哪些东西

由子进程自父进程继承到:
1、进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs))
2、环境(environment)变量
3、堆栈
4、内存
5、打开文件的描述符(注意对应的文件的位置由父子进程共享, 这会引起含糊情况) 执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描 述符设置,POSIX.1要求所有目录 流都必须在exec函数调用时关闭。更详细说明, 参见《APUE》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编 程》), 3.13节和8.9节)
6、信号(signal)控制设定
7、nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级, 数值越小,优先级越高) 进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级, 根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优 先执行)
8、进程组号
9、对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期 (session)ID, 一个对话期包括一个或多 个进程组, 更详细说明参见《APUE》 9.5节) 当前
10、工作目录 根目录(根目录不一定是“/”,它可由chroot函数改变)
11、文件方式创建屏蔽字(file mode creation mask (umask))
12、资源限制
13、控制终端

子进程所独有:

1、进程号
2、不同的父进程号(译者注: 即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
3、自己的文件描述符和目录流的拷贝(译者注: 目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
4、子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks) (译者注:锁定内存指被锁定的虚拟内存 页,锁定后, 不允许内核将其在必要时换出(page out), 详细说明参见《The GNU C Library Reference Manual》 2.2 版, 1999, 3.4.2节)
5、在tms结构中的系统时间(译者注:tms结构可由times函数获得, 它保存四个数据用于记录进程使用中央处理器 (CPU: Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
6、资源使用(resource utilizations)设定为0
7、阻塞信号集初始化为空集(译者注:原文此处不明确, 译文根据fork函数手册页稍做修改)
8、不继承由timer_create函数创建的计时器 不继承异步输入和输出
9、父进程设置的锁(因为如果是排他锁,被继承的话就矛盾了)

多进程改写服务器程序:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>

#define MSG_STR "Hello LingYun IoT Studio Client\n"

void print_usage(char *progname)
{
    
    printf("%s usage: \n", progname);
    printf("-p(--port): sepcify server listen port.\n");
    printf("-h(--Help): print this help information.\n");

    return ;
}

int main(int argc,char **argv)
{
    
    

    int servfd = 0;
    int cliefd = 0;
    
    int port =0; 
    int on = 1;
    int rv = -1; 
    int ch = 0;
    char buf[1024] = {
    0};

    socklen_t  len = 0;
    
    pid_t  pid;
    struct sockaddr_in  servaddr;
    struct sockaddr_in  clieaddr;
     
     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 )
    {
    
        print_usage(argv[0]);
        return 0;
    }


    servfd = socket(AF_INET,SOCK_STREAM,0);
    if(servfd <0)
    {
    
        printf("create servfd failure:%s\n",strerror(errno));
        return -1;
    }

    printf("create servfd[%d] successfully!\n",servfd);

    setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    servaddr.sin_family = AF_INET;
    servaddr.sin_port   = htons(port);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(servfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
    {
    
        printf("bind port[%d] failure:%s\n",port,strerror(errno));
        return -2;
    }


    listen(servfd,13);
 printf("bind port[%d] successfully!\n",port);


    while(1)
    {
    
        printf("Start accept new client incoming...\n");

        cliefd = accept(servfd,(struct sockaddr *)&clieaddr,&len);
        if(cliefd < 0)
        {
    
            printf("accept data[%d] failure:%s\n",cliefd,strerror(errno));
            close(cliefd);
            continue;
        }

        pid =fork();

        if(pid <0)
        {
    
            printf("fork[%d] failure:%s\n",pid,strerror(errno));
            close(cliefd);
            continue;
        }

        else if(pid >0)
        {
    
            close(cliefd);
            continue;
        }

        else if( pid == 0)
        {
    
            close(servfd);
            printf("Child process start to commuicate with socket client...\n");
            rv = read(cliefd,buf,sizeof(buf));

            if(rv < 0)
            {
    
                printf("read cliefd[%d] failure:%s\n",cliefd,strerror(errno));

                close(cliefd);
                exit(0);
            }
            else if(rv == 0)
            {
    
                printf("cliefd disconnect!\n ");
                close(cliefd);
                exit(0);
            }

            printf("Read %d bytes data from Server: %s\n", rv, buf);
            
            rv=write(cliefd, MSG_STR, strlen(MSG_STR));
            if(rv < 0)
            {
    
                printf("Write to client by sockfd[%d] failure: %s\n", cliefd, strerror(errno));
                close(cliefd);

                exit(0);
            }

             printf("close client socket[%d] and child process exit\n", cliefd);

             close(cliefd);

             exit(0);
        }

    }

    close(servfd);
    return 0;

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/A28384205/article/details/97941195

智能推荐

python色卡识别_用Python帮小姐姐选口红,人人都是李佳琦-程序员宅基地

文章浏览阅读502次。原标题:用Python帮小姐姐选口红,人人都是李佳琦 对于李佳琦,想必知道他的女生要远远多于男生,李佳琦最早由于直播向广大的网友们推荐口红,逐渐走红网络,被大家称作“口红一哥”。不可否认的是,李佳琦的直播能力确实很强,他能够抓住绝大多数人的心理,让大家喜欢看他的直播,看他直播推荐的口红适不适合自己,色号适合什么样子的妆容。为了提升效率,让自己的家人或者女友能够快速的挑选出合适自己妆容的口红色号,今..._获取口红品牌 及色号,色值api

linux awk命令NR详解,linux awk命令详解-程序员宅基地

文章浏览阅读3.6k次。简介awk命令的名称是取自三位创始人Alfred Aho 、Peter Weinberger 和 Brian Kernighan姓名的首字母,awk有自己的程序设计语言,设计简短的程序,读入文件,数据排序,处理数据,生成报表等功能。awk 通常用于文本处理和报表生成,最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作。awk 通常以文件的一行为处理单位..._linux awk nr

android 网络连接失败!failed to connect to /192.168.1.186(port 8080)_failed to connect to 192.168.88.218:80-程序员宅基地

文章浏览阅读1.3w次,点赞5次,收藏2次。在网上找了一个小时,一直没有头绪,因为上个星期还是好好的,最后看到一个大神的解答,只需要将防火墙关闭就好了.原本向测试功能的,却卡在了登录上.以此记录.另外好像还有种错误是电脑与手机连接的WiFi不同,也可以看看...._failed to connect to 192.168.88.218:80

matlab 多径衰落,利用MATLAB仿真多径衰落信道.doc-程序员宅基地

文章浏览阅读1.9k次。利用MATLAB仿真多种多径衰落信道摘要:移动信道的多径传播引起的瑞利衰落,时延扩展以及伴随接收过程的多普勒频移使接受信号受到严重的衰落,阴影效应会是接受的的信号过弱而造成通信的中断:在信道中存在噪声和干扰,也会是接收信号失真而造成误码,所以通过仿真找到衰落的原因并采取一些信号处理技术来改善信号接收质量显得很重要,这里利用MATLAB对多径衰落信道的波形做一比较。一,多径衰落信道的特点关于多径衰落..._matlab多径衰落工具箱

python对json的操作及实例解析_import json灰色-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏17次。Json简介:Json,全名 JavaScript Object Notation,是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。(来自百度百科)python关于json文_import json灰色

mysql实现MHA高可用详细步骤_mysql mha超详细教程-程序员宅基地

文章浏览阅读1.1k次,点赞6次,收藏3次。一、工作原理MHA工作原理总结为以下几条:(1) 从宕机崩溃的 master 保存二进制日志事件(binlog events);(2) 识别含有最新更新的 slave ;(3) 应用差异的中继日志(relay log) 到其他 slave ;(4) 应用从 master 保存的二进制日志事件(binlog events);(5) 通过Manager控制器提升一个 slave 为新 m..._mysql mha超详细教程

随便推点

Linux环境下主从搭建心得(高手勿喷)_linux的java主从策略是什么-程序员宅基地

文章浏览阅读194次。一 java环境安装:1 安装JDK 参考链接地址:https://blog.csdn.net/qq_42815754/article/details/82968464注:有网情况下直接 yum 一键安装:yum -y list java(1)首先执行以下命令查看可安装的jdk版本(2)选择自己需要的jdk版本进行安装,比如这里安装1.8,执行以下命令:yum install -y java-1.8.0-openjdk-devel.x86_64(3)安装完之后,查看安装的jdk 版本,输入以下指令_linux的java主从策略是什么

ACM第四题_acm竞赛题 i 'm from mars-程序员宅基地

文章浏览阅读104次。定义int 类型,由while实现A,B的连续输入,输出A+B的值按Ctrl Z结束循环。#include&amp;lt;iostream&amp;gt;using namespace std;int main(){ int A,B; while(cin&amp;gt;&amp;gt;A&amp;gt;&amp;gt;B) { cout&amp;lt;&amp;lt;A+B&amp;lt;&_acm竞赛题 i 'm from mars

TextView.SetLinkMovementMethod后拦截所有点击事件的原因以及解决方法-程序员宅基地

文章浏览阅读5.2k次。在需要给TextView的某句话添加点击事件的时候,我们一般会使用ClickableSpan来进行富文本编辑。与此同时我们还需要配合 textView.setMovementMethod(LinkMovementMethod.getInstance());方法才能使点击处理生效。但与此同时还会有一个问题:如果我们给父布局添加一个点击事件,需要在点击非链接的时候触发(例如RectclerV..._linkmovementmethod

JAVA实现压缩解压文件_java 解压zip-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏31次。JAVA实现压缩解压文件_java 解压zip

JDK8 新特性-Map对key和value分别排序实现_java comparingbykey-程序员宅基地

文章浏览阅读1.3w次,点赞7次,收藏21次。在Java 8 中使用Stream 例子对一个 Map 进行按照keys或者values排序.1. 快速入门 在java 8中按照此步骤对map进行排序.将 Map 转换为 Stream 对其进行排序 Collect and return a new LinkedHashMap (保持顺序)Map result = map.entrySet().stream() .sort..._java comparingbykey

GDKOI2021普及Day1总结-程序员宅基地

文章浏览阅读497次。第一次参加GDKOI,考完感觉还可以,结果发现还是不行,有一些地方细节打错,有些失分严重,总结出以下几点:1.大模拟一定要注意,细节打挂就是没分,像T1就是一道大模拟题,马上切了,后面就没想着检查以下,导致有些地方挂掉了,用民间数据一测,才85分。2.十年OI一场空,不开longlonglong longlonglong见祖宗。今天的T2本来想用暴力水点分的,结果没想到longlong→intlong long\to intlonglong→int,40→040\to040→0。3.代码实现能力太差,_gdkoi

推荐文章

热门文章

相关标签