写在前面
本题目为Ping程序的实现,为计算机网络课程设计,采用VS运行,忽略了一些报错,如C4996、C4233,程序能正常运行,可供同样需要完成课设的小伙伴们参考使用(ps:俺也是多方参考的,谢谢巨人的肩膀,😀)
一、课设要求
利用c语言实现的Ping命令,能用于测试一个主机到另一个主机间的联通情况,程序还提供了几个选项以实现不同的功能。
(1)实现Ping功能。程序能实现基本的Ping操作,发送ICMP回显请求报文,接收显应答报文。
(2)能输出指定条数的记录。程序提供了“-n”选项,用以输出指定条数的记录。
(3)能按照指定大小输出每条记录。程序提供了“datasize”选项,用以指定输出的数据报的大小。
(4)能输出用户帮助。程序提供了用户帮助,显示程序提供的选项以及选项格式等。
二、实现原理(俺认为是这样)
利用socket原始套接字(可自定义IP)实现发送/监听ICMP报文,实现基本的Ping功能,向目标主机发送ICMP回应请求,然后等待目标主机的ICMP回应答复。通过这种方式,它可以判断目标主机是否在线,以及网络延迟(即ping值)。
1.运行截图
功能1为默认Ping4次,数据报大小为32字节。其余功能均可指定次数或数据报大小,不做赘述。此处截图展示为功能4,指定次数和数据报大小。(也可直接Ping IP地址,俺懒得截图,可自己测试)
2.完整代码
#include <stdio.h> //标准输入输出函数
#include <stdlib.h> //实用程序库函数
#include <winsock.h> //网络编程socket相关部分API
#include<conio.h> //通用输入输出库
#include<stdbool.h> //bool:true\false
#pragma warning(disable:4996)
#pragma warning(disable:4233)
#pragma comment(lib, "ws2_32.lib") //导入库文件:Windows Sockets应用程序接口, 用于支持Internet和网络应用程序
#define ICMP_ECHOREPLY 0 //ICMP 回应答复类型码为0
#define ICMP_ECHOREQ 8 //ICMP 回应请求类型码为8
#define REQ_DATASIZE 32 //默认请求数据报大小
#define sent 4 //默认已发送条数
int sentnum = sent;
int PagSize = REQ_DATASIZE;
//定义 IP 首部格式
typedef struct IPHeader
{
u_char VIHL; //版本和首部长度
u_char ToS; //服务类型
u_short TotalLen; //总长度
u_short ID; //标识号
u_short Frag_Flags; //片偏移量
u_char TTL; //生存时间
u_char Protocol; //协议
u_short Checksum; //首部校验和
struct in_addr SrcIP; //源 IP 地址
struct in_addr DestIP; //目的地址
}IPHDR, * PIPHDR;
//定义 ICMP 首部格式
typedef struct ICMPHeader
{
u_char Type; //类型
u_char Code; //代码
u_short Checksum; //首部校验和
u_short ID; //标识
u_short Seq; //序列号
char Data; //数据
}ICMPHDR, * PICMPHDR;
//定义 ICMP 回应请求
typedef struct REQUEST
{
ICMPHDR icmpHdr;
DWORD dwTime;
char cData[REQ_DATASIZE];
}REQUEST, * PECHOREQUEST;
//定义 ICMP 回应答复
typedef struct REPLY
{
IPHDR ipHdr;
REQUEST echoRequest;
char cFiller[256];
}REPLY, * PECHOREPLY;
//计算校验和
u_short checksum(u_short* buffer, int len)
{
register int nleft = len;
register u_short* w = buffer;
register u_short answer;
register int sum = 0;
//使用 32 位累加器 ,进行 16 位的反馈计算
while (nleft > 1)
{
sum += *w++;
nleft -= 2;//每16位相加,因此字节数-2
}
//补全奇数位
if (nleft == 1)
{
u_short u = 0;
*(u_char*)(&u) = *(u_char*)w;
sum += u;
}
//将反馈的 16 位从高位移到低位
sum = (sum >> 16) + (sum & 0xffff); //高16位和低16位相加,sum & 0xffff将高16位置0
sum += (sum >> 16); //将低16位与溢出值相加
answer = ~sum; //取反码
return (answer);
}
//发送回应请求函数
int SendRequest(SOCKET s, struct sockaddr_in* lpstToAddr)
{
static REQUEST echoReq;
static int nId = 1;
static int nSeq = 1;
int nRet;
//填充回应请求消息
echoReq.icmpHdr.Type = ICMP_ECHOREQ;
echoReq.icmpHdr.Code = 0;
echoReq.icmpHdr.Checksum = 0;
echoReq.icmpHdr.ID = nId++;
echoReq.icmpHdr.Seq = nSeq++;
//填充要发送的数据
for (nRet = 0; nRet < PagSize; nRet++)
{
echoReq.cData[nRet] = '1' + nRet;
}
//存储发送的时间
echoReq.dwTime = GetTickCount();
//计算回应请求的校验和
echoReq.icmpHdr.Checksum = checksum((u_short*)&echoReq, sizeof(REQUEST));
//发送回应请求
nRet = sendto(s, (LPSTR)&echoReq, sizeof(REQUEST), 0, (struct sockaddr*)lpstToAddr, sizeof(SOCKADDR_IN));
if (nRet == SOCKET_ERROR)
{
printf("send to() error:%d\n", WSAGetLastError());
}
return (nRet);
}
//接收应答回复并进行解析
DWORD ReceiveReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char* pTTL)
{
REPLY echoReply;
int nRet;
int nAddrLen = sizeof(struct sockaddr_in);
//接收应答回复
nRet = recvfrom(s, (LPSTR)&echoReply, sizeof(REPLY), 0, (LPSOCKADDR)lpsaFrom, &nAddrLen);
//检验接收结果
if (nRet == SOCKET_ERROR)
{
printf("recvfrom() error:%d\n", WSAGetLastError());
}
//记录返回的 TTL
*pTTL = echoReply.ipHdr.TTL;
//返回应答时间
return(echoReply.echoRequest.dwTime);
}
//等待回应答复 ,使用select模型,设置等待时间,并返回相应的状态
int WaitReply(SOCKET s)
{
struct timeval timeout;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = s;
timeout.tv_sec = 1;
timeout.tv_usec = 0; //设计时间为1s,超过1s为超时
//调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或者能否向一个套接字写入数据。
return(select(1, &readfds, NULL, NULL, &timeout));//0:超时;大于0的值时,表示SOCKET数;失败时返回SOCKET_ERROR
}
//PING 功能实现
void Ping(char* pstrHost, bool logic)
{
char c;
SOCKET rawSocket;
LPHOSTENT lpHost;
struct sockaddr_in destIP;
struct sockaddr_in srcIP;
DWORD dwTimeSent;
DWORD dwElapsed;
u_char cTTL;
int nLoop;
int nRet, mintime = 100000, maxtime = 0, averagetime = 0;
int reveived = 0, lost = 0;
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); //创建原始套接字 ,ICMP 类型
// 第二个注释函数 socket
if (rawSocket == SOCKET_ERROR)
{
printf("socket() error:%d\n", WSAGetLastError());
return;
}
//检测目标主机
lpHost = gethostbyname(pstrHost); //返回对应于给定主机名的包含主机名字和地址信息的hostent结构的指针
if (lpHost == NULL)
{
printf("Host not found:%s\n", pstrHost);
return;
}
//设置目标机地址
destIP.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr)); // 设置目标 IP
destIP.sin_family = AF_INET; //地址规格
destIP.sin_port = 0;
//提示开始进行 PING
printf("\nPinging %s [%s] with %d bytes of data:\n", pstrHost, inet_ntoa(destIP.sin_addr), PagSize);
//发起多次 PING 测试
for (nLoop = 0; nLoop < sentnum; nLoop++) {
if (logic) sentnum = sentnum + 1;
//发送 ICMP 回应请求
SendRequest(rawSocket, &destIP);
//等待回复的数据
nRet = WaitReply(rawSocket);
if (nRet == SOCKET_ERROR)
{
printf("select() error:%d\n", WSAGetLastError());
break;
}
if (!nRet)
{
lost++;
printf("\nRequest time out.");
continue;
}
//接收回复
dwTimeSent = ReceiveReply(rawSocket, &srcIP, &cTTL);
reveived++;
//计算花费的时间
dwElapsed = GetTickCount() - dwTimeSent;
if (dwElapsed > maxtime) maxtime = dwElapsed;
if (dwElapsed < mintime) mintime = dwElapsed;
averagetime += dwElapsed;
printf("\nReply from %s: bytes = %d time = %ldms TTL = %d",
inet_ntoa(srcIP.sin_addr), PagSize, dwElapsed, cTTL);
if (_kbhit()) /* Use _getch to throw key away. */
{
if ((c = _getch()) == 0x2) //crrl -b
break;
}
else
Sleep(1000);
}
printf("\n\n");
printf("Ping statistics for %s:\n", inet_ntoa(srcIP.sin_addr));
printf(" Packets: Sent = %d, Received = %d, Lost = %d (%.f%% loss),\n",
sentnum, reveived, lost, (float)(lost * 1.0 / sentnum) * 100);
if (lost == 0)
{
printf("Approximate round trip times in milli-seconds:\n");
printf(" Mintime = %dms, Maxtime = %dms, Averagetime = %dms\n", mintime, maxtime, averagetime / sentnum);
}
printf("\n\n");
nRet = closesocket(rawSocket);
if (nRet == SOCKET_ERROR)
{
printf("closesocket() error:%d\n", WSAGetLastError());
}
}
/*显示信息函数*/
void UserHelp()
{
printf("\t\t\t使用说明: ping 域名或ip地址\n");
printf("\t\t\t功能1 Ping\n");
printf("\t\t\t功能2 输出指定条数的记录\n");
printf("\t\t\t功能3 按指定大小输出每条记录(最小32,最大1024)\n");
printf("\t\t\t功能4 按指定大小输出指定条数的记录\n");
printf("\t\t\t功能0 退出程序\n");
printf("\n\n");
}
//主程序
void main()
{
while (1)
{
WSADATA wsd; // 检测输入的参数
//初始化 Winsock
if (WSAStartup(MAKEWORD(1, 1), &wsd) != 0) {// 第一个函数说明 WSAStartup()
printf(" 加载 Winsock 失败 !\n");
}
char opt[200];
char* ptr = opt;
bool log = false;
int i; //获取用户输入的功能数字
int n,size;
printf("欢迎使用Ping程序\n");
UserHelp();
printf("请输入你所需要使用的功能:\n 功能");
scanf("%d", &i); //i为获取到的用户输入的功能数字
switch (i) {
case 1: //Ping功能
{
printf("Ping ");
scanf("%s", opt); //输入ping 的地址 字符串
break; }
case 2: //输出指定条数
{
printf("Ping ");
scanf("%s", opt);
printf("请输入想要Ping该服务器的次数:");
scanf("%d", &n);
sentnum = n;
break;
}
case 3: //输出指定大小
{
printf("Ping ");
scanf("%s", opt);
printf("请输入想要传送的数据包大小:");
scanf("%d", &size);
PagSize = size;
break;
}
case 4: //输出指定大小指定条数
{
printf("Ping ");
scanf("%s", opt);
printf("请输入想要Ping该服务器的次数:");
scanf("%d", &n);
printf("请输入想要传送的数据包大小:");
scanf("%d", &size);
sentnum = n;
PagSize = size;
break;
}
case 0: //退出程序
{
printf("正在退出程序..."); //1s后退出程序
Sleep(1000);
exit(0);
break;
}
default:
{
UserHelp();
}
}
//开始 PING
Ping(ptr, log);
//程序释放 Winsock 资源
WSACleanup();
}
}