做网站需要多少费用/大二网络营销实训报告
数据通信实验
文章目录
- 数据通信实验
- 一、 实验名称及内容
- 二、实验过程和结果
- 环境
- 程序设计
- 程序流程图
- 程序使用
- 程序主体说明
- 数据结构
- 函数
- 实验结果
一、 实验名称及内容
名称:利用 Winsock 完成类似于系统自带的 ping 远程主机的功能
内容:利用 Winsock 完成基于 ICMP 协议的 ping 程序,该程序完成类似于系统自带的 ping 远程主机的功能,可以直接 ping IP 地址,也可以自动进行域名解析,并且可以指定 ping 次数和统计 ping 结果,基本包含了系统自带的 ping 命令的基本功能。并且,若一台远程主机有多个 IP,则该程序会自动依次 ping 该主机所有 IP。用户可以通过该程序 ping 远程主机来测试连接性。
二、实验过程和结果
环境
物理主机系统:macOS Catalina 10.15.4
虚拟机系统:Windows 10 专业版 x64
计算机名:691B
虚拟机软件:Parallels Desktop 15 for Mac Pro Edition, version 15.1.4 (47270)
编程环境(IDE):Visual Studio 2019
程序设计
- 域名解析(获取远程主机名)
- 调用 WSAStartup() 函数,初始化 winsock
- 调用 getaddrinfo() 函数,获得指定 IP 或域名的主机信息,完成域名解析
- ping 远程主机,循环指定次数,默认 444 次
- 调用 WSAStartup() 函数,初始化 winsock
- 调用 socket() 函数创建一个 Socket (PF_INET, SOCK_RAW, IPPROTO_ICMP)
- 调用 setsockopt() 设置接收超时(1s)
- 设置目的地址
- 构造 ICMP 封包
- 构造 ICMP 报头
- 在报头后填充数据,可以任意
- 计算校验和
- 调用 sendto() 函数发送 ICMP 报文
- 调用 recvfrom() 函数接收 ICMP 报文
- 统计 ping 信息
程序流程图
程序使用
编译后在命令行运行可执行程序:
myping.exe [IP/DN] ([times](default 4 times))
如:
myping.exe baidu.com
myping.exe baidu.com 10
myping.exe 39.156.69.79 10
程序主体说明
数据结构
#include <stdio.h>
#include <time.h>
#include <Winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;#pragma comment (lib, "ws2_32.lib")// 2字节 对齐 sizeof(icmp_header) == 8
// 这是ping 在wireshark抓包中的数据结构
typedef struct icmp_header // ICMP报头
{unsigned char icmp_type; // 消息类型unsigned char icmp_code; // 代码unsigned short icmp_checksum; // 校验和unsigned short icmp_id; // 用来惟一标识此请求的ID号,通常设置为进程IDunsigned short icmp_sequence; // 序列号
} icmp_header;
函数
u_short ss2n(string s) // 将字符串转为数字,用来将argv指向的字符串类型的指定ping次数转为短整型
{stringstream ss;u_short u;ss << s;ss >> u;return u;
}
// 计算校验和
unsigned short chsum(struct icmp_header* picmp, int len)
{long sum = 0;unsigned short* pusicmp = (unsigned short*)picmp;while (len > 1){sum += *(pusicmp++);if (sum & 0x80000000)sum = (sum & 0xffff) + (sum >> 16);len -= 2;}if (len)sum += (unsigned short)*(unsigned char*)pusicmp;while (sum >> 16)sum = (sum & 0xffff) + (sum >> 16);return (unsigned short)~sum;
}
// ping 函数
static int respNum = 0;
static int minTime = 65535, maxTime = 0, sumTime = 0;
int ping(char* szDestIp)
{//printf("destIp = %s\n",szDestIp);int bRet = 1;WSADATA wsaData;int nTimeOut = 1000;//1s char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };icmp_header* pIcmp = (icmp_header*)szBuff;char icmp_data[32] = { 0 };WSAStartup(MAKEWORD(2, 2), &wsaData);// 创建原始套接字SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);// 设置接收超时setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));// 设置目的地址sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;inet_pton(AF_INET, szDestIp, &dest_addr.sin_addr);dest_addr.sin_port = htons(0);// 构造ICMP封包pIcmp->icmp_type = ICMP_ECHO_REQUEST;pIcmp->icmp_code = 0;pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();pIcmp->icmp_sequence = 0;pIcmp->icmp_checksum = 0;// 填充数据,可以任意 memcpy((szBuff + ICMP_HEADER_SIZE), "abcdelmnopqrstuvwiammekakuactor", 32);// 计算校验和pIcmp->icmp_checksum = chsum((struct icmp_header*)szBuff, sizeof(szBuff));sockaddr_in from_addr;char szRecvBuff[1024];int nLen = sizeof(from_addr);int ret, flag = 0;DWORD start = GetTickCount();ret = sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR*)&dest_addr, sizeof(SOCKADDR));//printf("ret = %d ,errorCode:%d\n",ret ,WSAGetLastError() ); int i = 0;//这里一定要用while循环,因为recvfrom 会接受到很多报文,包括 发送出去的报文也会被收到! 不信你可以用 wireshark 抓包查看,这个问题纠结来了一晚上 才猜想出来! while (1) {if (i++ > 5) {// icmp报文 如果到不了目标主机,是不会返回报文,多尝试几次接受数据,如果都没收到 即请求失败 flag = 1;break;}memset(szRecvBuff, 0, 1024);//printf("errorCode1:%d\n",WSAGetLastError() ); int ret = recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR*)&from_addr, &nLen);//printf("errorCode2:%d\n",WSAGetLastError() ); //printf("ret=%d,%s\n",ret,inet_ntoa(from_addr.sin_addr)) ; //接受到 目标ip的 报文 char str[INET_ADDRSTRLEN];char* ptr = (char*)inet_ntop(AF_INET, &from_addr.sin_addr, str, sizeof(str));if (strcmp(ptr, szDestIp) == 0) {respNum++;break;}}DWORD end = GetTickCount();DWORD time = end - start;if (flag) {printf("请求超时。\n");return bRet;}sumTime += time;if (minTime > time) {minTime = time;}if (maxTime < time) {maxTime = time;}// Windows的原始套接字开发,系统没有去掉IP协议头,需要程序自己处理。// ip头部的第一个字节(只有1个字节不涉及大小端问题),前4位表示ip协议版本号,后4位表示IP头部长度(单位为4字节)char ipInfo = szRecvBuff[0];// ipv4头部的第9个字节为TTL的值unsigned char ttl = szRecvBuff[8];//printf("ipInfo = %x\n",ipInfo);int ipVer = ipInfo >> 4;int ipHeadLen = ((char)(ipInfo << 4) >> 4) * 4;if (ipVer == 4) {//ipv4 //printf("ipv4 len = %d\n",ipHeadLen);// 跨过ip协议头,得到ICMP协议头的位置,不过是网络字节序。 // 网络字节序 是大端模式 低地址 高位字节 高地址 低位字节。-> 转换为 本地字节序 小端模式 高地址高字节 低地址低字节 icmp_header* icmp_rep = (icmp_header*)(szRecvBuff + ipHeadLen);//由于校验和是 2个字节 涉及大小端问题,需要转换字节序 unsigned short checksum_host = ntohs(icmp_rep->icmp_checksum);// 转主机字节序//printf("type = %d ,checksum_host = %x\n",icmp_rep,checksum_host);if (icmp_rep->icmp_type == 0) { //回显应答报文 printf("来自 %s 的回复:字节=32 时间=%2dms TTL=%d checksum=0x%x \n", szDestIp, time, ttl, checksum_host);}else {bRet = 0;printf("请求超时。type = %d\n", icmp_rep->icmp_type);}}else {// ipv6 icmpv6 和 icmpv4 不一样,要做对应的处理 //printf("ipv6 len = %d\n",ipLen); }return bRet;
}
// 主函数,包括完成域名解析功能
int main(int argc, char** argv)
{if (argc < 2) {printf("please input:myping ipaddr!\n");return 0;}u_short times;if (argv[2])times = ss2n(argv[2]);else times = 4;struct addrinfo* result = NULL;struct addrinfo* hostEntry = NULL;struct addrinfo hints;struct sockaddr_in addr;ZeroMemory(&hints, sizeof(hints));hints.ai_family = AF_INET; /* Allow IPv4 */hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */hints.ai_protocol = 0; /* Any protocol */hints.ai_socktype = SOCK_STREAM;char** ppAlias = NULL; // 主机别名char** ppAddr = NULL; // 点分十进制ip地址WORD sockVersion = MAKEWORD(2, 2);WSADATA wsaData;if (WSAStartup(sockVersion, &wsaData) != 0) {return false;}DWORD dwRetval;dwRetval = getaddrinfo(argv[1], NULL, &hints, &result); // 域名解析if (result == NULL) {cout << "无法解析域名。\n";return 0;}for (hostEntry = result; hostEntry != NULL; hostEntry = hostEntry->ai_next){addr = *(struct sockaddr_in*)hostEntry->ai_addr;char str[INET_ADDRSTRLEN];char* ptr = (char*)inet_ntop(AF_INET, &addr.sin_addr, str, sizeof(str));printf("\n正在 Ping %s 具有 32 字节的数据:\n", ptr);int i = 0;while (i < times){int result = ping(ptr);Sleep(500);i++;}printf("\n%s 的 Ping 统计信息:\n", argv[1]);printf(" 数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%d%% 丢失),\n", i, respNum, i - respNum, (i - respNum) * 100 / i);if (i - respNum >= 4) {return 0;}printf("往返行程的估计时间(以毫秒为单位):\n");printf(" 最短 = %dms,最长 = %dms,平均 = %dms\n", minTime, maxTime, sumTime / respNum);minTime = 65535, maxTime = 0, sumTime = 0;respNum = 0;}return 0;
}
实验结果
上图一共成功进行了五个实验。第一个实验是 ping 一个域名,选择 baidu.com,不设置 ping 次数,结果显示由于该域名解析出两个 IP,于是每个 IP 均成功 ping 了四次,四次均可达,连通性良好,最后对每个 IP 的 ping 结果都进行了正确统计。第二个实验仍 ping baidu.com,设置 ping 次数为 222,结果显示每个 IP 均成功 ping 了两次,两次均可达,连通性良好,最后对每个 IP 的 ping 结果都进行了正确统计。第三个和第四个实验是 ping baidu.com 其中一个域名 39.156.69.7939.156.69.7939.156.69.79,前者不设置 ping 次数,后者设置 ping 101010 次,均成功并符合程序设计的逻辑。最后一个实验是 ping google.com,由于该域名在中国内地被封禁,所以理应无法连通,实验结果也证明了这一点。最后用 macOS 的终端的 ping 命令来测试一下上述结果进行对照实验,结果一模一样,所以本设计的程序能够完成系统自带 ping 命令的基本功能。