UNP-UNIX網絡編程 第十五章:域協議

Unix域協議並不是一個實際的協議族,而是在單個主機上執行客戶/服務器通訊的一種方式,單個主機上執行通信,也就是所謂的進程間通信(IPC),所以Unix域套接字協議可以視作IPC方法之一。
Unix域提供兩類套接字:字節流套接字(類似TCP)和數據報套接字(類似DUP)。
Unix域中用於標識客戶和服務器的協議地址是普通文件系統中的路徑名(但需要和Unix域套接字關聯起來),否則無法讀寫這些文件。回憶一蛤,IPv4的協議地址由一個32位地址和16位端口號構成,IPv6協議地址則由一個128位地址和16位端口號組成。

1. Unix域套接字地址結構:

#include <sys/un.h>
struct sockaddr_un 
{
    sa_family_t sum_family;// AF_LOCAL或者AF_UNIX
    char sun_path[104];//字符串指代路徑(null終止)
};

sun_path表示與套接字關聯的地址,以NULL結尾,如果未指定地址則通過以空字符串作爲路徑名指示,也就是說sun_path[0]值爲0,這個效果就好像Ipv4的INADDR_ANY和IPv6的ADDR_ANY_INIT常值。

2. socketpair函數

這個是UNIX域套接字特有的函數,它創建兩個隨後連接起來的套接字。

#include <sys/socket.h>
int socketpair(int family , int type , int protocol ,int sockfd[2]);
//返回:成功則爲0,出錯則爲-1

family: 爲AF_LOCAL或者AF_UNIX。
type:既可以是SOCK_STREAM也可以是SOCK_DGRAM。
protocol:必須爲0。
sockfd[2]:新創建的兩個套接字別在sockfd[0]和sockfd[1]中返回。
當設置type參數爲SOCK_STREAM時,得到的結果就叫做流管道這與pipe創建的普通UNIX管道類似,差別在於流管道是全雙工的,也就是說,兩個描述符既可讀也可寫。回憶一蛤,用pipe創建的匿名管道,pipefd[0]用於讀操作,pipefd[1]用於寫操作。
3. UNIX域套接字編程:Unix域字節流客戶/服務器程序
當用於UNIX域套接口時,套接口函數中存在一些差異和限制:
(1)由bind創建的路徑名缺省訪問權限應爲0777(屬主用戶、組用戶和其他用戶都可讀、可寫並可執行),並按照當前umask值進行修正。
(2)與UNIX域套接口關聯的路徑名應該是一個絕對路徑名,而不是一個相對路徑名。
(3)在connect調用中指定的路徑名必須是一個當前捆綁在某個打開的UNIX域套接口上的路徑名,而且它們的套接口類型(字節流或數據報)也必須一致。
(4)調用connect連接一個UNIX域套接口涉及的權

16位源都口號,16爲目的端口號用於尋找發送端和接收端的應用進程,加上IP首部的源端IP及終端IP,唯一的確認一個TCP連接。
32位序號:標識發送的數據字節流,標識在這個報文段中的第一個數據字節,2^3 - 1後重新從0開始。包含該主機選擇的連接的ISN(Initial Sequence Number),要發送的第一個數據字節序號爲ISN+1.
32位確認序號:ACK爲1時有效,上次成功收到的數據字節序號+1(如接收到的爲1024--2048,則返回2049)。
4位首部長度:首部中32bits字的數目,TCP最多有60字節的長度,除去任選字段,正常爲20字節。
6bits:URG緊急指針;ACK確認序號有效;PSH接收方應儘快將此報文段交給應用層;RST重建連接;SYN同步序號,用來發起一個新連接;FIN發端完成發送任務。
16位窗口大小:TCP流量控制,字節數,起始於確認序列號指明的值,接收端期望收到的字節,最大爲65535.
16位檢驗和:包括計算TCP首部和數據綜合的二進制反碼和檢驗和。
16位緊急指針:URG爲1時有效,正向的偏移量,加上序號字段值表示最後一個字節的序號。
可選字段:例:MSS.

限測試等同於調用open以只讀方式訪問相應的路徑名。

(5)UNIX域字節流套接口類似於TCP套接口:它們都爲進程提供一個無記錄邊界的字節流接口。
(6)如果對於某個UNIX域字節流套接口的connect調用發現這個監聽套接口的隊列已滿,調用就立即返回一個ECONNREFUSED錯誤。這一點不同於TCP:如果TCP監聽套接口的隊列已滿,TCP監聽端就忽略新到達的SYN,而TCP連接發起端將數次發送SYN進行重試。
(7)UNIX域數據報套接口類似UDP套接口:它們都提供一個保留記錄邊界的不可靠的數據報服務。
(8)在一個未綁定的UNIX域套接口上發送數據報不會自動給這個套接口捆綁一個路徑名,這一點不同於UDP套接口:在一個未綁定的UDP套接口上發送UDP數據報導致給這個套接口捆綁一個臨時端口。這一點意味着除非數據報發送端已經捆綁一個路徑名到它的套接口,否則數據報接收端無法發回應答數據報。類似地,對於某個UNIX域數據報套接口的connect調用不會給本套接口綁定一個路徑名,這一點不同於TCP和UDP。

(一)字節流服務器:(5-12)

int main(int argc, char **argv)
{
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_un  cliaddr, servaddr;//兩個套接字地址結構的數據類型爲sockaddr_un
    void                sig_chld(int);
    //第一個參數是AF_LOCAL創建一個unix域字節流套接字
    listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
    //UNIXSTR_PATH常值爲/tmp/unix.str
    unlink(UNIXSTR_PATH);//unlink該路徑名,防止該路徑名已存在

    bzero(&servaddr, sizeof(servaddr));//初始化套接字地址結構
    servaddr.sun_family = AF_LOCAL;//sun_family設置爲AF_LOCAL
    strcpy(servaddr.sun_path, UNIXSTR_PATH);//路徑名複製到sun_path

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//bind(),地質結構總大小

    Listen(listenfd, LISTENQ);
    Signal(SIGCHLD, sig_chld);
    for ( ; ; ) 
    {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) 
        {
            if (errno == EINTR)
                continue;       /* back to for() */
            else
                err_sys("accept error");
        }

        if ( (childpid = Fork()) == 0) //子進程
        {   /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);   /* process request *///與前面的str_echo相同(第五章)
            exit(0);
        }
        Close(connfd);          /* parent closes connected socket */
    }
}
````




<div class="se-preview-section-delimiter"></div>

#(二)字節流客戶端:(5-4)




<div class="se-preview-section-delimiter"></div>

```c
int main(int argc, char **argv)
{
    int                 sockfd;
    struct sockaddr_un  servaddr;//sockaddr_un含有服務器地址的套接字地址結構

    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);//參數AF_LOCAL

    bzero(&servaddr, sizeof(servaddr));//初始化套接字地址結構
    servaddr.sun_family = AF_LOCAL;//與服務器相同
    strcpy(servaddr.sun_path, UNIXSTR_PATH);

    Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
    str_cli(stdin, sockfd);     /* do it all *///與前面的str_cli相同(第五章)
    exit(0);
}

(三)數據報服務器:(8-3)

int main(int argc, char **argv)
{
    int                 sockfd;
    struct sockaddr_un  servaddr, cliaddr;//兩個套接字地址結構的數據類型爲sockaddr_un
    //第一個參數是AF_LOCAL創建一個unix域數據報套接字
    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);
    //UNIXSTR_PATH常值爲/tmp/unix.str
    unlink(UNIXDG_PATH);//unlink該路徑名,防止該路徑名已存在
    bzero(&servaddr, sizeof(servaddr));//初始化套接字地址結構
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

    Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));//bind()

    dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));//8-4.dg_echo()函數
}

(四)數據報客戶端:(8-7)

int main(int argc, char **argv)
{
    int                 sockfd;
    struct sockaddr_un  cliaddr, servaddr;

    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);
/*-------------------------------------------------------------------------------------*/
    //必須顯式bind一個路徑名到我們的套接字,這樣服務器纔會回射應答的路徑名
    bzero(&cliaddr, sizeof(cliaddr));       /* bind an address for us */
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, tmpnam(NULL));//tmpnam賦值一個唯一的路徑名,bind到該套接字
    //沒有下面的bind,dg_echo的recvfrom調用會返回一個空的路徑名,導致服務器sendto時發生錯誤
    Bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
/*-------------------------------------------------------------------------------------*/
    bzero(&servaddr, sizeof(servaddr)); /* fill in server's address *///初始化套接字地址結構
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

    dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));//8-7.dg_cli()函數
    exit(0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章