位于网络层的协议,主要目的是使得网络可以互相通讯,该协议使用逻辑地址跨网络通讯,目前有两个版本IPV4,IPV6。html
在IPV4协议中IP地址是一个32位的数备,采用点分四组的表示法便于使用。每一个IP地址包含两个部分,网络地址和主机地址。算法
网络地址和主机地址的划分由子网掩码来决定。网络地址用来标示所链接到的局域网,主机地址则标示设备自己,子网掩码与IP地址等长,被设为1的部分标示IP地址的对应部分为网络地址,设为0则标示IP对应位为主机地址。编程
0-3 版本:IP所使用的版本服务器
4-7 首部长度:IP头的长度(4字节的整数倍)网络
8-15 服务类型:路由器使用该字段作流量优先排序socket
16-18 | 19-31 总长度:IP长度+数据包长度函数
32 标示符:一个惟一标示,用来区分数据包或数据分片的顺序工具
32 标记:标示数据包是否分片数据网站
32 分片偏移:分片数据时该字段有效,用于将数据包按顺序组合ui
64 存活时间:定义数据包的生存周期,路由器中转一次该值减一
64 协议:用来识别数据包上层协议类型
64 首部校验和:错误检测机制,确认内容是否损坏或被篡改
96 源IP地址:发送方主机的IP地址
128 目的IP地址:接收方主机的IP地址
160 选项:可选
160 or 192+ 数据:使用IP传递的实际数据
IP协议并非一个可靠的协议,它不保证数据被送达,那么保证数据送达的工做应该由其余的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。
ICMP(internet control message protocol)是internet控制报文协议。是TCP/IP协议族里的一个子协议,用于在IP主机、路由器之间传递控制信息。
控制信息是指网络通不通、主机是否可达、路由是否可用等网络自己的消息。
ICMP协议大体分为两类,一种是查询报文,一种是差错报文。查询报文由发送者发出,差错报文是由出错的主机返回发给源数据包的发送者。
PING能够说是ICMP的最著名的应用,系统自带工具当某一个网站上不去的时候。一般会ping一下这个网站,ping会回显一些有用的信息。
ICMP头相对较小并根据用途而改变,ICMP报文的前4个字节是统一的格式,共有三个字段:即类型,代码和检验和。
ICMP报文类型
各类类型的ICMP报文如所示,不现类型由报文中的类型字段和代码字段来共同决定。
常见的类型和代码有
ICMP回显(echo)请求和辉县应答报文格式
ICMP地址掩码请求和应答
ICMP时间戳请求与应答
ICMP不可达报文、ICMP超时报文
ICMP重定向报文
ICMP路由器请求报文格式
ICMP路由器通告报文格式
ICMP源站抑制差错报文格式
等等。。。。。
由于ICMP是基于IP协议运行的,因此咱们要先定义IP格式的结构体;
如下的是三个结构体分别对应了IP、ICMP请求、ICMP应答的发包格式。。
定义IP首部格式
//定义IP首部格式 typedef struct _IPHeader { u_char vile; // 版本和首部长度 u_char ser; // 服务类型 u_short totalLen; // 总长度 u_short id; // 标示符 u_short flag; // 标记+分片偏移 u_char ttl; // 存活时间 u_char protocol; // 协议 u_short checkSum; // 首部校验和 in_addr srcIP; // 源IP地址 in_addr destIP; // 目的IP地址 }IPHeader, *PIPHDR;
定义ICMP头部格式
//ICMP头部 typedef struct _ICMPHeader { u_char type; // 类型 u_char code; // 代码 u_short checkSum; // 校验和 u_short id; // 标示符 u_short seq; // 序列号 }ICMPHeader, *PICMPHDR;
ICMP时间戳请求报文格式
//ICMP时间戳请求报文 typedef struct _ECHOREQUEST { ICMPHeader icmpHeader; //ICMP头部 int time; //记录ping时间 char data[32]; //数据 }ECHOREQUEST, *pechorequest;
ICMP时间戳应答报文格式
//ICMP时间戳应答报文 typedef struct _ECHORESPONSE { IPHeader ipHeader; ECHOREQUEST echoRequest; char fill[255]; //接收其余多余的应答数据 }ECHORESPONSE, *PECHORESPONSE;
发送ICMP数据包函数
将请求的包的宽度写入结构体中,构造出ICMP的请求格式向目标服务器发送数据。
//发送ICMP数据包 sendEchoReQuest void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP) { static int id = 1; static int seq = 1; //ICMP请求 ECHOREQUEST echoRequest = { 0 }; //主要是用来记录请求应答的时间,当发送ECHO请求时记录发送时间,当接受到应答数据时,在用GetTickcount()- echoRequest.time这样就能获得请求应答须要多少时间了。 echoRequest.time = GetTickCount(); echoRequest.icmpHeader.type = 8; echoRequest.icmpHeader.code = 0; echoRequest.icmpHeader.id = id++; echoRequest.icmpHeader.seq = seq++; echoRequest.icmpHeader.checkSum = checksum((u_short*)&echoRequest, sizeof(echoRequest)); int re = sendto(sock, (char *)&echoRequest, sizeof(echoRequest), 0, (sockaddr*)&dstIP, sizeof(dstIP)); if (re == SOCKET_ERROR) { printf(" send error \n "); } return; }
接收ICMP数据包函数
有对应的发送数据包动做,对方服务器若是存活那么会发回来响应包;
//接收ICMP数据包 recvEchoReQuest void recvEchoReQuest(SOCKET sock, ECHORESPONSE * sponse, sockaddr_in *dstIP) { int size = sizeof(sockaddr); int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0, (sockaddr*)dstIP, &size); if (re == SOCKET_ERROR) { printf(" recvfrom error"); } return; }
// 计算校验和(16位二进制反码求和) 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; // 使用32bit的累加器,进行16bit的反馈计算 while (nleft > 1) { sum += *w++; nleft -= 2; } // 补全奇数位 if (nleft == 1) { u_short u = 0; *(u_char *)(&u) = *(u_char *)w; sum += u; } // 将反馈的16bit从高位移至地位 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return (answer); }
ICMP中检验和的计算算法为:
一、将检验和字段置为0
二、把需校验的数据当作以16位为单位的数字组成,依次进行二进制反码求和
三、把获得的结果存入检验和字段中
所谓二进制反码求和,就是:
一、将源数据转成反码
二、0+0=0 0+1=1 1+1=0进1
三、若最高位相加后产生进位,则最后获得的结果要加1
在实际实现的过程当中,比较常见的代码写法是:
一、将检验和字段置为0
二、把需校验的数据当作以16位为单位的数字组成,依次进行求和,并存到32位的整型中
三、把求和结果中的高16位(进位)加到低16位上,若是还有进位,重复第3步[实际上,这一步最多会执行2次]
四、将这个32位的整型按位取反,并强制转换为16位整型(截断)后返回
使用原始套接字编程调试的时候运行权限不够,是没法运行的。
能够在VS里作设置,在须要提高权限的时候自动重启VS提高权限。
点击项目右键->属性->配置属性->连接器->清单文件->UAC执行级别->requireAdministrator(选择)。
将上面的代码与思路整合就成了下面的实例代码;
#include "stdafx.h" #define _WINSOCK_DEPRECATED_NO_WARNINGS #include "data.h" //发送ICMP数据包 sendEchoReQuest void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP) { static int id = 1; static int seq = 1; //ICMP请求 ECHOREQUEST echoRequest = { 0 }; //主要是用来记录请求应答的时间,当发送ECHO请求时记录发送时间,当接受到应答数据时,在用GetTickcount()- echoRequest.time这样就能获得请求应答须要多少时间了。 echoRequest.time = GetTickCount(); echoRequest.icmpHeader.type = 8; echoRequest.icmpHeader.code = 0; echoRequest.icmpHeader.id = id++; echoRequest.icmpHeader.seq = seq++; echoRequest.icmpHeader.checkSum = checksum((u_short*)&echoRequest, sizeof(echoRequest)); int re = sendto(sock, (char *)&echoRequest, sizeof(echoRequest), 0, (sockaddr*)&dstIP, sizeof(dstIP)); if (re == SOCKET_ERROR) { printf(" send error \n "); } return; } //接收ICMP数据包 recvEchoReQuest void recvEchoReQuest(SOCKET sock, ECHORESPONSE * sponse, sockaddr_in *dstIP) { int size = sizeof(sockaddr); int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0, (sockaddr*)dstIP, &size); if (re == SOCKET_ERROR) { printf(" recvfrom error"); } return; } //ping函数 void Ping(char * host) { //2.建立套接字 SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); //获取域名对应的IP HOSTENT * lpHost = gethostbyname(host); sockaddr_in dstIP = { 0 }; dstIP.sin_family = AF_INET; dstIP.sin_addr.S_un.S_addr = *(u_long*)lpHost->h_addr; for (int i=0;i<4;i++) { //发送ICMP数据 sendEchoReQuest(sock, dstIP); //接收ICMP数据 ECHORESPONSE sponse; recvEchoReQuest(sock, &sponse, &dstIP); printf("来自 %s 的回复: 字节=32 时间=%dms TTL=%d \n", inet_ntoa(dstIP.sin_addr), GetTickCount() - sponse.echoRequest.time, sponse.ipHeader.ttl ); } } int main() { //1.初始化winsock环境 WSADATA wsa; WSAStartup(MAKEWORD(2, 2),&wsa); while (true) { Ping("www.baidu.com"); char in = getchar(); if (in == 'q') { break; } } //2.释放环境 WSACleanup(); return 0; }
参考:
C++实现Ping
http://www.cnblogs.com/goagent/p/4078940.html
给VS程序添加管理员权限等
http://www.voidcn.com/blog/wokaowokaowokao12345/article/p-5973906.html