SocketAddr类
SocketAddr类定义在lib/SocketAddr.hpp中,实现在lib/SocketAddr.cpp中。SocketAddr类封装了网络通信中经常用到的地址结构以及在这些结构上进行的操作。地址解析也是在SocketAddr的成员函数中完成的。
首先讨论一下Socket编程中用于表示网络地址的数据结构。
网络通信中的端点地址可以一般化的表示为(地址族,该族中的端点地址)。Socket接口系统中用来表示通用的网络地址的数据结构是sockaddr:
struct sockaddr { /* struct to hold an address */ u_char sa_len; /* or uint8_t */ /* total length */ u_short sa_family; /* type of address && address family:AF_xxx value */ char sa_data[14]; /* value of address && protocol-specific address */ };
其中sa_family表示地址所属的地址族,TCP/IP协议的地址族用常量AF_INET表示,而UNIX命名管道的地址族用常量AF_UNIX表示。
使用Socket的每个协议族都精确定义了自己的网络端点地址,并在头文件中提供了相应的结构声明。用来表示TCP/IP地址的数据结构如下:
struct sockaddr_in { u_char sin_len; /* or uint8_t */ /* total length(lenth of structure) */ u_short sin_family; /* type of address(AF_INET) */ u_short sin_port; /* 16-bit TCP or UDP port number */ /* protocol port number --- network byte ordered */ struct in_addr sin_addr; /* IP address(32-bit IPV4 address) --- network byte ordered */ char sin_zero[8]; /* unused (set to zero) */ };
其中,sin_len和sin_family和sockaddr机构中的sa_len以及 sa_family表示相同的数据。结构sockaddr_in将sockaddr中通用的端点地址sa_data(14字节长)针对TCP/IP的地址结构作了细化,分为8bit的端口地址sin_port和 32bit的IP地址。在Linux系统中,结构in_addr如下定义:
struct in_addr { unsigned long s_addr; /* or in_addr_t --- IP address */ };
可见,结构in_addr仅有一个成员,表示一个32bit的数据,即IP地址。对于通用地址结构中的其余bit,填充0。
Socket接口中的很多函数都是为通用的网络地 址结构设计的,例如,我们既可以用bind函数将一个socket绑定到一个TCP/IP的端口上,也可以用bind函数将一个socket绑定到一个UNIX命名管道上。因此,像bind, connect, recvfrom, sendto等函数都要求一个sockaddr结构作为指名地址的参数。这时,我们就要使用强制类型转换把表示IP地址的sockaddr_in结构转换为sockaddr结构进行函数调用。但实际上,sockaddr和 sockaddr_in结构表示的均是同一地址。它们在内存中对应的区域是重合的。
SocketAddr.hpp
/* SocketAddr.hpp */ #ifndef SOCKET_ADDR_H #define SOCKET_ADDR_H #include "headers.h" /* ------------------------------------------------------------------- */ class SocketAddr { public: SocketAddr( const char* inHostname = NULL, unsigned short inPort = 0, bool isIPv6 = false ); SocketAddr( const struct sockaddr* inAddr, Socklen_t inSize ); ~SocketAddr(); void setHostname( const char* inHostname ); // DNS lookup void getHostname( char* outHostname, size_t len ); // reverse DNS lookup void getHostAddress( char* outAddress, size_t len ); // dotted decimal void setPort( unsigned short inPort ); void setPortAny( void ); unsigned short getPort( void ); void setAddressAny( void ); // return pointer to the struct sockaddr struct sockaddr* get_sockaddr( void ); // return pointer to the struct sockaddr_in (IPv4) struct sockaddr_in* get_sockaddr_in( void ); // return pointer to the struct in_addr struct in_addr* get_in_addr( void ); #ifdef IPV6 // return pointer to the struct in_addr struct in6_addr* get_in6_addr( void ); #endif // return the sizeof the addess structure (struct sockaddr_in) Socklen_t get_sizeof_sockaddr( void ); bool isMulticast( void ); bool isIPv6(void) { return mIsIPv6; }; static bool are_Equal(sockaddr *first, sockaddr *second); static bool Hostare_Equal(sockaddr *first, sockaddr *second); static Socklen_t mAddress_size; protected: void zeroAddress( void ); iperf_sockaddr mAddress; bool mIsIPv6; }; // end class SocketAddr #endif /* SOCKET_ADDR_H */
SockedAddr类的功能比较单一,成员变量 mAddress就是SocketAddr的实例所表示的TCP/IP端口地址(包括IP地址和 TCP/UDP端口号)。类声明mAddress为 iperf_sockaddr类型的变量,而在文件/lib/headers.h中,有
typedef sockaddr_in iperf_sockaddr
因此,iperf_sockaddr实际上就是sockaddr_in类型的变量。SockedAddr的成员函数都是对mAddress进行读取或修改的操作的。比较复杂的成员函数是setHostname,它完成了地址解析的过程,源代码如下:
void SocketAddr::setHostname( const char* inHostname ) { assert( inHostname != NULL ); // trouble with below on FreeBSD 4.3-RELEASE (for one thing // inet_aton is ipv4 only... and thengethostbyname doesn't // appear to handle ipv4 addresses. // ..I think this works for both ipv6 & ipv4... we'll see #if defined(IPV6) { struct addrinfo *res; int ret_ga; #ifdef DBG_MJZ fprintf(stderr, "about to getaddrinfo on '%s'\n", inHostname); #endif ret_ga = getaddrinfo(inHostname, NULL, NULL, &res); if ( ret_ga ) { fprintf(stderr, "error: %s\n", gai_strerror(ret_ga)); exit(1); } if ( !res->ai_addr ) { fprintf(stderr, "getaddrinfo failed to get an address... target was '%s'\n", inHostname); exit(1); } #ifdef DBG_MJZ fprintf(stderr, "done with gai, ai_fam=%d ai_alen=%d addr=0x%08x...\n", res->ai_family, res->ai_addrlen, *((int *)(res->ai_addr))); #endif // Check address type before filling in the address // ai_family = PF_xxx; ai_protocol = IPPROTO_xxx, see netdb.h // ...but AF_INET6 == PF_INET6 if ( res->ai_family == AF_INET ) { mIsIPv6 = false; } else { mIsIPv6 = true; } memcpy(&mAddress, (res->ai_addr), (res->ai_addrlen)); freeaddrinfo(res); return; } #else mIsIPv6 = false; mAddress.sin_family = AF_INET; // first try just converting dotted decimal // on Windows gethostbyname doesn't understand dotted decimal int rc = inet_pton( AF_INET, inHostname, (unsigned char*)&(mAddress.sin_addr) ); if ( rc == 0 ) { struct hostent *hostP = gethostbyname( inHostname ); if ( hostP == NULL ) { /* this is the same as herror() but works on more systems */ const char* format; switch ( h_errno ) { case HOST_NOT_FOUND: format = "%s: Unknown host\n"; break; case NO_ADDRESS: format = "%s: No address associated with name\n"; break; case NO_RECOVERY: format = "%s: Unknown server error\n"; break; case TRY_AGAIN: format = "%s: Host name lookup failure\n"; break; default: format = "%s: Unknown resolver error\n"; break; } fprintf( stderr, format, inHostname ); exit(1); return; // TODO throw } memcpy(&(mAddress.sin_addr), *(hostP->h_addr_list), (hostP->h_length)); } #endif } // end setHostname
转载者注:
IP地址的三种表示格式:
1)ASCII(网络点分十进制字符串)
2) 网络地址(32位无符号整形,网络字节序,大头)
3)主机地址 (主机字节序)
在上段代码中,有一句很重要的语句:
int rc = inet_pton( AF_INET, inHostname, (unsigned char*)&(mAddress.sin_addr) );
int inet_pton(int family, const char *strptr, void * addrptr);
该函数实现的功能是将IP地址由表达式(即ASCII字符串格式)转换为数值格式(即存放到套接字地址结构中的二进制值)。
上述语句实现了将inHostname(点分十进制格式)转换为二进制格式,并存放到mAddress.sin_addr中。至于其他语句,大多是处理IPv6版本和出错处理。