首页 > 编程语言 >unp - 客户/服务器程序设计范式

unp - 客户/服务器程序设计范式

时间:2023-08-30 19:23:05浏览次数:49  
标签:unp 范式 服务器程序 int 进程 nleft read sockfd buf

网络服务常见知识点

unp中以一个 echo 服务为例

被中断的系统调用

重试 accept
 while (true) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0 && errno == EINTR) {
        continue;
    }
    /* code */
}
重试 read write
{
again:
  while ((n = read(sockfd, ptr, BUFSIZ)) > 0)
    write(sockfd, ptr, n);
  if (n < 0 && errno == EINTR)  // EINTR
    goto again;
  else if (n < 0)
    err_sys("str_echo: read error");
}

ssize_t readn(int fd, void *buf, size_t count) { // 可使用 recv() 与 MSG_WAITALL 代替
  size_t nleft = count;
  ssize_t nread;
  char *bufp = (char *)buf;
  while (nleft > 0) {
    if ((nread = read(fd, bufp, nleft)) < 0) {
      if (errno == EINTR) { // 系统调用被捕获信号中断
        continue;
      }
      return -1;
    } else if (nread == 0) {
      return count - nleft;
    }
    bufp += nread;
    nleft -= nread;
  }
  return count;
}

ssize_t writen(int fd, const void *buf, size_t count) {
  size_t nleft = count;
  ssize_t nwrite;
  char *bufp = (char *)buf;
  while (nleft > 0) {
    if ((nwrite = write(fd, bufp, nleft)) < 0) {
      if (errno == EINTR) {
        continue;
      }
      return -1;
    } else if (nwrite == 0) {
      continue;
    }
    bufp += nwrite;
    nleft -= nwrite;
  }
  return count;
}

SIGCHLD 僵尸进程

子进程结束变成僵尸进程,并给父进程发送一个SIGHLD信号,该信号默认忽略,只有当父进程调用wait或waitpid后子进程资源才被回收。

一个进程中所有线程的信号处理操作相同,每个线程可以有独自的 sigmask,使用 sigaction 指定信号处理函数时可指定 sigmask。

void sigchld_handler(int sig) {
  // 此处不能单单使用 wait
  // 当多个SIGCHLD信号同时到达时信号处理函数只被调用一次
  // 未被处理的信号被忽略而不是排队
  // 使用循环反复处理僵尸进程
  // 使用 waitpid(-1, NULL, WHOHANG) 中-1表示所有子进程,WNOHANG表示当没有僵尸子进程时函数不阻塞
  while (waitpid(-1, NULL, WNOHANG) > 0) {
    /* code */
  }
}

{
  struct sigaction act;
  act.sa_handler = sigchld_handler;
  act.sa_flags = SA_RESTART; // SA_RESTART 指定syscall被中断后有系统恢复; SA_INTERRUPT 指定被打断后不恢复 不是每个版本都支持此宏
  sigemptyset(&act.sa_mask);
  sigaction(SIGCHLD, &act, nullptr /*old action*/);
}

accept 返回前连接中止

服务器调用 listen,客户端调用 connect,此时客户端发送一个 RST 包,服务端调用accept返回一个 EPROTO 错误,此时只要忽略并重试 accept 即可

服务器进程终止

// echo 服务客户端
while (fgets(buf, sizeof(buf), stdin) != NULL) {
  writen(sockfd, buf, strlen(buf)); // write to server
  if ((n = readn(sockfd, buf, sizeof(buf))) == 0) {
    printf("the other side has been closed.\n");
    exit(0);
  }
}

当服务端关闭并发送 FIN 时,客户端可能阻塞在fgets,只有当输入结束后调用read时才能得知对方已关闭。
客户端实际上在应对两个描述符——套接字和用户输入,它不能单独阻塞在这两个源的某一个上,而是应该通过 select epoll 阻塞在其中任何一个,一旦杀死服务器子进程,客户就会被告知收到FIN

使用 select 阻塞在任意一个
// 注意:select epoll 不能和stdio混用
// io复用只从read系统调用层面检测是否有数据可读,而不管stdio缓冲区内是否有数据未读出
void str_cli(int fp_in, int fp_out, int sockfd) {
  int maxfdp1;
  int stdineof{0};  // 是否已经读到文件尾
  fd_set rset;
  char buf[BUFSIZ];

  FD_ZERO(&rset);
  while (true) {
    if (stdineof == 0) {
      FD_SET(fp_in, &rset);
    }
    FD_SET(sockfd, &rset);
    maxfdp1 = max(fp_in, sockfd) + 1;
    select(maxfdp1, &rset, NULL, NULL, NULL);

    if (FD_ISSET(sockfd, &rset)) {
      if (read(sockfd, buf, BUFSIZ) == 0) {
        if (stdineof == 1) {  // 正常结束
          return;
        }
        printf("server terminated.\n");
        return;
      }
      write(fp_out, buf, strlen(buf)); // 输出
    }

    if (FD_ISSET(fp_in, &rset)) {
      if (read(fp_in, buf, BUFSIZ) == 0) { // 文件读到 EOF
        stdineof = 1;
        shutdown(sockfd, SHUT_WR);
        FD_CLR(fp_in, &rset);
        continue;
      }
      write(sockfd, buf, strlen(buf));
    }
  }
}

SIGPIPE 信号

客户端在读回任何数据前执行两次针对服务器的写操作,服务器子进程死亡后,第一次引发RST,第二次写时内核向该进程发送一个SIGPIPE信号。该信号默认终止进程,写操作会返回 EPIPE。

服务器关闭

使用 shutdown 函数可单方面关闭套接字,并指定读写

  1. 宕机,此时客户端将不断尝试,直至超时,返回 ETIMEOUT EHOSTUNREACH ENETUNREACH
  2. 崩溃后重启,客户端发送消息则会收到一个RST响应并返回一个 ECONNRESET 错误,客户应通过 SO_KEEPALIVE 等套接字选项保证用户不主动发数据也能检测出连接重置
  3. 服务器关机,unix关机时 init 进程发送SIGTERM信号给所有进程,等固定时间后发送 SIGKILL 信号强行终止

客户/服务器程序设计范式

 

标签:unp,范式,服务器程序,int,进程,nleft,read,sockfd,buf
From: https://www.cnblogs.com/zhh567/p/17606424.html

相关文章

  • 途牛科技与火山引擎数智平台合作 打造企业大数据系统“降本”新范式
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 近日,南京途牛科技有限公司与火山引擎数智平台(VeDI)的合作获得新进展:途牛大数据系统全面迁移至火山引擎开源大数据平台E-MapReduce。  作为国内专注休闲旅游的数字一体化旅游服务商......
  • 因为celcery项目而抛出的 not enough values to unpack (expected 3, got 0)解决方案
    python=36celery=226django=266在自己刚刚接触celery需要写定时任务的时候,按照大佬写的跑一遍的时候(https://blog.csdn.net/qq_36441027/article/details/123851915),发现自己跑的时候, 就会出现这么诡异的问题。解决办法:pipinstall eventlet 再去cmd里面执行cel......
  • 应用管理平台Walrus开源,构建软件交付新范式
    今日,数澈软件Seal(以下简称“Seal”)宣布正式开源Walrus,这是一款基于平台工程理念的应用管理平台,致力于解决应用交付领域的深切痛点。 借助Walrus将云原生的能力和最佳实践扩展到非容器化环境,并支持任意应用形态统一编排部署,降低使用基础设施的复杂度,为研发和运维团队提供易用......
  • 应用管理平台Walrus开源,构建软件交付新范式
    今日,数澈软件Seal(以下简称“Seal”)宣布正式开源Walrus,这是一款基于平台工程理念的应用管理平台,致力于解决应用交付领域的深切痛点。 借助Walrus将云原生的能力和最佳实践扩展到非容器化环境,并支持任意应用形态统一编排部署,降低使用基础设施的复杂度,为研发和运维团队提供易用......
  • 无涯教程-Perl - unpack函数
    描述此函数使用TEMPLATE中指定的格式解压缩二进制字符串STRING。基本上颠倒打包的操作,根据提供的格式返回打包值的列表。Youcanalsoprefixanyformatfieldwitha%<number>toindicatethatyouwanta16-bitchecksumofthevalueofSTRING,insteadofthevalue.......
  • 深入探究 Python 中的装饰器与函数式编程范式
    在Python的后端开发中,装饰器是一种强大的技术,而函数式编程范式则能够带来更具表现力和模块化的代码。本文将深入探讨Python中的装饰器和函数式编程,帮助你更好地理解和应用这些技术,提升代码质量和可维护性。装饰器的基本概念装饰器是Python中的一种高级技术,它允许你在不修改原函数代......
  • 七大常用编程范式!看看你知道几个?
    一、编程范式是什么?编程范式是程序设计的一种基本方法和规范,它代表了特定编程语言的独特风格和方法。作为一种策略,编程范式帮助程序员解决各种计算问题,其选择可以优化代码的可读性、可维护性和可扩展性。常见的编程范式包括面向对象、函数式和逻辑式等,每种范式都有其独特的理念和......
  • 软件测试|Docker Kill/Pause/Unpause命令详细使用指南
    简介Docker是一种流行的容器化平台,提供了各种命令和功能来管理和操作容器。本文将详细介绍Docker中的三个重要命令:kill、pause和unpause。我们将深入了解它们的作用、用法和示例,帮助您更好地理解和使用这些命令。什么是DockerKill/Pause/Unpause命令?Docker提供了几个与容器生命周......
  • 什么是软件设计领域的 stateless 编程范式
    在软件设计领域,stateless编程范式是一种设计模式,其中程序或对象在其生命周期中不保存任何状态。换句话说,一个stateless程序或对象的行为仅仅取决于它的输入,而不依赖于任何先前的交互或数据。让我们来详细了解一下stateless编程范式。在大多数情况下,当我们谈论stateless,我们......
  • 遇到的问题------------时间格式转化时java.text.ParseException: Unparseable date:
    -时间格式转化时java.text.ParseException:Unparseabledate:""异常把String time=2013-09-22用 privatefinalstaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddhh:mm:ss");simpleDateFormat.parse(time.trim()));转化时报错java.text.......