1. 程式人生 > >Linux Socket學習 -- 無名套接字 AF_UNIX

Linux Socket學習 -- 無名套接字 AF_UNIX

來源: http://mylxiaoyi.iteye.com/blog/313863

無名套介面
套介面並不總是需要有一個地址。例如, socketpair函式建立了兩個彼此相連的兩個套介面,但是卻沒有地址。實際上,他們是無名套介面。想像一下冷戰期間美國總統與蘇聯之間的紅色電話。 他們任何一端並不需要電話號碼,因為他們是直接相連的。同樣,socketpair函式也是直接相連的,也並不需要地址。
匿名呼叫
有時在實際上,連線中的兩個套介面中的一個也沒有地址。對於要連線的遠端套介面,他必須要有一個地址來標識。然而,本地套介面是匿名的。建立起來的連線具有一個有地址的遠端套介面和另一個無地址的套介面。

生成地址


有 時我們並不會介意我們的本地址是什麼,但是我們需要一個來進行通訊。這對於需要連線到一個伺服器(例如一個RDBMS資料服務)的程式來說通常是正確的。 他們的本地地址僅為持續的連線所需要。分配確定的地址也可以完成,但是這增加了網路管理的工作。相應的,當地址可用時才會生成地址。
理解域
當Berkeley開發組正在構思BSD套介面介面時,TCP/IP仍在開發之中。與此同時,有一些其他的即將完成的協議正在為不同的組織所使用,例如X.25協議。其他的協議也正在研究之中。
我 們在上一章所見的socketpair函式,以及我們將會看到的socket函式,很明智的允許了其他協議需不是TCP/IP也許會用到的可能性。 socketpair函式的domain引數允許這種約束。為了討論的方便,讓我們先來回顧一下socketpair函式的概要:
#include<sys/types.h>
#include<sys/socket.h>
int socketpair(intdomain, int type, int protocol, int sv[2]);
通常,protocol引數指定為0。0允許作業系統選擇我們所選擇的domain的所用的預設協議。對於這些規則有一些例外,但是這超出了我們討論的範圍。
現在我們要來解釋一下domain引數。對於socketpair函式,這個值必須為AF_LOCAL或者AF_UNIX。在上一章,我們已經指出AF_UNIX巨集與舊版的AF_LOCAL等同。然而AF_LOCAL意味著什麼?他選擇了什麼呢?
常量的AF_前緣指明瞭地址族。domain引數選擇要使用的地址族。

格式化套介面地址

每 一個通訊協議指明瞭他自己的網路地址的格式。相應的,地址族用來指明要使用哪種型別的地址。常量AF_LOCAL(AF_UNIX)指明瞭地址將會按照本 地(UNIX)地址規則來格式化。常量AF_INET指明瞭地址將會符合IP地址規則。在一個地址族中,可以有多種型別。
在下面的部分中,我們將會檢測各種地址族的格式以及物理佈局。這是需要掌握的重要的一部分。人們使用BSD套介面介面時所遇到的困難,很多與地址初始化相關。

檢測通常的套介面地址
因為BSD套介面地址的開發早於ANSI C標準,所以沒有(void *)資料指標來接受任何結構地址。相應的BSD的解決選擇是定義一個通用的地址結構。通用的地址結構是用下面的C語言語句來定義的:
#include<sys/socket.h>
struct sockaddr {
   sa_family_t sa_family; /* Address Family */
   char        sa_data[14]; /* Address data. */
};
這裡的sa_family_t資料型別是一個無符號短整數,在Linux下為兩個位元組。整個結構為16個位元組。結構元素的sa_data[14]代表了地址資訊的其餘14個位元組。
下圖顯示了通用地址結構的物理佈局:

通用套介面地址結構對於程式而言並不是那樣有用。然而,他確實提供了其他地址結構必須適合的引用模型。例如,我們將會了解到所有地址必須在結構中的同樣的位置定義一個sa_family成員,因為這個元素決定了地址結構的剩餘位元組數。

格式化本地地址

這個地址結構用在我們的本地套介面中(我們的執行Linux的PC)。例如,當我們使用lpr命令排除要列印的檔案時,他使用一個本地套介面與我們的PC上假離線伺服器進行通訊。雖然也可以用TCP/IP協議來進行本地通訊,但是事實證明這是低效的。
傳統上,本地地址族已經被稱這為AF_UNIX域。這是因為這些地址使用本地UNIX檔案來作為套介面名字。
AF_LOCAL或者AF_UNIX的地址結構名為sockaddr_un。這個結構是通過在我們的C程式中包含下面的語句來定義的:
#include<sys/un.h>
sockaddr_un的地址結構:
struct sockaddr_un {
   sa_family_t sun_family;/* Address Family */
   char         sun_path[108]; /* Pathname*/
};
結構成員sun_family的值必須為AF_LOCAL或者AF_UNIX。這個值表明這個結構是通過sockaddr_un結構規則來進行格式化的。
結構成員sun_path[108]包含一個可用的UNIX路徑名。這個字元陣列並不需要結尾的null位元組。
在下面的部分中,我們將會了解到如何來初始化一個AF_LOCAL地址與定義他的長度。

格式化傳統本地地址
傳統本地地址的地址名空間為檔案系統路徑名。一個程序也許會用任何可用的路徑名來命名他的本地套介面。然則為了可用,命名套介面的程序必須可以訪問路徑名的所有目錄元件,並且有許可權來在指定的目錄中建立最終的套介面物件。
一些程式設計師喜歡在填充地址結構之前將其全部初始化為0。這通常是通過memset函式來做到的,並且這是一個不錯的主意。
struct sockaddr_unuaddr;
memset(&uaddr,0,sizeofuaddr);
這個函式會為我們將這個地址結構的所有位元組設定為0。
下面的例子演示了一個簡單的初始化sockaddr_un結構的C程式,然後呼叫netstat命令來證明他起到了作用。在這裡我們要先記住在socket與bind上的程式呼叫,這是兩個我們還沒有涉及到的函式。
/***************************************** 
 *
 * af_unix.c
 *
 * AF_UNIXSocket Example:
 *
 *******************************************/ 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<sys/un.h>

/*
 * Thisfunction reports the error and
 * exits backto the shell:
 */
static voidbail(const char *on_what)
{
   perror(on_what);
   exit(1);
}

int main(intargc,char **argv,char **envp)
{
   int z;        /* Status return code */
   int sck_unix;    /* Socket */
   struct sockaddr_un adr_unix;    /* AF_UNIX */
   int len_unix;    /* length */
   const char pth_unix[] = "/tmp/my_sock";    /*pathname */

   /*
    * Create a AF_UNIX (aka AF_LOCAL) socket:
    */
   sck_unix = socket(AF_UNIX,SOCK_STREAM,0);

   if(sck_unix == -1)
       bail("socket()");

   /*
    * Here we remove the pathname for the
    * socket,in case it existed from a 
    * prior run.Ignore errors (it maight 
    * not exist).
    */
   unlink(pth_unix);

   /*
    * Form an AF_UNIX Address:
    */
   memset(&adr_unix,0,sizeof adr_unix);

   adr_unix.sun_family = AF_LOCAL;
   strncpy(adr_unix.sun_path,pth_unix,
           sizeof adr_unix.sun_path-1)
       [sizeof adr_unix.sun_path-1] = 0;
   len_unix = SUN_LEN(&adr_unix);

   /*
    * Now bind the address to the socket:
    */
   z = bind(sck_unix,
           (struct sockaddr *)&adr_unix,
           len_unix);
   if(z == -1)
       bail("bind()");
   /*
    * Display all of our bound sockets
    */
   system("netstat -pa --unix 2>/dev/null |"
           "sed -n '/^ActiveUNIX/,/^Proto/P;"
           "/af_unix/P'");
   /*
    * Close and unlink our socket path:
    */
   close(sck_unix);
   unlink(pth_unix);

   return 0;
}
上面的這個例子的步驟如下:
1 在第28行定義了sck_unix來存放建立的套介面檔案描述符。
2 在第29行定義了本地地址結構並且命名為adr_unix。這個程式將會用一個AF_LOCAL套介面地址來處理這個結構。
3 通過呼叫socket函式來在第37行建立了一個套介面。在第39行檢測錯誤並報告。
4 在第48行呼叫unlink函式。因為AF_UNIX地址將會建立一個檔案系統物件,如果不再需要必須進行刪除。如果這個程式最後一次執行時沒有刪除,這條語句會試著進行刪除。
5 在第53行adr_unix的地址結構被清0。
6 在第55行將地址族初始化為AF_UNIX。
7 第57行到第59行向地址結構中拷貝路徑名"/tmp/my_sock"。在這裡使用程式碼在結構中添加了一個null位元組,因為在第61行Linux提供了巨集SUN_LEN()需要他。
8 在第61行計算地址的長度。這裡的程式使用了Linux提供的巨集。然而這個巨集依賴於adr_unix.sun_path[]結構成員的一個結束字元。
9 在第66行到68行呼叫bind函式,將格式化的地址賦值給第37行建立的套介面。
10 在第76行呼叫netstat命令來證明我們的地址已繫結到了套介面。
11 在第83 行關閉套介面。
12 當呼叫bind函式時為套介面所建立的UNIX路徑名在第66行被刪除。
在 第61行將長度賦值給len_unix,在這裡使用了SUN_LEN()巨集,但是並不會計算拷貝到adr_unix.sun_path[]字元陣列中的空 位元組。然而放置一個空位元組是必要的,因為SUN_LEN()巨集會呼叫strlen函式來計算UNIX路徑名的字串長度。
程式的執行結果如下:
$ ./af_unix
Active UNIX domainsockets (servers and established)
Proto RefCntFlags    Type     State I-NodePID/Program  name Path
unix0      []       STREAM          104129800/af_unix      /tmp/my_sock
$

格式化抽象本地地址
傳統AF_UNIX套介面名字的麻煩之一就在於總是呼叫檔案系統物件。這不是必須的,而且也不方便。如果原始的檔案系統物件並沒有刪除,而在bind呼叫時使用相同的檔名,名字賦值就會失敗。
Linux 2.2核心使得為本地套介面建立一個抽象名了成為可能。他的方法就是使得路徑名的第一個位元組為一個空位元組。在路徑名中空位元組之後的位元組才會成為抽象名字的一部分。下面的這個程式是上一個例子程式的修改版本。這個程式採用了一些不同的方法來建立一個抽象的名字。
/***************************************** 
 * af_unix2.c
 *
 * AF_UNIXSocket Example
 * CreateAbstract Named AF_UNIX/AF_LOCAL
 *******************************************/ 
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<sys/un.h>

/*
 * Thisfunction reports the error and 
 * exits backto the shell:
 */
static voidbail(const char *on_what)
{
   perror(on_what);
   exit(1);
}

int main(intargc,char **argv,char **envp)
{
   int z;        /* Status return code */
   int sck_unix;    /* Socket */
   struct sockaddr_un adr_unix;    /* AF_UNIX */
   int len_unix;    /* length */
   const char pth_unix[]    /* Abs .Name */
       = "Z*MY-SOCKET*";

   /*
    * Create an AF_UNIX (aka AF_UNIX) socket:
    */
   sck_unix = socket(AF_UNIX,SOCK_STREAM,0);
   if(sck_unix == -1)
       bail("socket()");

   /* 
    * Form an AF_UNIX Address
    */
   memset(&adr_unix,0,sizeof adr_unix);
   adr_unix.sun_family = AF_UNIX;
   strncpy(adr_unix.sun_path,pth_unix,
           sizeof adr_unix.sun_path-1)
       [sizeof adr_unix.sun_path-1] = 0;
   len_unix = SUN_LEN(&adr_unix);

   /*
    * Now make first byte null
    */
   adr_unix.sun_path[0] = 0;
    
   z = bind(sck_unix,(struct sockaddr *)&adr_unix,len_unix);
   if(z == -1)
       bail("bind()");
   /*
    * Display all of our bound sockets:
    */
   system("netstat -pa --unix 2>/dev/null |"
           "sed -n '/^Active UNIX/,/^Proto/P;"
           "/af_unix/P'");
   /*
    * Close and unlink our socket path:
    */
   close(sck_unix);
   return 0;
   /*
    * Now bind the address to the socket:
    */
}
這個程式的執行結果如下:
$ ./af_unix2
Active UNIX domainsockets (servers and established)
Proto RefCntFlags    Type     State I-Node PID/Programname Path
unix0       []      STREAM         1041435186/af_unix2    @*MY- SOCKET*
$
從這個輸出結果中我們可以看到,套介面地址是以 @*MYSOCKET*的名字出現的。開頭的@標誌是為netstat命令用來標識抽象UNIX套介面名字。其餘的字元是拷貝到字元陣列剩餘位置的字元。注意@字元出現在我們的Z字元應出現的地方。
整個程式的步驟與前一個程式的相同。然而,地址的初始化步驟有一些不同。這些步驟描述如下:
1 在第31行和第32行定義了套介面抽象名字的字串。注意字串的第一個字元為Z。在這個字串這個多餘的字元只是起到佔位的作用,因為實際上他會在第6步被一個空位元組代替。
2 在第45行通過呼叫memset函式將整個結構初始經為0。
3 在第47行將地址族設定為AF_UNIX。
4 在第49行使用strncpy函式將抽象名字拷貝到adr_unix.sun_path中。在這裡要注意,為了SUN_LEN()巨集的使用在目的字元陣列的放置了一個結束的空位元組。否則就不需要這個結束的空位元組。
5 在第53通過Linux所提供的SUN_LEN() C 巨集來計算地址的長度。這個巨集會在sun_path[]上呼叫strlen函式,所以需要提供了一個結束字元。
6 這一步是新的:sun_path[]陣列的第一個位元組被設定為空位元組。如果使用SUN_LEN()巨集,必須最後執行這一步。
在這一部分,我們瞭解瞭如何來建立AF_LOCAL和AF_UNIX的套介面地址。為了計算套介面地址的長度,我們使用SUN_LEN()巨集。然而,當計算抽象套介面名字時,我們要十分注意。