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);


UNP笔记(1)——基本结构体和工具函数

上一篇:[转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 伍 - 程序流程控制 | 3. BRA (分支) 指令


下一篇:uva 297 Quadtrees