socket學習(3)

無名套接口
套接口並不總是需要有一個地址。例如, 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個字節。
下圖顯示了通用地址結構的物理佈局:
id="iframe_0.053914902498945594" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://blog.chinaunix.net/photo/19185_070525162806.jpg?_=2462055%22%20style=%22border:none;max-width:872px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.053914902498945594',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no" style="margin: 0px; padding: 0px; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; border-style: none; width: 340px; height: 220px;">
通用套接口地址結構對於程序而言並不是那樣有用。然而,他確實提供了其他地址結構必須適合的引用模型。例如,我們將會瞭解到所有地址必須在結構中的同樣的位置定義一個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()宏。然而,當計算抽象套接口名字時,我們要十分注意。
發佈了19 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章