設(shè)計一個b2c網(wǎng)站b站不收費(fèi)網(wǎng)站
socket 編程是一種用于網(wǎng)絡(luò)通信的編程方式,在 socket 的協(xié)議族中除了常用的 AF_INET、AF_RAW、AF_NETLINK等以外,還有一個專門用于 IPC 的協(xié)議族 AF_UNIX,IPC 是 Linux 編程中一個重要的概念,常用的 IPC 方式有管道、消息隊列、共享內(nèi)存等,本文主要介紹用于本地進(jìn)程間通信的 UNIX Domain Socket,本文給出了多個具體的實(shí)例,每個實(shí)例均附有完整的源代碼;本文所有實(shí)例在 Ubuntu 20.04 上編譯測試通過,gcc版本號為:9.4.0;本文的實(shí)例中涉及多進(jìn)程編程等,本文對 Linux 編程的初學(xué)者有一些難度。
1 UNIX Domain Socket 的基本概念
- 本文不再回顧有關(guān) socket 編程的相關(guān)知識,但是閱讀本文需要掌握基本的 socket 編程方法,這方面的資料比較豐富,請自行解決;
- 關(guān)于 UNIX Domain Socket 的在線文檔:
man 7 unix
; - 在使用 socket() 創(chuàng)建一個 socket 時,如果使用 AF_UNIX 協(xié)議族,而不是我們常用的 AF_INET 時,創(chuàng)建的 sockst 被稱為 UNIX Domain Socket;
- AF_UNIX 是一個用來進(jìn)行進(jìn)程間通信的協(xié)議族,AF_LOCAL 和 AF_UNIX 是完全一樣的;
- AF_UNIX 協(xié)議族和 AF_INET 的主要區(qū)別:
- 當(dāng)使用 AF_UNIX 時,一個進(jìn)程發(fā)送的數(shù)據(jù)無需再經(jīng)過協(xié)議棧,而是通過內(nèi)核緩沖區(qū)將數(shù)據(jù)直接拷貝到另一個進(jìn)程的緩沖區(qū)中;
- 當(dāng)使用 AF_UNIX 時,無需再綁定 IP 地址,發(fā)送和接收過程也與 IP 地址無關(guān);
- 作為 IPC 方法,socket 的 AF_UNIX 家族并沒有共享內(nèi)存的效率高,我認(rèn)為其存在的價值主要是它的使用方法幾乎和 socket 網(wǎng)絡(luò)編程方法無異,這無疑給那些熟悉 socket 編程的程序員帶來了極大的方便;
- 使用 AF_UNIX 建立的 socket 被稱為 UNIX Domain Socket,又名 UDS 或者 IPC socket,常用于互聯(lián)網(wǎng)通信的,使用 AF_INET(AF_INET6) 建立的 socket 被稱為 Internet Socket;
- 一個 Internet socket 需要綁定在一個 IP 地址和一個端口上,與 Internet Socket 不同,UNIX socket 必須綁定到一個文件路徑上,在后面會詳細(xì)說明;
- UNIX socket 也可以是匿名的,也就是無需綁定到文件路徑上,沒有名稱,通常匿名的 UNIX socket 只能用于父子進(jìn)程或者有同一個父進(jìn)程的子進(jìn)程之間通信;
- 以下如無特別說明,socket 特指 UNIX Domain Socket,而非 Internet Socket。
2 命名 UNIX Socket 的使用
-
創(chuàng)建 UNIX Socket
#include <sys/socket.h> #include <sys/un.h>unix_socket = socket(AF_UNIX, type, 0);
- type 可以是:
- SOCK_STREAM:面向連接的數(shù)據(jù)流傳輸;
- SOCK_DGRAM:面向無連接的數(shù)據(jù)報傳輸;
- SOCK_SEQPACKET:面向連接的順序數(shù)據(jù)包傳輸,可以按照發(fā)送的順序傳遞消息;
- type 可以是:
-
綁定一個文件路徑
-
一個命名的 UNIX domain socket 必須綁定到一個路徑下;
-
在 IPv4 下,
Internet Socket
綁定 IP 地址和端口時,使用struct sockaddr_in
,在 Unix Socket 上綁定文件路徑時需要使用結(jié)構(gòu)struct sockaddr_un
struct sockaddr_un {sa_family_t sun_family; /* AF_UNIX */char sun_path[108]; /* Pathname */ };
- sun_family 一定是 AF_UNIX
- sun_path 指向一個文件路徑,比如
/tmp/unix_socket
-
bind()
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd 是使用 socket() 建立的 UNIX Socket;
- addr 指向
struct sockaddr_un
的指針
-
-
下面代碼片段創(chuàng)建了一個 socket 并綁定到了一個文件路徑上
...... int unix_sock; struct sockaddr_un serveraddr;unix_sock = socket(AF_UNIX, SOCK_STREAM, 0);memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sun_family = AF_UNIX; strcpy(serveraddr.sun_path, "/tmp/unix_socket");bind(unix_sock, (struct sockaddr *)&serveraddr, SUN_LEN(&serveraddr)); ......
-
建議使用宏
SUN_LEN(addr)
來計算填好文件路徑的struct sockaddr_un
的長度,因?yàn)槠渲械?sun_path
字段是不定長的,這個宏可以幫助我們計算出正確的結(jié)果,宏SUN_LEN(addr)
定義在頭文件sys/un.h
中; -
當(dāng)把一個 socket 綁定到一個文件上時,在文件系統(tǒng)上,這個文件會真實(shí)存在,但是這個文件不能使用 open() 打開,而只能使用 socket() 打開;
-
建議在使用完一個命名 UNIX domain socket 后,顯式地使用 unlink() 或者 remove() 將其刪除,避免其殘留在文件系統(tǒng)中;
-
盡管我們的例子中使用的文件名在
/tmp/
目錄下,但在實(shí)際應(yīng)用中我們并不建議這樣做,因?yàn)?/tmp/
目錄是任何人都可以讀寫的,這可能會帶來一些安全隱患,通常可以把這個文件建立在項(xiàng)目所在的目錄下,相對比較安全; -
當(dāng)我們用 bind() 將一個文件綁定到 socket 上時,相應(yīng)的文件將被建立,該文件的默認(rèn)權(quán)限為所綁定的 socket 的權(quán)限,通常情況下使用 socket() 建立的 socket 的權(quán)限是 0777,可以使用 fstat 獲得,但這時建立的 socket 文件的默認(rèn)屬性是 0775;
-
如果我們在 bind() 之前先使用 fchmod() 修改 socket 的權(quán)限,那么再使用 bind() 綁定一個文件時,其建立的文件的權(quán)限也會產(chǎn)生變化,下面這段代碼可以演示這種權(quán)限的變化;
...... #define SOCK_NAME "./unix_socket.sock" ...... int sockfd; struct sockaddr_un addr; struct stat sock_stat;sockfd = socket(AF_UNIX, SOCK_STREAM, 0); fchmod(sockfd, 0660);memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, SOCK_NAME); bind(sockfd, (struct sockaddr *)&addr, SUN_LEN(&addr));system("ls -l unix_socket.sock"); ......
-
這段代碼在當(dāng)前目錄下生成的 socket 文件的權(quán)限為 0660,最后一行的
system("ls -l")
顯示的文件清單將清楚地顯示出來,你可以嘗試一下,如果沒有 fchmod() 這行代碼,生成的文件的權(quán)限為 0775; -
使用
ls -l
顯示文件清單時,該文件的前面有一個 s 標(biāo)志,表示這是一個 socket 文件; -
socket 以及 socket 文件是可以使用 stat()、fstat() 獲取屬性,同時可以使用 chmod()、fchmod() 來改變權(quán)限的;
-
改變一個 socket 文件權(quán)限的意義在于安全性,因?yàn)槠渌M(jìn)程是需要通過 socket 文件來使用這個 UNIX domain socket 的,那么這個進(jìn)程必須要有相應(yīng)的權(quán)限才行;
-
可以使用
read()/write()、send()/recv()、sendto()/recvfrom()、sendmsg()/recvmsg()
來發(fā)送和接收數(shù)據(jù),與 Internet Socket 下的使用方法無大的差異; -
read()/write()
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
- 這對函數(shù)是文件的讀/寫函數(shù),因?yàn)?socket 返回的是標(biāo)準(zhǔn)的文件描述符,所以可以使用這兩個函數(shù)進(jìn)行接收和發(fā)送數(shù)據(jù);
- 這對函數(shù)通常僅用于面向連接的 socket,也就是通常不用于 SOCK_DGRAM 類型的 socket;
- 這對函數(shù)是比較簡單的,write() 基本不會出錯;
- 使用 read() 接收數(shù)據(jù)時可能會有阻塞問題,當(dāng)使用 SOCK_STREAM 時,可能還有粘包問題,不過這些問題在 Internet Socket 的 TCP 編程中也是一樣存在的,并不是 UNIX Socket 所特有的,如果有這方面的問題,請參考有關(guān)資料;
-
recv()/send()
#include <sys/types.h> #include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 這對函數(shù)理論上既可以用于有連接的 socket(AF_STREAM),也可以用于無連接的 socket(AF_DGRAM),但通常僅用于面向連接的 socket,也就是通常不用于 SOCK_DGRAM 類型的 socket;
- 與 read()/write() 相比,這對函數(shù)多了一個參數(shù) flags
- 在使用 send() 發(fā)送數(shù)據(jù)時,絕大多數(shù)情況下,flags 都可以設(shè)置為 0,最常用的 falgs 設(shè)置是 MSG_DONTWAIT,但對于向 UNIX Socket 發(fā)送數(shù)據(jù)而言,如果發(fā)送的數(shù)據(jù)不是很大,是不可能產(chǎn)生阻塞的,所以不需要設(shè)置 MSG_DONTWAIT;
- 在使用 recv() 接收數(shù)據(jù)時,當(dāng) socket 上沒有數(shù)據(jù)時,會產(chǎn)生阻塞,如果不希望阻塞,可以設(shè)置 flags 為 MSG_DONTWAIT,這樣可以讓程序有更好的適應(yīng)性;
- 在使用 recv() 接收數(shù)據(jù)時,與 Internet Socket 的 TCP 編程一樣,可能出現(xiàn)"粘包"問題,請參考相關(guān)資料;
-
recvfrom()/sendto()
#include <sys/types.h> #include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
-
這對函數(shù)通常多用于面向無連接的 socket,也就是通常僅用于 SOCK_DGRAM 類型的 socket;
-
在
Internet Socket
的 UDP 編程中,需要使用struct sockaddr_in
設(shè)置對端的 IP 地址和端口,而UNIX Socket
要使用struct sockaddr_un
來設(shè)置對端的文件路徑; -
源程序:sendto-recvfrom.c(點(diǎn)擊文件名下載源程序)演示了如何使用 sendto() 和 recvfrom() 進(jìn)行面向報文的進(jìn)程間通信;
-
編譯:
gcc -Wall -g sendto-recvfrom.c -o sendto-recvfrom
-
運(yùn)行:
./sendto-recvfrom
-
運(yùn)行截圖:
-
recvmsg()/sendmsg()
#include <sys/types.h> #include <sys/socket.h>ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
- 這對函數(shù)在 socket 上接收/發(fā)送數(shù)據(jù)相對較為復(fù)雜,主要是
struct msghdr
結(jié)構(gòu)比較復(fù)雜,這對函數(shù)既可以用于有連接的 socket(AF_STREAM),也可以用于無連接的 socket(AF_DGRAM),在實(shí)踐中也確實(shí)如此;
struct msghdr {void *msg_name; /* Optional address */socklen_t msg_namelen; /* Size of address */struct iovec *msg_iov; /* Scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* Ancillary data, see below */size_t msg_controllen; /* Ancillary data buffer len */int msg_flags; /* Flags (unused) */ };struct iovec {void *iov_base; /* Starting address */size_t iov_len; /* Number of bytes to transfer */ };
-
在多數(shù)的編程實(shí)踐中,我們并不需要使用這對函數(shù),使用
recv()/send()
、read()/write()
或者sendto()/recvfrom()
已經(jīng)足夠; -
struct msghdr
看似有很多字段,其實(shí)也并不是很復(fù)雜;- 最后一個字段
msg_flags
在 sendmsg() 中沒有使用,在 recvmsg() 調(diào)用返回時將被設(shè)置為某個標(biāo)志,可以不用管它; msg_control
和msg_controllen
是用來傳輸控制信息的,如果我們不傳輸控制信息,這兩個字段填 NULL 和 0 即可;- 最前面的兩個字段
msg_name
和mag_namelen
,在無連接的 socket(SOCK_DGRAM) 中有意義,在 sendmsg() 中用于指定目的地址,在 recvmsg() 中用于填充發(fā)送方的源地址,如果我們使用有連接的 socket(SOCK_STREAM),則這兩個字段只需填 NULL 和 0; msg_iov
是一個結(jié)構(gòu)數(shù)組,而msg_iovlen
為這個數(shù)組的元素個數(shù),這是這個結(jié)構(gòu)中的靈魂所在;
- 最后一個字段
-
關(guān)于這對函數(shù)的使用可以參考在線文檔
man sendmsg
、man recvmsg
和man writev
,本文并不打算詳細(xì)描述這對函數(shù)的使用方法,但會給出一個具體實(shí)例; -
源程序:sendmsg-recvmsg.c(點(diǎn)擊文件名下載源程序)演示了如何使用 sendmsg() 和 recvmsg() 進(jìn)行面向報文的進(jìn)程間通信;
-
編譯:
gcc -Wall -g sendmsg-recvmsg.c -o sendmsg-recvmsg
-
運(yùn)行:
./sendmsg-recvmsg
-
運(yùn)行截圖:
- 這對函數(shù)在 socket 上接收/發(fā)送數(shù)據(jù)相對較為復(fù)雜,主要是
-
實(shí)際上,這些在 socket 上發(fā)送/接收數(shù)據(jù)的函數(shù)并不需要成對使用,這個意思是說,我們可以使用 send() 發(fā)送數(shù)據(jù),然后使用 recvmsg() 去接收 send() 發(fā)送的數(shù)據(jù),沒有任何問題。
-
關(guān)閉 socket 和刪除 socket 所關(guān)聯(lián)的文件
#include <unistd.h>close(unix_sock) unlink(UNIX_SOCK_FILE_NAME);
3 UNIX socket 的抽象命名空間
當(dāng)我們使用
socket(AF_UNIX, ......)
建立一個 socket,并將其綁定到一個文件路徑上(比如:/tmp/unix_sock.sock
)時,系統(tǒng)會在文件系統(tǒng)上建立一個真實(shí)的文件,當(dāng)所有打開這個 socket 的進(jìn)程均關(guān)閉 socket 后,這個真實(shí)的文件并不會因此而消失,必須顯式地使用 unlink() 或者 remove() 刪除這個文件,才能將這個文件刪除,如果一個打開 socket 的進(jìn)程異常退出,沒有顯式刪除 socket 相關(guān)聯(lián)的文件,將導(dǎo)致在文件系統(tǒng)上留下一些沒有用處的文件,我們把這種現(xiàn)象稱為“文件系統(tǒng)污染”,顯然,UNIX domain socket 會造成文件系統(tǒng)污染;
-
Linux 為 Unix Domain Socket 提供一種特殊的尋址方案,即所謂的"抽象命名空間(Abstract Namespace)",它可以避免使用 UNIX domain socket 時造成文件系統(tǒng)污染;
-
使用抽象命名空間的 socket,在建立連接時可以不用在文件系統(tǒng)上建立一個真實(shí)文件,當(dāng)所有打開這個 socket 的進(jìn)程終止后,這個在抽象命名空間的 socket 也會隨之消失;
-
使用抽象命名空間建立 socket,不必再顯式地刪除其綁定的文件路徑,即使打開該 socket 的進(jìn)程是異常退出,這個 socket 也會隨著所有進(jìn)程的終止而消失;
-
顯式地刪除一個 socket 關(guān)聯(lián)的文件路徑還會帶來一些其它不可預(yù)知的問題,比如有可能刪除一個其它進(jìn)程正在使用的文件等等;
-
抽象命名空間最大的問題是其代碼的可移植性并不好,所以在實(shí)際應(yīng)用中很少見到相應(yīng)的代碼;
-
抽象命名空間的另一個問題是其安全性,在文件系統(tǒng)上建立的 socket 文件可以使用文件權(quán)限來控制其讀寫權(quán)限,相對較為安全,抽象命名空間沒有權(quán)限,任何知道其名稱的進(jìn)程均可以使用該 socket,其安全性不好;
-
實(shí)際上,在抽象命名空間中建立一個 socket 與在普通用戶空間上建立一個 socket 的區(qū)別并不大,在設(shè)定地址時,都是使用
struct sockaddr_un
,但使用抽象命名空間時sun_path
字段的第一個字符必須是'\0'
,下面代碼片段演示了如何將一個 socket 綁定到抽象命名空間上:......int sock_server = -1; int rc; socklen_t length; struct sockaddr_un server_addr;sock_server = socket(AF_UNIX, SOCK_STREAM, 0); memset(&server_addr, 0, sizeof(struct sockaddr_un)); server_addr.sun_family = AF_UNIX; strcpy(server_addr.sun_path, "#unix_sock.sock"); length = SUN_LEN(&server_addr); server_addr.sun_path[0] = '\0';bind(sock_server, (struct sockaddr *)&server_addr, length);......
-
這段代碼將字符串
#unix_sock.sock
拷貝到 sun_path 字段,其中的'#'
是一個占位字符,在后面的代碼中,'#'
被替換為'\0'
; -
要注意的是,一定要在替換
'#'
前使用SUN_LEN()
宏去計算server_addr
的長度,否則因?yàn)?'\0'
的替換將導(dǎo)致長度計算錯誤; -
源程序:abstract-socket.c(點(diǎn)擊文件名下載源程序)演示了如何使用抽象命名空間 socket 進(jìn)行進(jìn)程間通信;
- 該程序建立了兩個進(jìn)程,一個是服務(wù)端進(jìn)程,另一個是客戶端進(jìn)程;
- 服務(wù)端進(jìn)程在抽象命名空間上建立了一個 socket 并偵聽在該 socket 上;
- 客戶端進(jìn)程連接到該 socket 上并發(fā)送消息,服務(wù)端進(jìn)程收到消息并給客戶端一個回應(yīng)消息,然后兩個進(jìn)程分別退出;
- 為了簡化程序,突出抽象命名空間的使用,該程序的 server 進(jìn)程沒有處理多個客戶端進(jìn)程連接的情況;
- 在 server 進(jìn)程中,socket 綁定完抽象命名空間的 socket 名稱并開始偵聽該 socket 后,執(zhí)行了
lsof
命令,可以很清楚第看到一個名為@unix_socket.sock
,類型為STREAM
的對象被打開;
-
編譯:
gcc -Wall -g abstract-socket.c -o abstract-socket
-
運(yùn)行:
./abstract-socket
-
運(yùn)行截圖:
4 順序數(shù)據(jù)包(SOCK_SEQPACKET)
-
通常情況下,基于連接的 socket(SOCK_STREAM) 被認(rèn)為可以可靠地傳輸數(shù)據(jù),而基于無連接的 socket(SOCK_DGRAM) 被認(rèn)為是不可靠的,在傳輸數(shù)據(jù)中,有可能丟失數(shù)據(jù);
-
但是,使用 SOCK_STREAM 創(chuàng)建的 socket 是基于數(shù)據(jù)流的,數(shù)據(jù)報之間的邊界是不清晰的,接收方收到的數(shù)據(jù)包可能被拆分或者合并,導(dǎo)致收到的數(shù)據(jù)包可能并不是一個完整的數(shù)據(jù)報或者其中還包含著下一個數(shù)據(jù)報的一部分,這就是常說的“粘包(Sticky Packet)”;
-
使用 SOCK_DGRAM 創(chuàng)建的 socket 也是有明顯優(yōu)點(diǎn)的,它是基于數(shù)據(jù)報的,所以可以保證每次收到的數(shù)據(jù)是一個數(shù)據(jù)報,不會有類似“粘包”的問題;
-
當(dāng)然,我們希望有一種 socket,它是基于連接的,數(shù)據(jù)傳輸可靠,同時,數(shù)據(jù)報之間的邊界清晰,不會產(chǎn)生“粘包”;
-
順序數(shù)據(jù)包(SOCK_SEQPACKET)正是這樣一種 socket,它是基于連接的的可靠傳輸,同時保證有明確的報文邊界,通常認(rèn)為它是介于 SOCK_STREAM 和 SOCK_DGRAM 之間的一種連接形式;
-
目前這種 socket 僅能在
UNIX domain socket(AF_UNIX)
上使用,在Internet socket(AF_INET)
上不能使用,所以我們在通常的應(yīng)用中看不到使用SOCK_SEQPACKET
的例子; -
使用
SOCK_SEQPACKET
建立 socket 的編程方法與SOCK_STREAM
非常類似,除了在建立 socket 時使用SOCK_SEQPACKET
以外基本沒有任何區(qū)別; -
以下是
ChatGPT 3.5
給出的SOCK_SEQPACKET
的特性:- 有界數(shù)據(jù)包傳輸:
SOCK_SEQPACKET
提供有界數(shù)據(jù)包傳輸,這意味著它可以保留數(shù)據(jù)包的邊界。發(fā)送方在發(fā)送數(shù)據(jù)時定義了數(shù)據(jù)包的邊界,接收方可以按照這個邊界來接收和處理數(shù)據(jù)包。這對于需要確保消息邊界的應(yīng)用程序非常有用。 - 可靠性:與
SOCK_DGRAM
套接字類型不同,SOCK_SEQPACKET
提供可靠的數(shù)據(jù)傳輸。它使用面向連接的傳輸協(xié)議來確保數(shù)據(jù)的可靠性,即數(shù)據(jù)在發(fā)送和接收之間保持順序,并且不會丟失或重復(fù)。 - 面向連接:
SOCK_SEQPACKET
是面向連接的套接字類型。在進(jìn)行數(shù)據(jù)傳輸之前,需要先建立連接。連接的建立過程確保了通信雙方之間的可靠通信,并提供了數(shù)據(jù)包傳輸?shù)捻樞虮WC。 - 雙向通信:
SOCK_SEQPACKET
套接字類型支持雙向通信,即可以在連接上進(jìn)行雙向的數(shù)據(jù)傳輸。發(fā)送方可以發(fā)送數(shù)據(jù)給接收方,接收方可以發(fā)送響應(yīng)數(shù)據(jù)給發(fā)送方。 - 適用于可靠的流式服務(wù):由于
SOCK_SEQPACKET
提供可靠的、有界的數(shù)據(jù)包傳輸,它適用于需要確保消息邊界和可靠性的應(yīng)用程序。它通常用于基于TCP的應(yīng)用程序,如文件傳輸、視頻流傳輸和實(shí)時通信應(yīng)用。
需要注意的是,與其他套接字類型相比,
SOCK_SEQPACKET
套接字可能會引入一些性能開銷,因?yàn)樗枰S護(hù)消息邊界和數(shù)據(jù)包的順序。因此,在選擇套接字類型時,應(yīng)權(quán)衡應(yīng)用程序的需求和性能要求。 - 有界數(shù)據(jù)包傳輸:
-
源程序:seqpacket.c(點(diǎn)擊文件名下載源程序)演示了如何使用順序數(shù)據(jù)包進(jìn)行進(jìn)程間通信,同時演示了 SOCK_STREAM 的“粘包”現(xiàn)象以及 SOCK_SEQPACKET 有清晰報文邊界的特性;
- 建立了三個進(jìn)程,第一個是 SOCK_STREAM 類型 socket 的服務(wù)端進(jìn)程,第二個是 SOCK_SEQPACKET 類型 socket 的服務(wù)端進(jìn)程,第三個是客戶端進(jìn)程;
- 客戶端進(jìn)程使用同樣的程序分別向兩個服務(wù)端進(jìn)程發(fā)送了三條連續(xù)的報文;
- 兩個服務(wù)端進(jìn)程除了 socket 類型不同外,其它程序完全一樣;
- SOCK_STREAM 服務(wù)端進(jìn)程會一次性地收到客戶端發(fā)來的三條報文,三條報文“粘”在一起;
- SOCK_SEQPACKET 服務(wù)端每次只會收到一條完整報文,三條報文需要接收三次,報文邊界清晰;
- 為簡單起見,服務(wù)端程序連續(xù)收到 5 個 EAGAIN(EWOULDBLOCK) 錯誤時認(rèn)為客戶端進(jìn)程已經(jīng)完成發(fā)送;
-
編譯:
gcc -Wall -g seqpacket.c -o seqpacket
-
運(yùn)行:
./seqpacket
-
運(yùn)行截圖:
5 匿名 UNIX Socket 的使用
-
前面介紹的 UNIX domain socket 均是需要命名的,或者引用一個文件路徑,或者在抽象命名空間中引用一個名稱;
-
Linux 同樣支持匿名的 UNIX domain socket,就像在文章《IPC之一:使用匿名管道進(jìn)行父子進(jìn)程間通信的例子》介紹的匿名管道一樣,匿名 UNIX domain socket 也是僅能用于父子進(jìn)程或擁有同一個父進(jìn)程的子進(jìn)程間進(jìn)行通信;
-
在使用匿名管道進(jìn)行全雙工通信時,需要建立兩個管道,一個用于讀,另一個用于寫,匿名 UNIX domain socket 與之類似,也是需要建立一對 socket,一個用于讀,另一個用于寫,使用 socketpair() 建立一對 socket;
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h>int socketpair(int domain, int type, int protocol, int sv[2]);
- 調(diào)用成功,該函數(shù)返回 0,sv 數(shù)組中存放這一對 socket,調(diào)用失敗返回 -1,errno 中為錯誤代碼;
- 返回的一對 socket 是已經(jīng)連接好的,這意味著無需再調(diào)用其它函數(shù),可以直接在 sv 返回的 socket 上進(jìn)行讀/寫操作;
- socketpair() 目前只能用在 AF_UNIX 上,所以參數(shù) domain 只能是 AF_UNIX;
- type 可以是 SOCK_STREAM、SOCK_DGRAM 或者 SOCK_SEQPACKET;
- protocol 通常填 0 即可;
- sv 是一個整數(shù)數(shù)組的指針,用于在調(diào)用成功時返回一對 socket,調(diào)用失敗時,sv 數(shù)組的內(nèi)容不會被改動;
-
使用 socketpair() 建立的 socket 對,與使用 socket() 建立的 socket,在編程上沒有區(qū)別;
-
因?yàn)槭悄涿?#xff0c;所以只能通過繼承的方式將 socket 對傳遞給子進(jìn)程,所以只能用于父子進(jìn)程間或擁有同一父進(jìn)程的子進(jìn)程之間進(jìn)行通信;
-
源程序:socketpair.c(點(diǎn)擊文件名下載源程序)演示了如何 socketpair() 建立的 socket 對進(jìn)行進(jìn)程間通信;
- 在父進(jìn)程中建立一個 socket 對;
- 建立了兩個子進(jìn)程,第一個是服務(wù)端進(jìn)程,第二個是客戶端進(jìn)程,socket 對通過繼承傳到子進(jìn)程;
- 服務(wù)端進(jìn)程接收客戶端進(jìn)程發(fā)送的消息
-
編譯:
gcc -Wall -g socketpair.c -o socketpair
-
運(yùn)行:
./socketpair
-
運(yùn)行截圖: