1. 程式人生 > >UNP筆記(1)——基本結構體和工具函式

UNP筆記(1)——基本結構體和工具函式


一、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的地址結構體都要經過通用套接字結構體進行結構體指標強轉。
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) */
};
    通過比較通用套接字結構體和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。下面是我做的一個測試:
#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);