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