Socket I/O模式举例说明


--------------------------------------------------------------------------------
基本型号: 阻塞发送与接收
点评: 实际中经常使用, 简单, 占用资源小;

//usage: client 127.0.0.1 hello
int main(int argc, char *argv[])
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE];
  char snd_buf[MAXDATASIZE];
  struct sockaddr_in server_addr;

  if (argc < 3)
  {
    printf("Usage:%s [ip address] [any string]\n", argv[0]);
    return 1;
  }

  snd_buf[0] = '\0';
  strcat(snd_buf, argv[2]);

  //建立socket;
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }

  //配置对方(server)IPAddr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);

  //连接到服务器
  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    perror("connect");
    exit(1);
  }

  /* 同步阻塞模式  */
  if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(1);
  }
  printf("send:%s\n", snd_buf);

   /* 同步阻塞模式  */
  if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
  {
    perror("recv:");
    exit(1);
  }

  rcv_buf[recvbytes] = '\0';
  printf("recv:%s\n", rcv_buf);

  close(sockfd);
  return 0;
}

--------------------------------------------------------------------------------
改进型: 同步非阻塞发送与接收
点评: 过渡型, 一般不用;
  ...
  //配置对方(server)IPAddr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);
 
  /* Setting socket to nonblock */
  int flags = fcntl(sockfd, F_GETFL, 0);
  fcntl(sockfd, flags|O_NONBLOCK);  
  ...
  /* 同步非阻塞模式 */
  while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1)
  {
    sleep(10);
    printf("sleep\n");
  }
  printf("send:%s\n", snd_buf);
 
  /* 同步非阻塞模式 */
  while ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT)) == -1)
  {
    sleep(10);
    printf("sleep\n");
  }
  ...
 
--------------------------------------------------------------------------------
改进型: I/O复用(异步阻塞)模式 (select)
点评: 可同时检测多个socket, 可设置超时返回;

//e.g 接收server发送的数据并写入到文件TFILE中;
//usage: client 127.0.0.1 ready

#define SERVPORT 8080
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"

int main(int argc, char *argv[])
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE];
  char snd_buf[MAXDATASIZE];
  struct hostent *host;             /* struct hostent
                                     * {
                                     * char *h_name; // general hostname
                                     * char **h_aliases; // hostname's alias
                                     * int h_addrtype; // AF_INET
                                     * int h_length;
                                     * char **h_addr_list;
                                     * };
                                     */
  struct sockaddr_in server_addr;

  fd_set readset, writeset;
  int check_timeval = 1;
  struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
  int maxfd;
  int fp;
  int cir_count = 0;
  int ret;

  if (argc < 3)
  {
    printf("Usage:%s [ip address] [any string]\n", argv[0]);
    return 1;
  }

  *snd_buf = '\0';
  strcat(snd_buf, argv[2]);

  if ((fp = open(TFILE,O_WRONLY)) < 0)    //不是用fopen
  {
    perror("fopen:");
    exit(1);
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);

  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    perror("connect");
    exit(1);
  }

  //tell server u can trans datas now.
  if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(1);
  }
  printf("send:%s\n", snd_buf);

  while (1)
  {
    FD_ZERO(&readset);            //每次循环都要清空集合,否则不能检测描述符变化
    FD_SET(sockfd, &readset);     //添加描述符       
    FD_ZERO(&writeset);
    FD_SET(fp, &writeset);

    maxfd = sockfd > fp ? (sockfd+1) : (fp+1);    //描述符最大值加1

    ret = select(maxfd, &readset, NULL, NULL, NULL);   // 阻塞模式
    switch( ret)
    {
      case -1:
        exit(-1);
        break;
      case 0:
        break;
      default:
        if (FD_ISSET(sockfd, &readset))  //测试sock是否可读,即是否网络上有数据
        {
          recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
          rcv_buf[recvbytes] = '\0';
          printf("recv:%s\n", rcv_buf);

          if (FD_ISSET(fp, &writeset))
          {
            write(fp, rcv_buf, strlen(rcv_buf));   // 不是用fwrite
          }
          goto end;
        }
    }
    cir_count++;
    printf("CNT : %d \n",cir_count);
  }

end:
  close(fp);
  close(sockfd);
  return 0;
}

--------------------------------------------------------------------------------
改进型: 信号驱动I/O模式

. 注册一个信号处理函数
    使用recvfrom()读取数据
. 等待SIGIO信号
    
--------------------------------------------------------------------------------
异步非阻塞模式 (asynchronous IO)

点评: 使用较少;
与前面的信号驱动模型的主要区别在于:
    信号: 内核通知我们何时可以启动一个I/O操作;
    异步: 内核通知我们I/O操作何时完成;

以read系统调用为例, 步骤为:
a. 调用read;
b. read请求会立即返回,说明请求已经成功发起了。
c. 在后台完成读操作这段时间内,应用程序可以执行其他处理操作。
d. 当read的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。

//Client
//usage: client 127.0.0.1 hello

#define SERVPORT 8080
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"

int main(int argc, char *argv[])
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE];
  char snd_buf[MAXDATASIZE];
  struct hostent *host;             /* struct hostent
                                     * {
                                     * char *h_name; // general hostname
                                     * char **h_aliases; // hostname's alias
                                     * int h_addrtype; // AF_INET
                                     * int h_length;
                                     * char **h_addr_list;
                                     * };
                                     */
  struct sockaddr_in server_addr;

  fd_set readset, writeset;
  int check_timeval = 1;
  struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
  int maxfd;
  int fp;
  int cir_count = 0;
  int ret;

  if (argc < 3)
  {
    printf("Usage:%s [ip address] [any string]\n", argv[0]);
    return 1;
  }

  *snd_buf = '\0';
  strcat(snd_buf, argv[2]);

  if ((fp = open(TFILE,O_WRONLY)) < 0)    //不是用fopen
  {
    perror("fopen:");
    exit(1);
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);

  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    perror("connect");
    exit(1);
  }

  if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(1);
  }
  printf("send:%s\n", snd_buf);

  while(1)
  {
    FD_ZERO(&readset);            //每次循环都要清空集合,否则不能检测描述符变化
    FD_SET(sockfd, &readset);     //添加描述符       
    FD_ZERO(&writeset);
    FD_SET(fp, &writeset);

    maxfd = sockfd > fp ? (sockfd+1) : (fp+1);    //描述符最大值加1

    ret = select(maxfd, &readset, NULL, NULL, &timeout);   // 非阻塞模式
    switch( ret)
    {
      case -1:
        exit(-1);
        break;
      case 0:
        break;
      default:
        if (FD_ISSET(sockfd, &readset))  //测试sock是否可读,即是否网络上有数据
        {
          recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
          rcv_buf[recvbytes] = '\0';
          printf("recv:%s\n", rcv_buf);

          if (FD_ISSET(fp, &writeset))
          {
            write(fp, rcv_buf, strlen(rcv_buf));   // 不是用fwrite
          }
          goto end;
        }
    }
    timeout.tv_sec = check_timeval;    // 必须重新设置,因为超时时间到后会将其置零

    cir_count++;
    printf("CNT : %d \n",cir_count);
  }

end:
  close(fp);
  close(sockfd);

  return 0;
}

//Server

#define SERVPORT 8080
#define BACKLOG 10 // max numbef of client connection
#define MAXDATASIZE 100

int main(char argc, char *argv[])
{
  int sockfd, client_fd, addr_size, recvbytes;
  char rcv_buf[MAXDATASIZE], snd_buf[MAXDATASIZE];
  char* val;
  struct sockaddr_in server_addr;
  struct sockaddr_in client_addr;
  int bReuseaddr = 1;

  char IPdotdec[20];

  /* create a new socket and regiter it to os .
   * SOCK_STREAM means that supply tcp service,
   * and must connect() before data transfort.
   */
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }

  /* setting server's socket */
  server_addr.sin_family = AF_INET;         // IPv4 network protocol
  server_addr.sin_port = htons(SERVPORT);
  server_addr.sin_addr.s_addr = INADDR_ANY; // auto IP detect
  memset(&(server_addr.sin_zero),0, 8);

  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(int));
 
  if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))== -1)
  {
    perror("bind:");
    exit(1);
  }

  /*
   * watting for connection ,
   * and server permit to recive the requestion from sockfd
   */
  if (listen(sockfd, BACKLOG) == -1) // BACKLOG assign thd max number of connection
  {
    perror("listen:");
    exit(1);                                                                 
  }                                                                          

  while(1)                                                                   
  {                                                                          
    addr_size = sizeof(struct sockaddr_in);                                  

    /*                                                                       
     * accept the sockfd's connection,                                       
     * return an new socket and assign far host to client_addr               
     */                                                                      
    printf("watting for connect...\n");                                      
    if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_size)) == -1)   
    {                                                                        
      /* Nonblocking mode */                                                 
      perror("accept:");                                                     
      continue;                                                              
    }                                                                        

    /* network-digital to ip address */                                      
    inet_ntop(AF_INET, (void*)&client_addr, IPdotdec, 16);                   
    printf("connetion from:%d : %s\n",client_addr.sin_addr, IPdotdec);       

    //if (!fork())                                                           
    {                                                                        
      /* child process handle with the client connection */                  

      /* recive the client's data by client_fd */                            
      if ((recvbytes = recv(client_fd, rcv_buf, MAXDATASIZE, 0)) == -1)      
      {                                                                      
        perror("recv:");                                                     
        exit(1);                                                             
      }                                                                      
      rcv_buf[recvbytes]='\0';                                               
      printf("recv:%s\n", rcv_buf);                                          

      *snd_buf='\0';                                                         
      strcat(snd_buf, "welcome");                                            

      sleep(3);                                                              
      /* send the message to far-hosts by client_fd */                       
      if (send(client_fd, snd_buf, strlen(snd_buf), 0) == -1)                
      {                                                                      
        perror("send:");                                                     
        exit(1);                                                             
      }                                                                      
      printf("send:%s\n", snd_buf);                                          

      close(client_fd);                                                      
      //exit(1);                                                             
    }                                                                        

    //close(client_fd);                                                      
  }

  return 0;                                                                  
}

总结一下:

. 阻塞与非阻塞, blocking vs non-blocking
调用blocking IO会一直block住对应的进程直到操作完成,
而non-blocking IO在kernel还在准备数据的情况下会立刻返回。

. POSIX的定义的同步与异步:
(同步) A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
(异步) An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别就在于:
synchronous IO做”IO operation”的时候会将process阻塞。

故, 之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous I/O.


定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。
non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。
但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,
这个时候进程是被block了,在这段时间内,进程是被block的。

而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,
直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。


--------------------------------------------------------------------------------
辨析: recv和recvfrom

recv的recvfrom是可以替换使用的,只是recvfrom多了两个参数,可以用来接收对端的地址信息,
这个对于udp这种无连接的,可以很方便地进行回复。
而换过来如果你在udp当中也使用recv,那么就不知道该回复给谁了,如果你不需要回复的话,也是可以使用的。
另外就是对于tcp是已经知道对端的,就没必要每次接收还多收一个地址,没有意义,要取地址信息,在accept当中取得就可以加以记录了。

 

转载于:https://my.oschina.net/laozh/blog/811063

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值