海闊天空

          I'm on my way!
          隨筆 - 17, 文章 - 69, 評(píng)論 - 21, 引用 - 0
          數(shù)據(jù)加載中……

          經(jīng)典的Linux Socket 編程 示例代碼 (下)


          摘自:http://fanqiang.chinaunix.net/a4/b7/20010810/1200001101.html


          在例程main()函數(shù)快要結(jié)束時(shí),我們看到,在服務(wù)器接受了客戶機(jī)的連接請(qǐng)求后,將為其創(chuàng)建子進(jìn)程,并在子進(jìn)程中執(zhí)行代理服務(wù)程序do_proxy()。
          -----------------------------------------------------------------/****************************************************************
          function:    do_proxy
          description:  does the actual work of virtually connecting a client to the telnet service on the          isolated host.
          arguments:   usersockfd socket to which the client is connected. return value: none.
          calls:     none.
          globals:     reads hostaddr.
          ****************************************************************/
          void do_proxy (usersockfd)
          int usersockfd;
          {
          int isosockfd;
          fd_set rdfdset;
          int connstat;
          int iolen;
          char buf[2048];
          /* open a socket to connect to the isolated host */
          if ((isosockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
          errorout("failed to create socket to host");
          /* attempt a connection */
          connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
          switch (connstat) {
          case 0:
          break;
          case ETIMEDOUT:
          case ECONNREFUSED:
          case ENETUNREACH:
          strcpy(buf,sys_myerrlist[errno]);
          strcat(buf,"\r\n");
          write(usersockfd,buf,strlen(buf));
          close(usersockfd);
          exit(1);
          /* die peacefully if we can't establish a connection */
          break;
          default:
          errorout("failed to connect to host");
          }
          /* now we're connected, serve fall into the data echo loop */
          while (1) {
          /* Select for readability on either of our two sockets */
          FD_ZERO(&rdfdset);
          FD_SET(usersockfd,&rdfdset);
          FD_SET(isosockfd,&rdfdset);
          if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < 0)
          errorout("select failed");
          /* is the client sending data? */
          if (FD_ISSET(usersockfd,&rdfdset)) {
          if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
          break; /* zero length means the client disconnected */
          rite(isosockfd,buf,iolen);
          /* copy to host -- blocking semantics */
          }
          /* is the host sending data? */
          if (FD_ISSET(isosockfd,&rdfdset)) {
          f ((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
          break; /* zero length means the host disconnected */
          rite(usersockfd,buf,iolen);
          /* copy to client -- blocking semantics */
          }
          }
          /* we're done with the sockets */
          close(isosockfd);
          lose(usersockfd);
          }
          -----------------------------------------------------------------
          在 我們這段代理服務(wù)器例程中,真正連接用戶主機(jī)和遠(yuǎn)端主機(jī)的一段操作,就是由這個(gè)do_proxy()函數(shù)來完成的?;叵胍幌挛覀円婚_始對(duì)這段 proxy程序用法的介紹。先將我們的proxy與遠(yuǎn)端主機(jī)綁定,然后用戶通過proxy的綁定端口與遠(yuǎn)端主機(jī)建立連接。而在main()函數(shù)中,我們的 proxy由一段服務(wù)器程序與用戶主機(jī)建立了連接,而在這個(gè)do_proxy()函數(shù)中,proxy將與遠(yuǎn)端主機(jī)的相應(yīng)服務(wù)端口(由用戶在命令行參數(shù)中指 定)建立連接,并負(fù)責(zé)傳遞用戶主機(jī)和遠(yuǎn)端主機(jī)之間交換的數(shù)據(jù)。
          由于要和遠(yuǎn)端主機(jī)建立連接,所以我們看到do_proxy()函數(shù)的前半部分實(shí)際上相當(dāng)于一段標(biāo)準(zhǔn)的客戶機(jī)程序。首先創(chuàng)建一個(gè)新的套接字描述符 isosockfd,然后調(diào)用函數(shù)connect()與遠(yuǎn)端主機(jī)之間建立連接。函數(shù)connect()的定義為:
          -----------------------------------------------------------------
          #include <sys/types.h>
          #include <sys/socket.h>
          int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
          -----------------------------------------------------------------
          參數(shù)sockfd是調(diào)用函數(shù)socket()返回的套接字描述符,參數(shù)servaddr指向遠(yuǎn)程服務(wù)器的套接字地址結(jié)構(gòu),參數(shù)addrlen指定這個(gè) 套接字地址結(jié)構(gòu)的長(zhǎng)度。函數(shù)connect()執(zhí)行成功時(shí)返回"0",如果執(zhí)行失敗則返回"-1",并將全局變量errno設(shè)置為相應(yīng)的錯(cuò)誤類型。在例程 中的switch()函數(shù)調(diào)用中對(duì)以下三種出錯(cuò)類型進(jìn)行了處理: ETIMEDOUT、ECONNREFUSED和ENETUNREACH。這三個(gè)出錯(cuò)類型的意思分別為:ETIMEDOUT代表超時(shí),產(chǎn)生這種情況的原因 有很多,最常見的是服務(wù)器忙,無法應(yīng)答客戶機(jī)的連接請(qǐng)求;ECONNREFUSED代表連接拒絕,即服務(wù)器端沒有準(zhǔn)備好的傾聽套接字,或是沒有對(duì)傾聽套接 字的狀態(tài)進(jìn)行監(jiān)聽;ENETUNREACH表示網(wǎng)絡(luò)不可達(dá)。
          在本例中,connect()函數(shù)的第二個(gè)參數(shù)servaddr是全局變量hostaddr,其中存儲(chǔ)著函數(shù)parse_args()轉(zhuǎn)換好的命令行 參數(shù)。如果連接建立失敗,在例程中就調(diào)用我們自定義的函數(shù)errorout()輸出信息"failed to connect to host"。errorout()函數(shù)的定義為:
          -----------------------------------------------------------------
          /****************************************************************
          function:  errorout
          description: displays an error message on the console and kills the current process.
          arguments:  msg -- message to be displayed.
          return value: none -- does not return.
          calls:    none.
          globals:   none.
          ****************************************************************/
          void errorout (msg)
          char *msg;
          {
          FILE *console;
          console = fopen("/dev/console","a");
          fprintf(console,"proxyd: %s\r\n",msg);
          fclose(console);
          exit(1);
          }
          -----------------------------------------------------------------
          do_proxy()函數(shù)的后半部分是通過proxy建立用戶主機(jī)與遠(yuǎn)端主機(jī)之間的連接。我們既有proxy與用戶主機(jī)連接的套接字 (do_proxy()函數(shù)的參數(shù)usersockfd),又有proxy與遠(yuǎn)端主機(jī)連接的套接字isosockfd,那么最簡(jiǎn)單直接的通信建立方式就是 從一個(gè)套接字讀,然后直接寫到另一個(gè)套接字去。如:
          -----------------------------------------------------------------
          int n;
          char buf[2048];
          while((n=read(usersockfd, buf, sizeof(buf))>0)
          if(write(isosockfd, buf, n)!=n)
          err_sys("write wrror\n");
          -----------------------------------------------------------------
          這種形式的阻塞I/O在單向數(shù)據(jù)傳遞的時(shí)候是非常有效的,但是在我們的proxy操作中是要求用戶主機(jī)和遠(yuǎn)端主機(jī)雙向通信的,這樣就要求我們對(duì)兩個(gè)套 接字描述符既能夠讀由能夠?qū)?。如果還是采用這種方式的阻塞I/O的話,很有可能長(zhǎng)時(shí)間阻塞在一個(gè)描述符上。因此例程在處理這個(gè)問題的時(shí)候調(diào)用了 select()函數(shù),這個(gè)函數(shù)允許我們執(zhí)行I/O多路轉(zhuǎn)接。其具體含義就是select()函數(shù)可以構(gòu)造一個(gè)表,在這個(gè)表中包含了我們所有要用到的文件 描述符。然后我們可以調(diào)用一個(gè)函數(shù),這個(gè)函數(shù)可以檢測(cè)這些文件描述符的狀態(tài),當(dāng)某個(gè)(我們指定的)文件描述符準(zhǔn)備好進(jìn)行I/O操作時(shí),此函數(shù)就返回,告知 進(jìn)程哪個(gè)文件描述符已經(jīng)可以執(zhí)行I/O操作了。這樣就避免了長(zhǎng)時(shí)間的阻塞。
          還有一個(gè)函數(shù)poll()可以實(shí)現(xiàn)I/O多路轉(zhuǎn)接,由于在例程中調(diào)用的是select(),我們就只對(duì)select()進(jìn)行一下比較詳細(xì)的介紹。select()系列函數(shù)的詳細(xì)描述為:
          -----------------------------------------------------------------
          #include <sys/time.h>
          #include <sys/types.h>
          #include <unistd.h>
          int select(int n, fd_set *readfds, fd_set *writefds, fd_est *exceptfds, struct timeval *timeout);
          FD_CLR(int fd, fd_set *set);
          FD_ISSET(int fd, fd_set *set);
          FD_SET(int fd, fd_set *set);
          FD_ZERO(fd_set *set);
          -----------------------------------------------------------------
          select()函數(shù)將創(chuàng)建一個(gè)我們所關(guān)心的文件描述符表,它的參數(shù)將在內(nèi)核中為這些文件描述符設(shè)置我們所關(guān)心的條件,例如是否是可讀、是否可寫以及 是否異常,而且在參數(shù)中還可以設(shè)置我們希望等待的最大時(shí)間。在select()成功執(zhí)行時(shí),它將返回目前已經(jīng)準(zhǔn)備好的描述符數(shù)量,同時(shí)內(nèi)核可以告訴我們各 個(gè)描述符的狀態(tài)信息。如果超時(shí),則返回"0",如果出錯(cuò),則函數(shù)返回"-1",并同時(shí)設(shè)置errno為相應(yīng)的值。
          select()的最后一個(gè)參數(shù)timeout將設(shè)置等待時(shí)間。其中結(jié)構(gòu)timeval是在文件<bits/time.h>中定義的。
          -----------------------------------------------------------------
          struct timeval
          {
          __time_t tv_sec; /* Seconds */
          __time_t tv_usec; /* Microseconds */
          };
          -----------------------------------------------------------------
          參數(shù)timeout的設(shè)置有三種情況。象例程中這樣timeout==NULL時(shí),這表示用戶希望永遠(yuǎn)等待,直到我們指定的文件描述符中的一個(gè)已準(zhǔn)備 好,或者是捕捉到一個(gè)信號(hào)。如果是由于捕捉到信號(hào)而中斷了這個(gè)無限期的等待過程的話,select()將返回"-1",同時(shí)設(shè)置errno的值為 EINTR。
          如果timeout->tv_sec==0&&timeout->tv_usec==0,那么這表示完全不等待。 Select()測(cè)試了所有指定文件描述符后立即返回。這是得到多個(gè)描述符狀態(tài)而不阻塞select()函數(shù)的輪詢方法。
          如果timeout->tv_sec!=0||timeout->tv_usec!=0,那么這兩個(gè)參數(shù)的值即為我們希望函數(shù)等待的時(shí) 間。其中tv_sec設(shè)置時(shí)間單位為秒,tv_usec設(shè)置時(shí)間單位為微秒。如果在超時(shí)的時(shí)候,在我們指定的所有文件描述符里面仍然沒有任何一個(gè)準(zhǔn)備好的 話,則select()將返回"0"。
          中間三個(gè)參數(shù)的數(shù)據(jù)類型是fd_set,它的意思是文件描述符集,而readfds, writefds和exceptfds則分別是指向文件描述符集的指針,他們分別描述了我們所關(guān)心的可讀、可寫以及狀態(tài)異常的各個(gè)文件描述符。之所以我們 稱select()可以創(chuàng)建一個(gè)文件描述符"表",那個(gè)所謂的表就是由這三個(gè)參數(shù)指向的數(shù)據(jù)結(jié)構(gòu)組成的。其具體結(jié)構(gòu)如圖1所示。其中在每個(gè)set_fd數(shù) 據(jù)類型中都為我們關(guān)心的所有文件描述符保留了一位。所以在監(jiān)測(cè)文件描述符狀態(tài)的時(shí)候,就在這些set_fd數(shù)據(jù)結(jié)構(gòu)中查詢相關(guān)的位。
          第一個(gè)參數(shù)n用來說明到底需要遍歷多少個(gè)描述符位。n的值一般是這樣設(shè)置的,從我們關(guān)心的所有文件描述符中選出最大值再加1。例如我們?cè)O(shè)置的所有文件 描述符中最大的為6,那么將n設(shè)置為7,則系統(tǒng)在檢測(cè)描述符狀態(tài)的時(shí)候,就只用遍歷前7位(fd0~fd6)的狀態(tài)。不過如果不想這樣麻煩的話,我們可以 象例程中那樣將n的值直接設(shè)置為FD_SETSIZE。這是系統(tǒng)中設(shè)定的最大文件描述符個(gè)數(shù),不同的系統(tǒng)這個(gè)值也不相同,一般是256或是1024。這樣 在檢測(cè)描述符狀態(tài)的時(shí)候,函數(shù)將遍歷所有的描述符位。
          在調(diào)用select()函數(shù)實(shí)現(xiàn)多路I/O轉(zhuǎn)接時(shí),首先我們要聲明一個(gè)新的文件描述符集,就象例程中這樣:
          fd_set rdfdset;
          然后調(diào)用FD_ZERO()清空此文件描述符集的所有位,以免下面檢測(cè)描述符位的時(shí)候返回錯(cuò)誤結(jié)果:
          FD_ZERO(&rdfdset);
          然后調(diào)用FD_SET()在文件描述符集中設(shè)置我們關(guān)心的位。在本例中,我們關(guān)心的就是分別與用戶主機(jī)和遠(yuǎn)端主機(jī)連接的兩個(gè)套接字描述符,所以執(zhí)行這樣的語(yǔ)句:
          FD_SET(usersockfd,&rdfdset);
          FD_SET(isosockfd,&rdfdset);
          然后調(diào)用select()返回描述符狀態(tài),此時(shí)描述符狀態(tài)被存儲(chǔ)進(jìn)描述符集,也就是set_fd數(shù)據(jù)結(jié)構(gòu)中。在圖1中我們看到所有的描述符位狀態(tài)都是 "0",在select()返回后,例如fd0可讀,則在readfds描述符集中fd0對(duì)應(yīng)的位上將狀態(tài)標(biāo)志設(shè)置為"1",如果fd1可寫,則 writefds描述符集中fd1對(duì)應(yīng)的位上將狀態(tài)標(biāo)志設(shè)置為"1",狀態(tài)異常的情況也也與此相同。在本例中,我們只關(guān)心兩個(gè)套接字描述符是否可寫,因此 執(zhí)行這樣的select()函數(shù):
          select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL)
          那么在select()返回后怎樣檢測(cè)set_fd數(shù)據(jù)結(jié)構(gòu)中描述符位的狀態(tài)呢?這就要調(diào)用函數(shù)FD_ISSET(),如果對(duì)應(yīng)文件描述符的狀態(tài)為"已準(zhǔn)備好"(即描述符位為"1"),則FD_ISSET()返回"1",否則返回"0"。
          -----------------------------------------------------------------
          if (FD_ISSET(usersockfd,&rdfdset)) {
          if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
          break; /* zero length means the host disconnected */
          write(isosockfd,buf,iolen);
          -----------------------------------------------------------------
          這一段代碼就實(shí)現(xiàn)從套接字usersockfd(用戶主機(jī))到套接字isosockfd(遠(yuǎn)端主機(jī))的無阻塞傳輸。而下一段代碼實(shí)現(xiàn)反方向的無阻塞傳輸:
          -----------------------------------------------------------------
          if (FD_ISSET(isosockfd,&rdfdset)) {
          if ((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
          break; /* zero length means the host disconnected */
          write(usersockfd,buf,iolen);
          -----------------------------------------------------------------
          這樣就通過proxy實(shí)現(xiàn)了用戶主機(jī)與遠(yuǎn)端主機(jī)之間的通信。
          對(duì)這段proxy代碼我只是寫了一些自己的理解,大多數(shù)是一些函數(shù)的用法,這些都是linux網(wǎng)絡(luò)編程中一些最基礎(chǔ)的知識(shí),如果有不對(duì)的地方,還請(qǐng)各位大蝦批評(píng)指正。





          posted on 2009-11-17 21:14 石頭@ 閱讀(757) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 永新县| 浦江县| 阿尔山市| 名山县| 沈阳市| 高邑县| 鲁山县| 博乐市| 宁波市| 南丹县| 扶沟县| 凤冈县| 保山市| 内江市| 玉门市| 元氏县| 介休市| 曲沃县| 宁河县| 玉树县| 曲靖市| 齐河县| 清苑县| 聂荣县| 合肥市| 交口县| 汕头市| 闽清县| 祁连县| 阜阳市| 平果县| 平遥县| 苍山县| 北海市| 温宿县| 于田县| 怀仁县| 乡宁县| 金门县| 江阴市| 孟州市|