1.题目描述
了解 Tracert 程序的实现原理,并调试通过。然后参考 Tracert 程序和计算机网络教材 4.4.2 节, 计算机网络 课程设计指导书 2 编写一个 Ping 程序,并能测试本局域网的所有机器是否在线,运行界面如下图所示的 QuickPing 程序。
2.程序Demo
3.参考代码
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
#include "sstream"
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
//IP 报头
typedef struct {
unsigned char hdr_len: 4; //4 位头部长度
unsigned char version: 4; //4 位版本号
unsigned char tos; //8 位服务类型
unsigned short total_len; //16位总长度
unsigned short identifier; //16位标识符
unsigned short frag_and_flags; //3 位标志加 13 位片偏移
unsigned char ttl; //8 位生存时间
unsigned char protocol; //8 位上层协议号
unsigned short checksum; //16位校验和
unsigned long sourceIP; //32位源 IP 地址
unsigned long destIP; //32位目的 IP 地址
} IP_HEADER;
//ICMP 报头
typedef struct {
BYTE type; //8 位类型字段
BYTE code; //8 位代码字段
USHORT cksum; //16 位校验和
USHORT id; //16 位标识符
USHORT seq; //16 位序列号
} ICMP_HEADER;
//报文解码结构
typedef struct {
USHORT usSeqNo; //序列号
DWORD dwRoundTripTime; //往返时间
in_addr dwIPaddr; //返回报文的 IP 地址
} DECODE_RESULT;
//计算网际校验和函数
USHORT checksum(USHORT *pBuf, int iSize) {
unsigned long cksum = 0;
while (iSize > 1) {
cksum += *pBuf++;
iSize -= sizeof(USHORT);
}
if (iSize) {
cksum += *(UCHAR *) pBuf;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (USHORT) (~cksum);
}
//对数据包进行解码
BOOL DecodeIcmpResponse(char *pBuf, int iPacketSize, DECODE_RESULT &DecodeResult, BYTE ICMP_ECHO_REPLY, BYTE ICMP_TIMEOUT) {
//检查数据报大小的合法性
IP_HEADER *pIpHdr = (IP_HEADER *) pBuf;
int iIpHdrLen = pIpHdr->hdr_len * 4;
if (iPacketSize < (int) (iIpHdrLen + sizeof(ICMP_HEADER))) return FALSE;
//根据 ICMP 报文类型提取 ID 字段和序列号字段
ICMP_HEADER *pIcmpHdr = (ICMP_HEADER *) (pBuf + iIpHdrLen);
USHORT usID, usSquNo;
if (pIcmpHdr->type == ICMP_ECHO_REPLY) //ICMP 回显应答报文
{
usID = pIcmpHdr->id; //报文 ID
usSquNo = pIcmpHdr->seq; //报文序列号
} else if (pIcmpHdr->type == ICMP_TIMEOUT)//ICMP 超时差错报文
{
char *pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER); //载荷中的 IP 头
int iInnerIPHdrLen = ((IP_HEADER *) pInnerIpHdr)->hdr_len * 4; //载荷中的 IP 头长
ICMP_HEADER *pInnerIcmpHdr = (ICMP_HEADER *) (pInnerIpHdr + iInnerIPHdrLen);//载荷中的 ICMP头
usID = pInnerIcmpHdr->id; //报文 ID
usSquNo = pInnerIcmpHdr->seq; //序列号
} else {
return false;
}
//检查 ID 和序列号以确定收到期待数据报
if (usID != (USHORT) GetCurrentProcessId() || usSquNo != DecodeResult.usSeqNo) {
return false;
}
//记录 IP 地址并计算往返时间
DecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;
//处理正确收到的 ICMP 数据报
if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT) {
//输出往返时间信息
if (DecodeResult.dwRoundTripTime)
cout << " " << DecodeResult.dwRoundTripTime << "ms" << flush;
else
cout << " " << "<1ms" << flush;
}
return true;
}
// 数字转String
string num2str(int &num) {
string str;
stringstream ss;
ss << num;
ss >> str;
return str;
}
//提取IP
string GetIP(string ip) {
char separator = '.';
int i = 0, count = 3;
// Temporary string used to split the string.
string s;
while (ip[i] != '\0') {
if (ip[i] != separator) {
s += ip[i];
} else {
s += ip[i];
count--;
}
if (count == 0) {
break;
}
i++;
}
return s;
}
int main() {
cout<<" *======Tracert 与 Ping 程序设计与实现======* "<<endl;
//定义=====================================================================
int iTimeout = 10; //超时时间
const int DEF_MAX_HOP = 1; //检查次数
BOOLEAN Show_All = false; //是否输出所有信息
string Scan_ip;
//ICMP 类型字段
const BYTE ICMP_ECHO_REQUEST = 8; //请求回显
const BYTE ICMP_ECHO_REPLY = 0; //回显应答
const BYTE ICMP_TIMEOUT = 11; //传输超时
//其他常量定义
const int DEF_ICMP_DATA_SIZE = 32; //ICMP 报文默认数据字段长度
const int MAX_ICMP_PACKET_SIZE = 1024; //ICMP 报文最大长度(包括报头)
const DWORD DEF_ICMP_TIMEOUT = 100; //回显应答超时时间
int OnlineNum = 0;
//=======================================================================
cout << "请输入路由器IP:";
cin >> Scan_ip;
//初始化 Windows sockets 网络环境
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
const char *IpAddress;
string IpA;
cout <<endl<<" 开始扫描:"<<endl;
cout << "----------------------------------------------------------------" << endl;
cout << " 耗时 机器IP 序号 状态 |" << endl;
cout << "----------------------------------------------------------------" << endl;
for (int i = 1; i < 254; i++) {
IpA = GetIP(Scan_ip)+ num2str(i);
IpAddress = IpA.c_str();
// cout << IpA << endl;
//得到 IP 地址
u_long ulDestIP = inet_addr(IpAddress);
//转换不成功时按域名解析
if (ulDestIP == INADDR_NONE) {
hostent *pHostent = gethostbyname(IpAddress);
if (pHostent) {
ulDestIP = (*(in_addr *) pHostent->h_addr).s_addr;
} else {
cout << "输入的 IP 地址或域名无效!" << endl;
WSACleanup();
return 0;
}
}
//填充目地端 socket 地址
sockaddr_in destSockAddr;
ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
destSockAddr.sin_family = AF_INET;
destSockAddr.sin_addr.s_addr = ulDestIP;
//创建原始套接字
SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
//接收超时
setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *) &iTimeout, sizeof(iTimeout));
//发送超时
setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *) &iTimeout, sizeof(iTimeout));
//填充 ICMP 报文中每次发送时不变的字段
char IcmpSendBuf[sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE];//发送缓冲区
memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
ICMP_HEADER *pIcmpHeader = (ICMP_HEADER *) IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST; //类型为请求回显
pIcmpHeader->code = 0; //代码字段为 0
pIcmpHeader->id = (USHORT) GetCurrentProcessId(); //ID 字段为当前进程号
memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段
USHORT usSeqNo = 0; //ICMP 报文序列号
int iTTL = 1; //TTL 初始值为 1
BOOL bReachDestHost = FALSE; //循环退出标志
int iMaxHot = DEF_MAX_HOP; //循环的最大次数
DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数
while (!bReachDestHost && iMaxHot--) {
//设置 IP 报头的 TTL 字段
setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *) &iTTL, sizeof(iTTL));
// cout << iTTL << flush; //输出当前序号
//填充 ICMP 报文中每次发送变化的字段
((ICMP_HEADER *) IcmpSendBuf)->cksum = 0; //校验和先置为 0
((ICMP_HEADER *) IcmpSendBuf)->seq = htons(usSeqNo++); //填充序列号
((ICMP_HEADER *) IcmpSendBuf)->cksum = checksum((USHORT *) IcmpSendBuf,
sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE); //计算校验和
//记录序列号和当前时间
DecodeResult.usSeqNo = ((ICMP_HEADER *) IcmpSendBuf)->seq; //当前序号
DecodeResult.dwRoundTripTime = GetTickCount(); //当前时间
//发送 TCP 回显请求信息
sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr *) &destSockAddr, sizeof(destSockAddr));
//接收 ICMP 差错报文并进行解析处理
sockaddr_in from; //对端 socket 地址
int iFromLen = sizeof(from); //地址结构大小
int iReadDataLen; //接收数据长度
while (true) {
//接收数据
iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr *) &from, &
iFromLen);
if (iReadDataLen != SOCKET_ERROR)//有数据到达
{
//对数据包进行解码
if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, ICMP_TIMEOUT)) {
//到达目的地,退出循环
if (DecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
bReachDestHost = true;
break;
}
} else if (WSAGetLastError() == WSAETIMEDOUT & Show_All) //接收超时,输出*号
{
cout << '\t' << "请求超时 " << DEF_MAX_HOP - iMaxHot << "次" << endl;
break;
} else {
break;
}
}
iTTL++; //递增 TTL 值
}
if (WSAGetLastError() != WSAETIMEDOUT) {
cout << " " << IpA << " " << i << " 在线" << endl;
OnlineNum++;
if (Show_All)
cout << "================================================================" << endl;
} else if (WSAGetLastError() == WSAETIMEDOUT & Show_All) {
cout << " " << IpA << " " << i << " 离线" << endl;
cout << "================================================================" << endl;
}
}
cout << "================================================================" << endl;
cout << endl << "检测到 " << OnlineNum << " 台设备在线" << endl<< endl;
system("pause");
WSACleanup();
return 0;
}
4.导入ws2_32库到Clion :
导入ws2_32库到Clion项目-CSDN博客
2024 HNUST计算机网络课程设计-(ᕑᗢᓫ∗)˒芒果酱-参考文章
(代码可以参考,૮₍ ˃ ⤙ ˂ ₎ა 但同学们要认真编写哦)
-------------------------------------------------------------------------
1、网络聊天程序的设计与实现
C++ Socket 多线程 网络聊天室 支持用户端双向交流(2023)-CSDN博客
2、Tracert 与 Ping 程序设计与实现
Tracert 与 Ping 程序设计与实现(2024)-CSDN博客
3、滑动窗口协议仿真
滑动窗口协议仿真(2024)-CSDN博客
4、OSPF 路由协议原型系统设计与实现
OSPF 路由协议原型系统设计与实现-CSDN博客
5、基于 IP 多播的网络会议程序
基于 IP 多播的网络会议程序(2024)-CSDN博客
6、编程模拟 NAT 网络地址转换
编程模拟 NAT 网络地址转换(2024)-CSDN博客
7、网络嗅探器的设计与实现
网络嗅探器的设计与实现(2024)-转载-CSDN博客
8、网络报文分析程序的设计与实现
网络报文分析程序的设计与实现(2024)-CSDN博客
9、简单 Web Server 程序的设计与实现
简单 Web Server 程序的设计与实现 (2024)-CSDN博客
10、路由器查表过程模拟计算机网络 - 路由器查表过程模拟 C++(2024)-CSDN博客