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