2013-04-15 84 views
37

我正在查看函數,如connect()bind()在C套接字中,並注意他們採取指針sockaddr結構。我一直在閱讀並使您的應用程序與AF無關,因此使用sockaddr_storage結構體指針並將其轉換爲sockaddr指針是有用的,因爲它具有用於較大地址的所有額外空間。推理C插座sockaddr和sockaddr_storage

我想知道像connect()bind()是索要sockaddr指針去從一個指向一個比它期待一個更大的結構的指針訪問數據的功能如何。當然,您將它傳遞給您提供的結構的大小,但是函數使用的實際語法是:將IP地址從指向更大結構的指針轉到struct *sockaddr

這可能是因爲我來自面向對象的語言,但它看起來像一種黑客和一點點混亂。

回答

37

當您向sockaddr發送一個指向struct sockaddr_storage的指針時,希望指向struct sockaddr的函數可能會將您發送到sockaddr的指針類型化。通過這種方式,他們訪問它就好像它是一個struct sockaddr

struct sockaddr_storage被設計以適應既是struct sockaddr_instruct sockaddr_in6

你沒有創建自己的struct sockaddr,你通常會創建取決於你使用的IP版本struct sockaddr_instruct sockaddr_in6。爲了避免試圖知道你將使用什麼IP版本,你可以使用一個struct sockaddr_storage,它可以保存。這將通過connect(),bind()等功能被轉換爲struct sockaddr,並以這種方式進行訪問。

你可以看到以下這些結構的(填充是實現特定的,爲了對準):

struct sockaddr { 
    unsigned short sa_family; // address family, AF_xxx 
    char    sa_data[14]; // 14 bytes of protocol address 
}; 


struct sockaddr_in { 
    short   sin_family; // e.g. AF_INET, AF_INET6 
    unsigned short sin_port;  // e.g. htons(3490) 
    struct in_addr sin_addr;  // see struct in_addr, below 
    char    sin_zero[8]; // zero this if you want to 
}; 


struct sockaddr_in6 { 
    u_int16_t  sin6_family; // address family, AF_INET6 
    u_int16_t  sin6_port;  // port number, Network Byte Order 
    u_int32_t  sin6_flowinfo; // IPv6 flow information 
    struct in6_addr sin6_addr;  // IPv6 address 
    u_int32_t  sin6_scope_id; // Scope ID 
}; 

struct sockaddr_storage { 
    sa_family_t ss_family;  // address family 

    // all this is padding, implementation specific, ignore it: 
    char  __ss_pad1[_SS_PAD1SIZE]; 
    int64_t __ss_align; 
    char  __ss_pad2[_SS_PAD2SIZE]; 
}; 

所以你可以看到,如果函數需要一個IPv4地址,它只會讀前4個字節(因爲它假定結構類型爲struct sockaddr,否則它將讀取IPv6的全部16個字節)。

+0

因此,假設我有一個指向'struct sockaddr_storage'的指針,名爲'sas',並且結構中的所有字段都已正確地填充了地址和地址系列。我現在執行這個:'struct sockaddr * s =(struct sockaddr *)sas'。我現在怎樣才能把地址從's'中取出? –

+1

你不會直接使用'struct sockaddr'。你要做的就是將它轉換回'sockaddr_storage'或'sockaddr_in',然後讀取它。正如你可以從我的帖子看到'sockaddr'有足夠的空間來適應IPv4或v6地址。話雖如此,我不知道爲什麼你會想爲'struct sockaddr'強制轉換爲除了函數需要的參數之外的任何東西。 'struct sockaddr'不適用於'程序員使用'。 – theprole

+5

我正在讀一本書,說sockaddr不夠大,無法容納sockaddr_in6。 –

7

在具有至少一個虛擬函數的C++類中給出一個TAG。該標籤允許您將dynamic_cast<>()分配給您的班級派生的任何班級,反之亦然。標籤是什麼允許dynamic_cast<>()工作。或多或少,這可以是一個數字或一串...

在C中,我們只限於結構。但是,結構也可以分配一個TAG。事實上,如果你看看他的回答中發佈的所有結構,你會注意到它們都以2個字節(一個無符號短)開始,它表示我們稱之爲地址族。這定義到底是什麼結構,因此它的大小,字段等

因此,你可以做這樣的事情:

int bind(int fd, struct sockaddr *in, socklen_t len) 
{ 
    switch(in->sa_family) 
    { 
    case AF_INET: 
    if(len < sizeof(struct sockaddr_in)) 
    { 
     errno = EINVAL; // wrong size 
     return -1; 
    } 
    { 
     struct sockaddr_in *p = (struct sockaddr_in *) in; 
     ... 
    } 
    break; 

    case AF_INET6: 
    if(len < sizeof(struct sockaddr_in6)) 
    { 
     errno = EINVAL; // wrong size 
     return -1; 
    } 
    { 
     struct sockaddr_in6 *p = (struct sockaddr_in6 *) in; 
     ... 
    } 
    break; 

    [...other cases...] 

    default: 
    errno = EINVAL; // family not supported 
    return -1; 

    } 
} 

正如你可以看到,該函數可以檢查len參數,以確保該長度足以適應期望的結構,因此它們可以用reinterpret_cast<>()(因爲它將在C++中調用)指針。數據在結構中是否正確取決於呼叫者。這方面沒有多少選擇。預計這些函數會在使用數據之前驗證各種事物,並在發現問題時返回-1和errno

所以實際上,你有struct sockaddr_instruct sockaddr_in6你(重新詮釋)強制轉換爲struct sockaddrbind()功能(及其他)施放該指針回struct sockaddr_instruct sockaddr_in6後,他們檢查了sa_family成員並驗證了大小。

+0

雖然這不是一個C++問題,也許你可以澄清,並非所有C++類都有一個TAG,但只有那些至少有一個虛函數。 – MikeMB

+1

@MikeMB OP寫道:「這可能是因爲我來自OOP語言」,我認爲這意味着他想了解C的工作原理與他以前學習的相比。 –

+0

對不起,我可能應該更清楚地表達自己:我沒有任何問題引用C++。我只是想確保 - ***雖然**這不是一個問題,正在解決C++程序員* - **做**參考C++的部分儘可能精確(以防C++初學者絆倒就像我一樣), – MikeMB