首页 > 其他分享 >非阻塞式 IO

非阻塞式 IO

时间:2023-08-06 09:11:52浏览次数:27  
标签:FILENO toiptr friptr 阻塞 FD IO sockfd

非阻塞式 IO

套接字默认是阻塞的,分为以下4类:

  1. 输入操作:read、readv、recv、recvfrom、fecvmsg,对于面向流的TCP可使用自定义的readn函数或MSG_WAITALL标志指定等到某个固定数目的数据可读为止,没有数据可读时非阻塞IO立即返回一个EWOULDBLOCK错误
  2. 输出操作:write、writev、send、sendto、sendmsg,将应用进程缓冲区数据复制到内核缓冲区,对于TCP没有足够空间写数据时非阻塞输出函数立即返回一个 EWOULDBLOCK,UDP没有真正的内核发送缓冲区但可能因为其他原因阻塞
  3. 接收外来连接 accept,无连接时立即返回 EWOULDBLOCK
  4. 发起外出连接,即TCP的 connect 函数。调用非阻塞 connect 后在三次握手完成前返回一个 EINPROGRESS 错误,当客户端与服务端位于同一机器时可能会直接返回成功

System V 返回 EAGAIN 错误;Berkeley 返回 EWOULDBLOCK 错误。POSIX 规定这两个都可以,多数提供将二者定义为相同的值

UNP中用 "非阻塞IO+select" 处理客户端socket
#include "unp.h"

void str_cli(FILE *fp, int sockfd) {
  int maxfdp1, val, stdineof;
  ssize_t n, nwritten;
  fd_set rset, wset;
  char to[MAXLINE], fr[MAXLINE];
  char *toiptr, *tooptr, *friptr, *froptr;

  // 将套接字、输入输出IO设置为非阻塞
  val = Fcntl(sockfd, F_GETFL, 0);
  Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
  val = Fcntl(STDIN_FILENO, F_GETFL, 0);
  Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
  val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
  Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);

  // 初始化缓冲区
  toiptr = tooptr = to;
  friptr = froptr = fr;
  stdineof = 0;

  maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
  for (;;) {
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    // 输出缓冲区还有空间用于读取标准输入,且输入数据没读完
    if (stdineof == 0 && toiptr < &to[MAXLINE]) FD_SET(STDIN_FILENO, &rset);
    // 输入缓冲区还有空间用于读取套接字
    if (friptr < &fr[MAXLINE]) FD_SET(sockfd, &rset);
    // 有数据要发给服务器
    if (tooptr != toiptr) FD_SET(sockfd, &wset);
    // 有数据要输出到标准输出
    if (froptr != friptr) FD_SET(STDOUT_FILENO, &wset);

    Select(maxfdp1, &rset, &wset, NULL, NULL);

    // 从 stdin 读取
    if (FD_ISSET(STDIN_FILENO, &rset)) {
      if ((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
        if (errno != EWOULDBLOCK) err_sys("read error on stdin");
      } else if (n == 0) { // 读取到 EOF
        stdineof = 1;
        if (tooptr == toiptr) Shutdown(sockfd, SHUT_WR);
      } else {
        toiptr += n;
        FD_SET(sockfd, &wset);
      }
    }
    // 从socket读
    if (FD_ISSET(sockfd, &rset)) {
      if ((n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
        if (errno != EWOULDBLOCK) err_sys("read error on socket");
      } else if (n == 0) {
        if (stdineof)
          return;
        else
          err_quit("str_cli: server terminated prematurely");
      } else {
        friptr += n;
        FD_SET(STDOUT_FILENO, &wset);
      }
    }
    // 向 stdout 写
    if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {
      if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
        if (errno != EWOULDBLOCK) err_sys("write error to stdout");
      } else {
        froptr += nwritten;
        if (froptr == friptr) froptr = friptr = fr;
      }
    }
    // 向 socket 写
    if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)) {
      if ((nwritten = write(sockfd, tooptr, n)) < 0) {
        if (errno != EWOULDBLOCK) err_sys("write error to socket");
      } else {
        tooptr += nwritten;
        if (tooptr == toiptr) {
          toiptr = tooptr = to;
          if (stdineof) Shutdown(sockfd, SHUT_WR);
        }
      }
    }
  }
}

以上代码效率很高,但是代码太过复杂,可使用fork简化代码

使用 fork 实现双进程分别处理读写
 #include "unp.h"
void str_cli(FILE *fp, int sockfd) {
  pid_t pid;
  char sendline[MAXLINE], recvline[MAXLINE];

  if ((pid = Fork()) == 0) {
    while (Readline(sockfd, recvline, MAXLINE) > 0) Fputs(recvline, stdout);
    kill(getppid(), SIGTERM); // 通知父进程不再有数据
    exit(0);
  }

  while (Fgets(sendline, MAXLINE, fp) != NULL)
    Writen(sockfd, sendline, strlen(sendline));

  Shutdown(sockfd, SHUT_WR); // 父进程只负责向socket写,所以此处只能通过 shtdown 关闭写端
  pause(); // 等待子进程发送 SIGTERM 信号
  return;
}

不同版本性能:

非阻塞 connect

非阻塞connect返回 EINPROGRESS 后可使用select检测这个连接成功或失败

  1. 节约时间
  2. 同时建立多个连接
  3. 可给select指定时间限制

注意:

  1. 如果双方在一台主机,调用connect时连接立即建立
  2. 在调用select前可能正确建立连接,套接字处于可读写状态
  3. 连接成功建立时,描述符可写;出错时描述符可读可写

判断select返回套接字是否成功:

  1. 使用 getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0 检查套接字是否存在待处理错误
  2. 调用 getpeername,返回 ENOTCONN 表示失败返回,可通过 getsockopt 获取错误
  3. 以值未0的长度参数调用read,返回失败则connect失败,errno给出失败原因,如果连接成功建立则read返回0
  4. 再调用 connect 一次,如若返回 EISCONN 表示套接字已连接

一个阻塞的connect调用在等待三次握手时被中断,如捕获信号,这时应把它当作非阻塞IO调用select处理

非阻塞 accept

IO复用select 必须搭配非阻塞accept以防止“定时问题”

客户端使用connect建立连接后发送一个 RST 断开连接。
服务器的select调用返回,期间建立连接并加入队列,在接收RST后将连接移出队列。
使用accept接收连接时,因为连接已断开所以阻塞式accept会阻塞在这里,直到下一个客户端连接上来。

 

标签:FILENO,toiptr,friptr,阻塞,FD,IO,sockfd
From: https://www.cnblogs.com/zhh567/p/17609068.html

相关文章

  • CodeForces 1856E1 PermuTree (easy version)
    洛谷传送门CF传送门考虑局部贪心,假设我们现在在\(u\),我们希望\(u\)不同子树中的\((v,w),a_v<a_u<a_w\)的对数尽量多。我们实际上只关心子树内\(a_u\)的相对大小关系,不关心它们具体是什么。如果\(u\)只有两个儿子\(v,w\),我们可以让\(v\)子树内的\(a\)全部......
  • CodeForces 1856E2 PermuTree (hard version)
    洛谷传送门CF传送门考虑局部贪心,假设我们现在在\(u\),我们希望\(u\)不同子树中的\((v,w),a_v<a_u<a_w\)的对数尽量多。我们实际上只关心子树内\(a_u\)的相对大小关系,不关心它们具体是什么。如果\(u\)只有两个儿子\(v,w\),我们可以让\(v\)子树内的\(a\)全部......
  • 【反思】洛谷8月月赛 Div.2 & RiOI Round 2 赛后反思
    RiOIR2赛后反思赛时开了一个T1,但是\(0pts\),然后就跑去跟人对线然后复盘(主要是我的锅,我忘记对线怎么开始的了)到了吃饭(雾不过本来我也不会做,不能怪人家赛后是shenshen教我T1+看的若归老师的反思捏推歌:歌爱ユキ&稲葉曇《キミに回帰缐》(希望没打错是我的错吗铅笔......
  • Unity 编辑器选择器工具类Selection 常用函数和用法
    Unity编辑器选择器工具类Selection常用函数和用法点击封面跳转下载页面简介在Unity中,Selection类是一个非常有用的工具类,它提供了许多函数和属性,用于操作和管理编辑器中的选择对象。本文将介绍Selection类的常用函数和用法,并提供相应的示例代码。静态属性1.activeConte......
  • session与token
    1.base64Base64是一种用于将二进制数据编码为文本的编码方案,它使用ASCII字符串格式来表示二进制数据。Base64常用于在只能处理ASCII字符的系统上传输数据,如电子邮件系统、web服务器以及编码媒体文件。在Base64中,每个字符代表6个数据位,因此可以将3个字节的二进制数据编码为4......
  • Windows 在VMware Workstation 17上安装macOS Sonoma 虚拟机
    前言macOS在一些方面做得比Windows更好。但是,macOS只能安装在苹果电脑上,虽然黑苹果能满足部分人的要求,但依然有大部分机型无法安装黑苹果。而在虚拟机上,大部分用户都可以用上macOS,虽然流畅度等一般都没有白苹果、黑苹果好,但用来体验和日常使用是问题不大的。如果用来开发或编程之类......
  • 【刷题笔记】6. ZigZag Conversion
    题目Thestring "PAYPALISHIRING" iswritteninazigzagpatternonagivennumberofrowslikethis:(youmaywanttodisplaythispatterninafixedfontforbetterlegibility)PAHNAPLSIIGYIRAndthenreadlinebyline: &q......
  • 对IoC容器和Bean的学习笔记
    WhatWeMeanby"Spring"Theterm"Spring"meansdifferentthingsindifferentcontexts.ItcanbeusedtorefertotheSpringFrameworkprojectitself,whichiswhereitallstarted.Overtime,otherSpringprojectshavebeenbuilton......
  • 论文解读(Moka‑ADA)《Moka‑ADA: adversarial domain adaptation with model‑orient
     Note:[wechat:Y466551|可加勿骚扰,付费咨询]论文信息论文标题:Moka‑ADA:adversarialdomainadaptation withmodel‑orientedknowledgeadaptation forcross‑domainsentimentanalysis论文作者:MaoyuanZhangXiangLiFeiWu论文来源:2023aRxiv论文地址:download 论......
  • 关于union和合并单元格的区别
    选择与单元格A1中的值相等的所有单元格SubselectSameCells()DimgoalRangeAsRange,indexCellAsRangeSetgoalRange=Range("A1")ForEachindexCellInRange("A1:B5")IfindexCell.Value=Range("A1").ValueThen......