UNP筆記(1)——基本結構體和工具函式
阿新 • • 發佈:2019-01-22
一、socket相關結構體
socket相關的結構體主要是存放地址的一些結構體,例如sockaddr_in(最常用)、sockaddr_in6(IPv6地址結構體)、sockaddr(socket的函式裡面都用這個當引數,其他結構體強轉過來)和sockaddr_storage。
1.IPv4套接字地址結構體
#include <netinet/in.h> struct sockaddr_in { __uint8_t sin_len; //結構體長度,這裡為16。(1 + 1 + 2 + 4 + 8 = 16) sa_family_t sin_family; //協議族型別,一般為AF_INET。實際上是__uint8_t,佔1位元組。 in_port_t sin_port; //埠號 最大65535 佔2位元組 struct in_addr sin_addr; //這裡存放的是IP地址,結構體定義在下方 佔4位元組char sin_zero[8]; //預留的空間,一般置0,佔8位元組}; }; struct in_addr { in_addr_t s_addr; //in_addr_t是__uint32_t的typedef。 佔4位元組 };
註釋說明了各個變數的用途,需要注意的是,sin_port和sin_addr.s_addr存放的埠號和ip地址是網路位元組序的,實際上這個結構體並不在網路上傳輸。 當前網路裡面使用最多的就是IPv4套接字地址,也就是大家熟知的sockaddr_in。所有的socket函式裡面,都是通過地址結構體來告知核心對端或者自己的IP地址和埠。該結構體定義如下:
2.IPv6套接字地址結構體
IPv6的套接字我在實際程式設計中運用很少,平時也沒有留意過。v6的套接字地址結構體比v4版的多了幾個欄位來存放流資訊和一些v6特性有關的資訊。結構體定義如下:struct sockaddr_in6 { __uint8_t sin6_len; /* length of this struct(sa_family_t) 結構體長度*/ sa_family_t sin6_family; /* AF_INET6 (sa_family_t) 協議族*/ in_port_t sin6_port; /* Transport layer port # (in_port_t) 埠*/ __uint32_t sin6_flowinfo; /* IP6 flow information 流資訊*/ struct in6_addr sin6_addr; /* IP6 address 128bit的IP地址*/ __uint32_t sin6_scope_id; /* scope zone index v6相關的資訊*/ }; /*in6_addr在Mac OS 中的定義*/ struct in6_addr { union { __uint8_t __u6_addr8[16]; __uint16_t __u6_addr16[8]; __uint32_t __u6_addr32[4]; } __u6_addr; /* 128-bit IP6 address */ }; /*in6_addr在POSIX中的定義*/ struct in6_addr { union { __uint8_t __u6_addr8[16]; } __u6_addr; /* 128-bit IP6 address */ };
我平時使用Mac OS居多,在讀本書時發現Mac中的in6_addr定義和POSIX有區別,Mac OS中是一個union結構。個人理解應該是可以分16節,分8節,分4節,方便其他的一些地址讀取的操作,但是裝的東西還是一樣的。與v4一樣,埠和ip地址都是網路位元組序。
3.通用套接字地址結構體
通用套接字地址結構體是作為最終傳給socket相關函式的結構體,不管是v4還是v6的地址結構體都要經過通用套接字結構體進行結構體指標強轉。通過比較通用套接字結構體和v4、v6套接字結構體,可以看出前兩個變數長度是一樣的,第三個變數一個char陣列。之前看到有網友討論sa_data[14]裝不下v6的地址,其實是對結構體指標強轉性質不太熟悉。強轉成sockaddr*指標後,sa_data[x]指向的是原結構體內部的不同偏移量所在的單元。如果是ipv6強轉過來的話,sa_data[7]就可以定址到v6地址結構體的in6_addr,從而獲取地址。原帖見http://bbs.csdn.net/topics/380026132?page=1。下面是我做的一個測試:struct sockaddr { __uint8_t sa_len; /* total length */ sa_family_t sa_family; /* [XSI] address family */ char sa_data[14]; /* [XSI] addr value (actually larger) */ };
#include <iostream>
struct sockaddr {
uint8_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
struct in6_addr {
union {
char __u6_addr8[16];
} __u6_addr; /* 128-bit IP6 address */
};
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
uint16_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* scope id (new in RFC2553) */
};
int main(int argc, const char * argv[])
{
struct sockaddr_in6 sk_in6;
std::cout<<"size of sin6_family = "<<sizeof(sk_in6.sin6_family)<<std::endl;
for(int i = 0;i < 16;++i){
sk_in6.sin6_addr.__u6_addr.__u6_addr8[i] = '3';
}
for(int i = 0;i < 16;++i){
std::cout<<sk_in6.sin6_addr.__u6_addr.__u6_addr8[i]<<std::endl;
}
std::cout<<"---------"<<std::endl;
struct sockaddr *_sockaddr = (sockaddr*)&sk_in6;
struct in6_addr *_in6_addr = (in6_addr*)&_sockaddr->sa_data[7];
for(int i = 0;i < 16;++i){
std::cout<<_in6_addr->__u6_addr.__u6_addr8[i]<<std::endl;
}
return 0;
}
執行結果是輸出了存到v6中的ip地址,我這裡全部輸的是字元'3'。
4.新的通用套接字地址結構sockaddr_storage
這個新的通用套接字在UNP中稱之為儲存套接字。在socket.h裡面找了一下,基本socket函式並沒用使用sockaddr_storage來當做通用套接字。由於書中並沒有將這個結構體用途說的太清楚,個人估計這個結構體應該是用來儲存,而不是用來傳參的。struct sockaddr_storage {
__uint8_t ss_len; /* address length */
sa_family_t ss_family; /* [XSI] address family */
char __ss_pad1[_SS_PAD1SIZE];
__int64_t __ss_align; /* force structure storage alignment */
char __ss_pad2[_SS_PAD2SIZE];
};
二、socket基本函式-工具類
我個人將基本的socket函式分為兩類,一類是工具類,主要用來做一些位元組排序或者地址轉換等簡單的操作,不涉及到功能的。另一類就是功能類。
1.位元組排序函式
位元組排序實際上就是大端、小端的問題。大端就是高地址放LSB,低地址放MSB。UNP上說Mac大端,實際測試了一下發現是小端,估計是新核心的原因。 由於網路上傳輸的資料要滿足大端位元組序,所以小端系統在發資料的時候就要做一個排序。如果系統是大端,那麼就不用了。Posix中是用4個函式來完成位元組排序的。這裡我們給出的是Mac系統下的程式碼,這個大同小異,不用深究。#define ntohs(x) __DARWIN_OSSwapInt16(x)
#define htons(x) __DARWIN_OSSwapInt16(x)
#define ntohl(x) __DARWIN_OSSwapInt32(x)
#define htonl(x) __DARWIN_OSSwapInt32(x)
在大端系統中,這些函式直接定義成空巨集就行了。
ntohs中的s實際上就是short的意思代表16位,用來對埠號進行位元組排序。ntohl中的l自然是32位用來轉換ip地址。
2.位元組操作函式
UNP書中十分推薦用這三個函式來替代memcmp,memcpy和memset的置0這三個ANSI C中的用法。理由是更加安全,bzero的引數少一個(置0當然少一個...)。memcpy在源地址和目的地址相同的時候結果會不可預期。int bcmp(const void *, const void *, size_t) __POSIX_C_DEPRECATED(200112L);
void bcopy(const void *, void *, size_t) __POSIX_C_DEPRECATED(200112L);
void bzero(void *, size_t) __POSIX_C_DEPRECATED(200112L);
3.ASCII碼地址轉網路位元組序地址
程式中往往是通過字串來定義IP地址的,要在網路中傳輸首先要將字串轉換為網路位元組序的IP地址,其實這裡隱含了一個位元組排序過程。
舊式的轉換函式這裡就不講了,因為對v6地址不通用,雖然我在windows程式開發中還經常使用他們。甚至用到了專案當中,目測它們不會在v6的環境下被用到,哈哈。
1)inet_pton函式
pton的意思就是指標轉網路位元組。第一個引數是af_family,第二個引數是字串指標,第三個引數用來是接受結果的。int inet_pton(int family, const char * strptr, void *addrptr);
2)inet_ntop函式
ntop的意思剛好和上面相反,不做累述了。const char *inet_ntop(int family, const void *addr, char *strptr, socklen_t len);