1. 程式人生 > >Linux 環境程序間通訊(六) 套介面(轉)

Linux 環境程序間通訊(六) 套介面(轉)

轉自https://www.ibm.com/developerworks/cn/linux/l-ipc/part6/, 作者:鄭彥興

一個套介面可以看作是程序間通訊的端點(endpoint),每個套介面的名字都是唯一的(唯一的含義是不言而喻的),其他程序可以發現、連線並且 與之通訊。通訊域用來說明套介面通訊的協議,不同的通訊域有不同的通訊協議以及套介面的地址結構等等,因此,建立一個套介面時,要指明它的通訊域。比較常 見的是unix域套介面(採用套介面機制實現單機內的程序間通訊)及網際通訊域。

linux目前的網路核心程式碼主要基於伯克利的BSD的unix實現,整個結構採用的是一種面向物件的分層機制。層與層之間有嚴格的介面定義。這裡我們引用[1]中的一個圖表來描述linux支援的一些通訊協議:


 

我們這裡只關心IPS,即因特網協議族,也就是通常所說的TCP/IP網路。我們這裡假設讀者具有網路方面的一些背景知識,如瞭解網路的分層結構,通常所說的7層結構;瞭解IP地址以及路由的一些基本知識。

目前linux網路API是基於BSD套介面的(系統V提供基於流I/O子系統的使用者介面,但是linux核心目前不支援流I/O子系 統)。套介面可以說是網路程式設計中一個非常重要的概念,linux以檔案的形式實現套介面,與套介面相應的檔案屬於sockfs特殊檔案系統,建立一個套接 口就是在sockfs中建立一個特殊檔案,並建立起為實現套介面功能的相關資料結構。換句話說,對每一個新建立的BSD套介面,linux核心都將在 sockfs特殊檔案系統中建立一個新的inode。描述套介面的資料結構是socket,將在後面給出。

下面是在網路程式設計中比較重要的幾個資料結構,讀者可以在後面介紹程式設計API部分再回過頭來了解它們。

套介面是由socket資料結構代表的,形式如下: 


  1. struct socket
  2. {
  3. socket_state state; /* 指明套介面的連線狀態,一個套介面的連線狀態可以有以下幾種
  4. 套介面是空閒的,還沒有進行相應的埠及地址的繫結;還沒有連線;正在連線中;已經連線;正在解除連線。 */
  5.   unsigned long flags;
  6.   struct proto_ops ops; /* 指明可對套介面進行的各種操作 */
  7.   struct inode inode; /* 指向sockfs檔案系統中的相應inode *
    /
  8.   struct fasync_struct *fasync_list; /* Asynchronous wake up list */
  9.   struct file *file; /* 指向sockfs檔案系統中的相應檔案 */
  10. struct sock sk; /* 任何協議族都有其特定的套介面特性,該域就指向特定協議族的套介面對
  11. 象。 */
  12.   wait_queue_head_t wait;
  13.   short type;
  14.   unsigned char passcred;
  15. };

由於歷史的緣故,在bind、connect等系統呼叫中,特定於協議的套介面地址結構指標都要強制轉換成該通用的套介面地址結構指標。結構形式如下: 


  1. struct sockaddr {
  2.     sa_family_t    sa_family;    /* address family, AF_xxx    */
  3.     char        sa_data[14];    /* 14 bytes of protocol address    */
  4. };


  1. struct sockaddr_in
  2.   {
  3.     __SOCKADDR_COMMON (sin_);    /* 描述協議族 */
  4.     in_port_t sin_port;            /* 埠號 */
  5.     struct in_addr sin_addr;        /* 因特網地址 */
  6.     /* Pad to size of `struct sockaddr'. */
  7.     unsigned char sin_zero[sizeof (struct sockaddr) -
  8.              __SOCKADDR_COMMON_SIZE -
  9.              sizeof (in_port_t) -
  10.              sizeof (struct in_addr)];
  11.   };

一般來說,讀者最關心的是前三個域,即通訊協議、埠號及地址。

int socket( int domain, int type, int ptotocol);

引數domain指明通訊域,如PF_UNIX(unix域),PF_INET(IPv4),PF_INET6(IPv6) 等;type指明通訊型別,如SOCK_STREAM(面向連線方式)、SOCK_DGRAM(非面向連線方式)等。一般來說,引數protocol可設 置為0,除非用在原始套介面上(原始套介面有一些特殊功能,後面還將介紹)。

注:socket()系統呼叫為套介面在sockfs檔案系統中分配一個新的檔案和dentry對 象,並通過檔案描述符把它們與呼叫程序聯絡起來。程序可以像訪問一個已經開啟的檔案一樣訪問套介面在sockfs中的對應檔案。但程序絕不能呼叫 open()來訪問該檔案(sockfs檔案系統沒有可視安裝點,其中的檔案永遠不會出現在系統目錄樹上),當套介面被關閉時,核心會自動刪除 sockfs中的inodes。

根據傳輸層協議(TCP、UDP)的不同,客戶機及伺服器的處理方式也有很大不同。但是,不管通訊雙方使用何種傳輸協議,都需要一種標識自己的機制。

通訊雙方一般由兩個方面標識:地址和埠號(通常,一個IP地址和一個埠號常常被稱為一個套介面)。根據地址可以定址到主機,根據埠號則可以定址到主機提供特定服務的程序,實際上,一個特定的埠號代表了一個提供特定服務的程序。

對於使用TCP傳輸協議通訊方式來說,通訊雙方需要給自己繫結一個唯一標識自己的套介面,以便建立連線;對於使用UDP傳輸協議,只需 要伺服器繫結一個標識自己的套介面就可以了,使用者則不需要繫結(在需要時,如呼叫connect時[注1],核心會自動分配一個本地地址和本地埠號)。 繫結操作由系統呼叫bind()完成:

int bind( int sockfd, const struct sockaddr * my_addr, socklen_t my_addr_len)

第二個引數對於Ipv4來說,實際上需要填充的結構是struct sockaddr_in,前面已經介紹了該結構。這裡只想強調該結構的第一個域,它表明該套介面使用的通訊協議,如AF_INET。聯絡socket系統 呼叫的第一個引數,讀者可能會想到PF_INET與AF_INET究竟有什麼不同?實際上,原來的想法是每個通訊域(如PF_INET)可能對應多個協議 (如AF_INET),而事實上支援多個協議的通訊域一直沒有實現。因此,在linux核心中,AF_***與PF_***被定義為同一個常數,因此,在 程式設計時可以不加區分地使用他們。

注1:在採用非面向連線通訊方式時,也會用到connect()呼叫,不過與在面向連線中的 connect()呼叫有本質的區別:在非面向連線通訊中,connect呼叫只是先設定一下對方的地址,核心為本地套介面記下對方的地址,然後採用 send()來發送資料,這樣避免每次傳送時都要提供相同的目的地址。其中的connect()呼叫不涉及握手過程;而在面向連線的通訊方式 中,connect()要完成一個嚴格的握手過程。

對於採用面向連線的傳輸協議TCP實現通訊來說,一個比較重要的步驟就是通訊雙方建立連線(如果採用udp傳輸協議則不需要),由系統呼叫connect()完成:

int connect( int sockfd, const struct sockaddr * servaddr, socklen_t addrlen)

第一個引數為本地呼叫socket後返回的描述符,第二個引數為伺服器的地址結構指標。connect()向指定的套介面請求建立連線。

注:與connect()相對應,在伺服器端,通過系統呼叫listen(),指定伺服器端的套介面為監聽套介面,監聽每一個向伺服器套介面發出的連線請求,並通過握手機制建立連線。核心為listen()維護兩個佇列:已完成連線佇列和未完成連線佇列。

伺服器端通過監聽套介面,為所有連線請求建立了兩個佇列:已完成連線佇列和未完成連線佇列(每個監聽套介面都對應這樣兩個佇列,當然, 一般伺服器只有一個監聽套介面)。通過accept()呼叫,伺服器將在監聽套介面的已連線佇列頭中,返回用於代表當前連線的套介面描述字。

int accept( int sockfd, struct sockaddr * cliaddr, socklen_t * addrlen)

第一個引數指明哪個監聽套介面,一般是由listen()系統呼叫指定的(由於每個監聽套介面都對應已連線和未連線兩個佇列,因此它的 內部機制實質是通過sockfd指定在哪個已連線佇列頭中返回一個用於當前客戶的連線,如果相應的已連線佇列為空,accept進入睡眠)。第二個引數指 明客戶的地址結構,如果對客戶的身份不感興趣,可指定其為空。

注:對於採用TCP傳輸協議進行通訊的伺服器和客戶機來說,一定要經過客戶請求建立連線,伺服器接受連線請求這一過程;而對採用UDP傳輸協議的通訊雙方則不需要這一步驟。

(5)通訊

客戶機可以通過套介面接收伺服器傳過來的資料,也可以通過套介面向伺服器傳送資料。前面所有的準備工作(建立套介面、繫結等操作)都是為這一步驟準備的。

常用的從套介面中接收資料的呼叫有:recv、recvfrom、recvmsg等,常用的向套介面中傳送資料的呼叫有send、sendto、sendmsg等。


  1. int recv(int s, void *
  2.         buf, size_t 
  3.         len, int 
  4.         flags)
  5. int recvfrom(int s, void *
  6.         buf, size_t 
  7.         len, int 
  8.         flags, struct sockaddr *
  9.         from, socklen_t *
  10.         fromlen)
  11. int recvmsg(int s, struct msghdr *
  12.         msg, int 
  13.         flags)
  14. int send(int s,const void *
  15.         msg, size_t 
  16.         len, int 
  17.         flags)
  18. int sendto(int s, const void *
  19.         msg, size_t 
  20.         len, int 
  21.         flags const struct sockaddr *
  22.         to, socklen_t 
  23.         tolen)
  24. int sendmsg(int s, const struct msghdr *
  25.         msg, int 
  26.         flags)

這裡不再對這些呼叫作具體的說明,只想強調一下,recvfrom()以及recvmsg()可用於面向連線的套介面,也可用於面向非 連線的套介面;而recv()一般用於面向連線的套介面。另外,在呼叫了connect()之後,就應給呼叫send()而不是sendto()了,因為 呼叫了connect之後,目標就已經確定了。

前面講到,socket()系統呼叫返回套介面描述字,實際上它是一個檔案描述符。所以,可以對套介面進行通常的讀寫操作,即使用 read()及write()方法。在實際應用中,由於面向連線的通訊(採用TCP傳輸協議)是可靠的,同時又保證位元組流原有的順序,所以更適合用 read及write方法。而非面向連線的通訊(採用UDP傳輸協議)是不可靠的,位元組流也不一定保持原有的順序,所以一般不宜用read及write方 法。

由close()來完成此項功能,它唯一的引數是套介面描述字,不再贅述。

到處可以發現基於套介面的客戶機及伺服器程式,這裡不再給出完整的範例程式碼,只是給出它們的典型呼叫程式碼,並給出簡要說明。


  1. ... ...
  2. int listen_fd, connect_fd;
  3. struct sockaddr_in serv_addr, client_addr;
  4. ... ...
  5. listen_fd = socket ( PF_INET, SOCK_STREAM, 0 );
  6. /* 建立網際Ipv4域的(由PF_INET指定)面向連線的(由SOCK_STREAM指定,
  7. 如果建立非面向連線的套介面則指定為SOCK_DGRAM)
  8. 的套介面。第三個引數0表示由核心確定預設的傳輸協議,
  9. 對於本例,由於建立的是可靠的面向連線的基於流的套介面,
  10. 核心將選擇TCP作為本套介面的傳輸協議) */
  11. bzero( &serv_addr, sizeof(serv_addr) );
  12. serv_addr.sin_family = AF_INET ; /* 指明通訊協議族 */
  13. serv_addr.sin_port = htons( 49152 ) ; /* 分配埠號 */
  14. inet_pton(AF_INET, " 192.168.0.11", &serv_addr.sin_sddr) ;
  15. /* 分配地址,把點分十進位制IPv4地址轉化為32位二進位制Ipv4地址。 */
  16. bind( listen_fd, (struct sockaddr*) serv_addr, sizeof ( struct sockaddr_in )) ; 
  17. /* 實現繫結操作 */
  18. listen( listen_fd, max_num) ; 
  19. /* 套介面進入偵聽狀態,max_num規定了核心為此套介面排隊的最大連線個數 */
  20. for( ; ; ) {
  21. ... ...
  22. connect_fd = accept( listen_fd, (struct sockaddr*)client_addr, &len ) ; /* 獲得連線fd. */
  23. ... ...                    /* 傳送和接收資料 */
  24. }

注:埠號的分配是有一些慣例的,不同的埠號對應不同的服務或程序。比如一般都把埠號21分配給 FTP伺服器的TCP/IP實現。埠號一般分為3段,0-1023(受限的眾所周知的埠,由分配數值的權威機構IANA管 理),1024-49151(可以從IANA那裡申請註冊的埠),49152-65535(臨時埠,這就是為什麼程式碼中的埠號為49152)。

對於多位元組整數在記憶體中有兩種儲存方式:一種是低位元組在前,高位元組在後,這樣的儲存順序被稱為低端位元組序(little- endian);高位元組在前,低位元組在後的儲存順序則被稱為高階位元組序(big-endian)。網路協議在處理多位元組整數時,採用的是高階位元組序,而不 同的主機可能採用不同的位元組序。因此在程式設計時一定要考慮主機位元組序與網路位元組序間的相互轉換。這就是程式中使用htons函式的原因,它返回網路位元組序的 整數。


  1. ... ...
  2. int socket_fd;
  3. struct sockaddr_in serv_addr ;
  4. ... ...
  5. socket_fd = socket ( PF_INET, SOCK_STREAM, 0 );
  6. bzero( &serv_addr, sizeof(serv_addr) );
  7. serv_addr.sin_family = AF_INET ; /* 指明通訊協議族 */
  8. 相關推薦

    Linux 環境程序通訊 介面()

    轉自https://www.ibm.com/developerworks/cn/linux/l-ipc/part6/, 作者:鄭彥興一個套介面可以看作是程序間通訊的端點(endpoint),每個套介面的名字都是唯一的(唯一的含義是不言而喻的),其他程序可以發現、連線並且 與之通訊。通訊域用來說明套介面通訊的協

    Linux環境程序通訊 訊息佇列()

    轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part3/, 作者:鄭彥興訊息佇列(也叫做報文佇列)能夠克服早期unix通訊機制的一些缺點。作為早期unix通訊機制之一的訊號能夠傳送的資訊量有限,後來雖然 POSIX 1003.1b在訊號的實時性方面作了

    Linux環境程序通訊: 共享記憶體()

    轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html, 作者:鄭彥興系統呼叫mmap()通過對映一個普通檔案實現共享記憶體。系統V則是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對

    Linux環境程序通訊: 共享記憶體()

    轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html, 作者:鄭彥興採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對於像管道和訊息佇列等通訊方式,則需要在內 核和使用者空間

    Linux環境程序通訊: 訊號()

    訊號本質訊號是在軟體層次上對中斷機制的一種模擬,在原理上,一個程序收到一個訊號與處理器收到一箇中斷請求可以說是一樣的。訊號是非同步的,一個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。訊號是程序間通訊機制中唯一的非同步通訊機制,可以看作是非同步通知,通知接收訊號的程序有哪些事

    Linux環境程序通訊 訊號燈()

    轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/, 作者:鄭彥興訊號燈與其他程序間通訊方式不大相同,它主要提供對程序間共享資源訪問控制機制。相當於記憶體中的標誌,程序可以根據它判定是否能夠訪問某些共享資源,同時,程序也可以修改該標誌。除了用於訪

    Linux環境程序通訊: 訊號()

    從訊號傳送到訊號處理函式的執行完畢對於一個完整的訊號生命週期(從訊號傳送到相應的處理函式執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:訊號誕生;訊號在程序中註冊完畢;訊號在程序中的登出完畢;訊號處理函式執行完畢。相鄰兩個事件的時間間隔構成訊號生命週期的一個階段。 下面闡述四個事件的實

    Linux環境程序通訊 管道及有名管道()

    管道是Linux支援的最初Unix IPC形式之一,具有以下特點:管道是半雙工的,資料只能向一個方向流動;需要雙方通訊時,需要建立起兩個管道;只能用於父子程序或者兄弟程序之間(具有親緣關係的程序);單獨構成一種獨立的檔案系統:管道對於管道兩端的程序而言,就是一個檔案,但它不是普通的檔案,它不屬於某種檔案系統,

    Linux環境程序通訊 訊號燈

    一、訊號燈概述訊號燈與其他程序間通訊方式不大相同,它主要提供對程序間共享資源訪問控制機制。相當於記憶體中的標誌,程序可以根據它判定是否能夠訪問某些共享資源,同時,程序也可以修改該標誌。除了用於訪問控制外,還可用於程序同步。訊號燈有以下兩種型別:二值訊號燈:最簡單的訊號燈形式,

    Linux環境程序通訊 共享記憶體

    系統呼叫mmap()通過對映一個普通檔案實現共享記憶體。系統V則是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對應特殊檔案系統shm中的一個檔案(這是通過shmid_kernel結構聯絡起來的),後面還將闡述。 1、系統V共

    Linux 程序通訊共享記憶體

    可以說, 共享記憶體是一種最為高效的程序間通訊方式, 因為程序可以直接讀寫記憶體, 不需要任何資料的複製。 為了在多個程序間交換資訊, 核心專門留出了一塊記憶體區, 這段記憶體區可以由需要訪問的程序將其對映到自己的私有地址空間。 因此, 程序就可以直接讀寫這一記憶體區而不需要

    Android IPC程序通訊Socket

    網路通訊之Socket 特點:功能強大,可通過網路傳輸位元組流,支援一對多併發即時通訊。 不支援RPC。 服務端實現: public class SorviceSocket extends Service { private static final String TAG

    Linux程序通訊IPC之訊號量詳解與測試用例

    學習環境centos6.5 Linux核心2.6 程序間通訊概述 1. 程序通訊機制 一般情況下,系統中執行著大量的程序,而每個程序之間並不是相互獨立的,有些程序之間經常需要互相傳遞訊息。但是每個程序在系統中都有自己的地址空間,作業系統通過頁表

    Linux程序通訊IPC之訊息佇列詳解及測試用例

    學習環境 Centos6.5 Linux 核心 2.6 什麼是訊息佇列? 訊息佇列是SystemV版本中三種程序通訊機制之一,另外兩種是訊號量和共享儲存段。訊息佇列提供了程序間傳送資料塊的方法,而且每個資料塊都有一個型別標識。訊息佇列是基於訊息的,而管

    Linux程序通訊IPC之共享記憶體詳解與測試用例

    學習環境centos6.5 Linux核心2.6 什麼是共享記憶體 共享記憶體允許兩個或更多程序訪問同一塊記憶體。當一個程序改變了這塊記憶體中的內容的的時候,其他程序都會察覺到這個更改。 效率: 因為所有程序共享同一塊記憶體,共享記憶體在各種程序

    Linux程序通訊IPC方式總結

    程序間通訊概述 程序通訊的目的 資料傳輸  一個程序需要將它的資料傳送給另一個程序,傳送的資料量在一個位元組到幾M位元組之間 共享資料  多個程序想要操作共享資料,一個程序對共享資料 通知事件 一個程序需要向另一個或一組程序傳送訊息,通知它(它們)

    Linux程序通訊匿名管道命名管道共享記憶體,訊息佇列,訊號量

    目錄 程序間通訊的介紹 管道 匿名管道 原理: 程式碼實現 匿名管道特性 實現管道符 |  命名管道 命名管道特性 程式碼實現 管道讀寫規則 作業系統中ipc的相關命令 共享記憶體(重點) 生命週期: 程式碼實現 程式碼實現獲

    Linux 程序通訊訊號量

    1 訊號量概述 訊號量和其他IPC不同,並沒有在程序之間傳送資料,訊號量用於多程序在存取共享資源時的同步控制就像交通路口的紅路燈一樣,當訊號量大於0,表示綠燈允許通過,當訊號量等於0,表示紅燈,必須停下來等待綠燈才能通過。 程序間的互斥關係與同步關係存在的根源在於臨界資

    Linux 程序通訊IPC的特性

    1.識別符號和鍵 每個核心中的IPC結構(訊息佇列、訊號量或共享儲存段)都用一個非負整數的識別符號 (identifier)加以引用。 例如,要向一個訊息佇列傳送訊息或者從一個訊息佇列取訊息,只需要知道其佇列識別符號。 當一個IPC結構被建立,然後又被刪除時,與這種結構

    linux程序通訊IPC小結

    linux IPC型別 1、匿名管道 2、命名管道 3、訊號 4、訊息佇列 5、共享記憶體 6、訊號量 7、Socket 1、匿名管道 過程: 1、管道實質是一個核心緩衝區,先進先出(佇列)讀取緩衝區記憶體資料 2、一個數據只能讀一次,讀完後在緩衝區就不存在