由于IP层的协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,如果用的话也仅是用于设置IP地址。用socket函数生成原始套接字后,即可用sendto()或sendmsg()函数发送数据和用函数recvfrom()或recvmsg()接收数据。
ping程序发ICMP响应请求给某一主机,该主机返回一个ICMP响应应答,程序收到应答则显示结果.为了完成这个功能,首先应创建协议为ICMP的原始套接字以接收/发送ICMP包,然后需要构造ICMP包,通过原始套接字发送给对方主机.
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include<conio.h>
#pragma comment(lib, "ws2_32.lib")// 导入库文件
#define ICMP_ECHOREPLY 0 //ICMP 回应答复
#define ICMP_ECHOREQ 8 //ICMP 回应请求
#define REQ_DATASIZE 32 // 请求数据报大小
#include <iostream>
using namespace std;
//定义 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 ECHOREQUEST
{
ICMPHDR icmpHdr;
DWORD dwTime;
char cData[REQ_DATASIZE];
}ECHOREQUEST, *PECHOREQUEST;
//定义 ICMP 回应答复
typedef struct ECHOREPLY
{
IPHDR ipHdr;
ECHOREQUEST echoRequest;
char cFiller[256];
}ECHOREPLY,*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;
}
//补全奇数位
if ( nleft == 1 )
{
u_short u = 0;
*(u_char *)(&u) = *(u_char*)w;
sum += u;
}
//将反馈的 16 位从高位移到低位
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
//发送回应请求函数
int SendEchoRequest(SOCKET s, struct sockaddr_in *lpstToAddr)
{
static ECHOREQUEST 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 < REQ_DATASIZE; nRet++)
{
echoReq.cData[nRet] = '1' + nRet;
}
//存储发送的时间
echoReq.dwTime = GetTickCount();
//计算回应请求的校验和
echoReq.icmpHdr.Checksum = checksum((u_short*)&echoReq, sizeof(ECHOREQUEST));
//发送回应请求
nRet = sendto(s,(LPSTR)&echoReq,sizeof(ECHOREQUEST),0,(struct sockaddr*)lpstToAddr,sizeof(SOCKADDR_IN));
if (nRet == SOCKET_ERROR)
{
printf("send to() error:%d\n", WSAGetLastError());
}
return (nRet);
}
//接收应答回复并进行解析
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
{
ECHOREPLY echoReply;
int nRet;
int nAddrLen = sizeof(struct sockaddr_in);
//接收应答回复
nRet = recvfrom(s,(LPSTR)&echoReply,sizeof(ECHOREPLY),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 WaitForEchoReply(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;
return(select(1, &readfds, NULL, NULL, &timeout));
}
//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,k=4;
int nRet,minimum=100000,maximum=0,average=0;
int sent=4,reveived=0,lost=0;
//创建原始套接字 ,ICMP 类型
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
// 第二个注释函数 socket
if (rawSocket == SOCKET_ERROR)
{
printf("socket() error:%d\n", WSAGetLastError());
return;
}
//检测目标主机
lpHost = gethostbyname(pstrHost);
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),REQ_DATASIZE);
//发起多次 PING 测试
for (nLoop=0; nLoop<k; nLoop++){
if (logic) k=k+1;
//发送 ICMP 回应请求
SendEchoRequest(rawSocket, &destIP);
//等待回复的数据
nRet = WaitForEchoReply(rawSocket);
if(nRet == SOCKET_ERROR)
{
printf("select() error:%d\n", WSAGetLastError());
break;
}
if (!nRet)
{
lost++;
printf("\nRequest time out.");
continue;
}
//接收回复
dwTimeSent = RecvEchoReply(rawSocket, &srcIP, &cTTL);
reveived++;
//计算花费的时间
dwElapsed = GetTickCount() - dwTimeSent;
if(dwElapsed > maximum) maximum=dwElapsed;
if(dwElapsed < minimum) minimum=dwElapsed;
average+=dwElapsed;
printf("\nReply from %s: bytes = %d time = %ldms TTL = %d",
inet_ntoa(srcIP.sin_addr),REQ_DATASIZE,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",
sent,reveived,lost,(float)(lost*1.0/sent)*100);
if(lost==0)
{
printf("Approximate round trip times in milli-seconds:\n");
printf(" Minimum = %dms, Maximum = %dms, Average = %dms\n",minimum,maximum,average/sent);
}
printf("\n\n");
nRet = closesocket(rawSocket);
if (nRet == SOCKET_ERROR)
{
printf("closesocket() error:%d\n", WSAGetLastError());
}
}
//主程序
void main()
{
printf("Welcome to the Ping Test\n");
while(1)
{
WSADATA wsd;// 检测输入的参数
//初始化 Winsock
if(WSAStartup(MAKEWORD(1, 1), &wsd) != 0){// 第一个函数说明 WSAStartup()
printf(" 加载 Winsock 失败 !\n");
}
char opt1[100];
char *ptr=opt1;
bool log=false;
printf("Ping ");
cin.getline(opt1,100,'\n');//ping 的地址 字符串
if(strstr(opt1, "-t")!=NULL)
{
log=true;
strncpy(ptr,opt1+0,strlen(opt1)-3);// 把原字符串的最后三位截取
ptr[strlen(opt1)-2]=0;
//printf("%s", ptr);
}
//开始 PING
Ping(ptr,log);
//程序释放 Winsock 资源
WSACleanup();
}
}
实现效果: