1. 程式人生 > >linux網路程式設計之用select方法實現io複用(基於udp)

linux網路程式設計之用select方法實現io複用(基於udp)

1、基本概念


  IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。IO多路複用適用如下場合:

  (1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。

  (2)當一個客戶同時處理多個套介面時,而這種情況是可能的,但很少出現。

  (3)如果一個TCP伺服器既要處理監聽套介面,又要處理已連線套介面,一般也要用到I/O複用。

  (4)如果一個伺服器即要處理TCP,又要處理UDP,一般要使用I/O複用。

  (5)如果一個伺服器要處理多個服務或多個協議,一般要使用I/O複用。

  與多程序和多執行緒技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必建立程序/執行緒,也不必維護這些程序/執行緒,從而大大減小了系統的開銷。

2、select函式



該函式准許程序指示核心等待多個事件中的任何一個傳送,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒。函式原型如下:

#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)

返回值:就緒描述符的數目,超時返回0,出錯返回-1
函式引數介紹如下:

(1)第一個引數maxfdp1指定待測試的描述字個數,它的值是待測試的最大描述字加1(因此把該引數命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。

因為檔案描述符是從0開始的。

(2)中間的三個引數readset、writeset和exceptset指定我們要讓核心測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設為空指標。struct fd_set可以理解為一個集合,這個集合中存放的是檔案描述符,可通過以下四個巨集進行設定:

          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);   // 檢查集合中指定的檔案描述符是否可以讀寫 

(3)timeout告知核心等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用於指定這段時間的秒數和微秒數。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds
       };


這個引數有三種可能:


(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此,把該引數設定為空指標NULL。
(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該引數所指向的timeval結構中指定的秒數和微秒數。

(3)根本不等待:檢查描述字後立即返回,這稱為輪詢。為此,該引數必須指向一個timeval結構,而且其中的定時器值必須為0。

3 、 實現基於udp客戶端到服務端的通訊

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>


int main()
{
   char buf[100] = "";
   int udp_fd = 0;
   struct sockaddr_in addr;
   struct sockaddr_in cliaddr;

   //對套接字初始化
   bzero(&addr, sizeof(addr));
   addr.sin_family = AF_INET;
   addr.sin_port = htons(8000);
   addr.sin_addr.s_addr = htonl(INADDR_ANY);

   bzero(&cliaddr, sizeof(cliaddr));
   cliaddr.sin_family = AF_INET;
   cliaddr.sin_port = htons(8000);

   //建立套介面
   if ((udp_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
   {
      perror("socket create failed\n");
      exit(EXIT_FAILURE);
   }
   puts("socket create success");
   //設定埠
   if (bind(udp_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
   {
       perror("bind fail\n");
       close(udp_fd);
       exit(EXIT_SUCCESS);
   }
   puts("bind success");
   puts("input:  \"192.168.221.x\" to send msg to body");
   while (1)
   {
       char buf[100] = "";
       fd_set rset;//建立檔案描述符的聚合變數
       FD_ZERO(&rset);
       FD_SET(0, &rset);
       FD_SET(udp_fd, &rset);
       write(1, "chenyu:", 7);
       if (select(udp_fd + 1, &rset, NULL, NULL, NULL) > 0)
       {
          if (FD_ISSET(0, &rset)) //測試0是否可以讀寫 ,可以的話返回大於0的資料 
          {
              fgets(buf, sizeof(buf), stdin);              
              buf[strlen(buf) + 1] = '\0';
              char ipbuf[16] = "";
	      inet_pton(AF_INET, buf + 6, &cliaddr);
              printf("\r\033[39m[%s]:\033[39m%s", inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf)), buf);
	      //iprintf("ip is  %s\n", inet_ntop(AF_INET, &cliaddr.sin_addr, ipbuf, sizeof(ipbuf)));
  	      if (strcmp(buf, "exit") == 0)
              {  
	          close(udp_fd);
	          exit(EXIT_SUCCESS);
              }
              sendto(udp_fd, buf, strlen(buf), 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
          }
          if (FD_ISSET(udp_fd, &rset))
          {
             puts("has go in rset udp_fd");
             struct sockaddr_in addr;
	     char ipbuf[1024] = "";
	     socklen_t addrlen = sizeof(addr);
             bzero(&addr, sizeof(addr));
             puts("receive data start");
             recvfrom(udp_fd, buf, 100, 0 , (struct sockaddr*)&addr, &addrlen);
             printf("\r\033[34m[%s]:\033[34m%s", inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf)), buf);
             puts("receive data end");
	     }
       }
   } 
}



4、執行結果