無名套介面
套介面並不總是需要有一個地址。例如, 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(int domain, 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_un uaddr;
memset(&uaddr,0,sizeof uaddr);
這個函數會為我們將這個地址結構的所有位元組設定為0。
下面的例子示範了一個簡單的初始化sockaddr_un結構的C程式,然後調用netstat命令來證明他起到了作用。在這裡我們要先記住在socket與bind上的程式調用,這是兩個我們還沒有涉及到的函數。
/*****************************************
*
* af_unix.c
*
* AF_UNIX Socket 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>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,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 '/^Active UNIX/,/^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 domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 0 [] STREAM 104129 800/af_unix /tmp/my_sock
$
格式化抽象本地地址
傳統AF_UNIX套介面名字的麻煩之一就在於總是調用檔案系統對象。這不是必須的,而且也不方便。如果原始的檔案系統對象並沒有刪除,而在bind調用時使用相同的檔案名稱,名字賦值就會失敗。
Linux 2.2核心使得為本地套介面建立一個抽象名了成為可能。他的方法就是使得路徑名的第一個位元組為一個空位元組。在路徑名中空位元組之後的位元組才會成為抽象名字的一部分。下面的這個程式是上一個例子程式的修改版本。這個程式採用了一些不同的方法來建立一個抽象的名字。
/*****************************************
* af_unix2.c
*
* AF_UNIX Socket Example
* Create Abstract 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>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,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 domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 0 [] STREAM 104143 5186/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()宏。然而,當計算抽象套介面名字時,我們要十分注意。