网络socket多进程编程之服务器

ぃ、小莉子 提交于 2020-03-10 18:40:12


在写多进程编程之前,我们先了解一下fork系统调用。

fork系统调用

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

②Linux下有两个基本的系统调用可以用于创建子进程:fork()和vfork()。在我们编程的过程中,一个函数调用只有一次返回(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()系统调用会创建一个新的子进程,这个子进程是父进程的一个副本。这也意味着,系统在创建新的子进程成功后,会将父进程的文本段、数据段、堆栈都复制一份给子进程,但子进程有自己独立的空间,子进程对这些内存的修改并不会影响父进程空间的相应内存。这时系统中出现两个基本完全相同的进程(父、子进程),这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。如果需要确保让父进程或子进程先执行,则需要程序员在代码中通过进程间通信的机制来自己实现。我们可以看一下下面的图
在这里插入图片描述

网络socket多进程编程之服务器代码示例

在Linux下多进程编程之后,我们就可以使用多进程编程写socket服务器了其流程图和代码如下:在这里插入图片描述

/*********************************************************************************
 *      Copyright:  (C) 2020 makun<1394987689@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  djc_socket_server.c
 *    Description:  This file fork_socket_server
 *                 
 *        Version:  1.0.0(2020年02月28日)
 *         Author:  makun <1394987689@qq.com>
 *      ChangeLog:  1, Release initial version on "2020年02月28日 13时59分51秒"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <stdlib.h>


#define MSG_STR "Hello lingyun  iot stduio 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                 sockfd = -1;
    int                 clifd;
    struct sockaddr_in  servaddr;
    struct sockaddr_in  cliaddr;
    socklen_t           len;
    int                 port = 0;
    int                 ch;
    int                 rv = -1;
    int                 on = 1;
    pid_t               pid;
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;
  }

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

    setsockopt(sockfd, 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(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    if(rv < 0)
    {
        printf("socket[%d] bind to port[%d] failure:%s\n",sockfd,port,strerror(errno));
        return -2;
    }
      
    listen(sockfd,13);
    printf("start to listen on port [%d]\n", port);
    while(1)
    {
        printf("start accept new client incoming..\n");
        clifd=accept(sockfd,(struct sockaddr *)&cliaddr,&len);
        if(clifd < 0)
    {
        printf("accept client failure:%s\n", strerror(errno));
        continue;
    }

    printf("accept new clienr[%s:%d] successfully\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
    pid=fork();
    if( pid < 0 )
    {
        printf("fork creat child pricess failure:%s\n",strerror(errno));
        continue;
    }
    else if( pid > 0)
    {
        close(clifd);
        continue;
    }
    else if( pid ==0 )
    {
        char        buf[1024];

        close(sockfd);
        printf("Chid process start to commuicate with socket client..\n");
        memset(buf , 0, sizeof(buf));
        rv=read(clifd,buf,sizeof(buf));
        if(rv < 0)
        {
             printf("Read data from client sockfd[%d] failure:%s\n",clifd,strerror(errno));
        close(clifd);
        exit(0);
        }
        else if( rv == 0)
        {
             printf("socket[%d]get disconnected\n", clifd);
             close(clifd);
             exit(0);
        }
        else if( rv > 0)
        {
            printf("read %d bytss data from server:%s\n",rv,buf);
        }
        rv=write(clifd, MSG_STR, strlen(MSG_STR));
       if(rv < 0)
       {
           printf("Write to client by sockfd[%d] failure: %s\n", sockfd,
           strerror(errno));
           close(clifd);
           exit(0);
       }
       sleep(1);
       printf("close client socket[%d] and child process exit\n", clifd);
       close(clifd);
       exit(0);
    }
}
close(sockfd);

return 0;
}

linux下编程结果如下:
在这里插入图片描述
在这里插入图片描述
结果分析:在该程序中,父进程accept()接收到新的连接后,就调用fork()系统调用来创建子进程来处理与客户端的通信。因为子进程会继承父进程处于listen状态的socket 文件描述符(sockfd),也会继承父进程accept()返回的客户端socket 文件描述符(clifd),但子进程只处理与客户端的通信,这时他会将父进程的listen的文件描述符sockfd关闭;同样父进程只处理监听的事件,所以会将clifd关闭。此时父子进程同时运行完成不同的任务,子进程只负责跟已经建立的客户端通信,而父进程只用来监听到来的socket客户端连接。所以当有新的客户端到来时,父进程就有机会来处理新的客户连接请求了,同时每来一个客户端都会创建一个子进程为其服务。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!