最近在看些Windows下网络编程问题。看到原始套接字的使用,于是看了Ping程序的功能实现。
大部分人用ping命令只是作为查看另一个系统的网络连接是否正常的一种简单方法。这里我介绍下在Windows下实现ping程序的两种方法。
一是使用原始套接字的方法:
代码如下:
- #include<Winsock2.h>
- #pragmacomment(lib,"ws2_32.lib")
- #pragmacomment(lib,"Iphlpapi.lib")
- typedefstructicmp_hdr
- {
- unsignedcharicmp_type;
- unsignedcharicmp_code;
- unsignedshorticmp_checksum;
- //回显头
- unsignedshorticmp_id;
- unsignedshorticmp_sequence;
- unsignedlongicmp_timestamp;
- }ICMP_HDR,*PICMP_HDR;
- typedefstruct_IPHeader//20字节的IP头
- {
- UCHARiphVerLen;//版本号和头长度(各占4位)
- UCHARipTOS;//服务类型
- USHORTipLength;//封包总长度,即整个IP报的长度
- USHORTipID;//封包标识,惟一标识发送的每一个数据报
- USHORTipFlags;//标志
- UCHARipTTL;//生存时间,就是TTL
- UCHARipProtocol;//协议,可能是TCP、UDP、ICMP等
- USHORTipChecksum;//校验和
- ULONGipSource;//源IP地址
- ULONGipDestination;//目标IP地址
- }IPHeader,*PIPHeader;
- USHORTchecksum(USHORT*buffer,intsize)
- {
- unsignedlongcksum=0;
- while(size>1)
- {
- cksum+=*buffer++;
- size-=sizeof(USHORT);
- }
- if(size)
- {
- cksum+=*(UCHAR*)buffer;
- }
- cksum=(cksum>>16)+(cksum&0xffff);
- cksum+=(cksum>>16);
- return(USHORT)(~cksum);
- }
- BOOLSetTimeout(SOCKETs,intnTime,BOOLbRecv)
- {
- intret=::setsockopt(s,SOL_SOCKET,
- bRecv?SO_RCVTIMEO:SO_SNDTIMEO,(char*)&nTime,sizeof(nTime));
- returnret!=SOCKET_ERROR;
- }
- intmain(intargc,char*argv[])
- {
- if(argc<2)
- {
- printf("Usage:PingIPaddress.\n");
- return-1;
- }
- charszDestIp[20];
- strcpy_s(szDestIp,argv[1]);
- //初始化套接字
- WSADATAwsaData;
- intwsaset=WSAStartup(0x101,&wsaData);
- SOCKETsRaw=::socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
- inta=::GetLastError();
- SetTimeout(sRaw,1000,TRUE);
- SOCKADDR_INdest;
- dest.sin_family=AF_INET;
- dest.sin_port=htons(0);
- dest.sin_addr.S_un.S_addr=inet_addr(szDestIp);
- //创建ICMP封包
- charbuff[sizeof(ICMP_HDR)+32];
- ICMP_HDR*pIcmp=(ICMP_HDR*)buff;
- //填写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封包
- USHORTnSeq=0;
- charrecvBuf[1024];
- SOCKADDR_INfrom;
- intnLen=sizeof(from);
- while(TRUE)
- {
- staticintnCount=0;
- intnRet;
- if(nCount++==4)
- 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("timedout\n");
- continue;
- }
- printf("recvfrom()failed:%d\n",::WSAGetLastError());
- return-1;
- }
- //下面开始解析接收到的ICMP封包
- intnTick=::GetTickCount();
- if(nRet<sizeof(IPHeader)+sizeof(ICMP_HDR))
- {
- printf("Toofewbytesfrom%s\n",::inet_ntoa(from.sin_addr));
- }
- //接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头
- ICMP_HDR*pRecvIcmp=(ICMP_HDR*)(recvBuf+20);//(ICMP_HDR*)(recvBuf+sizeof(IPHeader));
- if(pRecvIcmp->icmp_type!=0)//回显
- {
- printf("nonechotype%drecvd\n",pRecvIcmp->icmp_type);
- return-1;
- }
- if(pRecvIcmp->icmp_id!=::GetCurrentProcessId())
- {
- printf("someoneelse'spacket!\n");
- return-1;
- }
- printf("%dbytesfrom%s:",nRet,inet_ntoa(from.sin_addr));
- printf("icmp_seq=%d.",pRecvIcmp->icmp_sequence);
- printf("time:%dms",nTick-pRecvIcmp->icmp_timestamp);
- printf("\n");
- ::Sleep(1000);
- }
- system("pause");
- return0;
- }
这是使用了原始套接字,实现发送ICMP数据包,实现ping程序。
在Linux中的Ping使用了这种方式。
这里是一个比较完整的关于Ping程序介绍的文章:http://www.ibm.com/developerworks/cn/linux/network/ping/index.html
这是Windows下实现的Ping程序:http://blog.csdn.net/tancfjob/archive/2008/05/07/2408978.aspx
但是在使用Windows下实现Ping的时候发现使用原始套接字需要管理员权限,但是Windows下使用ping程序并不需要使用管理员权限。感谢shadowstar 提示了我使用IcmpSendEcho来实现功能。
下面是使用IcmpSendEcho的示例代码:
- #pragmacomment(lib,"ws2_32.lib")
- #pragmacomment(lib,"Iphlpapi.lib")
- int__cdeclmain(intargc,char**argv){
- //Declareandinitializevariables
- HANDLEhIcmpFile;
- unsignedlongipaddr=INADDR_NONE;
- DWORDdwRetVal=0;
- charSendData[]="DataBuffer";
- LPVOIDReplyBuffer=NULL;
- DWORDReplySize=0;
- //Validatetheparameters
- if(argc!=2){
- printf("usage:%sIPaddress\n",argv[0]);
- return1;
- }
- ipaddr=inet_addr(argv[1]);
- if(ipaddr==INADDR_NONE){
- printf("usage:%sIPaddress\n",argv[0]);
- return1;
- }
- hIcmpFile=IcmpCreateFile();
- if(hIcmpFile==INVALID_HANDLE_VALUE){
- printf("\tUnabletoopenhandle.\n");
- printf("IcmpCreatefilereturnederror:%ld\n",GetLastError());
- return1;
- }
- ReplySize=sizeof(ICMP_ECHO_REPLY)+sizeof(SendData);
- ReplyBuffer=(VOID*)malloc(ReplySize);
- if(ReplyBuffer==NULL){
- printf("\tUnabletoallocatememory\n");
- return1;
- }
- dwRetVal=IcmpSendEcho(hIcmpFile,ipaddr,SendData,sizeof(SendData),
- NULL,ReplyBuffer,ReplySize,1000);
- if(dwRetVal!=0){
- PICMP_ECHO_REPLYpEchoReply=(PICMP_ECHO_REPLY)ReplyBuffer;
- structin_addrReplyAddr;
- ReplyAddr.S_un.S_addr=pEchoReply->Address;
- printf("\tSenticmpmessageto%s\n",argv[1]);
- if(dwRetVal>1){
- printf("\tReceived%ldicmpmessageresponses\n",dwRetVal);
- printf("\tInformationfromthefirstresponse:\n");
- }
- else{
- printf("\tReceived%ldicmpmessageresponse\n",dwRetVal);
- printf("\tInformationfromthisresponse:\n");
- }
- printf("\tReceivedfrom%s\n",inet_ntoa(ReplyAddr));
- printf("\tStatus=%ld\n",
- pEchoReply->Status);
- printf("\tRoundtriptime=%ldmilliseconds\n",
- pEchoReply->RoundTripTime);
- }
- else{
- printf("\tCalltoIcmpSendEchofailed.\n");
- printf("\tIcmpSendEchoreturnederror:%ld\n",GetLastError());
- return1;
- }
- return0;
- }
此段代码需要ws2_32.lib和Iphlpapi.lib,包含头文件iphlpapi.h和icmpapi.h