Windows下C语言实现计算机网络底层Ping功能
2016-11-14 21:05
537 查看
一、在Windows环境下,实现ping(即发送一个ICMP的echo报文并对目标返回的回应报文进行正确的解析)
二、ICMP(Internet Control Message Protocol,网际控制协议),它允许主机或路由器报告差错情况和提供有关异常情况的报告。
ICMP提供的功能:错误诊断、拥塞控制、路径控制和查询服务
如当一个分组无法到达目的站点或TTL超时后,路由器就会丢弃此分组,并向源站点返回一个目的站点不可到达的ICMP报文。
实现ping主要涉及到ICMP回显请求和应答报文以及ICMP超时报文。回显ICMP协议数据包基本格式如下:
1、8bits类型和8bits代码字段一起决定了ICMP报文的类型:
类型8、代码报文0———-回显请求数据包
类型0、代码报文0———-回显应答数据包
类型11、代码字段为0——TTL超时
类型11、代码字段为1——数据包重组时间超时
2、16bits校验和字段:
包括数据在内的整个ICMP报文的校验和
3、16bits标识符字段:
用于标识本ICMP进程
4、16bits序列号字段:
用于判断回显应答报文
三、Ping程序的实现方法
主机向远程计算机发出ICMP回显请求后,远程计算机会拦截住这个请求,然后生成一条回显应答请求,再通过网络传回给主机。假如因某方面的原因,不能抵达目标主机,就会生成对应的ICMP错误消息(例如:主机不可达),由通信路径上的某台路由器返回。假定与远程主机的物理连接并不存在问题,但远程主机已经关闭或没有设置对应的网络事件作出响应,便需由自己的程序来执行超时鉴定,侦测出这样的情况。
四、ping程序代码
二、ICMP(Internet Control Message Protocol,网际控制协议),它允许主机或路由器报告差错情况和提供有关异常情况的报告。
ICMP提供的功能:错误诊断、拥塞控制、路径控制和查询服务
如当一个分组无法到达目的站点或TTL超时后,路由器就会丢弃此分组,并向源站点返回一个目的站点不可到达的ICMP报文。
实现ping主要涉及到ICMP回显请求和应答报文以及ICMP超时报文。回显ICMP协议数据包基本格式如下:
1、8bits类型和8bits代码字段一起决定了ICMP报文的类型:
类型8、代码报文0———-回显请求数据包
类型0、代码报文0———-回显应答数据包
类型11、代码字段为0——TTL超时
类型11、代码字段为1——数据包重组时间超时
2、16bits校验和字段:
包括数据在内的整个ICMP报文的校验和
3、16bits标识符字段:
用于标识本ICMP进程
4、16bits序列号字段:
用于判断回显应答报文
三、Ping程序的实现方法
主机向远程计算机发出ICMP回显请求后,远程计算机会拦截住这个请求,然后生成一条回显应答请求,再通过网络传回给主机。假如因某方面的原因,不能抵达目标主机,就会生成对应的ICMP错误消息(例如:主机不可达),由通信路径上的某台路由器返回。假定与远程主机的物理连接并不存在问题,但远程主机已经关闭或没有设置对应的网络事件作出响应,便需由自己的程序来执行超时鉴定,侦测出这样的情况。
四、ping程序代码
#include<stdio.h> #include<Winsock2.h> #include<ws2tcpip.h> #include<stdlib.h> #include<malloc.h> #include<string.h> #pragma comment(lib , "Ws2_32.lib") #define ICMP_ECHO_REQUEST 8 //定义回显请求类型 #define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度 #define DEF_ICMP_PACK_SIZE 32 //定义数据包长度 #define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度 #define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒 #define ICMP_TIMEOUT 11 //ICMP超时报文 #define ICMP_ECHO_REPLY 0 //定义回显应答类型 /* *IP报头结构 */ typedef struct { byte h_len_ver ; //IP版本号 byte tos ; // 服务类型 unsigned short total_len ; //IP包总长度 unsigned short ident ; // 标识 unsigned short frag_and_flags ; //标志位 byte ttl ; //生存时间 byte proto ; //协议 unsigned short cksum ; //IP首部校验和 unsigned long sourceIP ; //源IP地址 unsigned long destIP ; //目的IP地址 } IP_HEADER ; /* *定义ICMP数据类型 */ typedef struct _ICMP_HEADER { byte type ; //类型-----8 byte code ; //代码-----8 unsigned short cksum ; //校验和------16 unsigned short id ; //标识符-------16 unsigned short seq ; //序列号------16 unsigned int choose ; //选项-------32 } ICMP_HEADER ; typedef struct { int usSeqNo ; //记录序列号 DWORD dwRoundTripTime ; //记录当前时间 byte ttl ; //生存时间 in_addr dwIPaddr ; //源IP地址 } DECODE_RESULT ; /* *产生网际校验和 */ unsigned short GenerateChecksum(unsigned short *pBuf , int iSize) { unsigned long cksum = 0 ; //开始时将网际校验和初始化为0 while(iSize > 1) { cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中 iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16 } //如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加 if(iSize) { cksum += *(unsigned char*)pBuf ; } //之前的结果产生了进位,需要把进位也加入最后的结果中 cksum = (cksum >> 16) + (cksum & 0xffff) ; cksum += (cksum >> 16) ; return (unsigned short)(~ cksum) ; } /* *对ping应答信息进行解析 */ boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult) { IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ; int iIphedLen = 20 ; if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER))) { printf("size error! \n") ; return 0 ; } //指针指向ICMP报文的首地址 ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ; unsigned short usID , usSeqNo ; //获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文 if(pIcmpHrd->type == ICMP_ECHO_REPLY) { usID = pIcmpHrd->id ; //接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序 usSeqNo = ntohs(pIcmpHrd->seq) ; } if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo) { printf("usID error!\n") ; return 0 ; } //记录对方主机的IP地址以及计算往返的时延RTT if(pIcmpHrd->type == ICMP_ECHO_REPLY) { stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ; stDecodeResult->ttl = pIpHrd->ttl ; stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ; return 1 ; } return 0 ; } void Ping(char *IP) { unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形 if(ulDestIP == INADDR_NONE) { //转化不成功时按域名解析 HOSTENT *pHostent = gethostbyname(IP) ; if(pHostent) { ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形 } else { printf("地址解析失败!\n") ; return ; } } //填充目的Socket地址 SOCKADDR_IN destSockAddr ; //定义目的地址 ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空 destSockAddr.sin_family = AF_INET ; destSockAddr.sin_addr.s_addr = ulDestIP ; destSockAddr.sin_port = htons(0); //初始化WinSock WORD wVersionRequested = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(wVersionRequested,&wsaData) != 0) { printf("初始化WinSock失败!\n") ; return ; } //使用ICMP协议创建Raw Socket SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ; if(sockRaw == INVALID_SOCKET) { printf("创建Socket失败 !\n") ; return ; } //设置端口属性 int iTimeout = DEF_ICMP_TIMEOUT ; if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败!\n") ; return ; } if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败!\n") ; return ; } //定义发送的数据段 char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ; //填充ICMP数据包个各字段 ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf; pIcmpHeader->type = ICMP_ECHO_REQUEST ; pIcmpHeader->code = 0 ; pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ; memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ; //循环发送四个请求回显icmp数据包 int usSeqNo = 0 ; DECODE_RESULT stDecodeResult ; while(usSeqNo <= 3) { pIcmpHeader->seq = htons(usSeqNo) ; pIcmpHeader->cksum = 0 ; pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位 //记录序列号和当前时间 stDecodeResult.usSeqNo = usSeqNo ; stDecodeResult.dwRoundTripTime = GetTickCount() ; //发送ICMP的EchoRequest数据包 if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR) { //如果目的主机不可达则直接退出 if(WSAGetLastError() == WSAEHOSTUNREACH) { printf("目的主机不可达!\n") ; exit(0) ; } } SOCKADDR_IN from ; int iFromLen = sizeof(from) ; int iReadLen ; //定义接收的数据包 char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ; while(1) { iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ; if(iReadLen != SOCKET_ERROR) { if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult)) { printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d\n" , inet_ntoa(stDecodeResult.dwIPaddr) , iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ; } break ; } else if(WSAGetLastError() == WSAETIMEDOUT) { printf("time out ! *****\n") ; break ; } else { printf("发生未知错误!\n") ; break ; } } usSeqNo++ ; } //输出屏幕信息 printf("Ping complete...\n") ; closesocket(sockRaw) ; WSACleanup() ; } int main(int argc , char* argv[]) { char com[10] , IP[20] ; while(1){ printf("command>>") ; scanf("%s %s" , com , IP) ; if(strcmp(com , "ping") == 0) { Ping(IP) ; } else { printf("command error ! \n") ; } } return 0 ; }
相关文章推荐
- Linux下用C语言实现Ping程序功能
- 用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通
- 谁能帮我写个用C语言实现计算机网络零比特流插入
- 在linux中用C语言实现ping命令的部分功能
- 用C语言实现Ping程序功能
- 利用Windows重定向器实现对网络计算机的读写
- 用C语言实现Ping程序功能
- (转载)用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- C#实现简单的 Ping 的功能,用于测试网络是否已经联通
- 计算机网络中CRC冗余码的c语言实现
- 用C语言实现Ping程序功能
- 用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通
- 用C语言实现Ping程序功能
- 用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 在linux中用C语言实现ping命令的部分功能
- Linux用ICMP协议实现简单Ping网络监测功能