SlideShare a Scribd company logo
I/O Multiplexing: select and poll
   •Introduction
   •I/O models
   •select function
   •Rewrite str_cli function
   •Supporting batch input with shutdown function
   •Rewrite concurrent TCP echo server with select
   •pselect function: avoiding signal loss in race condition
   •poll function: polling more specific conditions than
    select
   •Rewrite concurrent TCP echo server with poll
                                                                                        1
Some materials in these slides are taken from Prof. Ying-Dar Lin with his permission of usage
Introduction
•I/O multiplexing: to be notified, by kernel, if one or
 more I/O conditions are ready.
•Scenarios in networking applications:
  –a client handling multiple descriptors (stdio/socket)
  –a client handling multiple sockets
  –a TCP server handling a listening socket and its
    connected sockets (兩件事都自己來)
  –a server handling both TCP and UDP
  –a server handling multiple services and protocols
                                                      2
I/O動作如何進行?
•一般process無法直接對I/O裝置下命令,必須
 透過system call請求kernel幫忙進行I/O動作
•kernel會對每個I/O裝置維護一個buffer
               wait                   copy
  I/O Device          Kernel’buffer
                            s                Process


•對輸入而言,等待(wait)資料輸入至buffer需要
 時間,從buffer搬移(copy)資料也需要時間。
•根據等待模式不同,I/O動作可分為五種模式
                                                       3
Five I/O Models
•blocking I/O: blocked all the way
•nonblocking I/O: if no data in buffer, immediate
 returns EWOULDBLOCK
•I/O multiplexing (select and poll): blocked
 separately in wait and copy
•signal driven I/O (SIGIO): nonblocked in wait
 but blocked in copy (signaled when I/O can be
 initiated)
•asynchronous I/O (aio_): nonblocked all the way
 (signaled when I/O is complete)
                                                4
Comparison of Five I/O Models
blocking nonblocking I/O multiplexing signal-driven            asynchronous
initiate   check          check          establish                initiate




                                                                                  wait for data
           check                          SIGIO                   specify




                                  blocked
           check                          handler              signal/handler
           check
           check
           check           ready        notification
    blocked




                          initiate        initiate




                                                                                  copy data
                 blocked




                                                    blocked
                                  blocked



complete      complete          complete         complete      notification

              synchronous I/O               asynchronous I/O
                                                                              5
I/O Multiplexing: 使用select
•本章要做的是I/O Multiplexing(第三種I/O
 model): 使用select system call
•select要求kernel測試某裝置是否滿足
 我們設定的條件。若滿足則return,否則
 等待至此條件滿足時為止(select會被
 block)
•select呼叫可以指定等待的時間上限
•select呼叫可以指定測試多個I/O裝置
                             6
對輸入裝置使用select
•對輸入裝置而言,可以使用select要求
 kernel測試某裝置是否ready for reading
•當此裝置的buffer中已有相當數量(可設
 定)的輸入資料時,此裝置即是ready for
 reading
•select return後,由我們的程式自行呼
 叫其它的system call將buffer中的資料搬
 回來。
                              7
對輸出裝置使用select
•對輸出裝置而言,可以使用select要求
 kernel測試某裝置是否ready for writing
•當此裝置的buffer中已有相當空間(可設
 定)可放置輸出資料時,此裝置即是ready
 for writing
•select return後,由我們的程式自行呼
 叫其它的system call將欲輸出資料搬入
 buffer中。
                              8
select Function
 #include <sys/select.h>
 #include <sys/time.h>
 int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
             const struct timeval *timeout);
           returns: positive count of ready descriptors, 0 on timeout, -1 on error

 struct timeval {          (null: wait forever; 0: do not wait)
          long tv_sec; /*second */
          long tv_usec; /* microsecond */
 };
readset, writeset, and exceptset specify the descriptors that
we want the kernel to test for reading, writing, and
exception conditions, respectively.
maxfdp1 is the maximum descriptor to be tested plus one.
                                                                                9
select Fucntion
 •We can call select and tell the kernel to
  return only when                       Buffer
     –any descriptor in {1, 4, 5} is ready for 中已有
                                                  空間
      reading (buffer中已有資料)
     –any descriptor in {2, 7} is ready for writing
OR
     –any descriptor in {1, 4} has an exception
      condition pending
     –after 10.2 seconds have elapsed            裝置有例
                                               外狀況
                                                 10
Specifying Descriptor Values
• We need to declare variables of data type fd_set and
  use macros to manipulate these variables.
fd_set --- implementation dependent
           four macros: void FD_ZERO(fd_set *fdset);
                         void FD_SET(int fd, fd_set *fdset);
                         void FD_CLR(int fd, fd_set *fdset);
                         int FD_ISSET(int fd, fd_set *fdset);

fd_set rset;
                                           maxfdp1 = 6
FD_ZERO(&rset);
FD_SET(1, &rset);           Turn on bits for
FD_SET(4, &rset);           descriptors 1, 4, and 5
FD_SET(5, &rset);                                               11
Socket Ready Conditions for select
Condition                 readable?      writeable?     Exception?

enough data to read       x
read-half closed          x
new connection ready      x
writing space available                  x
write-half closed                        x
pending error             x              x
TCP out-of-band data                                    x

Low-water mark (enough data/space to read/write in
       socket receive/send buffer): default is 1/2048, may be set by
       SO_RCVLOWAT/SO_SNDLOWAT socket option
Maximum number of descriptors for select?
       Redefine FD_SETSIZE and recompile kernel                    12
Low-Water Mark (低水位)
•對socket receive buffer而言
  –如收到data量不足low-water mark, socket is
   not ready for reading
  –Default = 1 byte
•對socket send buffer而言
  –如可用空間(available space)不足low-water
   mark, socket is not ready for writing
  –Default = 2048 byte
                                       13
用select改寫str_cli
•上一章的str_cli用兩個system calls分別
 取得input
 –fgets用於stdin的user input
                            均為Blocking I/O
 –readline用於socket input
•這樣沒有辦法同時等待兩個inputs。因此
 當client等在fgets時,無法同時取得
 socket input進來的資料
•本章改用select來同時等待兩個inputs
                                             14
Ch. 5
                 兩個版本的比較
                                                        Ch. 6

         呼叫fgets從stdin                      呼叫select同時等
         讀取使用者的輸入                           待socket及stdin
                               socket is
                               readable
          yes                                       stdin is
return          EOF?
                                                    readable
                       no   呼叫readline從
                            socket讀取資料
         呼叫writen將輸入
         資料輸出至socket                                呼叫fgets從stdin
                            呼叫fputs將讀取的             讀取使用者的輸入
                            資料輸出至stdout
         呼叫readline從                                 yes
         socket讀取資料                        return          EOF?
                                                                  no
         呼叫fputs將讀取的
         資料輸出至stdout                                呼叫writen將輸入
                                                    資料輸出至socket
                                                             15
Rewrite str_cli Function with select

                                client
                                           select for readability
    data or EOF         stdin              on either stdin or socket
                                socket
                                               用select同時等待stdio及
                        error            EOF   socket input. 當任一裝置
                                               ready for reading時select
                                               會return
                  TCP

                                               當select return時怎知道是哪
                                               些裝置ready for reading呢?
              RST data FIN
用FD_ISSET測試傳回值
                    readset, writeset, 和 exceptset是雙向變數
可得知那些裝置是ready       (value-result argemnets)           16
Rewrite str_cli Function with select
#include     "unp.h"                 select/strcliselect01.c

void
str_cli(FILE *fp, int sockfd)
{
     int           maxfdp1;
     fd_set        rset;
     char          sendline[MAXLINE], recvline[MAXLINE];

     FD_ZERO(&rset);         傳回檔案指標fp
                             的descriptor no
     for ( ; ; ) {
    放在 FD_SET(fileno(fp), &rset);
                                             只測試read
  loop內 FD_SET(sockfd, &rset);
   Why? maxfdp1 = max(fileno(fp), sockfd) + 1;
            Select(maxfdp1, &rset, NULL, NULL, NULL); 17
用FD_ISSET測試
                      裝置是否ready                   select/strcliselect01.c
        if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
              if (Readline(sockfd, recvline, MAXLINE) == 0)
                    err_quit("str_cli: server terminated prematurely");
              Fputs(recvline, stdout);
        }                用FD_ISSET測試
                         裝置是否ready
        if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
              if (Fgets(sendline, MAXLINE, fp) == NULL)
                    return;     /* all done */
              Writen(sockfd, sendline, strlen(sendline));
        }                                                   User按了
    }                                                       Ctrl+D
}
                                                                     18
使用select函數常犯的兩個錯誤

‧忘記maxfdp1是descriptor的最大值加1
‧忘記readset, writeset, 和 exceptset是雙向
 變數
  –select return時會改變它們的值
  –因此再次呼叫select時別忘了要重新設定
   這些變數的內容



                                       19
Redirecting Input on UNIX
            Systems
•Standard I/O - keyboard and screen
•Input Redirection symbol (<)
  –for UNIX and DOS
  –Example:
        sum < input
  –Rather than inputting values by hand, read them
   from a file


                                                     20
Stop-and-Wait (Interactive) Mode

   •原來版本的str_cli強制輪流處理stdin輸入
    與socket輸入 提早輸入並不   不管多晚輸
                            會提早處理              入都會等待
          keyin     keyin            keyin
 client
                  socket    socket           socket
                      in        in               in



server
                                                      time
                                                             21
Batch Mode
   •在新版str_cli使用input redirection
                                                          將stdin重導
                                                          至file1.txt
           tcpcli04 206.62.226.35 < file1.txt
                                                                   就算不
          lines                                  檔案最後的EOF          用輸入
                             EOF
                   …        …                    會使fgets傳回         重導向
 client                                          NULL,str_cli      也有此
                                                  會因此return        問題,
    不需                     …                                       只是較
    等待                                                             不嚴重
                                                 main會close掉
                                                TCP connection,
server                                          導致server尚未回
                                   time
                                                  傳的資料遺失               22
Solution: Use Shutdown Instead
            of Close
•In str_cli, close write-half of TCP
 connection, by shutdown, while leaving
 read-half open.
              先關client至server方向的connection
              server至client方向的connection暫時不要關



         等到server的資料全部送回來
         後再關掉server至client方向的
               connection
                                            23
Function shutdown
#include <sys/socket.h>
int shutdown(int sockfd, int howto); returns: 0 if OK, -1 on error
howto: SHUT_RD, SHUT_WR, SHUT_RDWR


 •initiates TCP normal termination
  regardless of descriptor’reference
                           s
  count
 •selectively closes one direction of the
  connection (SHUT_RD or SHUT_WR)
                                                               24
Rewrite str_cli with select and shutdown
 #include    "unp.h"             select/strcliselect02.c

 void                                判斷client至server方向連
 str_cli(FILE *fp, int sockfd)       結是否已斷的旗標變數
 {
      int           maxfdp1, stdineof;
      fd_set        rset;
      char          sendline[MAXLINE], recvline[MAXLINE];

     stdineof = 0;
     FD_ZERO(&rset);
                                   當client至server方向連結
     for ( ; ; ) {
                                   未斷時才要test stdin
            if (stdineof == 0)
                   FD_SET(fileno(fp), &rset);
            FD_SET(sockfd, &rset);
            maxfdp1 = max(fileno(fp), sockfd) + 1;
            Select(maxfdp1, &rset, NULL, NULL, NULL);       25
select/strcliselect02.c
           if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
                 if (Readline(sockfd, recvline, MAXLINE) == 0) {
    client至server if (stdineof == 1)
    方向連結已斷                  return;      /* normal termination */
                       else
                            err_quit("str_cli: server terminated prematurely");
                  }
                  Fputs(recvline, stdout);
            }
            if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
                  if (Fgets(sendline, MAXLINE, fp) == NULL) {
                         stdineof = 1;
                         Shutdown(sockfd, SHUT_WR);            /* send FIN */
                         FD_CLR(fileno(fp), &rset);          只斷client至
                         continue;                           server方向連結
                  }
                  Writen(sockfd, sendline, strlen(sendline));
            }
       }
}                                                                             26
Concurrent TCP Echo Server with select

 •A single server process using select to handle
  any number of clients (不fork child process)
 •Need to keep track of the clients by client[ ]
  (client descriptor array) and rset (read descriptor set)
                                                 listening socket
              client[ ]                                    terminated client
            [0] -1 maxi
                                                                  existing client
            [1] 5                 stdin/stdout/stderr
            [2] -1      not
                        used        fd0 fd1 fd2 fd3 fd4 fd5
                               rset 0 0      0 1     0 1
[FD_SETSIZE-1]   -1
                                           maxfd +1 = 6
   儲存已open socket的descriptor                   用於呼叫select                   27
Rewrite Concurrent TCP Echo Server with select

Initialization
      #include       "unp.h"
                                            tcpcliserv/tcpservselect01.c
      int main(int argc, char **argv)
      {
           int                     i, maxi, maxfd, listenfd, connfd, sockfd;
           int                     nready, client[FD_SETSIZE];
           ssize_t                 n;
           fd_set                  rset, allset;
           char                    line[MAXLINE];
           socklen_t               clilen;
           struct sockaddr_in      cliaddr, servaddr;

           listenfd = Socket(AF_INET, SOCK_STREAM, 0);

           bzero(&servaddr, sizeof(servaddr));
           servaddr.sin_family   = AF_INET;
           servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
           servaddr.sin_port    = htons(SERV_PORT);                            28
tcpcliserv/tcpservselect01.c
Initialization (cont.)
           Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

           Listen(listenfd, LISTENQ);

           maxfd = listenfd;/* initialize */
           maxi = -1;/* index into client[] array */
           for (i = 0; i < FD_SETSIZE; i++)
                client[i] = -1;/* -1 indicates available entry */
           FD_ZERO(&allset);
           FD_SET(listenfd, &allset);

                                 測試listening socket是
                                 否ready for reading
                                                                         29
Loop
                                                             tcpcliserv/tcpservselect01.c
for ( ; ; ) { select的傳回值是ready的裝置數目
             rset = allset;          /* structure assignment */
             nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
     新       if (FD_ISSET(listenfd, &rset)) {          /* new client connection */
       連           clilen = sizeof(cliaddr);
         結         connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);          不會block
                   for (i = 0; i < FD_SETSIZE; i++)
                          if (client[i] < 0) {
  放入第一個                          client[i] = connfd; /* save descriptor */
  找到的空格                          break;
                          }
                   if (i == FD_SETSIZE)                         加進select要test
                          err_quit("too many clients");         的descriptor set
                    FD_SET(connfd, &allset);           /* add new descriptor to set */
如果沒有其 if (connfd > maxfd)
它ready的                   maxfd = connfd;                /* for select */
descriptor if (i > maxi)
                          maxi = i;                        /* max index in client[] array */
                   if (--nready <= 0)
                          continue;                         /* no more readable descriptors */
             }                                                                             30
Loop (cont.)                                         tcpcliserv/tcpservselect01.c

            for (i = 0; i <= maxi; i++) { /* check all clients for data */
                   if ( (sockfd = client[i]) < 0)
                          continue;                   跳過沒有放descriptor的空格
                   if (FD_ISSET(sockfd, &rset)) {
             已            if ( (n = Readline(sockfd, line, MAXLINE)) == 0) {
               連                       /* connection closed by client */
                 結               Close(sockfd);
                                 FD_CLR(sockfd, &allset);            從要test的
                                 client[i] = -1;
                                                      變空格            descriptor set中
                          } else
                                                                     去除
                                 Writen(sockfd, line, n);
                          if (--nready <= 0)
                                 break;                   /* no more readable descriptors */
                   }
        }
                                       如果沒有其它ready的
    }
                                       descriptor,可以提早離開loop
}
                                                                                      31
前頁程式的潛在問題
•select 在 socket 的 input buffer 有資
 料可讀時即會return 此 socket 已 ready
 for reading
•Readline 被設計成要讀到以換行字元
 結束的一列文字(或是socket被結束掉)才
 會return
•有心人士可能利用這項弱點進行攻擊

                                 32
Denial of Service Attacks

•問題出在Server程式可能block在Readline中
•Attack scenario:
  –a malicious client sends 1 byte of data (other
   than a newline) and sleep
  –server hangs until the malicious client either
   sends a newline or terminates
  –(server 要讀到 newline, Readline 才能 return)

                                                    33
Solutions to the Attack
•When a server is handling multiple
 clients the server can never block in a
 function call related to a single client

•Possible solutions:
  –Use nonblocking I/O (Ch. 15)
  –separate thread/process for each client
  –timeout on I/O operations (Sec. 13.2)
                                             34
poll Function: polling more specific
         conditions than select
                             結構陣列儲存位址 陣列中的資料數目                             微秒

#include <poll.h>
int poll (struct pollfd *fdarray, unsigned long ndfs, int timeout);
          returns: count of ready descriptors, 0 on timeout, -1 on error

     每個descriptor要test的條件是用pollfd結構來表示
     這些pollfd結構集合成一個陣列
struct pollfd {        要測的file descriptor number; -1表此結構無效
         int fd;        /* a descriptor to poll */
         short events; /* events of interested fd, value argument */
         short revents; /* events that occurred on fd, result argument */
};

要測試的條件                   真正測到的事件
                                                                       35
poll函數中的事件設定
 •events與revents由下列bit flag 組成
Constant     events revents   Description
POLLIN          x      x      normal or priority band to read
POLLRDNORM      x      x      normal data to read
POLLRDBAND      x      x      priority band data to read
POLLPRI         x      x      high-priority data to read
POLLOUT         x      x      normal data can be written
POLLWRNORM      x      x      normal data can be written
POLLWRBAND      x      x      priority band data can be written
POLLERR                x      an error has occurred
POLLHUP                x      hangup has occurred
POLLNVAL               x      descriptor is not an open file
                                                          36
設定events與測試revents
‧設定events: 使用bitwise OR
  struct pollfd          client[OPEN_MAX];
  …
  client[0].events = POLLRDNORM | POLLRDBAND;

‧測試revents: 使用bitwise AND
  struct pollfd          client[OPEN_MAX];
  …
  if (client[0].revents & POLLRDNORM)
       …

                                                37
Three Classes of Data Identified
            by poll
•normal, priority band, and high priority

                       normal       priority band   high priority
All regular TCP data    x
All UDP data            x
TCP’out-of-band data
      s                               x
Half-closed TCP         x
Error for TCP           x (or POLLERR)
New connection          x             x

                                                             38
Concurrent TCP Echo Server with poll

•When using select, the server maintains array client[ ]
 and descriptor set rset. When using poll, the server
 maintains array client of pollfd structure.
•Program flow:
   –allocate array of pollfd structures
   –initialize (listening socket: first entry in client)
      (set POLLRDNORM in events)
   –call poll; check for new connection
      (check, in revents, and set, in events, POLLRDNORM)
   –check for data on an existing connection
      (check POLLRDNORM or POLLERR in revents)
                                                        39
Rewrite Concurrent TCP Echo Server
                       with poll
Initialization                                  tcpcliserv/tcpservpoll01.c
      #include       "unp.h"
      #include       <limits.h>          /* for OPEN_MAX */
      int main(int argc, char **argv)
      {
           int                      i, maxi, listenfd, connfd, sockfd;
           int                      nready;
           ssize_t                 n;                    結構陣列
           char                    line[MAXLINE];
           socklen_t               clilen;
           struct pollfd           client[OPEN_MAX];
           struct sockaddr_in       cliaddr, servaddr;
           listenfd = Socket(AF_INET, SOCK_STREAM, 0);
           bzero(&servaddr, sizeof(servaddr));
           servaddr.sin_family        = AF_INET;
           servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
                                                                         40
           servaddr.sin_port        = htons(SERV_PORT);
tcpcliserv/tcpservpoll01.c

Initialization (cont.)


       Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

       Listen(listenfd, LISTENQ);

       client[0].fd = listenfd;                第0個元素放
       client[0].events = POLLRDNORM;          listening socket
       for (i = 1; i < OPEN_MAX; i++)
              client[i].fd = -1;   /* -1 indicates available entry */
       maxi = 0;                    /* max index into client[] array */

                                                                          41
Loop                                                      tcpcliserv/tcpservpoll01.c
for ( ; ; ) {
                                                          wait forever
                nready = Poll(client, maxi+1, INFTIM);
                                            bitwise AND
     if (client[0].revents & POLLRDNORM) { /* new client connection */
            clilen = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);         不會block
            for (i = 1; i < OPEN_MAX; i++)
                   if (client[i].fd < 0) {
  放入第一個                   client[i].fd = connfd; /* save descriptor */
  找到的空格                   break;
                   }
            if (i == OPEN_MAX)
                                                           for data socket
                   err_quit("too many clients");
            client[i].events = POLLRDNORM;
            if (i > maxi)
             maxi = i;                        /* max index in client[] array */
            if (--nready <= 0)
                   continue;                         /* no more readable descriptors */
     }                                                                           42
tcpcliserv/tcpservpoll01.c
for (i = 1; i <= maxi; i++) { /* check all clients for data */
                 if ( (sockfd = client[i].fd) < 0)
                        continue;                      bitwise AND
                 if (client[i].revents & (POLLRDNORM | POLLERR)) {
                        if ( (n = readline(sockfd, line, MAXLINE)) < 0) {
                               if (errno == ECONNRESET) {
                                            /* connection reset by client */
                                      Close(sockfd);
                                      client[i].fd = -1;
                               } else                           變無效資料
                                      err_sys("readline error");
                        } else if (n == 0) {
                                      /*4connection closed by client */
                               Close(sockfd);
                               client[i].fd = -1;
                        } else
                                                           變無效資料
                               Writen(sockfd, line, n);
                        if (--nready <= 0)
                               break;                      /* no more readable descriptors */
  } } } }                                                                               43
Conclusion
•本章介紹如何用I/O Multiplexing來取代
 blocking I/O,以同時等待兩個以上的I/O
 裝置
•I/O Multiplexing可用select或poll來
 達成
•為何不使用nonblocking I/O呢? 就算
 Buffer中沒資料也可立即return耶…
             return後要做啥?
                              44

More Related Content

PDF
The Dream Stream Team for Pulsar and Spring
PDF
分散DB Apache Kuduのアーキテクチャ DBの性能と一貫性を両立させる仕組み 「HybridTime」とは
PDF
BPF / XDP 8월 세미나 KossLab
PDF
Go Programming Patterns
PDF
어떻게 하면 데이터 사이언티스트가 될 수 있나요?
PPTX
FeatHub_FFA_2022
PPTX
Fugaku, the Successes and the Lessons Learned
PDF
Kamailio - Load Balancing Load Balancers
The Dream Stream Team for Pulsar and Spring
分散DB Apache Kuduのアーキテクチャ DBの性能と一貫性を両立させる仕組み 「HybridTime」とは
BPF / XDP 8월 세미나 KossLab
Go Programming Patterns
어떻게 하면 데이터 사이언티스트가 될 수 있나요?
FeatHub_FFA_2022
Fugaku, the Successes and the Lessons Learned
Kamailio - Load Balancing Load Balancers

What's hot (20)

PPTX
Vlans (virtual local area networks)
PPT
Data Loss and Duplication in Kafka
PDF
rtpengine and kamailio - or how to simulate calls at scale
PDF
Pwning in c++ (basic)
PDF
Waris l2vpn-tutorial
PPT
Glibc malloc internal
PDF
NiFi 시작하기
PDF
Introduction to RPKI
PDF
Three Ways Kamailio Can Help Your FreeSWITCH Deployment
PDF
Windowsのパケットモニタ作成
PDF
Reliable Event Delivery in Apache Kafka Based on Retry Policy and Dead Letter...
PDF
Boost UDP Transaction Performance
PPTX
DMVPN configuration - Configuring Cisco dynamic Multipoint VPN - HUB, SPOKES,...
PDF
rtpengine - Media Relaying and Beyond
PPTX
为啥别读HotSpot VM的源码(2012-03-03)
PPT
Multicasting and multicast routing protocols
PDF
MacOS memory allocator (libmalloc) Exploitation
PDF
Linux BPF Superpowers
PDF
Linux Networking Explained
PDF
Go and Uber’s time series database m3
Vlans (virtual local area networks)
Data Loss and Duplication in Kafka
rtpengine and kamailio - or how to simulate calls at scale
Pwning in c++ (basic)
Waris l2vpn-tutorial
Glibc malloc internal
NiFi 시작하기
Introduction to RPKI
Three Ways Kamailio Can Help Your FreeSWITCH Deployment
Windowsのパケットモニタ作成
Reliable Event Delivery in Apache Kafka Based on Retry Policy and Dead Letter...
Boost UDP Transaction Performance
DMVPN configuration - Configuring Cisco dynamic Multipoint VPN - HUB, SPOKES,...
rtpengine - Media Relaying and Beyond
为啥别读HotSpot VM的源码(2012-03-03)
Multicasting and multicast routing protocols
MacOS memory allocator (libmalloc) Exploitation
Linux BPF Superpowers
Linux Networking Explained
Go and Uber’s time series database m3
Ad

Similar to Select and poll functions (20)

PPT
Np unit iv i
PDF
PPT-3.pdf
DOCX
LINUX RS232程式設計
PDF
Choose one of these three options A IPC using FIFO B Shar.pdf
PDF
CC++ echo serverThis assignment is designed to introduce network .pdf
PPT
PDF
Networking lab
PDF
In C programming please CSCI 4534 Operating Systems Program.pdf
PPT
Computer networkppt4577
PPTX
分散式系統
PPT
5.IPC.ppt JSS science and technology university
PDF
#define LOGFILE signal_log.txt Signal handlers void sigusr1.pdf
PPTX
ipc.pptx
PDF
PPT
PPT
15237197jvkhcjkgckghckghxckgjckufliufi.ppt
PPT
PDF
CODE FOR echo_client.c A simple echo client using TCP #inc.pdf
PPT
Socket programming
Np unit iv i
PPT-3.pdf
LINUX RS232程式設計
Choose one of these three options A IPC using FIFO B Shar.pdf
CC++ echo serverThis assignment is designed to introduce network .pdf
Networking lab
In C programming please CSCI 4534 Operating Systems Program.pdf
Computer networkppt4577
分散式系統
5.IPC.ppt JSS science and technology university
#define LOGFILE signal_log.txt Signal handlers void sigusr1.pdf
ipc.pptx
15237197jvkhcjkgckghckghxckgjckufliufi.ppt
CODE FOR echo_client.c A simple echo client using TCP #inc.pdf
Socket programming
Ad

Recently uploaded (20)

PPT
Teaching material agriculture food technology
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Empathic Computing: Creating Shared Understanding
PDF
cuic standard and advanced reporting.pdf
PPTX
Spectroscopy.pptx food analysis technology
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
KodekX | Application Modernization Development
PDF
Encapsulation theory and applications.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
Teaching material agriculture food technology
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Understanding_Digital_Forensics_Presentation.pptx
Empathic Computing: Creating Shared Understanding
cuic standard and advanced reporting.pdf
Spectroscopy.pptx food analysis technology
MIND Revenue Release Quarter 2 2025 Press Release
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Advanced methodologies resolving dimensionality complications for autism neur...
20250228 LYD VKU AI Blended-Learning.pptx
Per capita expenditure prediction using model stacking based on satellite ima...
Building Integrated photovoltaic BIPV_UPV.pdf
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Mobile App Security Testing_ A Comprehensive Guide.pdf
NewMind AI Weekly Chronicles - August'25 Week I
Chapter 3 Spatial Domain Image Processing.pdf
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
KodekX | Application Modernization Development
Encapsulation theory and applications.pdf
The AUB Centre for AI in Media Proposal.docx

Select and poll functions

  • 1. I/O Multiplexing: select and poll •Introduction •I/O models •select function •Rewrite str_cli function •Supporting batch input with shutdown function •Rewrite concurrent TCP echo server with select •pselect function: avoiding signal loss in race condition •poll function: polling more specific conditions than select •Rewrite concurrent TCP echo server with poll 1 Some materials in these slides are taken from Prof. Ying-Dar Lin with his permission of usage
  • 2. Introduction •I/O multiplexing: to be notified, by kernel, if one or more I/O conditions are ready. •Scenarios in networking applications: –a client handling multiple descriptors (stdio/socket) –a client handling multiple sockets –a TCP server handling a listening socket and its connected sockets (兩件事都自己來) –a server handling both TCP and UDP –a server handling multiple services and protocols 2
  • 3. I/O動作如何進行? •一般process無法直接對I/O裝置下命令,必須 透過system call請求kernel幫忙進行I/O動作 •kernel會對每個I/O裝置維護一個buffer wait copy I/O Device Kernel’buffer s Process •對輸入而言,等待(wait)資料輸入至buffer需要 時間,從buffer搬移(copy)資料也需要時間。 •根據等待模式不同,I/O動作可分為五種模式 3
  • 4. Five I/O Models •blocking I/O: blocked all the way •nonblocking I/O: if no data in buffer, immediate returns EWOULDBLOCK •I/O multiplexing (select and poll): blocked separately in wait and copy •signal driven I/O (SIGIO): nonblocked in wait but blocked in copy (signaled when I/O can be initiated) •asynchronous I/O (aio_): nonblocked all the way (signaled when I/O is complete) 4
  • 5. Comparison of Five I/O Models blocking nonblocking I/O multiplexing signal-driven asynchronous initiate check check establish initiate wait for data check SIGIO specify blocked check handler signal/handler check check check ready notification blocked initiate initiate copy data blocked blocked blocked complete complete complete complete notification synchronous I/O asynchronous I/O 5
  • 6. I/O Multiplexing: 使用select •本章要做的是I/O Multiplexing(第三種I/O model): 使用select system call •select要求kernel測試某裝置是否滿足 我們設定的條件。若滿足則return,否則 等待至此條件滿足時為止(select會被 block) •select呼叫可以指定等待的時間上限 •select呼叫可以指定測試多個I/O裝置 6
  • 7. 對輸入裝置使用select •對輸入裝置而言,可以使用select要求 kernel測試某裝置是否ready for reading •當此裝置的buffer中已有相當數量(可設 定)的輸入資料時,此裝置即是ready for reading •select return後,由我們的程式自行呼 叫其它的system call將buffer中的資料搬 回來。 7
  • 8. 對輸出裝置使用select •對輸出裝置而言,可以使用select要求 kernel測試某裝置是否ready for writing •當此裝置的buffer中已有相當空間(可設 定)可放置輸出資料時,此裝置即是ready for writing •select return後,由我們的程式自行呼 叫其它的system call將欲輸出資料搬入 buffer中。 8
  • 9. select Function #include <sys/select.h> #include <sys/time.h> int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); returns: positive count of ready descriptors, 0 on timeout, -1 on error struct timeval { (null: wait forever; 0: do not wait) long tv_sec; /*second */ long tv_usec; /* microsecond */ }; readset, writeset, and exceptset specify the descriptors that we want the kernel to test for reading, writing, and exception conditions, respectively. maxfdp1 is the maximum descriptor to be tested plus one. 9
  • 10. select Fucntion •We can call select and tell the kernel to return only when Buffer –any descriptor in {1, 4, 5} is ready for 中已有 空間 reading (buffer中已有資料) –any descriptor in {2, 7} is ready for writing OR –any descriptor in {1, 4} has an exception condition pending –after 10.2 seconds have elapsed 裝置有例 外狀況 10
  • 11. Specifying Descriptor Values • We need to declare variables of data type fd_set and use macros to manipulate these variables. fd_set --- implementation dependent four macros: void FD_ZERO(fd_set *fdset); void FD_SET(int fd, fd_set *fdset); void FD_CLR(int fd, fd_set *fdset); int FD_ISSET(int fd, fd_set *fdset); fd_set rset; maxfdp1 = 6 FD_ZERO(&rset); FD_SET(1, &rset); Turn on bits for FD_SET(4, &rset); descriptors 1, 4, and 5 FD_SET(5, &rset); 11
  • 12. Socket Ready Conditions for select Condition readable? writeable? Exception? enough data to read x read-half closed x new connection ready x writing space available x write-half closed x pending error x x TCP out-of-band data x Low-water mark (enough data/space to read/write in socket receive/send buffer): default is 1/2048, may be set by SO_RCVLOWAT/SO_SNDLOWAT socket option Maximum number of descriptors for select? Redefine FD_SETSIZE and recompile kernel 12
  • 13. Low-Water Mark (低水位) •對socket receive buffer而言 –如收到data量不足low-water mark, socket is not ready for reading –Default = 1 byte •對socket send buffer而言 –如可用空間(available space)不足low-water mark, socket is not ready for writing –Default = 2048 byte 13
  • 14. 用select改寫str_cli •上一章的str_cli用兩個system calls分別 取得input –fgets用於stdin的user input 均為Blocking I/O –readline用於socket input •這樣沒有辦法同時等待兩個inputs。因此 當client等在fgets時,無法同時取得 socket input進來的資料 •本章改用select來同時等待兩個inputs 14
  • 15. Ch. 5 兩個版本的比較 Ch. 6 呼叫fgets從stdin 呼叫select同時等 讀取使用者的輸入 待socket及stdin socket is readable yes stdin is return EOF? readable no 呼叫readline從 socket讀取資料 呼叫writen將輸入 資料輸出至socket 呼叫fgets從stdin 呼叫fputs將讀取的 讀取使用者的輸入 資料輸出至stdout 呼叫readline從 yes socket讀取資料 return EOF? no 呼叫fputs將讀取的 資料輸出至stdout 呼叫writen將輸入 資料輸出至socket 15
  • 16. Rewrite str_cli Function with select client select for readability data or EOF stdin on either stdin or socket socket 用select同時等待stdio及 error EOF socket input. 當任一裝置 ready for reading時select 會return TCP 當select return時怎知道是哪 些裝置ready for reading呢? RST data FIN 用FD_ISSET測試傳回值 readset, writeset, 和 exceptset是雙向變數 可得知那些裝置是ready (value-result argemnets) 16
  • 17. Rewrite str_cli Function with select #include "unp.h" select/strcliselect01.c void str_cli(FILE *fp, int sockfd) { int maxfdp1; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; FD_ZERO(&rset); 傳回檔案指標fp 的descriptor no for ( ; ; ) { 放在 FD_SET(fileno(fp), &rset); 只測試read loop內 FD_SET(sockfd, &rset); Why? maxfdp1 = max(fileno(fp), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL); 17
  • 18. 用FD_ISSET測試 裝置是否ready select/strcliselect01.c if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if (Readline(sockfd, recvline, MAXLINE) == 0) err_quit("str_cli: server terminated prematurely"); Fputs(recvline, stdout); } 用FD_ISSET測試 裝置是否ready if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ if (Fgets(sendline, MAXLINE, fp) == NULL) return; /* all done */ Writen(sockfd, sendline, strlen(sendline)); } User按了 } Ctrl+D } 18
  • 19. 使用select函數常犯的兩個錯誤 ‧忘記maxfdp1是descriptor的最大值加1 ‧忘記readset, writeset, 和 exceptset是雙向 變數 –select return時會改變它們的值 –因此再次呼叫select時別忘了要重新設定 這些變數的內容 19
  • 20. Redirecting Input on UNIX Systems •Standard I/O - keyboard and screen •Input Redirection symbol (<) –for UNIX and DOS –Example: sum < input –Rather than inputting values by hand, read them from a file 20
  • 21. Stop-and-Wait (Interactive) Mode •原來版本的str_cli強制輪流處理stdin輸入 與socket輸入 提早輸入並不 不管多晚輸 會提早處理 入都會等待 keyin keyin keyin client socket socket socket in in in server time 21
  • 22. Batch Mode •在新版str_cli使用input redirection 將stdin重導 至file1.txt tcpcli04 206.62.226.35 < file1.txt 就算不 lines 檔案最後的EOF 用輸入 EOF … … 會使fgets傳回 重導向 client NULL,str_cli 也有此 會因此return 問題, 不需 … 只是較 等待 不嚴重 main會close掉 TCP connection, server 導致server尚未回 time 傳的資料遺失 22
  • 23. Solution: Use Shutdown Instead of Close •In str_cli, close write-half of TCP connection, by shutdown, while leaving read-half open. 先關client至server方向的connection server至client方向的connection暫時不要關 等到server的資料全部送回來 後再關掉server至client方向的 connection 23
  • 24. Function shutdown #include <sys/socket.h> int shutdown(int sockfd, int howto); returns: 0 if OK, -1 on error howto: SHUT_RD, SHUT_WR, SHUT_RDWR •initiates TCP normal termination regardless of descriptor’reference s count •selectively closes one direction of the connection (SHUT_RD or SHUT_WR) 24
  • 25. Rewrite str_cli with select and shutdown #include "unp.h" select/strcliselect02.c void 判斷client至server方向連 str_cli(FILE *fp, int sockfd) 結是否已斷的旗標變數 { int maxfdp1, stdineof; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; stdineof = 0; FD_ZERO(&rset); 當client至server方向連結 for ( ; ; ) { 未斷時才要test stdin if (stdineof == 0) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fp), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL); 25
  • 26. select/strcliselect02.c if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if (Readline(sockfd, recvline, MAXLINE) == 0) { client至server if (stdineof == 1) 方向連結已斷 return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } Fputs(recvline, stdout); } if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ if (Fgets(sendline, MAXLINE, fp) == NULL) { stdineof = 1; Shutdown(sockfd, SHUT_WR); /* send FIN */ FD_CLR(fileno(fp), &rset); 只斷client至 continue; server方向連結 } Writen(sockfd, sendline, strlen(sendline)); } } } 26
  • 27. Concurrent TCP Echo Server with select •A single server process using select to handle any number of clients (不fork child process) •Need to keep track of the clients by client[ ] (client descriptor array) and rset (read descriptor set) listening socket client[ ] terminated client [0] -1 maxi existing client [1] 5 stdin/stdout/stderr [2] -1 not used fd0 fd1 fd2 fd3 fd4 fd5 rset 0 0 0 1 0 1 [FD_SETSIZE-1] -1 maxfd +1 = 6 儲存已open socket的descriptor 用於呼叫select 27
  • 28. Rewrite Concurrent TCP Echo Server with select Initialization #include "unp.h" tcpcliserv/tcpservselect01.c int main(int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; ssize_t n; fd_set rset, allset; char line[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); 28
  • 29. tcpcliserv/tcpservselect01.c Initialization (cont.) Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); maxfd = listenfd;/* initialize */ maxi = -1;/* index into client[] array */ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1;/* -1 indicates available entry */ FD_ZERO(&allset); FD_SET(listenfd, &allset); 測試listening socket是 否ready for reading 29
  • 30. Loop tcpcliserv/tcpservselect01.c for ( ; ; ) { select的傳回值是ready的裝置數目 rset = allset; /* structure assignment */ nready = Select(maxfd+1, &rset, NULL, NULL, NULL); 新 if (FD_ISSET(listenfd, &rset)) { /* new client connection */ 連 clilen = sizeof(cliaddr); 結 connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); 不會block for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { 放入第一個 client[i] = connfd; /* save descriptor */ 找到的空格 break; } if (i == FD_SETSIZE) 加進select要test err_quit("too many clients"); 的descriptor set FD_SET(connfd, &allset); /* add new descriptor to set */ 如果沒有其 if (connfd > maxfd) 它ready的 maxfd = connfd; /* for select */ descriptor if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) continue; /* no more readable descriptors */ } 30
  • 31. Loop (cont.) tcpcliserv/tcpservselect01.c for (i = 0; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = client[i]) < 0) continue; 跳過沒有放descriptor的空格 if (FD_ISSET(sockfd, &rset)) { 已 if ( (n = Readline(sockfd, line, MAXLINE)) == 0) { 連 /* connection closed by client */ 結 Close(sockfd); FD_CLR(sockfd, &allset); 從要test的 client[i] = -1; 變空格 descriptor set中 } else 去除 Writen(sockfd, line, n); if (--nready <= 0) break; /* no more readable descriptors */ } } 如果沒有其它ready的 } descriptor,可以提早離開loop } 31
  • 32. 前頁程式的潛在問題 •select 在 socket 的 input buffer 有資 料可讀時即會return 此 socket 已 ready for reading •Readline 被設計成要讀到以換行字元 結束的一列文字(或是socket被結束掉)才 會return •有心人士可能利用這項弱點進行攻擊 32
  • 33. Denial of Service Attacks •問題出在Server程式可能block在Readline中 •Attack scenario: –a malicious client sends 1 byte of data (other than a newline) and sleep –server hangs until the malicious client either sends a newline or terminates –(server 要讀到 newline, Readline 才能 return) 33
  • 34. Solutions to the Attack •When a server is handling multiple clients the server can never block in a function call related to a single client •Possible solutions: –Use nonblocking I/O (Ch. 15) –separate thread/process for each client –timeout on I/O operations (Sec. 13.2) 34
  • 35. poll Function: polling more specific conditions than select 結構陣列儲存位址 陣列中的資料數目 微秒 #include <poll.h> int poll (struct pollfd *fdarray, unsigned long ndfs, int timeout); returns: count of ready descriptors, 0 on timeout, -1 on error 每個descriptor要test的條件是用pollfd結構來表示 這些pollfd結構集合成一個陣列 struct pollfd { 要測的file descriptor number; -1表此結構無效 int fd; /* a descriptor to poll */ short events; /* events of interested fd, value argument */ short revents; /* events that occurred on fd, result argument */ }; 要測試的條件 真正測到的事件 35
  • 36. poll函數中的事件設定 •events與revents由下列bit flag 組成 Constant events revents Description POLLIN x x normal or priority band to read POLLRDNORM x x normal data to read POLLRDBAND x x priority band data to read POLLPRI x x high-priority data to read POLLOUT x x normal data can be written POLLWRNORM x x normal data can be written POLLWRBAND x x priority band data can be written POLLERR x an error has occurred POLLHUP x hangup has occurred POLLNVAL x descriptor is not an open file 36
  • 37. 設定events與測試revents ‧設定events: 使用bitwise OR struct pollfd client[OPEN_MAX]; … client[0].events = POLLRDNORM | POLLRDBAND; ‧測試revents: 使用bitwise AND struct pollfd client[OPEN_MAX]; … if (client[0].revents & POLLRDNORM) … 37
  • 38. Three Classes of Data Identified by poll •normal, priority band, and high priority normal priority band high priority All regular TCP data x All UDP data x TCP’out-of-band data s x Half-closed TCP x Error for TCP x (or POLLERR) New connection x x 38
  • 39. Concurrent TCP Echo Server with poll •When using select, the server maintains array client[ ] and descriptor set rset. When using poll, the server maintains array client of pollfd structure. •Program flow: –allocate array of pollfd structures –initialize (listening socket: first entry in client) (set POLLRDNORM in events) –call poll; check for new connection (check, in revents, and set, in events, POLLRDNORM) –check for data on an existing connection (check POLLRDNORM or POLLERR in revents) 39
  • 40. Rewrite Concurrent TCP Echo Server with poll Initialization tcpcliserv/tcpservpoll01.c #include "unp.h" #include <limits.h> /* for OPEN_MAX */ int main(int argc, char **argv) { int i, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; 結構陣列 char line[MAXLINE]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 40 servaddr.sin_port = htons(SERV_PORT);
  • 41. tcpcliserv/tcpservpoll01.c Initialization (cont.) Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); client[0].fd = listenfd; 第0個元素放 client[0].events = POLLRDNORM; listening socket for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1; /* -1 indicates available entry */ maxi = 0; /* max index into client[] array */ 41
  • 42. Loop tcpcliserv/tcpservpoll01.c for ( ; ; ) { wait forever nready = Poll(client, maxi+1, INFTIM); bitwise AND if (client[0].revents & POLLRDNORM) { /* new client connection */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); 不會block for (i = 1; i < OPEN_MAX; i++) if (client[i].fd < 0) { 放入第一個 client[i].fd = connfd; /* save descriptor */ 找到的空格 break; } if (i == OPEN_MAX) for data socket err_quit("too many clients"); client[i].events = POLLRDNORM; if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) continue; /* no more readable descriptors */ } 42
  • 43. tcpcliserv/tcpservpoll01.c for (i = 1; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = client[i].fd) < 0) continue; bitwise AND if (client[i].revents & (POLLRDNORM | POLLERR)) { if ( (n = readline(sockfd, line, MAXLINE)) < 0) { if (errno == ECONNRESET) { /* connection reset by client */ Close(sockfd); client[i].fd = -1; } else 變無效資料 err_sys("readline error"); } else if (n == 0) { /*4connection closed by client */ Close(sockfd); client[i].fd = -1; } else 變無效資料 Writen(sockfd, line, n); if (--nready <= 0) break; /* no more readable descriptors */ } } } } 43
  • 44. Conclusion •本章介紹如何用I/O Multiplexing來取代 blocking I/O,以同時等待兩個以上的I/O 裝置 •I/O Multiplexing可用select或poll來 達成 •為何不使用nonblocking I/O呢? 就算 Buffer中沒資料也可立即return耶… return後要做啥? 44