日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C/C++:Winsock网络编程—ping命令的简单实现

發(fā)布時(shí)間:2025/3/15 c/c++ 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C/C++:Winsock网络编程—ping命令的简单实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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)圖:

  • ping 中有顯示 TTL 值,這個(gè)就是從ip頭部中取得。
  • ip頭部的長度是從 IHL 中的值。
  • 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ù)有:

  • setsockopt
  • 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)的長度
    );

  • sendto
  • 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
    );

  • recvfrom
  • 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
    );

  • u_short ntohs(u_short hostshort); 網(wǎng)絡(luò)字節(jié)序 轉(zhuǎn)本地字節(jié)序
  • 實(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。