网络编程学习笔记一: 获取远端域名IP地址

前言

以下是博主对网络编程实践的一些理解与体会,个人理解比较多,如有出错欢迎不吝赐教。

获取给定域名的远端IP地址

实验环境

visual studio 2022

整体格式

  1. 导入头文件,装载DLL库
  2. 对windows Sockets dll进行初始化
  3. 获取输入的域名
  4. 对域名进行解析
  5. 输出解析结果

源代码展示

废话不多说先直接上代码,代码分析在后头

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")//引入静态链接库,装载DDL
#pragma warning(disable: 4996) //禁用第4996号错误,很关键
using namespace std;
int main()
{
	// 声明和初始化变量
	WSADATA wsaData;
	int iResult;
	DWORD dwError;
	int i = 0;
	struct hostent* remotehostname;
	char hostname[50]; //用于存储输入的域名
	struct in_addr addr;

	// 初始化 Winsock
	//MAKEWORD(2, 2)表示网络库版本为2.2,环境的网络库信息存在wsaData结构体中。
	//如果函数调用成功,则返回0
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

	//判断初始化是否成功
	if (iResult != 0)
	{
		cout<<"WSAStartup failed: "<<iResult<<endl;
		return 1;
	}
	
	cout << "请输入想要查询的域名:";
	//输入想要解析的主机名称,用scanf_s也可以
	gets_s(hostname);
	//scanf_s("%s", hostname, 50);

	cout<<"\n输入的域名为:"<<hostname<<endl;

	//提高了程序的可移植性,在不同的主机上可以动态的获取主机的ip,不用手动修改程序
	remotehostname = gethostbyname(hostname);

	// 对返回结果进行判断
	if (remotehostname == NULL)//如果获取失败
	{
		dwError = WSAGetLastError();//获取上次错误操作的错误号
		if (dwError != 0)
		{
			if (dwError == WSAHOST_NOT_FOUND) //WSAHOST_NOT_FOUND=11001L 指示关键字没有找到
			{
				cout<<"Host not found"<<endl;
				return 1;

			}
			else if (dwError == WSANO_DATA)  //WSANO_DATA=11004L  指示关键字没有找到
			{
				cout<<"No data record found"<<endl;
				return 1;

			}
			else
			{
				cout<<"Function failed with error: %ld"<<dwError<<endl;
				return 1;

			}
		}
	}
	else
	{
		// 输出地址类型和地址长度
		cout<<"Official name: "<<remotehostname->h_name<<endl;

		//输出可选的ip地址
		i = 0;
		while (remotehostname->h_aliases[i])
			cout << "Alternate name " << i + 1 << ": " << remotehostname->h_aliases[i++] << endl;


		printf("Address type: ");
		switch (remotehostname->h_addrtype)//判断远端ip的地址类型
		{
			case AF_INET:  //如果是ipv4类型的
				cout << "ipv4(AF_INET)" << endl;
				break;
			case AF_NETBIOS:  
				cout << "AF_NETBIOS" << endl;
				break;
			default:
				cout << remotehostname->h_addrtype << endl;
				break;
		}

		cout << "Address length: "<<remotehostname->h_length<<endl;
		
		i = 0;
		// 如果返回的是 IPv4 的地址,则输出
		if (remotehostname->h_addrtype == AF_INET)
		{
			while (remotehostname->h_addr_list[i])
			{
				//两种输出点分十进制ip地址的方式
				addr.s_addr = *(u_long*)remotehostname->h_addr_list[i++];
				cout << "IP Address " << i << ": " << inet_ntoa(addr)<<endl;

				//BYTE* cp = (BYTE*)&*(u_long*)remotehostname->h_addr_list[i++];
				//printf("IP Address: %u.%u.%u.%u\n", cp[0], cp[1], cp[2], cp[3]);

			}
		}
		else if (remotehostname->h_addrtype == AF_NETBIOS)
		{
			cout << "NETBIOS address was returned" << endl;
		}
	}
	iResult = WSACleanup();//终止Windows Sockets DLL 的使用,释放资源
	if (iResult != 0)
	{
		cout << "WSACleanup failed: " << iResult << endl;
		return -1;
	}
	return 0;
}

运行结果展示

在这里插入图片描述

代码调试存在的问题

  1. 编译器不支持老旧的函数
    在这里插入图片描述
    解决方法(二选一,也可以同时食用):
    (1) 在代码中添加#pragma warning(disable: 4996)禁用第4996号错误。
    (2) 打开工程属性(项目->属性)
    在这里插入图片描述

将SDL检查设置为否(C/C++ ->常规 -> SDL检查->否->应用)
在这里插入图片描述

SDL检查在VS中默认是打开状态,用于强制编译器警告。

  1. 域名输入默认为空
    本文代码中使用的是用户自定义输入域名,也可以使用系统默认参数设置域名,具体实现方法如下:
hostname = argv[1];
remotehostname = gethostbyname(hostname);

如果使用这种这种方式获取域名,会发现运行后无域名输入,直接退出了程序。
因为编译器默认argv[1]输入的值为空值(NULL),因此判定为没有域名输入,退出函数。
解决方法:
在命令参数中设置想要输入的域名(属性->调试->设置命令参数->确定)
在这里插入图片描述

对部分数据结构的解释

  1. 对WSAStartup()函数的理解

(1) 函数原型为:int WSAAPI WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
(2) 该函数是一切的开始,任何网络编程项目首先需要做的事情就是调用该函数对Winsock进行初始化。

  1. 对gethostbyname()函数的理解

(1) 函数原型:struct hostent *gethostbyname(const char *hostname);
(2) 函数参数:给定的远端域名。注意域名形式是const char类型,不是字符串string类型,如果类型不对需要进行转换。
(3) 函数返回值:一个hostent类型结构体,用于存放关于域名解析的各种信息。
(4) 该函数是最常用的函数之一,搭配gethostname()函数使用可以提高程序的可移植性。

  1. 对hostent结构体的理解

结构体内容

struct hostent
{
	char *h name; 
	char **h aliases; 
	int h addrtype; 
	int h length; 
	char **h addr list; 
}

(1) *h_name:用于存放正式主机名,一维字符串数组,输出时直接输出即可。
(2) **h_aliases:用于存放远端域名的别名,由于一个域名的别名可能有很多,因此用二维字符串数组来存放。输出的时候按行扫描即可。
(3) h_addrtype:用于存放主机IP地址类型,ipv4,ipv6等。
(4) h_length:用于存放地址字节长度,对于IPV4是四字节(32位)
(5) **h_addr_list:用于存放主机域名对应的IP地址,由于域名可能有多个,因此对应的IP地址也可能有多个,用二维字符串数组存放。

  1. 对IP地址输出的理解

存储在**h_addr_list中的IP地址是不能直接输出的,需要转换为我们所熟知的点分十进制表示方式才可以进行输出。
转换方法一般有两种:(注:以下代码粘贴到上述源代码中即可运行)
(1) 使用系统提供的in_addr结构体

addr.s_addr = *(u_long*)remotehostname->h_addr_list[i++];
cout << "IP Address " << i << ": " << inet_ntoa(addr)<<endl;

首先将字符显式强转为u_long类型,存储到结构体的s_addr变量中,然后调用inet_ntoa()函数将结构体进行转换,输出转换后的点分十进制IP地址。
(2) 使用BYTE类型进行强转

BYTE* cp = (BYTE*)&*(u_long*)remotehostname->h_addr_list[i++];
printf("IP Address: %u.%u.%u.%u\n", cp[0], cp[1], cp[2], cp[3]);

首先任然是将字符显式强转为u_long类型,不过之后再次对其进行强转,转换为BYTE*类型,最后按位置输出字符串中的数值。

总结

  1. 在一切开始之前一定要调用WSAStartup()函数,用于初始化,不然你会很痛苦。
  2. WSAStartup()和WSACleanup()函数配套使用,做事要有始有终。
  3. 遇到错误要有耐心,使用断点调试分析出错原因,细心分析。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值