2、Winsock的寻址方式和字节顺序

  本节讲述在Winsock中主机地址信息的表示方法, 以及相关的操作函数

  2.1 Winsock寻址

  因为Winsock要兼容多个协议,所以必须使用通用的寻址方式。 TCP/IP 使用IP地址和端口号来指定一个地址, 但是其他协议也许采用不同的形式。 如果Winsock强迫使用制定的寻址方式,添加其他协议就不大可能了。 Winsock的第一个版本使用sockaddr结构来解决此问题。

struct sockaddr
{
	u_short sa_family;
	char	sa_data[14];
};

  在这个结构中,第一个成员sa_family 制定了这个地址使用的地址家族。 sa_data 成员存储的数据在不同的地址家族中的可能不同。 本书仅仅使用Internet地址家族(TCP/IP),Winsock已经定义了sockaddr结构的TCP/IP版本————sockaddr_in结构。 它们本质上是相同的结构,但是第2个更容易操作。

  在Winsock中,应用程序通过SOCKADDR_IN结构来制定IP地址和端口号,定义如下:

struct sockaddr_in
{
	short		sin_family;			// 地址家族(即制定地址格式),应为AF_INET
	u_short		sin_port;			// 端口号
	struct		in_addr sin_addr;	// IP地址
	char		sin_zero[8];		// 空字节,要设为0
};
  (1) sin_family 域必须设为AF_INET, 它告诉Winsock程序使用的是IP地址家族

  (2) sin_port 域制定了TCP或UDP通信服务的端口号。应用程序在选择端口号时必须小心,因为有一些端口号是保留给公共服务使用的,如FTP和HTTP。 基本上,端口号可分成如下3个范围: 公共的、注册的、动态的(或私有的)。

          0~1023 由IANA 管理, 保留为公共的服务使用

         1024~49151 是普通用户注册的端口号, 由IANA列出

          49152~65535 是动态/或私有的端口号

    普通用户应用程序应该选择1024~49151之间的注册了的端口号,以避免使用了一个其他应用程序或者系统服务已经使用的端口号。 在49152~65535 之间的端口号也可以自由地使用,因为没有服务注册这些端口号。

   (3) sin_addr 域用来存储IP地址(32位), 它被定义为一个联合来处理整个32位的值,两个16位部分或者两个字节单独分开。 描述32位IP地址的in_addr结构定义如下:

struct in_addr
{
	union 
	{
		struct 
		{
			u_char s_b1, s_b2, s_b3, s_b4;		// 以4个u_char来描述
		}S_un_b;
		struct 
		{
			u_short s_w1, s_w2;					// 以两个u_short来描述
		}S_un_w;
		u_long S_addr;							// 以一个u_long来描述
	}S_un;
};
    用字符串“aa.bb.cc.dd"表示IP地址时,字符串中由点分开的4个域是以字符串的形式对in_addr结构中的4个u_char值的描述。由于每个字节的数值范围是0~255,所以各域的值都不可超过255.

   (4) 最后一个域 sin_zero 没有使用, 是为了与SOCKADDR结构大小相同才设置的。

   应用程序可以使用inet_addr函数将一个由小数点分隔的十进制IP地址字符串转化成由32位二进制数表示的IP地址。inet_ntoa是inet_addr函数的逆函数, 它将一个网络字节顺序的32位IP地址转化成字符串

unsigned long inet_addr(const char* p);			// 将一个"aa.bb.cc.dd"类型的IP地址字符串转化为32位的二进制数
char* inet_ntoa(struct in_addr in);				// 将32位的二进制数转化为字符串
  注意,inet_addr 返回的32位二进制数是用网络顺序存储的, 下一小节详细讲述字节顺序


  2.2 字节顺序

   字节顺序是长度跨越多个字节的数据被存储的顺序。 例如,一个32位的长整型0x12345678  跨越4个字节(每个字节8位)。 Intel x86机器使用了小尾顺序(little-endian), 意思是最不重要的字节首先存储。 因此,数据0x12345678在内存中的存放顺序是0x78、0x56、0x34、0x12。大多数不使用小尾顺序的机器使用大尾顺序(big-endian),即最重要的字节首先存储。同样的值在内存中的存放顺序将是0x12、0x34、0x56、0x78。因为协议数据要在这个机器间传输, 所以就必须选定其中的一种方式做为标准。否则会引起混淆。

  TCP/IP同意规定使用大尾方式传输数据, 也称为网络字节顺序。 例如,端口号(它是一个16位的数字) 12345(0x3039)的存储顺序 是0x30、0x39。 32位的IP地址也是以这种方式存储的,IP地址的4部分存储在4个字节中,第一部分存储在第一个字节中。

  上述sockaddr和sockaddr_in结构中,除了sin_family成员(它不是协议的一部分)外,其他所有值必须以网络字节顺序存储。 Winsock提供了一些函数来处理本地机器的字节顺序和网络字节顺序的转换。

u_short		htons(u_short hostshort);		// 将u_short类型变量从主机字节顺序转化到TCP/IP网络字节顺序
u_long		htonal(u_long hostlong);		// 将u_long类型变量从主机字节顺序转化到TCP/IP网络字节顺序
u_short		ntohs(u_short netshort);		// 将u_short类型变量从TCP/IP网络字节顺序转化到主机字节顺序
u_long		ntohl(u_long netlong);			// 将u_long类型变量从TCP/IP网络字节顺序转化到主机字节顺序
  这些API是品太无关的。 使用他们可以保证程序正确地运行在所有机器上。

  下面代码事例了如何初始化sockaddr_in结构。

sockaddr_in sockAddr;
// 设置地址家族
sockAddr.sin_family = AF_INET;
// 转化端口好6789到网络字节顺序, 并安排它到正确的成员
sockAddr.sin_port = htons((6789);
// inet_addr函数转化一个"aa.bb.cc.dd"类型的IP地址字符串的长整形
// 它是以网络字节顺序记录IP地址
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");


  2.3 获取地址信息

   通常,主机上的借口被静态的制定一个IP地址,或者是由配置协议来分配,如动态主机配置协议(DHCP)。如果DHCP服务器不能到达,系统会使用Automatic Private IP Addressing(APIPA) 自动分配169.254.0.0/16 范围内的地址。

   1. 获取本机IP地址

   获取本机的IP地址比较简单,下面的GetAllIps 例子打印出了本机使用的所有IP(一个适配器一个IP地址), 程序代码如下:

#pragma once
#include "initsock.h"
#include <iostream>

CInitSock initSock;			// 初始化Winsock库

int main()
{
	char szHost[256];
	memset(szHost, 0, sizeof(szHost));

	// 取得本地主机名称
	::gethostname(szHost, 256);

	// 通过主机名得到地址信息
	hostent* pHost = ::gethostbyname(szHost);

	// 打印出所有IP地址
	in_addr addr;
	for (int i = 0;; i++)
	{
		char* p = pHost->h_addr_list[i];		// p指向一个32位的IP地址
		if (p == NULL )
		{
			break;
		}
		memcpy(&addr.S_un.S_addr, p, pHost->h_length);
		char* szIp = ::inet_ntoa(addr);
		printf("本机IP地址:%s \n", szIp);
	}
	getchar();

	return 0;
}
GetAllIps先调用了gethostname取得本地主机的名字, 然后通过主机名得到其地址的信息

  2 获取MAC地址

  有时为了检测网络, 或者为了一些其他特殊的目的,需要自己来直接操作原始数据贴, 这就需要获取字集和LAN中其他主机的MAC地址。

  获取本地机器的MAC地址很容易, 使用帮助函数GetAdaptersInfo即可。 此函数的作用是获取本地机器的适配器信息, 用法如下:

DWORD GetAdaptersInfo(
	PIP_ADAPTER_INFO pAdapterInfo,		// 指向一个缓冲区, 用来取得IP_ADAPTER_INFO结构的列表
	PULONG pOutBufLen					// 用来制定上面缓冲区的大小。 如果大小不够,此参数返回所需大小
	);	// 函数调用成功返回WRROR_SUCCESS
IP_ADAPTER_INFO 结构包含了本地计算机上网络适配器的信息, 定义如下:

typedef struct _IP_ADAPTER_INFO {
    struct _IP_ADAPTER_INFO* Next;<span style="white-space:pre">					</span>// 指向适配器列表中的下一个适配器(计算机可能有多个适配器)
    DWORD ComboIndex;<span style="white-space:pre">							</span>// 保留字节
    char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];<span style="white-space:pre">			</span>// 适配器名称
    char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];<span style="white-space:pre">		</span>// 对适配器的描述
    UINT AddressLength;<span style="white-space:pre">							</span>// MAC地址的长度(应为6个字节)
    BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];<span style="white-space:pre">				</span>// MAC地址
    DWORD Index;<span style="white-space:pre">							</span>// 适配器索引
    UINT Type;<span style="white-space:pre">								</span>// 适配器类型,如MIB_IF_TYPE_ETHERNET等
    UINT DhcpEnabled;<span style="white-space:pre">							</span>// 指定此适配器是否有效了DHCP(动态主机配置)协议
    PIP_ADDR_STRING CurrentIpAddress;<span style="white-space:pre">					</span>// 保留字段
    IP_ADDR_STRING IpAddressList;<span style="white-space:pre">					</span>// 与此适配器相关的IP地址列表
    IP_ADDR_STRING GatewayList;<span style="white-space:pre">						</span>// 网关地址列表
    IP_ADDR_STRING DhcpServer;<span style="white-space:pre">						</span>// HDCP服务器
    BOOL HaveWins;<span style="white-space:pre">							</span>// 指定此适配器是否使用WINS
    IP_ADDR_STRING PrimaryWinsServer;<span style="white-space:pre">					</span>// WINS服务器的主IP地址
    IP_ADDR_STRING SecondaryWinsServer;<span style="white-space:pre">					</span>// WINS服务器的第二个IP地址
    time_t LeaseObtained;<span style="white-space:pre">						</span>// 获取当前DHCP租用的事件
    time_t LeaseExpires;<span style="white-space:pre">						</span>// 当前DHCP租用期满的时间
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

  下面的例子LocalHostInfo打印出了本机的IP地址、网络(内部LAN)的子网掩码、网关的IP地址和本机的MAC地址。 之后还需要用到

// 注意之前需要加上#include <Iphlpapi.h>  以及 #pragma comment(lib,"Iplpapi.lib")

// 全局数据
u_char	g_ucLocalMac[6];		// 本地MAC地址
DWORD	g_dwGatewayIP;			// 网关MAC地址
DWORD	g_dwLocalIP;			// 本地IP地址
DWORD	g_dwMask;				// 子网掩码

BOOL GetGlobalData()
{
	PIP_ADAPTER_INFO	pAdapterInfo = NULL;
	ULONG				ulLen = 0;

	// 为适配器结构申请内存
	::GetAdaptersInfo(pAdapterInfo, &ulLen);

	pAdapterInfo = (PIP_ADAPTER_INFO)::GlobalAlloc(GPTR, ulLen);

	// 取得本地适配器结构信息
	if (::GetAdaptersInfo(pAdapterInfo,&ulLen) == ERROR_SUCCESS)
	{
		if ( pAdapterInfo != NULL )
		{
			memcpy(g_ucLocalMac, pAdapterInfo->Address, 6);
			g_dwGatewayIP = ::inet_addr(pAdapterInfo->GatewayList.IpAddress.String);
			g_dwLocalIP = ::inet_addr(pAdapterInfo->IpAddressList.IpAddress.String);
			g_dwMask = ::inet_addr(pAdapterInfo->IpAddressList.IpMask.String);
		}
	}

	printf("\n----------------------本地主机信息-------------------------\n\n");

	in_addr in;
	in.S_un.S_addr = g_dwLocalIP;
	printf("		IP Address:%s\n", ::inet_ntoa(in));

	in.S_un.S_addr = g_dwMask;
	printf("		Subnet Mask:%s\n", ::inet_ntoa(in));

	in.S_un.S_addr = g_dwGatewayIP;
	printf("		Default Gatway:%s\n", ::inet_ntoa(in));

	u_char* p = g_ucLocalMac;
	printf("		MAC Address:%02X-%02X-%02X-%02X-%02X-%02X\n", p[0], p[1], p[2], p[3], p[4], p[5]);

	printf("\n\n");
	return TRUE;
}

这里调用自定义函数GetGlobalData之后, 程序运行结果如图所示,  根据自己的机器而定
























  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值