程序实现步骤:
1、初始化Windows Sockets网络环境
WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
2、构造目的端Socket地址
3、创建原始套接字
4、定义IP和ICMP头部数据结构
5、发送报文
6、接收报文
其中ICMP回显请求与回显应答报文结构如下图:
typedef struct IcmpHeader{//ICMP的报头格式
BYTE i_type;//ICMP类型码
BYTE i_code;//子类型码
USHORT i_cksum;//校验和
USHORT i_id;//ICMP数据报的ID号
USHORT i_seq;//ICMP数据报的序列号
ULONG timestamp;//可选数据部分,可以忽略
};
IP数据报格式:
typedef struct IpHeader{//IP报头格式
unsigned char hdr_len;//4位头部长度
unsigned char version;//4位版本号
unsigned char tos;//8位服务类型
unsigned short total_len;//16位总长度
unsigned short identifier;//16位标示符
unsigned short frag_and_flags;//3位标志加13位片偏移
unsigned char tt1;//8位生存时间
unsigned char protocol;//8位上层协议号
unsigned short checksum;//16位校验和
unsigned long sourceIP;//32位源IP地址
unsigned long destIP;//32位目的IP地址
};
部分成员函数解释:
1、SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,protocol,NULL,0,0);
AF_INET:代表通信域是TCP/IP协议簇
SOCK_RAW:代表要创建的套接字类型是原始套接字
protocol:指定协议类型,可取IPPROTO_ICMP(ICMP协议)\IPPROTO_IGMP(IGMP协议)\IPPROTO_TCP(TCP协议)\IPPROTO_UDP(UDP协议)\IPPROTO_IP(IP协议)\IPPROTO_RAM(原始IP)
2、int setsockopt(SOCKET s,int level,int optname,const char* optval,int optlen);
s(套接字): 指向一个打开的套接口描述字
level:(级别): 指定选项代码的类型。
SOL_SOCKET: 基本套接口
IPPROTO_IP: IPv4套接口
IPPROTO_IPV6: IPv6套接口
IPPROTO_TCP: TCP套接口
optname(选项名): 选项名称
optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ }
optlen(选项长度) :optval 的大小
3、int SENDTO(int sockfd,const void* msg, int len , unsignde int flags,struct sockaddr* to,int tolen);
sockfd:发送方的数据报套接字描述符
msg:指向发送缓存区的指针
len:发送缓存区的长度
flags:发送的方式,一般置0
to:接收方的网络地址
tolen:结构sockaddr的长度
返回值:发送成功的话返回实际发送的字节数,错误返回-1
4、int recvefrom(int sockfd,const void* buf, int len , unsignde int flags,struct sockaddr* from,int fromlen)
sockfd:接收方的数据报套接字描述符
buf:指向发送缓存区的指针
len:发送缓存区的长度
flags:接收的方式,一般置0
from:返回发送方的网络地址
tolen:结构sockaddr的长度
返回值:发送成功的话返回实际接收的字节数,错误返回-1
校验和计算方法(网际校验和算法):
将被校验数据按16位划分(如果被校验的数据字节长度为奇数,则在尾部的字节补一个字节的0),然后对每16位分组求反码和,最后将结果取反码得到校验和。
具体代码及注释如下:
// Console_PING.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "winsock2.h"
#include "winsock.h"
#pragma comment(lib,"ws2_32.lib")
//常量定义
const int MAX_ICMP_PACKET_SIZE=1024;//ICMP报文最大长度(包括报头)
const int DEF_ICMP_DATA_SIZE=32;//ICMP报文默认数据字段长度
const int ICMP_ECHO_REQUEST=8;//ICMP类型字段,8表示请求回显
typedef struct IcmpHeader{//ICMP的报头格式
BYTE i_type;//ICMP类型码
BYTE i_code;//子类型码
USHORT i_cksum;//校验和
USHORT i_id;//ICMP数据报的ID号
USHORT i_seq;//ICMP数据报的序列号
ULONG timestamp;//可选数据部分,可以忽略
};
typedef struct IpHeader{//IP报头格式
unsigned char hdr_len;//4位头部长度
unsigned char version;//4位版本号
unsigned char tos;//8位服务类型
unsigned short total_len;//16位总长度
unsigned short identifier;//16位标示符
unsigned short frag_and_flags;//3位标志加13位片偏移
unsigned char tt1;//8位生存时间
unsigned char protocol;//8位上层协议号
unsigned short checksum;//16位校验和
unsigned long sourceIP;//32位源IP地址
unsigned long destIP;//32位目的IP地址
};
void fill_icmp_data(char * icmp_data,int datasize);//填充ICMP数据报函数
USHORT checksum(USHORT * pBuf,int iSize);//计算校验和函数
void decode_resp(char *buf,int bytes,struct sockaddr_in *from);//接收报文后判断是否是目的端回送的报文
int _tmain(int argc, _TCHAR* argv[])
{
//初始化
WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,0);//创建原始套接字
if(sockRaw==INVALID_SOCKET)
fprintf(stderr,"WSASocket() failed:%d",WSAGetLastError());//创建失败
//设置socket的超时机制
int timeout=1000;//设置延时为1秒
int bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
if(bread==SOCKET_ERROR)
fprintf(stderr,"failed to set recv timeout:%d",WSAGetLastError());
//填充数据报
char icmp_data[MAX_ICMP_PACKET_SIZE];
memset(icmp_data,0,MAX_ICMP_PACKET_SIZE);
int datasize=DEF_ICMP_DATA_SIZE;
datasize+=sizeof(IcmpHeader);
fill_icmp_data(icmp_data,datasize);
((IcmpHeader*)icmp_data)->i_cksum=0;
((IcmpHeader*)icmp_data)->timestamp=GetTickCount();
((IcmpHeader*)icmp_data)->i_seq=0;
((IcmpHeader*)icmp_data)->i_cksum=checksum((USHORT*)icmp_data,datasize);
//发送数据报文
sockaddr_in dest;
dest.sin_family=AF_INET;
dest.sin_addr.s_addr=inet_addr("**.**.**.**");//我ping的是网关
int bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));
//发送失败
if(bwrote==SOCKET_ERROR)
return 0;
if(bwrote
//接收回应报文
sockaddr_in from;
int fromLen=sizeof(from);
char recvbuf[MAX_ICMP_PACKET_SIZE];
int recve=recvfrom(sockRaw,recvbuf,MAX_ICMP_PACKET_SIZE,0,(struct sockaddr*)&from,&fromLen);
if(recve<=0)
return 0;//接收失败
decode_resp(recvbuf,bread,&from);
system("PAUSE");
return 0;
}
void fill_icmp_data(char * _icmp_data,int _datasize)
{
IcmpHeader *icmp_hdr;
char *datapart;
icmp_hdr=(IcmpHeader*)_icmp_data;
icmp_hdr->i_type=ICMP_ECHO_REQUEST;//设置类型信息
icmp_hdr->i_id=(USHORT)GetCurrentThreadId();//设置ID号为当前线程号
datapart=_icmp_data+sizeof(IcmpHeader);
memset(datapart,'E',_datasize-sizeof(IcmpHeader));//在ICMP数据部分填入数据
}
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);
}
void decode_resp(char *buf,int bytes,struct sockaddr_in *from)
{
IpHeader *iphdr;
IcmpHeader *icmphdr;
unsigned short iphdrlen;
iphdr=(IpHeader *)buf;
iphdrlen=iphdr->hdr_len*4;//IP报头的长度
icmphdr=(IcmpHeader*)(buf+iphdrlen);
if(icmphdr->i_id==(USHORT)GetCurrentThreadId())//判断是否是目的端发回的消息
return;
printf("IP %s正在使用中!",inet_ntoa(from->sin_addr));
}