张越的那本《Visual c++网络程序设计实例详解》很好,他的代码写得很漂亮!

 

      网络编程方面的书籍,那是遗弃许久。这一段时间再次拾起,以补不足!

 

      这是他第一章的实例,模拟ping来发送ICMP数据包:

 

1、程序源码

// // comm.h文件 // 包含一些公共函数 #ifndef __COMM_H__ #define __COMM_H__ // 校验和的计算 // 以16位的字为单位将缓冲区的内容相加,如果缓冲区长度为奇数, // 则再加上一个字节。它们的和存入一个32位的双字中 USHORT checksum(USHORT* buff, int size); BOOL SetTTL(SOCKET s, int nValue); BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv = TRUE); #endif // __COMM_H__

 

// // protoinfo.h文件 /* 定义协议格式 定义协议中使用的宏 */ #ifndef __PROTOINFO_H__ #define __PROTOINFO_H__ #define ETHERTYPE_IP 0x0800 #define ETHERTYPE_ARP 0x0806 typedef struct _ETHeader // 14字节的以太头 { UCHAR dhost[6]; // 目的MAC地址destination mac address UCHAR shost[6]; // 源MAC地址source mac address USHORT type; // 下层协议类型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等 } ETHeader, *PETHeader; #define ARPHRD_ETHER 1 // ARP协议opcodes #define ARPOP_REQUEST 1 // ARP 请求 #define ARPOP_REPLY 2 // ARP 响应 typedef struct _ARPHeader // 28字节的ARP头 { USHORT hrd; // 硬件地址空间,以太网中为ARPHRD_ETHER USHORT eth_type; // 以太网类型,ETHERTYPE_IP ?? UCHAR maclen; // MAC地址的长度,为6 UCHAR iplen; // IP地址的长度,为4 USHORT opcode; // 操作代码,ARPOP_REQUEST为请求,ARPOP_REPLY为响应 UCHAR smac[6]; // 源MAC地址 UCHAR saddr[4]; // 源IP地址 UCHAR dmac[6]; // 目的MAC地址 UCHAR daddr[4]; // 目的IP地址 } ARPHeader, *PARPHeader; // 协议 #define PROTO_ICMP 1 #define PROTO_IGMP 2 #define PROTO_TCP 6 #define PROTO_UDP 17 typedef struct _IPHeader // 20字节的IP头 { UCHAR iphVerLen; // 版本号和头长度(各占4位) UCHAR ipTOS; // 服务类型 USHORT ipLength; // 封包总长度,即整个IP报的长度 USHORT ipID; // 封包标识,惟一标识发送的每一个数据报 USHORT ipFlags; // 标志 UCHAR ipTTL; // 生存时间,就是TTL UCHAR ipProtocol; // 协议,可能是TCP、UDP、ICMP等 USHORT ipChecksum; // 校验和 ULONG ipSource; // 源IP地址 ULONG ipDestination; // 目标IP地址 } IPHeader, *PIPHeader; // 定义TCP标志 #define TCP_FIN 0x01 #define TCP_SYN 0x02 #define TCP_RST 0x04 #define TCP_PSH 0x08 #define TCP_ACK 0x10 #define TCP_URG 0x20 #define TCP_ACE 0x40 #define TCP_CWR 0x80 typedef struct _TCPHeader // 20字节的TCP头 { USHORT sourcePort; // 16位源端口号 USHORT destinationPort; // 16位目的端口号 ULONG sequenceNumber; // 32位序列号 ULONG acknowledgeNumber; // 32位确认号 UCHAR dataoffset; // 高4位表示数据偏移 UCHAR flags; // 6位标志位 //FIN - 0x01 //SYN - 0x02 //RST - 0x04 //PUSH- 0x08 //ACK- 0x10 //URG- 0x20 //ACE- 0x40 //CWR- 0x80 USHORT windows; // 16位窗口大小 USHORT checksum; // 16位校验和 USHORT urgentPointer; // 16位紧急数据偏移量 } TCPHeader, *PTCPHeader; typedef struct _UDPHeader { USHORT sourcePort; // 源端口号 USHORT destinationPort;// 目的端口号 USHORT len; // 封包长度 USHORT checksum; // 校验和 } UDPHeader, *PUDPHeader; #endif // __PROTOINFO_H__

 

 

// // comm.cpp文件 #include <winsock2.h> #include <windows.h> #include "Ws2tcpip.h" #include "comm.h" USHORT checksum(USHORT* buff, int size) { unsigned long cksum = 0; while(size>1) { cksum += *buff++; size -= sizeof(USHORT); } // 是奇数 if(size) { cksum += *(UCHAR*)buff; } // 将32位的chsum高16位和低16位相加,然后取反 cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); } BOOL SetTTL(SOCKET s, int nValue) { int ret = ::setsockopt(s, IPPROTO_IP, IP_TTL, (char*)&nValue, sizeof(nValue)); return ret != SOCKET_ERROR; } BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv) { int ret = ::setsockopt(s, SOL_SOCKET, bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime)); return ret != SOCKET_ERROR; }

 

/// // ping.cpp文件 #include "../common/initsock.h" #include "../common/protoinfo.h" #include "../common/comm.h" #include <stdio.h> CInitSock theSock; typedef struct icmp_hdr { unsigned char icmp_type; // 消息类型 unsigned char icmp_code; // 代码 unsigned short icmp_checksum; // 校验和 // 下面是回显头 unsigned short icmp_id; // 用来惟一标识此请求的ID号,通常设置为进程ID unsigned short icmp_sequence; // 序列号 unsigned long icmp_timestamp; // 时间戳 } ICMP_HDR, *PICMP_HDR; int main() { // 目的IP地址,即要Ping的IP地址 char szDestIp[] = "119.147.15.13"; // 127.0.0.1 // 创建原始套节字 SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 设置接收超时 SetTimeout(sRaw, 1000, TRUE); // 设置目的地址 SOCKADDR_IN dest; dest.sin_family = AF_INET; dest.sin_port = htons(0); dest.sin_addr.S_un.S_addr = inet_addr(szDestIp); // 创建ICMP封包 char buff[sizeof(ICMP_HDR) + 32]; ICMP_HDR* pIcmp = (ICMP_HDR*)buff; // 填写ICMP封包数据,请求一个ICMP回显 pIcmp->icmp_type = 8; pIcmp->icmp_code = 0; pIcmp->icmp_id = (USHORT)::GetCurrentProcessId(); pIcmp->icmp_checksum = 0; pIcmp->icmp_sequence = 0; // 填充数据部分,可以为任意 memset(&buff[sizeof(ICMP_HDR)], 'E', 32); // 开始发送和接收ICMP封包 USHORT nSeq = 0; char recvBuf[1024]; SOCKADDR_IN from; int nLen = sizeof(from); while(TRUE) { static int nCount = 0; int nRet; // ping次数 if(nCount++ == 1000) break; pIcmp->icmp_checksum = 0; pIcmp->icmp_timestamp = ::GetTickCount(); pIcmp->icmp_sequence = nSeq++; pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32); nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest)); if(nRet == SOCKET_ERROR) { printf(" sendto() failed: %d /n", ::WSAGetLastError()); return -1; } nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen); if(nRet == SOCKET_ERROR) { if(::WSAGetLastError() == WSAETIMEDOUT) { printf(" timed out/n"); continue; } printf(" recvfrom() failed: %d/n", ::WSAGetLastError()); return -1; } // 下面开始解析接收到的ICMP封包 int nTick = ::GetTickCount(); if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR)) { printf(" Too few bytes from %s /n", ::inet_ntoa(from.sin_addr)); } // 接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头 // (ICMP_HDR*)(recvBuf + sizeof(IPHeader)); ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); if(pRecvIcmp->icmp_type != 0) // 回显 { printf(" nonecho type %d recvd /n", pRecvIcmp->icmp_type); return -1; } if(pRecvIcmp->icmp_id != ::GetCurrentProcessId()) { printf(" someone else's packet! /n"); return -1; } printf("从 %s 返回 %d 字节:", inet_ntoa(from.sin_addr),nRet); printf(" 数据包序列号 = %d. /t", pRecvIcmp->icmp_sequence); printf(" 延时大小: %d ms", nTick - pRecvIcmp->icmp_timestamp); printf(" /n"); // 每一秒发送一次就行了 ::Sleep(1000); } return 0; }

 

2、打开OmniPeek,运行程序并抓包分析截图如下:

VC模拟ping发送ICMP数据包_tcp

 

3、源代码下载地址:

 

      网盘下载:http://www.rayfile.com/files/d828bc5e-9120-11de-bb76-0014221b798a/