C/C++:Winsock网络编程—ping命令的简单实现
Winsock網絡編程—ping命令的簡單實現
前言
先聲明 博主實現的是Windows平臺的ping命令的簡單實現,沒有做域名解析,只能直接ping ip。我們要實現ping 肯定得先知道ping的實現原理,ping 發送的 ICMP報文。實際上的落腳點 就是對 ICMP協議和IP協議 結構的學習 以及 如何使用Winsock API 來實現ICMP報文的組包和解包。需要使用wireshark 抓包軟件 配合學習,這樣可以驗證你分析的對不對。
網絡協議基礎知識
ip協議結構圖:
icmp協議結構圖:
類型和代碼字段 所有情況如下,我們主要用的是 請求回顯 和 回顯應答
字節序問題
只有當 數據類型長度超過1個字節是 才會出現字節序問題。
網絡傳輸的字節序和本地存儲的字節序 有可能是一樣 也有可能不一樣。
網絡字節序 是大端對齊模式(低地址 放高字節,高地址 放低字節)。
本地字節序 就要分CPU架構了,一般小型計算機 都是小端對齊模式(低地址 放低字節,高地址 放高字節)。
Winsock api 函數
完成 icmp報文的發送和接受 使用的api 函數有:
https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-setsockopt
int WSAAPI setsockopt(
SOCKET s,
int level, // 選項級別
int optname, // 選項名
const char *optval, // 選項值
int optlen // 選項的長度
);
https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-sendto
int WSAAPI sendto(
SOCKET s,
const char *buf,
int len,
int flags, // 調用模式flag
const sockaddr *to,
int tolen
);
https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-recvfrom
int recvfrom(
SOCKET s,
char *buf,
int len,
int flags,
sockaddr *from, // 寫出參數,為源地址
int *fromlen
);
實現效果
先看下效果 再說
和 wireshark 抓包數據對比
實現代碼
一次ICMP 報文請求的核心代碼
int ping(char *szDestIp) {//printf("destIp = %s\n",szDestIp);int bRet = 1;WSADATA wsaData;int nTimeOut = 1000;//1s char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };icmp_header *pIcmp = (icmp_header *)szBuff;char icmp_data[32] = { 0 };WSAStartup(MAKEWORD(2, 2), &wsaData);// 創建原始套接字SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);// 設置接收超時setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));// 設置目的地址sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);dest_addr.sin_port = htons(0);// 構造ICMP封包pIcmp->icmp_type = ICMP_ECHO_REQUEST;pIcmp->icmp_code = 0;pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();pIcmp->icmp_sequence = 0;pIcmp->icmp_checksum = 0;// 填充數據,可以任意 memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);// 計算校驗和pIcmp->icmp_checksum = chsum((struct icmp_header *)szBuff, sizeof(szBuff));sockaddr_in from_addr;char szRecvBuff[1024];int nLen = sizeof(from_addr);int ret,flag = 0; DWORD start = GetTickCount();ret = sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));//printf("ret = %d ,errorCode:%d\n",ret ,WSAGetLastError() ); int i = 0; //這里一定要用while循環,因為recvfrom 會接受到很多報文,包括 發送出去的報文也會被收到! 不信你可以用 wireshark 抓包查看,這個問題糾結來了一晚上 才猜想出來! while(1){if(i++ > 5){// icmp報文 如果到不了目標主機,是不會返回報文,多嘗試幾次接受數據,如果都沒收到 即請求失敗 flag = 1;break;}memset(szRecvBuff,0,1024);//printf("errorCode1:%d\n",WSAGetLastError() ); int ret = recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);//printf("errorCode2:%d\n",WSAGetLastError() ); //printf("ret=%d,%s\n",ret,inet_ntoa(from_addr.sin_addr)) ; //接受到 目標ip的 報文 if( strcmp(inet_ntoa(from_addr.sin_addr),szDestIp) == 0) {respNum++;break;}} DWORD end = GetTickCount();DWORD time = end -start; if(flag){printf("請求超時。\n");return bRet;}sumTime += time;if( minTime > time){minTime = time;}if( maxTime < time){maxTime = time;}// Windows的原始套接字 開發,系統沒有去掉IP協議頭,需要程序自己處理。// ip頭部的第一個字節(只有1個字節不涉及大小端問題),前4位 表示 ip協議版本號,后4位 表示IP 頭部長度(單位為4字節)char ipInfo = szRecvBuff[0];// ipv4頭部的第9個字節為TTL的值char ttl = szRecvBuff[8];//printf("ipInfo = %x\n",ipInfo);int ipVer = ipInfo >> 4;int ipHeadLen = ((char)( ipInfo << 4) >> 4) * 4;if( ipVer == 4) {//ipv4 //printf("ipv4 len = %d\n",ipHeadLen);// 跨過ip協議頭,得到ICMP協議頭的位置,不過是網絡字節序。 // 網絡字節序 是大端模式 低地址 高位字節 高地址 低位字節。-> 轉換為 本地字節序 小端模式 高地址高字節 低地址低字節 icmp_header* icmp_rep = (icmp_header*)(szRecvBuff + ipHeadLen);//由于校驗和是 2個字節 涉及大小端問題,需要轉換字節序 unsigned short checksum_host = ntohs(icmp_rep->icmp_checksum);// 轉主機字節序 和wireshark 抓取的報文做比較 //printf("type = %d ,checksum_host = %x\n",icmp_rep,checksum_host);if(icmp_rep->icmp_type == 0){ //回顯應答報文 //來自 61.135.169.121 的回復: 字節=32 時間=1ms TTL=57printf("來自 %s 的回復:字節=32 時間=%2dms TTL=%d checksum=0x%x \n", szDestIp, time, ttl, checksum_host);} else{bRet = 0;printf("請求超時。type = %d\n",icmp_rep->icmp_type);} }else{// ipv6 icmpv6 和 icmpv4 不一樣,要做對應的處理 //printf("ipv6 len = %d\n",ipLen); } return bRet; }代碼下載
就一個C文件 可以用IDE打開運行,需要鏈接 ws2_32.lib 庫。博主用的devc++,完整工程代碼下載,或者Github最新代碼。
總結
以上是生活随笔為你收集整理的C/C++:Winsock网络编程—ping命令的简单实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用异步I/O复制文件及详解
- 下一篇: QT5成长之路绪论