Linux中BSD套介面開發的基礎介紹

來源:互聯網
上載者:User

這是關於如何用各種可以得到的介面為Linux開發網路程式的系列文章的第一篇。就像大多數Unix-based的作業系統一樣,Linux支援將TCP/IP作為本地的網路傳輸協議。在這個系列中,我們假定你已經比較熟悉Linux上的C編程和Linux的一些系統知識諸如signals,forking等等。

  這篇文章是關於如何用BSD套介面建立網路程式的基礎介紹 。在下一篇中,我們會解決涉及到建立(網路)deamon進程的問題。而且今後的文章我們還會涉及到使用遠端程序呼叫(RPC),以及用CORBA/distributed objects進行開發。

  一、TCP/IP的基礎介紹

  TCP/IP協議族允許兩個運行在同一台電腦或者由網路連接在一起的兩台電腦上的程式進行通訊。這個協議族是專門為了在不可靠的網路上進行通訊設計的。TCP/IP允許兩個基本的操作模式——連線導向的可靠的傳輸(指TCP)和不需連線的(connectionless)不可靠的傳輸(UDP)。

  TCP提供帶有對上層協議透明的中繼功能的,順序的,可靠的,雙向的(bi-directional),以串連為基礎的位元組傳輸串流。TCP將你的資訊分割成資料報(不大於64kb)並保證所有的資料報無誤的按照順序都到達目的地。由於以串連為基礎,所以一個虛擬串連必須在一個網路實體(network entity)和另一個之間進行通訊前建立。UDP相反則提供一個(非常快的)不需連線的不可靠訊息傳輸(訊息的大小是一個確定的最大長度)。

  為了使程式間可以相互連信,不論他們是在同一個機器(通過loopback介面)還是不同主機,每一個程式都必須有獨立的地址。

  TCP/IP地址由兩部分組成——用來辨別機器的IP地址和用來辨別在那台機器上的特定程式的連接埠地址。

  地址可以是點分(dotted-quad)符號形式的(如,127.0.0.1)或者是主機名稱形式的(如,www.csdn.net)。系統可以使用/etc/hosts或DNS網域名稱服務 (DNS)(如果可以獲得的話)進行主機名稱到點分符號地址(也就是IP地址)的轉換。

  連接埠從1號開始編號。1和IPP0RT_RESERVED(在/usr/include/netinet/in.h中定義,通常為1024)之間的段口號保留給系統使用(也就是說,你必須以root的身份建立一個網路服務來綁定這部分的連接埠)。

  最簡單的網路程式大都用的客戶-伺服器模型。一個服務進程等待一個客戶進程串連他。當串連建立時,伺服器代表客戶執行特定的任務,通常這這以後串連就中斷了。

 二、使用BSD套介面介面

  最通行的TCP/IP編程方法就是使用BSD套介面介面編程。通過它,網路端點(network endpoints)(IP地址和連接埠地址)以套介面(sockets)的形式出現。

  這套套介面IPC(interprocess communication,進程間通訊)設施(從4.2BSD開始引入)的設計是為了能讓網路程式的設計能夠獨立於不同的底層通訊設施。

  1、建立一個伺服器程式

  要使用BSD介面建立一個伺服器程式,你必須通過以下步驟:

  (1)通過函數socket()建立一個套介面
  (2)通過函數bind()綁定一個地址(IP地址和連接埠地址)。這一步確定了伺服器的位置,使用戶端知道如何訪問。
  (3)通過函數listem()監聽(listen)連接埠的新的串連請求。
  (4)通過函數accept()接受新的串連。

  通常,維護代表了客戶的請求可能需要花費相當長的一段時間。在處理一個請求時,接收和處理新的請求也應該是高效的。達到這種目的的最通常的做法是讓伺服器通過fork()函數拷貝一份自己的進程來接受新的串連。

  以下的例子顯示了伺服器是如何用C實現的:

/*
* Simple "Hello, World!" server
* Ivan Griffin

*/

/* Hellwolf Misty translated */

#include /* */
#include /* exit() */
#include /* memset(), memcpy() */
#include /* uname() */
#include
#include  /* socket(), bind(),
               listen(), accept() */
#include
#include
#include
#include /* fork(), write(), close() */

/*
* constants
*/
const char MESSAGE[] = "Hello, World!\n";
const int BACK_LOG = 5;

/*

*程式需要一個命令列參數:需要綁定的連接埠號碼
*/
int main(int argc, char *argv[])
{
  int serverSocket = 0,
    on = 0,
    port = 0,
    status = 0,
    childPid = 0;
  struct hostent *hostPtr = NULL;
  char  hostname[80] = "";
  struct sockaddr_in serverName = { 0 };

  if (2 != argc)
  {
    fprintf(stderr, "Usage: %s \n",
argv[0]);
    exit(1);
  }
  port = atoi(argv[1]);
/ *
*socket()系統調用,帶有三個參數:
*    1、參數domain指明通訊域,如PF_UNIX(unix域),PF_INET(IPv4),
*      PF_INET6(IPv6)等
*    2、type指明通訊類型,最常用的如SOCK_STREAM(連線導向可靠方式,
*      比如TCP)、SOCK_DGRAM(非連線導向的非可靠方式,比如UDP)等。
*    3、參數protocol指定需要使用的協議。雖然可以對同一個協議
*      家族(protocol family)(或者說通訊域(domain))指定不同的協議
*      參數,但是通常只有一個。對於TCP參數可指定為IPPROTO_TCP,對於
*      UDP可以用IPPROTO_UDP。你不必顯式制定這個參數,使用0則根據前
*      兩個參數使用預設的協議。  
*/
  serverSocket = socket(PF_INET, SOCK_STREAM,
IPPROTO_TCP);
  if (-1 == serverSocket)
  {
    perror("socket()");
    exit(1);
  }
 
  /*
   * 一旦套介面被建立,它的運作機制可以通過套介面選項(socket option)進行修改。   
   */

  /*
   * SO_REUSEADDR選項的設定將套介面設定成重新使用舊的地址(IP地址加連接埠號碼)而不等待
   * 注意:在Linux系統中,如果一個socket綁定了某個連接埠,該socket正常關閉或程式退出後,
   * 在一段時間內該連接埠依然保持被綁定的狀態,其他程式(或者重新啟動 的原程式)無法綁定該連接埠。
   *
   * 下面的調用中:SOL_SOCKET代表對SOCKET層進行操作
   */
  on = 1;

  status = setsockopt(serverSocket, SOL_SOCKET,
SO_REUSEADDR,
    (const char *) &on, sizeof(on));

  if (-1 == status)
  {
    perror("setsockopt(...,SO_REUSEADDR,...)");
  }

  /* 當串連中斷時,需要延遲關閉(linger)以保證所有資料都
   * 被傳輸,所以需要開啟SO_LINGER這個選項
   * linger的結構在/usr/include/linux/socket.h中定義:
   *  struct linger
   *  {
   *   int l_onoff;  /* Linger active */
   *   int l_linger; /* How long to linger */
   *  };

  *  如果l_onoff為0,則延遲關閉特性就被取消。如果非零,則允許套介面延遲關閉。
   *  l_linger欄位則指明延遲關閉的時間
   */

  {
    struct linger linger = { 0 };

    linger.l_onoff = 1;
    linger.l_linger = 30;
    status = setsockopt(serverSocket,
SOL_SOCKET, SO_LINGER,
(const char *) &linger,
sizeof(linger));

    if (-1 == status)
    {
      perror("setsockopt(...,SO_LINGER,...)");
    }
  }

  /*
   * find out who I am
   */

  status = gethostname(hostname,
sizeof(hostname));
  if (-1 == status)
  {
    perror("gethostname()");
    exit(1);
  }

  hostPtr = gethostbyname(hostname);
  if (NULL == hostPtr)
  {
    perror("gethostbyname()");
    exit(1);
  }

  (void) memset(&serverName, 0,
sizeof(serverName));
  (void) memcpy(&serverName.sin_addr,
hostPtr->h_addr,
hostPtr->h_length);
   /*
   *h_addr是h_addr_list[0]的同義字,
   *  h_addr_list是一組地址的數組
   *長度為4(byte)代表一個IP地址的長度
   */
/*
* 為了使伺服器綁定本機所有的IP地址,
* 上面一行代碼需要用下面的代碼代替
* serverName.sin_addr.s_addr=htonl(INADDR_ANY);
*/

  serverName.sin_family = AF_INET;
  /* htons:h(host byteorder,主機位元組序)
   *     to n(network byteorder,網路位元組序
   *     s(short類型)
   */
  serverName.sin_port = htons(port);
/* 在一個地址(本例中的serverSocket)被建立後
* 它就應該被綁定到我們獲得的套介面。
*/
  status = bind(serverSocket,
(struct sockaddr *) &serverName,
    sizeof(serverName));
  if (-1 == status)
  {
    perror("bind()");
    exit(1);
  }
/* 現在套介面就可以被用來監聽新的串連。
* BACK_LOG指定了未決串連監聽隊列(listen queue for pending connections)
* 的最大長度。當一個新的串連到達,而隊列已滿的話,客戶就會得到串連拒絕錯誤。

* (這就是dos拒絕服務的攻擊的基礎)。
*/
  status = listen(serverSocket, BACK_LOG);
  if (-1 == status)
  {
    perror("listen()");
    exit(1);
  }
/* 從這裡開始,套介面就開始準備接受請求,並為他們服務。
* 本例子是用for迴圈來達到這個目的。一旦串連被接受(accpepted),
* 伺服器可以通過指標獲得客戶的地址以便進行一些諸如記錄客戶登陸之類的
* 任務。
  for (;;)
  {
    struct sockaddr_in clientName = { 0 };
    int slaveSocket, clientLength =
sizeof(clientName);

    (void) memset(&clientName, 0,
sizeof(clientName));

    slaveSocket = accept(serverSocket,
(struct sockaddr *) &clientName,
&clientLength);
    if (-1 == slaveSocket)
    {
      perror("accept()");
      exit(1);
    }

    childPid = fork();

    switch (childPid)
    {
    case -1: /* ERROR */
      perror("fork()");
      exit(1);

    case 0: /* child process */

      close(serverSocket);

      if (-1 == getpeername(slaveSocket,
(struct sockaddr *) &clientName,
&clientLength))
      {
        perror("getpeername()");
      }
      else
      {
      printf("Connection request from %s\n",
          inet_ntoa(clientName.sin_addr));
      }

      /*
       * Server application specific code
       * goes here, e.g. perform some
       * action, respond to client etc.
       */
      write(slaveSocket, MESSAGE,
  strlen(MESSAGE));
       /* 也可以使用帶緩衝的ANSI函數fprint,
        * 只要你記得必要時用fflush重新整理緩衝
        */
      close(slaveSocket);
      exit(0);

    default: /* parent process */
      close(slaveSocket);/* 這是一個非常好的習慣
                * 父進程關閉子進程的套介面描述符
                * 正如上面的子進程關閉父進程的套介面描述符。
                */              
    }
  }

  return 0;
}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.