星期四, 四月 26, 2007

C语言 网络地址

在网络编程中,或多或少都会与网络地址打交道,我们从易到难,再来一个三层境界,菜鸟级,入门级,进阶级。下面一一说明。

菜鸟级:
菜鸟都知道,网络编程中要用到IP地址,IP地址是一个32为的整数,是一个unsigned int或unsigned long, 我编程中见到的int与long都是4个自己的!
将一个doted字符串IP地址转化为整型IP地址: unsigned long inet_addr( const char* cp ); 失败的化返回INADDR_NONE(0xffffffff) 竟然是一个广播地址,所以这个操作完成后,一定要检查返回值!
整形IP地址其实就是下面的结构(这个结构被称为最恶心的结构),你可以直接转过去:
typedef struct in_addr {
union {
struct {
u_char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct {
u_short s_w1,s_w2;
} S_un_w;
u_long S_addr;
} S_un;
} in_addr;
将一个整型IP地址转华为doted字符串IP地址: char* FAR inet_ntoa( struct in_addr in ); 你完全可以直接参数整数!这里要注意的仍然是返回值,这个值保存在被Socket实现内部的内存中,MSDN中说明Windows下,同一线程中下一次调用inet_ntoa将会破坏这个值,所以你可能需要保存这个返回值。这个内存也不需要释放。
一种可能的实现如下:
char FAR * inet_ntoa (struct in_addr in)
{
static char strRet[16];
// ...
return strRet;
}

以后请不要对这个指针的使用有太多的疑惑!
黄金规则: 所有被返回的地址都是网络地址(字节序从左到右)
Linux下的表中C库中,IP地址的操作与转化可以使用下面的函数,不多说了,请大家man!
     #include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int
inet_aton(const char *cp, struct in_addr *pin);

in_addr_t
inet_addr(const char *cp);

in_addr_t
inet_network(const char *cp);

char *
inet_ntoa(struct in_addr in);

const char *
inet_ntop(int af, const void * restrict src, char * restrict dst,
socklen_t size);

int
inet_pton(int af, const char * restrict src, void * restrict dst);

struct in_addr
inet_makeaddr(in_addr_t net, in_addr_t lna);

in_addr_t
inet_lnaof(struct in_addr in);

in_addr_t
inet_netof(struct in_addr in);

char *
inet_net_ntop(int af, const void *src, int bits, char *dst, size_t size);

int
inet_net_pton(int af, const char *src, void *dst, size_t size);
除了上面的两个函数外,Windows下不再有其他的inet_开头的兼容函数!

诸位可能要嚷嚷了,我老早以前就不是菜鸟了,好的!让我们马上进入下一个阶段!

入门级:
入门级的两个代表是函数是 struct HOSTENT* FAR gethostbyaddr( const char* addr, int len, int type ); 与 struct hostent* FAR gethostbyname( const char* name );
我们再来看另外一个重要的结构:
typedef struct hostent {
char FAR* h_name;
char FAR FAR** h_aliases;
short h_addrtype;
short h_length;
char FAR FAR** h_addr_list;
} hostent;

h_name
Official name of the host (PC). If using the DNS or similar resolution system, it is the Fully Qualified Domain Name (FQDN) that caused the server to return a reply. If using a local hosts file, it is the first entry after the IP address.
h_aliases
Null-terminated array of alternate names.
h_addrtype
Type of address being returned.
h_length
Length of each address, in bytes.
h_addr_list
Null-terminated list of addresses for the host. Addresses are returned in network byte order. The macro h_addr is defined to be h_addr_list[0] for compatibility with older software.
struct hostent* FAR gethostbyname( const char* name ) 这个函数的恶心之处在于在Windows下,输入的参数不能是doted字符串IP地址,只能时域名,要不然工作不正常.Linux没有这样的限制。
struct HOSTENT* FAR gethostbyaddr( const char* addr, int len, int type ) 的恶心之处在于参数过于复杂且相互影响,各参数描述如下
addr
[in] Pointer to an address in network byte order. (一个描述地址的结构转化为字符串指针)
len
[in] Length of the address, in bytes. (地址结构的长度,不同的长度代表不同的地址类型)
type
[in] Type of the address, such as the AF_INET address family type (defined as TCP, UDP, and other associated Internet protocols). Address family types and their corresponding values are defined in the Winsock2.h header file. (地址组类型,到现在位置,我页不知道到底有多少地址类型)
这三个恶心的参数导致了这个函数强大的扩展能力。
整形IP地址 unsigned long addr = inet_addr("192.168.1.39"); struct hostend* remoteHost = gethostbyaddr((char *) &addr, sizeof(unsigned long)(4), AF_INET);
sockaddr型的地址: struct sockaddr addr; struct hostend* remoteHost = gethostbyaddr((char *) &addr, sizeof(struct sockaddr)(16), addr.sa_family);

我们说道sockaddr结构了,他的定义如下:
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
这个结构也不太稳定,容易变异. 如果是internet地址组,也就是sa_family==AF_INET的情况下,这个结构变成
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
上面的结构里出现了结构in_addr, 怎么,忘了,到菜鸟级再去看看!另外sockaddr类的结构中包含了应用成信息,有个端口号! hostent 结构里的 short h_addrtype; short h_length; char FAR FAR** h_addr_list; 直接受到gethostbyaddr的三个参数的控制!
下面还有几个我没有用过的,也不知道Linux下是否对应!
struct sockaddr_in6 {
short sin6_family;
u_short sin6_port;
u_long sin6_flowinfo;
struct in6_addr sin6_addr;
u_long sin6_scope_id;
};
struct sockaddr_in6_old {
short sin6_family;
u_short sin6_port;
u_long sin6_flowinfo;
struct in6_addr sin6_addr;
};

struct in6_addr {
union {
u_char Byte[16];
u_short Word[8];
} u;
};
typedef struct _SOCKADDR_IRDA {
u_short irdaAddressFamily;
u_char irdaDeviceID[4];
char irdaServiceName[25];
} SOCKADDR_IRDA,
typedef struct sockaddr_storage {
short ss_family;
char __ss_pad1[_SS_PAD1SIZE];
__int64 __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
} SOCKADDR_STORAGE,
gethostbyname 和 gethostbyaddr在Windows下已经被deprecated,用getaddrinfo 代替!我们进入入第三个阶段来看看这个函数!

进阶级:
先来看看下面的函数与结构
int getaddrinfo( const TCHAR* nodename, const TCHAR* servname, const struct addrinfo* hints, struct addrinfo** res );
nodename
[in] Pointer to a null-terminated string containing a host (node) name or a numeric host address string. The numeric host address string is a dotted-decimal IPv4 address or an IPv6 hex address.
servname
[in] Pointer to a null-terminated string containing either a service name or port number.
hints
[in] Pointer to an addrinfo structure that provides hints about the type of socket the caller supports. See Remarks.
res
[out] Pointer to a linked list of one or more addrinfo structures containing response information about the host.
typedef struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
TCHAR* ai_canonname;
struct sockaddr* ai_addr;
struct addrinfo* ai_next;
} addrinfo;

ai_flags
Flags that indicate options used in the getaddrinfo function. See AI_PASSIVE, AI_CANONNAME, and AI_NUMERICHOST.
ai_family
Protocol family, such as PF_INET.
ai_socktype
Socket type, such as SOCK_RAW, SOCK_STREAM, or SOCK_DGRAM.
ai_protocol
Protocol, such as IPPROTO_TCP or IPPROTO_UDP. For protocols other than IPv4 and IPv6, set ai_protocol to zero.
ai_addrlen
Length of the ai_addr member, in bytes.
ai_canonname
Canonical name for the host.
ai_addr
Pointer to a sockaddr structure.
ai_next
Pointer to the next structure in a linked list. This parameter is set to NULL in the last addrinfo structure of a linked list.

int getnameinfo( const struct sockaddr* sa, socklen_t salen, TCHAR* host, DWORD hostlen, TCHAR* serv, DWORD servlen, int flags );
sa
[in] Pointer to a socket address structure containing the address and port number of the socket. For IPv4, the sa parameter points to a sockaddr_in structure; for IPv6, the sa parameter points to a sockaddr_in6 structure.
salen
[in] Length of the structure pointed to in the sa parameter, in bytes.
host
[out] Pointer to the host name. The host name is returned as a Fully Qualified Domain Name (FQDN) by default.
hostlen
[in] Length of the buffer pointed to by the host parameter, in bytes. The caller must provide a buffer large enough to hold the host name, including terminating NULL characters. A value of zero indicates the caller does not want to receive the string provided in host.
serv
[out] Pointer to the service name associated with the port number.
servlen
[in] Length of the buffer pointed to by the serv parameter, in bytes. The caller must provide a buffer large enough to hold the service name, including terminating null characters. A value of zero indicates the caller does not want to receive the string provided in serv.
flags
[in] Flags used to customize processing of the getnameinfo function. See Remarks.



void freeaddrinfo( struct addrinfo* ai );
ai
[in] Pointer to the addrinfo structure or linked list of addrinfo structures to be freed. All dynamic storage pointed to within the addrinfo structure(s) is also freed.
----------------------------------------------------------
...

//--------------------------------
// Declare and initialize variables.
char* ip = "127.0.0.1";
char* port = "27015";
struct addrinfo aiHints;
struct addrinfo *aiList = NULL;
int retVal;

//--------------------------------
// Setup the hints address info structure
// which is passed to the getaddrinfo() function
memset(&aiHints, 0, sizeof(aiHints));
aiHints.ai_family = AF_INET;
aiHints.ai_socktype = SOCK_STREAM;
aiHints.ai_protocol = IPPROTO_TCP;

//--------------------------------
// Call getaddrinfo(). If the call succeeds,
// the aiList variable will hold a linked list
// of addrinfo structures containing response
// information about the host
if ((retVal = getaddrinfo(ip, port, &aiHints, &aiList)) != 0) {
printf("getaddrinfo() failed.n");
}
------------------------------------------------------------
...
//-----------------------------------------
// Declare and initialize variables
struct sockaddr_in saGNI;
char hostName[256];
char servInfo[256];
u_short port;
port = 27015;

//-----------------------------------------
// Set up sockaddr_in structure which is passed
// to the getnameinfo function
saGNI.sin_family = AF_INET;
saGNI.sin_addr.s_addr = inet_addr(ip);
saGNI.sin_port = htons(port);

//-----------------------------------------
// Call getnameinfo
if ((retVal = getnameinfo((SOCKADDR *)&saGNI,
sizeof(sockaddr),
hostName,
256,
servInfo,
256,
NI_NUMERICSERV)) != 0) {
printf("getnameinfo() failed.n");
printf("Error #: %ldn", WSAGetLastError());
}

这几个函数就比较高级,与前面两级的的函数相比,已经上升到应用而不是停留在网络,而且用户自主管理内存,这样的好处是支持可重入的并发使用,到了这个阶段,也不需要多说了,手册就在那里,自己看了!
下面在在第一二阶段的函数基础上对getaddrinfo的简单实现!作为对此文的总结!
int
fake_getaddrinfo(const char *hostname, const char* strport, const struct addrinfo* hints,struct addrinfo **ai)
{
struct hostent *he;
int addr = inet_addr(hostname);
struct sockaddr_in *saddr;
*ai = (struct addrinfo *)malloc(sizeof(struct addrinfo));
/*
The gethostbyname function cannot resolve IP address strings passed to it.
Such a request is treated exactly as if an unknown host name were passed.
Use inet_addr to convert an IP address string the string to an actual IP address,
then use another function, gethostbyaddr, to obtain the contents of the hostent structure.
*/
if(addr==INADDR_NONE)
{
he = gethostbyname(hostname);
if (!he)
return (-1);
ai->ai_family = hints->ai_family;
ai->ai_socktype = hints->ai_socktype; //SOCK_RAW, SOCK_STREAM, or SOCK_DGRAM.
ai->ai_protocol = hints->ai_proocol; //IPPROTO_TCP or IPPROTO_UDP or 0
ai->ai_canonname = hostname;
ai->ai_addrlen = sizeof(struct sockaddr);
if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen)))
return (-1);
saddr = (struct sockaddr_in*)ai->ai_addr;
memset(saddr,0,sizeof(struct sockaddr_in));
saddr->sin_family = AF_INET;
saddr->sin_port = atoi(strport);
memcpy(&saddr->sin_addr, he->h_addr, sizeof(struct in_addr));
ai->ai_next = NULL;
}else
{
he = gethostbyaddr(&addr, sizeof(int), AF_INET );
if (!he)
return (-1);
ai->ai_family = he->h_addrtype;
ai->ai_socktype = hints->ai_socktype; //SOCK_RAW, SOCK_STREAM, or SOCK_DGRAM.
ai->ai_protocol = hints->ai_proocol; //IPPROTO_TCP or IPPROTO_UDP or 0
ai->ai_canonname = he->h_name;
ai->ai_addrlen = sizeof(struct sockaddr);
if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen)))
return (-1);
saddr = (struct sockaddr_in*)ai->ai_addr;
memset(saddr,0,sizeof(struct sockaddr_in));
saddr->sin_family = AF_INET;
saddr->sin_port = atoi(strport);
memcpy(&saddr->sin_addr, he->h_addr, sizeof(struct in_addr));
ai->ai_next = NULL;
}
return (0);
}

void fake_freeaddrinfo(struct addrinfo *ai)
{
free(ai->ai_addr);
free(ai);
}

没有评论: