理解struct msghdr
當我第一次看到他時,他看上去彷佛是一個須要建立的巨大的結構。可是不要怕。其結構定義以下:
struct msghdr {
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
size_t msg_controllen;
int msg_flags;
};
結構成員能夠分爲四組。他們是:
套接口地址成員msg_name與msg_namelen。
I/O向量引用msg_iov與msg_iovlen。
附屬數據緩衝區成員msg_control與msg_controllen。
接收信息標記位msg_flags。
在咱們將這個結構分爲上面的幾類之後,結構看起來就不那樣巨大了。
成員msg_name與msg_namelen
這些成員只有當咱們的套接口是一個數據報套接口時才須要。msg_name成員指向咱們要發送或是接收信息的套接口地址。成員msg_namelen指明瞭這個套接口地址的長度。
當調用recvmsg時,msg_name會指向一個將要接收的地址的接收區域。當調用sendmsg時,這會指向一個數據報將要發送到的目的地址。
注意,msg_name定義爲一個(void *)數據類型。咱們並不須要將咱們的套接口地址轉換爲(struct sockaddr *)。
成員msg_iov與msg_iovlen
這些成員指定了咱們的I/O向量數組的位置以及他包含多少項。msg_iov成員指向一個struct iovec數組。咱們將會回憶起I/O向量指向咱們的緩衝區。成員msg_iov指明瞭在咱們的I/O向量數組中有多少元素。
成員msg_control與msg_controllen
這些成員指向了咱們附屬數據緩衝區而且代表了緩衝區大小。msg_control指向附屬數據緩衝區,而msg_controllen指明瞭緩衝區大小。
成員msg_flags
當使用recvmsg時,這個成員用於接收特定的標記位(他並不用於sendmsg)。在這個位置能夠接收的標記位以下表所示:
標記位 描述
MSG_EOR 當接收到記錄結尾時會設置這一位。這一般對於SOCK_SEQPACKET套接口類型十分有用。
MSG_TRUNC 這個標記位代表數據的結尾被截短,由於接收緩衝區過小不足以接收所有的數據。
MSG_CTRUNC 這個標記位代表某些控制數據(附屬數據)被截短,由於緩衝區過小。
MSG_OOB 這個標記位代表接收了帶外數據。
MSG_ERRQUEUE 這個標記位代表沒有接收到數據,可是返回一個擴展錯誤。
咱們能夠在recvmsg(2)與sendmsg(2)的man手冊頁中查看更多的信息。
附屬數據結構與宏
recvmsg與sendmsg函數容許程序發送或是接收附屬數據。然而,這些額外的信息受限於必定的格式規則。這一節將會介紹控制信息頭與程序將會用來管理這些信息的宏。
簡介struct cmsghdr結構
附
屬信息能夠包括0,1,或是更多的單獨附屬數據對象。在每個對象以前都有一個struct
cmsghdr結構。頭部以後是填充字節,而後是對象自己。最後,附屬數據對象以後,下一個cmsghdr以前也許要有更多的填充字節。在這一章,咱們將
要關注的附屬數據對象是文件描述符與證書結構。
下圖顯示了一個包含附屬數據的緩衝區是如何組織的。
咱們須要注意如下幾點:
cmsg_len與CMSG_LEN()宏值所顯示的長度相同。
CMSG_SPACE()宏能夠計算一個附屬數據對象的所必需的空白。
msg_controllen是CMSG_SPACE()長度以後,而且爲每個附屬數據對象進行計算。
控制信息頭部自己由下面的C結構定義:
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/* u_char cmsg_data[]; */
};
其成員描述以下:
成員 描述
cmsg_len 附屬數據的字節計數,這包含結構頭的尺寸。這個值是由CMSG_LEN()宏計算的。
cmsg_level 這個值代表了原始的協議級別(例如,SOL_SOCKET)。
cmsg_type 這個值代表了控制信息類型(例如,SCM_RIGHTS)。
cmsg_data 這個成員並不實際存在。他用來指明實際的額外附屬數據所在的位置。
這一章所用的例子程序只使用SOL_SOCKET的cmsg_level值。這一章咱們感興趣的控制信息類型以下(cmsg_level=SOL_SOCKET):
cmsg_level 描述
SCM_RIGHTS 附屬數據對象是一個文件描述符
SCM_CREDENTIALS 附屬數據對象是一個包含證書信息的結構
簡介cmsg(3)宏
因爲附屬數據結構的複雜性,Linux系統提供了一系列的C宏來簡化咱們的工做。另外,這些宏能夠在不一樣的UNIX平臺之間進行移植,而且採起了一些措施來防止未來的改變。這些宏是由cmsg(3)的man手冊頁來進行描述的,其概要以下:
#include
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);
void *CMSG_DATA(struct cmsghdr *cmsg);
CMSG_LEN()宏
這個宏接受咱們但願放置在附屬數據緩衝區中的對象尺寸做爲輸入參數。若是咱們回顧一個咱們前面的介紹,咱們就會發現這個宏會計算cmsghdr頭結構加上所須要的填充字符的字節長度。這個值用來設置cmsghdr對象的cmsg_len成員。
下面的例子演示了若是附屬數據是一個文件描述符,咱們應如何來計算cmsg_len成員的值:
int fd; /* File descriptor */
printf("cmsg_len = %d/n",CMSG_LEN(sizeof fd));
CMSG_SPACE()宏
這個宏用來計算附屬數據以及其頭部所需的總空白。儘管CMSG_LEN()宏計算了一個類似的長度,CMSG_LEN()值並不包括可能的結尾的填充字符。CMSG_SPACE()宏對於肯定所需的緩衝區尺寸是十分有用的,以下面的示例代碼所示:
int fd; /* File Descriptor */
char abuf[CMSG_SPACE(sizeof fd)];
這個例子在abuf[]中聲明瞭足夠的緩衝區空間來存放頭部,填充字節以及附屬數據自己,和最後的填充字節。若是在緩衝區中有多個附屬數據對象,必定要同時添加多個CMSG_SPACE()宏調用來獲得所需的總空間。
CMSG_DATA()宏
這個宏接受一個指向cmsghdr結構的指針。返回的指針值指向跟隨在頭部以及填充字節以後的附屬數據的第一個字節(若是存在)。若是指針mptr指向一個描述文件描述符的可用的附屬數據信息頭部,這個文件描述符能夠用下面的代碼來獲得:
struct cmsgptr *mptr;
int fd; /* File Descriptor */
. . .
fd = *(int *)CMSG_DATA(mptr);
CMSG_ALIGN()宏
這是一個Linux擴展宏,而不是Posix.1g標準的一部分。指定一個字節長度做爲輸入,這個宏會計算一個新的長度,這個新長度包括爲了維護對齊所須要的額外的填充字節。
CMSG_FIRSTHDR()宏
這
個宏用於返回一個指向附屬數據緩衝區內的第一個附屬對象的struct cmsghdr指針。輸入值爲是指向struct
msghdr結構的指針(不要與struct
cmsghdr相混淆)。這個宏會估計msghdr的成員msg_control與msg_controllen來肯定在緩衝區中是否存在附屬對象。然
後,他會計算返回的指針。
若是不存在附屬數據對象則返回的指針值爲NULL。不然,這個指針會指向存在的第一個struct cmsghdr。這個宏用在一個for循環的開始處,來開始在附屬數據對象中遍歷。
CMSG_NXTHDR()宏
這個用於返回下一個附屬數據對象的struct cmsghdr指針。這個宏會接受兩個輸入參數:
指向struct msghdr結構的指針
指向當前struct cmsghdr的指針
若是沒有下一個附屬數據對象,這個宏就會返回NULL。
遍歷附屬數據
當接收到一個附屬數據時,咱們可使用CMSG_FIRSTHDR()與CMSG_NXTHDR()宏來在附屬數據對象中進行遍歷。下面的示例代碼顯示了for循環的一般格式以及宏的相應用法:
struct msghdr msgh; /* Message Hdr */
struct cmsghdr *cmsg;0 /*Ptr to ancillary hdr */
int *fd_ptr; /* Ptr to file descript.*/
int received_fd; /* The file descriptor */
for ( cmsg=CMSG_FIRSTHDR(&msgh); cmsg!=NULL; cmsg=CMSG_NXTHDR(&msgh,cmsg) ) {
if ( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS ) {
fd_ptr = (int *) CMSG_DATA(cmsg);
received_fd = *fd_ptr;
break;
}
}
if ( cmsg == NULL ) {
/* Error: No file descriptor recv'd */
}
建立附屬數據
要發送一個文件描述符的進程必須使用正確的格式化數據來建立一個附屬數據緩衝區。下面的代碼展現的一般的建立過程:
struct msghdr msg; /* Message header */
struct cmsghdr *cmsg; /* Ptr to ancillary hdr */
int fd; /* File descriptor to send */
char buf[CMSG_SPACE(sizeof fd)]; /* Anc. buf */
int *fd_ptr; /* Ptr to file descriptor */
msg.msg_control = buf;
msg.msg_controllen = sizeof buf;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof fd);
/* Initialize the payload: */
fd_ptr = (int *)CMSG_DATA(cmsg);
*fd_ptr = fd;
/*
* Sum of the length of all control
* messages in the buffer:
*/
msg.msg_controllen = cmsg->cmsg_len;數組