張越的那本《Visual c++網路程式設計執行個體詳解》很好,他的代碼寫得很漂亮!
網路編程方面的書籍,那是遺棄許久。這一段時間再次拾起,以補不足!
這是他第一章的執行個體,類比ping來發送ICMP資料包:
1、程式源碼
//////////////////////////////////////////////////<br />// comm.h檔案</p><p>// 包含一些公用函數</p><p>#ifndef __COMM_H__<br />#define __COMM_H__</p><p>// 校正和的計算<br />// 以16位的字為單位將緩衝區的內容相加,如果緩衝區長度為奇數,<br />// 則再加上一個位元組。它們的和存入一個32位的雙字中<br />USHORTchecksum(USHORT* buff, int size);</p><p>BOOLSetTTL(SOCKET s, int nValue);<br />BOOLSetTimeout(SOCKET s, int nTime, BOOL bRecv = TRUE);</p><p>#endif // __COMM_H__
//////////////////////////////////////////////////<br />// protoinfo.h檔案</p><p>/*</p><p>定義協議格式<br />定義協議中使用的宏</p><p> */</p><p>#ifndef __PROTOINFO_H__<br />#define __PROTOINFO_H__</p><p>#define ETHERTYPE_IP 0x0800<br />#define ETHERTYPE_ARP 0x0806</p><p>typedef struct _ETHeader // 14位元組的以太頭<br />{<br />UCHARdhost[6];// 目的MAC地址destination mac address<br />UCHARshost[6];// 源MAC地址source mac address<br />USHORTtype;// 下層協議類型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等<br />} ETHeader, *PETHeader;</p><p>#define ARPHRD_ETHER 1</p><p>// ARP協議opcodes<br />#defineARPOP_REQUEST1// ARP 請求<br />#defineARPOP_REPLY2// ARP 響應</p><p>typedef struct _ARPHeader// 28位元組的ARP頭<br />{<br />USHORThrd;//硬體地址空間,乙太網路中為ARPHRD_ETHER<br />USHORTeth_type;// 乙太網路類型,ETHERTYPE_IP ??<br />UCHARmaclen;//MAC地址的長度,為6<br />UCHARiplen;//IP地址的長度,為4<br />USHORTopcode;//作業碼,ARPOP_REQUEST為請求,ARPOP_REPLY為響應<br />UCHARsmac[6];//源MAC地址<br />UCHARsaddr[4];//源IP地址<br />UCHARdmac[6];//目的MAC地址<br />UCHARdaddr[4];//目的IP地址<br />} ARPHeader, *PARPHeader;</p><p>// 協議<br />#define PROTO_ICMP 1<br />#define PROTO_IGMP 2<br />#define PROTO_TCP 6<br />#define PROTO_UDP 17</p><p>typedef struct _IPHeader// 20位元組的IP頭<br />{<br /> UCHAR iphVerLen; // 版本號碼和頭長度(各佔4位)<br /> UCHAR ipTOS; // 服務類型<br /> USHORT ipLength; // 封包總長度,即整個IP報的長度<br /> USHORT ipID; // 封包標識,惟一標識發送的每一個資料報<br /> USHORT ipFlags; // 標誌<br /> UCHAR ipTTL; // 存留時間,就是TTL<br /> UCHAR ipProtocol; // 協議,可能是TCP、UDP、ICMP等<br /> USHORT ipChecksum; // 校正和<br /> ULONG ipSource; // 源IP地址<br /> ULONG ipDestination; // 目標IP地址<br />} IPHeader, *PIPHeader; </p><p>// 定義TCP標誌<br />#define TCP_FIN 0x01<br />#define TCP_SYN 0x02<br />#define TCP_RST 0x04<br />#define TCP_PSH 0x08<br />#define TCP_ACK 0x10<br />#define TCP_URG 0x20<br />#define TCP_ACE 0x40<br />#define TCP_CWR 0x80</p><p>typedef struct _TCPHeader// 20位元組的TCP頭<br />{<br />USHORTsourcePort;// 16位源連接埠號碼<br />USHORTdestinationPort;// 16位目的連接埠號碼<br />ULONGsequenceNumber;// 32位序號<br />ULONGacknowledgeNumber;// 32位確認號<br />UCHARdataoffset;// 高4位表示資料位移<br />UCHARflags;// 6位標誌位<br />//FIN - 0x01<br />//SYN - 0x02<br />//RST - 0x04<br />//PUSH- 0x08<br />//ACK- 0x10<br />//URG- 0x20<br />//ACE- 0x40<br />//CWR- 0x80</p><p>USHORTwindows;// 16位視窗大小<br />USHORTchecksum;// 16位校正和<br />USHORTurgentPointer;// 16位緊急資料位移量<br />} TCPHeader, *PTCPHeader;</p><p>typedef struct _UDPHeader<br />{<br />USHORTsourcePort;// 源連接埠號碼<br />USHORTdestinationPort;// 目的連接埠號碼<br />USHORTlen;// 封包長度<br />USHORTchecksum;// 校正和<br />} UDPHeader, *PUDPHeader;</p><p>#endif // __PROTOINFO_H__
//////////////////////////////////////////////////<br />// comm.cpp檔案</p><p>#include <winsock2.h><br />#include <windows.h><br />#include "Ws2tcpip.h"</p><p>#include "comm.h"</p><p>USHORT checksum(USHORT* buff, int size)<br />{<br />unsigned long cksum = 0;<br />while(size>1)<br />{<br />cksum += *buff++;<br />size -= sizeof(USHORT);<br />}<br />// 是奇數<br />if(size)<br />{<br />cksum += *(UCHAR*)buff;<br />}<br />// 將32位的chsum高16位和低16位相加,然後取反<br />cksum = (cksum >> 16) + (cksum & 0xffff);<br />cksum += (cksum >> 16);<br />return (USHORT)(~cksum);<br />}</p><p>BOOL SetTTL(SOCKET s, int nValue)<br />{<br />int ret = ::setsockopt(s, IPPROTO_IP, IP_TTL, (char*)&nValue, sizeof(nValue));<br />return ret != SOCKET_ERROR;<br />}</p><p>BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv)<br />{<br />int ret = ::setsockopt(s, SOL_SOCKET,<br />bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));<br />return ret != SOCKET_ERROR;<br />}<br />
///////////////////////////////////////////<br />// ping.cpp檔案</p><p>#include "../common/initsock.h"<br />#include "../common/protoinfo.h"<br />#include "../common/comm.h"</p><p>#include <stdio.h></p><p>CInitSock theSock;</p><p>typedef struct icmp_hdr<br />{<br /> unsigned char icmp_type;// 訊息類型<br /> unsigned char icmp_code;// 代碼<br /> unsigned short icmp_checksum;// 校正和</p><p>// 下面是回顯頭<br /> unsigned short icmp_id;// 用來惟一標識此請求的ID號,通常設定為進程ID<br /> unsigned short icmp_sequence;// 序號<br /> unsigned long icmp_timestamp; // 時間戳記<br />} ICMP_HDR, *PICMP_HDR;</p><p>int main()<br />{<br />// 目的IP地址,即要Ping的IP地址<br />char szDestIp[] = "119.147.15.13";// 127.0.0.1</p><p>// 建立原始套節字<br />SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);</p><p>// 設定接收逾時<br />SetTimeout(sRaw, 1000, TRUE);</p><p>// 設定目的地址<br />SOCKADDR_IN dest;<br />dest.sin_family = AF_INET;<br />dest.sin_port = htons(0);<br />dest.sin_addr.S_un.S_addr = inet_addr(szDestIp);</p><p>// 建立ICMP封包<br />char buff[sizeof(ICMP_HDR) + 32];<br />ICMP_HDR* pIcmp = (ICMP_HDR*)buff;</p><p>// 填寫ICMP封包資料,請求一個ICMP回顯<br />pIcmp->icmp_type = 8;<br />pIcmp->icmp_code = 0;<br />pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();<br />pIcmp->icmp_checksum = 0;<br />pIcmp->icmp_sequence = 0;</p><p>// 填充資料部分,可以為任意<br />memset(&buff[sizeof(ICMP_HDR)], 'E', 32);</p><p>// 開始發送和接收ICMP封包<br />USHORTnSeq = 0;<br />char recvBuf[1024];<br />SOCKADDR_IN from;<br />int nLen = sizeof(from);<br />while(TRUE)<br />{<br />static int nCount = 0;<br />int nRet;</p><p>// ping次數<br />if(nCount++ == 1000)<br />break;</p><p>pIcmp->icmp_checksum = 0;<br />pIcmp->icmp_timestamp = ::GetTickCount();<br />pIcmp->icmp_sequence = nSeq++;<br />pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32);<br />nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));<br />if(nRet == SOCKET_ERROR)<br />{<br />printf(" sendto() failed: %d /n", ::WSAGetLastError());<br />return -1;<br />}<br />nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);<br />if(nRet == SOCKET_ERROR)<br />{<br />if(::WSAGetLastError() == WSAETIMEDOUT)<br />{<br />printf(" timed out/n");<br />continue;<br />}<br />printf(" recvfrom() failed: %d/n", ::WSAGetLastError());<br />return -1;<br />}</p><p>// 下面開始解析接收到的ICMP封包<br />int nTick = ::GetTickCount();<br />if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))<br />{<br />printf(" Too few bytes from %s /n", ::inet_ntoa(from.sin_addr));<br />}</p><p>// 接收到的資料中包含IP頭,IP頭大小為20個位元組,所以加20得到ICMP頭<br />// (ICMP_HDR*)(recvBuf + sizeof(IPHeader));<br />ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20);<br />if(pRecvIcmp->icmp_type != 0)// 回顯<br />{<br />printf(" nonecho type %d recvd /n", pRecvIcmp->icmp_type);<br />return -1;<br />}</p><p>if(pRecvIcmp->icmp_id != ::GetCurrentProcessId())<br />{<br />printf(" someone else's packet! /n");<br />return -1;<br />}</p><p>printf("從 %s 返回 %d 位元組:", inet_ntoa(from.sin_addr),nRet);<br />printf(" 資料包序號 = %d. /t", pRecvIcmp->icmp_sequence);<br />printf(" 延時大小: %d ms", nTick - pRecvIcmp->icmp_timestamp);<br />printf(" /n");</p><p>// 每一秒發送一次就行了<br />::Sleep(1000);<br />}<br />return 0;<br />}</p><p>
2、開啟OmniPeek,運行程式並抓包分析如下:
3、原始碼:
網盤下載:http://www.rayfile.com/files/d828bc5e-9120-11de-bb76-0014221b798a/