• c++ 网络编程(二) linux 下多进程socket通信 多个客户端与单个服务端交互代码实现回声服务器


    原文作者:aircraft

    原文链接:https://www.cnblogs.com/DOMLX/p/9612820.html

    本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。c++ 网络编程课设入门超详细教程 ---目录

    锲子-- 预备知识优雅的关闭套接字连接:

    基于TCP的半关闭

    TCP中的断开连接过程比建立连接过程更重要,因为建立连接过程一般不会出现什么大的变数,但断开过程就有可能发生预想不到的情况,因此要准确的掌控。

    • 单方面断开连接带来的问题
      Linux的close函数和Windows的closesocket函数是完全断开连接。完全断开是指无法传输数据也不能接收数据。因此,一方这样直接断开连接就显得不太优雅了。如:主机A发送完最后的数据后,调用close函数单方断开了连接,那么最终,由主机B传输的,主机A必须接收的确认数据也销毁了(四次握手)。
      为了解决这类问题,我们一般采用半关闭的方法,这是指可以传输数据但无法接收,或可以接收数据但无法传输。就是只关闭流的一半。

    • 套接字和流(Stream)
      两台主机通过套接字建立连接后进入可交换数据的状态,我们把这种状态看作一种流。如流水一样,水朝一个方向流动,同样,在套接字的流中,数据也只能向一个方向移动。示例图如下:
      这里写图片描述
      一旦两台主机建立了套接字连接,每个主机就会拥有单独的输入流和输出流。如图,其中一个主机的输入流与另一主机的输出流相连,而输出流则与另一主机的输入流相连。我们这章讲的优雅断开连接其实就是断开其中1个流,而非同时断开两个流。

    • 针对优雅断开的shutdown函数

    int shutdown(int sock, int howto);
    sock:需要断开的套接字文件描述符
    howto:断开连接的方式,有三种:SHUT_RD:断开输入流,SHUT_WR:断开输出流,SHUT_RDWR:同时断开

    LINUX下:

    一.服务端代码

    下面用了多个close来关闭文件描述符,可能有的小伙伴会有疑惑。。。。我就说一句,创建进程的时候会把父进程的资源都复制 一份,而你这个子进程只需要保留自己需要处理的资源,其他的自然要关闭掉,

    不然父亲一个儿子一个 待会打起来怎么办  嘿嘿

    注意了:就像进程间的通信需要属于操作系统的资源管道来进行,套接字也属于操作系统,所以创建新进程也还是只有原来的那个,复制的资源只不过是文件描述符而已,我们关闭的也是这个文件描述符

    //基于多进程的并发服务器实现
    //注:子进程会复制父进程拥有的所有资源
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <signal.h>
    #include <sys/wait.h>
     
    #define BUF_SIZE 30
     
    void error_handling(char *message);
    void read_childproc(int sig);
     
    int main(int argc, char *argv[])
    {
      //定义TCP连接变量
      int serv_sock;
      int clnt_sock;
      struct sockaddr_in serv_addr;
      struct sockaddr_in clnt_addr;
      socklen_t clnt_addr_size;
      int str_len;
      char buf[BUF_SIZE];
      //定义信号处理变量
      pid_t pid;
      struct sigaction act;
      int state;
      
      if(argc!=2)
      {
        exit(1);
      }
      //配置信号处理函数
      act.sa_handler=read_childproc;
      sigemptyset(&act.sa_mask);
      act.sa_flags=0;
      sigaction(SIGCHLD, &act, 0);
      
      //TCP套接字配置
      serv_sock=socket(PF_INET, SOCK_STREAM, 0);
      if(serv_sock == -1)
        error_handling("socket error!");
     
      memset(&serv_addr, 0, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;    //IPV4协议族
      serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    //主机字节序(host)转换成网络字节序(net)(大端序)
      serv_addr.sin_port = htons(atoi(argv[1]));    //端口号
      if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind error");
     
      if(listen(serv_sock, 5) == -1)
        error_handling("listen error");
      
      while(1)
      {
        clnt_addr_size = sizeof(clnt_addr);
        clnt_sock=accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
        if(clnt_sock == -1)
          continue;
        else
          puts("new client connected...");
        
        pid=fork();    //创建新进程
        if(pid==-1)
        {
          close(clnt_sock);
          continue;
        }
        if(pid==0)    //子进程运行区域
        {
          close(serv_sock);    //在子进程中要关闭服务器套接字文件描述符
          while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0)
        write(clnt_sock, buf, str_len);
          
          close(clnt_sock);//执行完关闭自己的文件描述符
          puts("client disconnected...");
          return 0;
        }
        else    //父进程运行区域
        {
          //调用fork函数后,要将无关的套接字文件描述符关闭掉
          close(clnt_sock);
        }
      }
      
      close(serv_sock);
      return 0;
    }
     
    void error_handling(char *message)
    {
      fputs(message, stderr);
      fputc('
    ', stderr);
      exit(1);
    }
     
    //一旦有子进程结束就调用这个函数
    void read_childproc(int sig)
    {
      int status;
      pid_t id=waitpid(-1, &status, WNOHANG);//等待子进程终止
      if(WIFEXITED(status))
      {
        printf("remove proc id: %d 
    ", id);
        printf("child send: %d 
    ", WEXITSTATUS(status));
      }
    }

    二.客户端代码

    这里说一下这里用多进程分割I/O(输入/输出),是为了代码的分割提高程序优化,在输入数据的时候不需要考虑输出,在一个地方不用写两个地方的代码,虽然代码可能变多了,但是程序确实优化了,老经验的程序员就能体会到了

    然后为什么write_routine里还要调用shutdown给服务端传输EOF,MAIN函数最后不是有close可以向服务端发送吗???  这是因为我们创建了子进程,没有办法通过一次调用close传递EOF,不然会出大问题的!!  所以自己在子进程里手工调用shutdown发送EOF,告诉服务端:“嘿哥们,我差不多要凉凉了      下辈子有缘再见吧QAQ”  哈哈哈哈哈哈哈

    //分割IO实现分割数据的收发过程
    //父进程负责接收,子进程负责发送
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
     
    #define BUF_SIZE 30
     
    void error_handling(char *message);
    void read_routine(int sock, char *buf);
    void write_routine(int sock, char *buf);
     
    int main(int argc, char *argv[])
    {
      int sock;
      pid_t pid;
      struct sockaddr_in serv_addr;
      int str_len;
      char buf[BUF_SIZE];
      
      if(argc!=3)
      {
        exit(1);
      }
      
      sock=socket(PF_INET, SOCK_STREAM, 0);
      if(sock == -1)
        error_handling("socket error!");
      
      memset(&serv_addr, 0, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
      serv_addr.sin_port = htons(atoi(argv[2]));
     
      if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("connect error");
      else
        puts("Connected.....");
      
      pid=fork();
      if(pid==0)    //子进程写
        write_routine(sock, buf);
      else    //父进程读
        read_routine(sock, buf);
      
      close(sock);
      return 0;
    }
     
    void error_handling(char *message)
    {
      fputs(message, stderr);
      fputc('
    ', stderr);
      exit(1);
    }
     
    //这里实现读入有关代码
    void read_routine(int sock, char* buf)
    {
      while(1)
      {
        int str_len=read(sock, buf, BUF_SIZE);
        if(str_len==0)
          return;    //接受到EOF结束符时返回
        
        buf[str_len]=0;
        printf("message from server: %s 
    ", buf);
      }
    }
     
    //这里负责实现输出有关代码
    void write_routine(int sock, char* buf)
    {
      while(1)
      {
        fgets(buf, BUF_SIZE, stdin);
        if(!strcmp(buf, "q
    ") || !strcmp(buf, "Q
    "))
        {
          shutdown(sock, SHUT_WR);
          return;
        }
        write(sock, buf, strlen(buf));
      }
    }

     同时多进程服务端也是有缺点的,每创建一个进程就代表大量的运算与内存空间占用,相互进程数据交换也很麻烦。。。那么怎么解决呢,在我后面的博客也许会给出答案-----hhhhhhh

    最后说一句啦。本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。c++ 网络编程课设入门超详细教程 ---目录

    参考书籍《TCP/IP网络编程---尹圣雨》

    若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识

  • 相关阅读:
    PHP 快递单号查询api接口源码指导
    电商平台适用基础快递查询api接口对接demo解决方案
    智能物流查询api接口demo(php示例)
    解决在TP5中无法使用快递鸟查询API接口方案
    解析快递鸟在线预约取件API接口对接编码
    快递鸟批量打印电子面单接口及控件安装
    「note」原根照抄
    「atcoder
    Solution -「NOI 2021」轻重边
    Solution Set -「ARC 124」
  • 原文地址:https://www.cnblogs.com/DOMLX/p/9612820.html
Copyright © 2020-2023  润新知