Linux下各類TCP網路伺服器的實現原始碼

來源:互聯網
上載者:User

大家都知道各類網路伺服器程式的編寫步驟,並且都知道網路伺服器就兩大類:迴圈服務和並發服務。這裡附上原始碼來個小結吧。
首先,迴圈網路伺服器編程實現的步驟是這樣的: 這種伺服器模型是典型迴圈服務,如果不加上多進程/線程技術,此種服務輸送量有限,大家都可以看到,如果前一個串連服務資料沒有收發完畢後面的串連沒辦法處理。所以一般有多進程技術,對一個新串連啟用一個新進程去處理,而監聽socket繼續監聽。
/************關於本文檔******************************************** *filename: Linux下各類TCP網路伺服器的實現原始碼 *purpose: 記錄Linux下各類tcp服務程式原始碼 *wrote by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.9999mb.com) Linux愛好者 Linux知識傳播者 SOHO族 開發人員 最擅長C語言 *date time:2006-07-04 22:00:00 *Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途 * 但請遵循GPL *Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力 *********************************************************************/
一個迴圈TCP服務原始碼(因為用fork進行多進程服務了,所以這種服務現實中也有用)如下:

  1. /*----------------------原始碼開始--------------------------------------------*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. #include <sys/wait.h>
  10. /*********************************************************************
  11. *filename: cycletcpserver.c
  12. *purpose: 迴圈tcp服務端程式
  13. *tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.9999mb.com)
  14. Linux愛好者 Linux知識傳播者 SOHO族 開發人員 最擅長C語言
  15. *date time:2006-07-04 22:00:00
  16. *Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
  17. * 但請遵循GPL
  18. *Thanks to: Google.com
  19. *********************************************************************/
  20. int main(int argc, char ** argv)
  21. {
  22.     int sockfd,new_fd; /* 監聽socket: sock_fd,資料轉送socket: new_fd */
  23.     struct sockaddr_in my_addr; /* 本機地址資訊 */
  24.     struct sockaddr_in their_addr; /* 客戶地址資訊 */
  25.     unsigned int sin_size, myport, lisnum;
  26.     if(argv[1])  myport = atoi(argv[1]);
  27.     else myport = 7838;
  28.     if(argv[2])  lisnum = atoi(argv[2]);
  29.     else lisnum = 2;
  30.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  31.         perror("socket");
  32.         exit(1);
  33.     }
  34.     my_addr.sin_family=PF_INET;
  35.     my_addr.sin_port=htons(myport);
  36.     my_addr.sin_addr.s_addr = INADDR_ANY;
  37.     bzero(&(my_addr.sin_zero), 0);
  38.     if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
  39.         perror("bind");
  40.         exit(1);
  41.     }
  42.     if (listen(sockfd, lisnum) == -1) {
  43.         perror("listen");
  44.         exit(1);
  45.     }
  46.     while(1) {
  47.         sin_size = sizeof(struct sockaddr_in);
  48.         if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
  49.             perror("accept");
  50.             continue;
  51.         }
  52.         printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
  53.         if (!fork()) { /* 子進程程式碼片段 */
  54.             if (send(new_fd, "Hello, world!\n", 14, 0) == -1) {
  55.                 perror("send");
  56.                 close(new_fd);
  57.                 exit(0);
  58.             }
  59.         }
  60.         close(new_fd); /*父進程不再需要該socket*/
  61.         waitpid(-1,NULL,WNOHANG);/*等待子進程結束,清除子進程所佔用資源*/
  62.     }
  63. }
  64. /*----------------------原始碼結束--------------------------------------------*/

複製代碼

一個測試用戶端代碼如下:

  1. /*----------------------原始碼開始--------------------------------------------*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <netdb.h>
  7. #include <sys/types.h>
  8. #include <netinet/in.h>
  9. #include <sys/socket.h>
  10. #define MAXDATASIZE 100 /*每次最大資料轉送量 */
  11. /*********************************************************************
  12. *filename: cycletcpclient.c
  13. *purpose: 迴圈tcp用戶端程式
  14. *tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.9999mb.com)
  15. Linux愛好者 Linux知識傳播者 SOHO族 開發人員 最擅長C語言
  16. *date time:2006-07-04 22:20:00
  17. *Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
  18. * 但請遵循GPL
  19. *Thanks to: Google.com
  20. *Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
  21. *********************************************************************/
  22. int main(int argc, char *argv[])
  23. {
  24.     int sockfd, numbytes;
  25.     char buf[MAXDATASIZE];
  26.     struct hostent *he;
  27.     struct sockaddr_in their_addr;
  28.     unsigned int myport;
  29.     if(argv[2]) myport = atoi(argv[2]);
  30.     else myport = 7838;
  31.     if (argc != 3) {
  32.         fprintf(stderr,"usage: %s hostname port\n", argv[0]);
  33.         exit(1);
  34.     }
  35.     if((he=gethostbyname(argv[1]))==NULL) {
  36.         herror("gethostbyname");
  37.         exit(1);
  38.     }
  39.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  40.         perror("socket");
  41.         exit(1);
  42.     }
  43.     their_addr.sin_family=PF_INET;
  44.     their_addr.sin_port=htons(myport);
  45.     their_addr.sin_addr = *((struct in_addr *)he->h_addr);
  46.     bzero(&(their_addr.sin_zero),0);
  47.     if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
  48.         perror("connect");
  49.         exit(1);
  50.     }
  51.     if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
  52.         perror("recv");
  53.         exit(1);
  54.     }
  55.     buf[numbytes] = 0;
  56.     printf("Received: %s\n",buf);
  57.     close(sockfd);
  58.     return 0;
  59. }
  60. /*----------------------原始碼結束--------------------------------------------*/

複製代碼

用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分別編譯上述代碼後運行情況如下: 服務端運行顯示:

administrator@ubuzlf:/data/example/c$ ./tcpserver server: got connection from 127.0.0.1 server: got connection from 127.0.0.1 server: got connection from 127.0.0.1

用戶端運行顯示:

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838 Received: Hello, world!
administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838 Received: Hello, world!
administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838 Received: Hello, world!

不得不說的一個概念性問題:阻塞與非阻塞 在阻塞服務中,當伺服器運行到accept語句而沒有客戶串連服務要求到來,那麼會發生什麼情況? 這時伺服器就會停止在accept語句上等待串連服務要求的到來;同樣,當程式運行到接收資料語句recv時,如果沒有資料可以讀取,則程式同樣會停止在接收語句上。這種情況稱為阻塞(blocking)。 但如果你希望伺服器僅僅注意檢查是否有客戶在等待串連,有就接受串連;否則就繼續做其他事情,則可以通過將 socket設定為非阻塞方式來實現:非阻塞socket在沒有客戶在等待時就使accept調用立即返回 。 通過設定socket為非阻塞方式,可以實現“輪詢”若干socket。當企圖從一個沒有資料等待處理的非阻塞socket讀入資料時,函數將立即返回,並且傳回值置為-1,並且errno置為EWOULDBLOCK。但是這種“輪詢”會使CPU處於忙等待方式,從而降低效能。考慮到這種情況,假設你希望伺服器監聽串連服務要求的同時從已經建立的串連讀取資料,你也許會想到用一個accept語句和多個recv()語句,但是由於accept及recv都是會阻塞的,所以這個想法顯然不會成功。 調用非阻塞的socket會大大地浪費系統資源。而調用select()會有效地解決這個問題,它允許你把進程本身掛起來,而同時使系統核心監聽所要求的一組檔案描述符的任何活動,只要確認在任何被監控的檔案描述符上出現活動,select()調用將返回指示該檔案描述符已準備好的資訊,從而實現了為進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費CPU開銷。
其次,並發伺服器,在上述cycletcpserver.c中,由於使用了fork技術也可以稱之為並發伺服器,但這種伺服器並不是真正意義上的IO多工並發伺服器,並且由於沒有處理阻塞問題,實際應用有各種各樣的問題。
一個典型IO多工單進程並發伺服器流程如下: /*IO多工並發服務流程圖*/ 下面是一個示範IO多工來源程式,是一個連接埠轉寄程式,但它的用處相當大,實際應用中的各類代理軟體或連接埠映射軟體都是基於這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎上實現的。原始碼如下:

  1. /*----------------------原始碼開始--------------------------------------------*/
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/time.h>
  6. #include <sys/types.h>
  7. #include <string.h>
  8. #include <signal.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11. #include <arpa/inet.h>
  12. #include <errno.h>
  13. static int forward_port;
  14. #undef max
  15. #define max(x,y) ((x) > (y) ? (x) : (y))
  16. /*************************關於本文檔************************************
  17. *filename: tcpforwardport.c
  18. *purpose: 示範了select的用法,這是一個極好的代理軟體核心,專門作連接埠映射用
  19. *tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.9999mb.com)
  20. Linux愛好者 Linux知識傳播者 SOHO族 開發人員 最擅長C語言
  21. *date time:2006-07-05 19:00:00
  22. *Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
  23. * 但請遵循GPL
  24. *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊裡提供了這份原始碼
  25. *Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
  26. *********************************************************************/
  27. static int listen_socket (int listen_port) {
  28.     struct sockaddr_in a;
  29.     int s;
  30.     int yes;
  31.     if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
  32.         perror ("socket");
  33.         return -1;
  34.     }
  35.     yes = 1;
  36.     if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) <
  37. 0) {
  38.         perror ("setsockopt");
  39.         close (s);
  40.         return -1;
  41.     }
  42.     memset (&a, 0, sizeof (a));
  43.     a.sin_port = htons (listen_port);
  44.     a.sin_family = AF_INET;
  45.     if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
  46.         perror ("bind");
  47.         close (s);
  48.         return -1;
  49.     }
  50.     printf ("accepting connections on port %d\n", (int) listen_port);
  51.     listen (s, 10);
  52.     return s;
  53. }
  54. static int connect_socket (int connect_port, char *address) {
  55.     struct sockaddr_in a;
  56.     int s;
  57.     if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
  58.         perror ("socket");
  59.         close (s);
  60.         return -1;
  61.     }
  62.     memset (&a, 0, sizeof (a));
  63.     a.sin_port = htons (connect_port);
  64.     a.sin_family = AF_INET;
  65.     if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
  66.         perror ("bad IP address format");
  67.         close (s);
  68.         return -1;
  69.     }
  70.     if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
  71.         perror ("connect()");
  72.         shutdown (s, SHUT_RDWR);
  73.         close (s);
  74.         return -1;
  75.     }
  76.     return s;
  77. }
  78. #define SHUT_FD1 { \
  79.     if (fd1 >= 0) {   \
  80.         shutdown (fd1, SHUT_RDWR);  \
  81.         close (fd1);  \
  82.         fd1 = -1;     \
  83.     }   \
  84. }
  85. #define SHUT_FD2 { \
  86.     if (fd2 >= 0) {   \
  87.         shutdown (fd2, SHUT_RDWR);  \
  88.         close (fd2);  \
  89.         fd2 = -1;     \
  90.     }   \
  91. }
  92. #define BUF_SIZE 1024
  93. int main (int argc, char **argv) {
  94.     int h;
  95.     int fd1 = -1, fd2 = -1;
  96.     char buf1[BUF_SIZE], buf2[BUF_SIZE];
  97.     int buf1_avail, buf1_written;
  98.     int buf2_avail, buf2_written;
  99.     if (argc != 4) {
  100.         fprintf (stderr, "Usage\n\tfwd   \n");
  101.         exit (1);
  102.     }
  103.     signal (SIGPIPE, SIG_IGN);
  104.     forward_port = atoi (argv[2]);
  105.     /*建立監聽socket*/
  106.     h = listen_socket (atoi (argv[1]));
  107.     if (h < 0) exit (1);
  108.     for (;;) {
  109.         int r, nfds = 0;
  110.         fd_set rd, wr, er;
  111.         FD_ZERO (&rd);
  112.         FD_ZERO (&wr);
  113.         FD_ZERO (&er);
  114.         FD_SET (h, &rd);
  115.         /*把監聽socket和可讀socket三個一起放入select的可讀控制代碼列表裡*/
  116.         nfds = max (nfds, h);
  117.         if (fd1 > 0 && buf1_avail < BUF_SIZE) {
  118.             FD_SET (fd1, &rd);
  119.             nfds = max (nfds, fd1);
  120.         }
  121.         if (fd2 > 0 && buf2_avail < BUF_SIZE) {
  122.             FD_SET (fd2, &rd);
  123.             nfds = max (nfds, fd2);
  124.         }
  125.         /*把可寫socket兩個一起放入select的可寫控制代碼列表裡*/
  126.         if (fd1 > 0 && buf2_avail - buf2_written > 0) {
  127.             FD_SET (fd1, &wr);
  128.             nfds = max (nfds, fd1);
  129.         }
  130.         if (fd2 > 0 && buf1_avail - buf1_written > 0) {
  131.             FD_SET (fd2, &wr);
  132.             nfds = max (nfds, fd2);
  133.         }
  134.         /*把有異常資料的socket兩個一起放入select的異常控制代碼列表裡*/
  135.         if (fd1 > 0) {
  136.             FD_SET (fd1, &er);
  137.             nfds = max (nfds, fd1);
  138.         }
  139.         if (fd2 > 0) {
  140.             FD_SET (fd2, &er);
  141.             nfds = max (nfds, fd2);
  142.         }
  143.         /*開始select*/
  144.         r = select (nfds + 1, &rd, &wr, &er, NULL);
  145.         if (r == -1 && errno == EINTR) continue;
  146.         if (r < 0) {
  147.             perror ("select()");
  148.             exit (1);
  149.         }
  150.         /*處理新串連*/
  151.         if (FD_ISSET (h, &rd)) {
  152.             unsigned int l;
  153.             struct sockaddr_in client_address;
  154.             memset (&client_address, 0, l = sizeof (client_address));
  155.             r = accept (h, (struct sockaddr *)&client_address, &l);
  156.             if (r < 0) {
  157.                 perror ("accept()");
  158.             } else {
  159.                 /*關閉原有串連,把新串連作為fd1,同時串連新的目標fd2*/
  160.                 SHUT_FD1;
  161.                 SHUT_FD2;
  162.                 buf1_avail = buf1_written = 0;
  163.                 buf2_avail = buf2_written = 0;
  164.                 fd1 = r;
  165.                 fd2 = connect_socket (forward_port, argv[3]);
  166.                 if (fd2 < 0) {
  167.                     SHUT_FD1;
  168.                 } else
  169.                     printf ("connect from %s\n", inet_ntoa(client_address.sin_addr));
  170.             }
  171.         }
  172.         /* NB: read oob data before normal reads */
  173.         if (fd1 > 0)
  174.         if (FD_ISSET (fd1, &er)) {
  175.             char c;
  176.             errno = 0;
  177.             r = recv (fd1, &c, 1, MSG_OOB);
  178.             if (r < 1) {
  179.                 SHUT_FD1;
  180.             } else
  181.                 send (fd2, &c, 1, MSG_OOB);
  182.         }
  183.         if (fd2 > 0)
  184.         if (FD_ISSET (fd2, &er)) {
  185.             char c;
  186.             errno = 0;
  187.             r = recv (fd2, &c, 1, MSG_OOB);
  188.             if (r < 1) {
  189.                 SHUT_FD1;
  190.             } else
  191.                 send (fd1, &c, 1, MSG_OOB);
  192.         }
  193.         /* NB: read data from fd1 */
  194.         if (fd1 > 0)
  195.         if (FD_ISSET (fd1, &rd)) {
  196.             r = read (fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
  197.             if (r < 1) {
  198.                 SHUT_FD1;
  199.             } else
  200.                 buf1_avail += r;
  201.         }
  202.         /* NB: read data from fd2 */
  203.         if (fd2 > 0)
  204.         if (FD_ISSET (fd2, &rd)) {
  205.             r = read (fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
  206.             if (r < 1) {
  207.                 SHUT_FD2;
  208.             } else
  209.                 buf2_avail += r;
  210.         }
  211.         /* NB: write data to fd1 */
  212.         if (fd1 > 0)
  213.         if (FD_ISSET (fd1, &wr)) {
  214.             r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written);
  215.             if (r < 1) {
  216.                 SHUT_FD1;
  217.             } else
  218.                 buf2_written += r;
  219.         }
  220.         /* NB: write data to fd1 */
  221.         if (fd2 > 0)
  222.         if (FD_ISSET (fd2, &wr)) {
  223.             r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written);
  224.             if (r < 1) {
  225.                 SHUT_FD2;
  226.             } else
  227.                 buf1_written += r;
  228.         }
  229.         /* check if write data has caught read data */
  230.         if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0;
  231.         if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0;
  232.         /* one side has closed the connection, keep writing to the other side until empty */
  233.         if (fd1 < 0 && buf1_avail - buf1_written == 0) {
  234.             SHUT_FD2;
  235.         }
  236.         if (fd2 < 0 && buf2_avail - buf2_written == 0) {
  237.             SHUT_FD1;
  238.         }
  239.     }
  240.     return 0;
  241. }
  242. /*----------------------原始碼結束--------------------------------------------*/
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.