• 正文
    • 套接字概念
    • 網(wǎng)絡(luò)字節(jié)序
    • ?IP地址轉(zhuǎn)換函數(shù)
    • sockaddr數(shù)據(jù)結(jié)構(gòu)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

網(wǎng)絡(luò)套接字、網(wǎng)絡(luò)字節(jié)序、sockaddr結(jié)構(gòu)

02/10 15:26
1067
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

套接字概念

Socket本身有“插座”的意思,在Linux環(huán)境下,用于表示進(jìn)程間網(wǎng)絡(luò)通信的特殊文件類型。本質(zhì)為內(nèi)核借助緩沖區(qū)形成的偽文件。

既然是文件,那么理所當(dāng)然的,我們可以使用文件描述符引用套接字。與管道類似的,Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使得讀寫套接字和讀寫文件的操作一致。區(qū)別是管道主要應(yīng)用于本地進(jìn)程間通信,而套接字多應(yīng)用于網(wǎng)絡(luò)進(jìn)程間數(shù)據(jù)的傳遞。

套接字的內(nèi)核實現(xiàn)較為復(fù)雜,不宜在學(xué)習(xí)初期深入學(xué)習(xí)。

TCP/IP協(xié)議中,“IP地址+TCP或UDP端口號”唯一標(biāo)識網(wǎng)絡(luò)通訊中的一個進(jìn)程。“IP地址+端口號”就對應(yīng)一個socket。欲建立連接的兩個進(jìn)程各自有一個socket來標(biāo)識,那么這兩個socket組成的socket pair就唯一標(biāo)識一個連接。因此可以用Socket來描述網(wǎng)絡(luò)連接的一對一關(guān)系。

套接字通信原理如下圖所示:

在網(wǎng)絡(luò)通信中,套接字一定是成對出現(xiàn)的。一端的發(fā)送緩沖區(qū)對應(yīng)對端的接收緩沖區(qū)。我們使用同一個文件描述符索發(fā)送緩沖區(qū)和接收緩沖區(qū)。

TCP/IP協(xié)議最早在BSD UNIX上實現(xiàn),為TCP/IP協(xié)議設(shè)計的應(yīng)用層編程接口稱為socket API。本章的主要內(nèi)容是socket API,主要介紹TCP協(xié)議的函數(shù)接口,最后介紹UDP協(xié)議和UNIX Domain Socket的函數(shù)接口。

網(wǎng)絡(luò)字節(jié)序

我們已經(jīng)知道,內(nèi)存中的多字節(jié)數(shù)據(jù)相對于內(nèi)存地址有大端和小端之分,磁盤文件中的多字節(jié)數(shù)據(jù)相對于文件中的偏移地址也有大端小端之分。網(wǎng)絡(luò)數(shù)據(jù)流同樣有大端小端之分,那么如何定義網(wǎng)絡(luò)數(shù)據(jù)流的地址呢?發(fā)送主機通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內(nèi)存地址從低到高的順序發(fā)出,接收主機把從網(wǎng)絡(luò)上接到的字節(jié)依次保存在接收緩沖區(qū)中,也是按內(nèi)存地址從低到高的順序保存,因此,網(wǎng)絡(luò)數(shù)據(jù)流的地址應(yīng)這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址。

TCP/IP協(xié)議規(guī)定,網(wǎng)絡(luò)數(shù)據(jù)流應(yīng)采用大端字節(jié)序,即低地址高字節(jié)。例如上一節(jié)的UDP段格式,地址0-1是16位的源端口號,如果這個端口號是1000(0x3e8),則地址0是0x03,地址1是0xe8,也就是先發(fā)0x03,再發(fā)0xe8,這16位在發(fā)送主機的緩沖區(qū)中也應(yīng)該是低地址存0x03,高地址存0xe8。但是,如果發(fā)送主機是小端字節(jié)序的,這16位被解釋成0xe803,而不是1000。因此,發(fā)送主機把1000填到發(fā)送緩沖區(qū)之前需要做字節(jié)序的轉(zhuǎn)換。同樣地,接收主機如果是小端字節(jié)序的,接到16位的源端口號也要做字節(jié)序的轉(zhuǎn)換。如果主機是大端字節(jié)序的,發(fā)送和接收都不需要做轉(zhuǎn)換。同理,32位的IP地址也要考慮網(wǎng)絡(luò)字節(jié)序和主機字節(jié)序的問題。

為使網(wǎng)絡(luò)程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯后都能正常運行,可以調(diào)用以下庫函數(shù)做網(wǎng)絡(luò)字節(jié)序和主機字節(jié)序的轉(zhuǎn)換。

#include?<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位長整數(shù),s表示16位短整數(shù)。

如果主機是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換然后返回,如果主機是大端字節(jié)序,這些函數(shù)不做轉(zhuǎn)換,將參數(shù)原封不動地返回。

?IP地址轉(zhuǎn)換函數(shù)

早期:

#include?<sys/socket.h>#include?<netinet/in.h>#include?<arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);in_addr_t inet_addr(const char *cp);char *inet_ntoa(struct in_addr in);

只能處理IPv4的ip地址

注意參數(shù)是struct in_addr

現(xiàn)在:

#include?<arpa/inet.h>int?inet_pton(int?af, const?char?*src, void?*dst);const?char?*inet_ntop(int?af, const void?*src,?char?*dst, socklen_t size);

支持IPv4和IPv6

其中inet_pton和inet_ntop不僅可以轉(zhuǎn)換IPv4的in_addr,還可以轉(zhuǎn)換IPv6的in6_addr。

因此函數(shù)接口是void *addrptr。

sockaddr數(shù)據(jù)結(jié)構(gòu)

strcut sockaddr 很多網(wǎng)絡(luò)編程函數(shù)誕生早于IPv4協(xié)議,那時候都使用的是sockaddr結(jié)構(gòu)體,為了向前兼容,現(xiàn)在sockaddr退化成了(void *)的作用,傳遞一個地址給函數(shù),至于這個函數(shù)是sockaddr_in還是sockaddr_in6,由地址族確定,然后函數(shù)內(nèi)部再強制類型轉(zhuǎn)化為所需的地址類型。

sockaddr數(shù)據(jù)結(jié)構(gòu)

struct?sockaddr?{??sa_family_t?sa_family; ? ??/* address family, AF_xxx */??char?sa_data[14]; ? ? ?/* 14 bytes of protocol address */};

使用 sudo grep -r "struct sockaddr_in {" ?/usr 命令可查看到struct sockaddr_in結(jié)構(gòu)體的定義。一般其默認(rèn)的存儲位置:/usr/include/linux/in.h 文件中。

struct?sockaddr_in {? __kernel_sa_family_t sin_family; ? ? ??/* Address family */? ? 地址結(jié)構(gòu)類型? __be16 sin_port; ? ? ? ? ? ? ??/* Port number */? ? 端口號??struct?in_addr sin_addr; ? ? ? ? ?/* Internet address */? IP地址??/* Pad to size of `struct sockaddr'. */??unsigned?char?__pad[__SOCK_SIZE__ -?sizeof(short?int) -??sizeof(unsigned?short?int) -?sizeof(struct?in_addr)];};
struct?in_addr { ? ? ? ? ? ?/* Internet address. */? __be32 s_addr;};
struct?sockaddr_in6 {??unsigned?short?int?sin6_family; ? ??/* AF_INET6 */? __be16 sin6_port; ? ? ? ? ??/* Transport layer port # */? __be32 sin6_flowinfo; ? ? ? ??/* IPv6 flow information */??struct?in6_addr sin6_addr; ? ? ?/* IPv6 address */? __u32 sin6_scope_id; ? ? ? ??/* scope id (new in RFC2553) */};
struct?in6_addr {??union?{? ? __u8 u6_addr8[16];? ? __be16 u6_addr16[8];? ? __be32 u6_addr32[4];? } in6_u;??#define?s6_addr ? ? in6_u.u6_addr8? #define?s6_addr16 ? in6_u.u6_addr16? #define?s6_addr32 ? ? in6_u.u6_addr32};
#define?UNIX_PATH_MAX 108? struct sockaddr_un {? __kernel_sa_family_t sun_family; ??/* AF_UNIX */? char sun_path[UNIX_PATH_MAX]; ??/* pathname */};

IPv4和IPv6的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結(jié)構(gòu)體表示,包括16位端口號和32位IP地址,IPv6地址用sockaddr_in6結(jié)構(gòu)體表示,包括16位端口號、128位IP地址和一些控制字段。

UNIX Domain Socket的地址格式定義在sys/un.h中,用sock-addr_un結(jié)構(gòu)體表示。各種socket地址結(jié)構(gòu)體的開頭都是相同的,前16位表示整個結(jié)構(gòu)體的長度(并不是所有UNIX的實現(xiàn)都有長度字段,如Linux就沒有),后16位表示地址類型。

IPv4、IPv6和Unix Domain Socket的地址類型分別定義為常數(shù)AF_INET、AF_INET6、AF_UNIX。這樣,只要取得某種sockaddr結(jié)構(gòu)體的首地址,不需要知道具體是哪種類型的sockaddr結(jié)構(gòu)體,就可以根據(jù)地址類型字段確定結(jié)構(gòu)體中的內(nèi)容。

因此,socket API可以接受各種類型的sockaddr結(jié)構(gòu)體指針做參數(shù),例如bind、accept、connect等函數(shù),這些函數(shù)的參數(shù)應(yīng)該設(shè)計成void *類型以便接受各種類型的指針,但是sock API的實現(xiàn)早于ANSI C標(biāo)準(zhǔn)化,那時還沒有void *類型,因此這些函數(shù)的參數(shù)都用struct sockaddr *類型表示,在傳遞參數(shù)之前要強制類型轉(zhuǎn)換一下,例如:

struct?sockaddr_in servaddr;bind(listen_fd, (struct?sockaddr *)&servaddr,?sizeof(servaddr)); ? ?/* initialize servaddr */

 

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

Linux、C、C++、Python、Matlab,機器人運動控制、多機器人協(xié)作,智能優(yōu)化算法,貝葉斯濾波與卡爾曼濾波估計、多傳感器信息融合,機器學(xué)習(xí),人工智能。