C/C++用socket实现ping功能

注:PING功能使用ICMP协议来实现,先行了解ICMP协议格式有助于理解下面代码。

代码由网友源码改写(原始代码位置 http://bbs.csdn.net/topics/230001156,原为win32版本,此处改为了unix版本,以mac os或linux系统下直接gcc编译)

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>// gethostbyname

typedef int SOCKET;

typedef struct icmp_hdr
{
    unsigned char   icmp_type; // 消息类型
    unsigned char   icmp_code; // 代码
    unsigned short  icmp_checksum; // 校验和
    // 下面是回显头
    unsigned short  icmp_id; // 用来惟一标识此请求的ID号
    unsigned short  icmp_sequence; // 序列号
    unsigned long   icmp_timestamp; // 时间戳
} ICMP_HDR, *PICMP_HDR;

typedef struct _IPHeader// 20字节的IP头
{
    uint8_t     iphVerLen; // 版本号和头长度(各占4位)
    uint8_t     ipTOS; // 服务类型
    uint16_t    ipLength; // 封包总长度,即整个IP报的长度
    uint16_t    ipID;  // 封包标识,惟一标识发送的每一个数据报
    uint16_t    ipFlags; // 标志
    uint8_t     ipTTL; // 生存时间,就是TTL
    uint8_t     ipProtocol; // 协议,可能是TCP、UDP、ICMP等
    uint16_t    ipChecksum; // 校验和
    uint32_t    ipSource; // 源IP地址
    uint32_t    ipDestination; // 目标IP地址
} IPHeader, *PIPHeader;

uint16_t checksum(uint16_t* buff, int size);

bool SetTimeout(SOCKET s, int nTime, bool bRecv = TRUE);

inline uint32_t GetIpByName(const char * name)
{
    hostent* pHostent = gethostbyname(name);
    if(pHostent == nullptr) return INADDR_NONE;
    uint32_t host = *(uint32_t*)pHostent->h_addr_list[0];
    return host;
}

int main(int args, const char ** argv)
{
    // 目的IP地址,即要Ping的IP地址
    const char * szDestIp = (args > 1)?argv[1]:"127.0.0.1";
    auto ip = inet_addr(szDestIp);
    if((int)ip == -1) ip = GetIpByName(szDestIp); // nds
    if((int)ip == -1)
    {
        printf("invalid ip address:%s\n", szDestIp);
        return -1;
    }
    uint16_t pID = (uint16_t)::time(0);
    printf("ping %s\n", szDestIp);
    // 创建原始套节字
    SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if(sRaw == -1)
    {
        auto eno = errno;
        if(eno == 1)
        {
            // 必须使用sudo才能建立Raw socket
            printf("Operation not permitted!\n");
        }
        else
        {
            printf("Cannot create socket! Error %d", eno);
        }
        return -1;
    }
    // 设置接收超时
    if(!SetTimeout(sRaw, 1000, TRUE))
    {
        printf("Cannot set timeout!\n");
    }
    // 设置目的地址
    sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_port = htons(0);
    dest.sin_addr.s_addr = ip;
    // 创建ICMP封包
    char buff[sizeof(ICMP_HDR) + 32];
    ICMP_HDR* pIcmp = (ICMP_HDR*)buff;
    // 填写ICMP封包数据
    pIcmp->icmp_type = 8; // 请求一个ICMP回显
    pIcmp->icmp_code = 0;
    pIcmp->icmp_id = (uint16_t)pID;
    pIcmp->icmp_checksum = 0;
    pIcmp->icmp_sequence = 0;
    // 填充数据部分,可以为任意
    memset(&buff[sizeof(ICMP_HDR)], 'E', 32);
    // 开始发送和接收ICMP封包
    uint16_t    nSeq = 0;
    char recvBuf[1024];
    sockaddr_in from;
    socklen_t nLen = sizeof(from);
    while(TRUE)
    {
        static int nCount = 0;
        long nRet;
        if(nCount++ == 4)
            break;
        pIcmp->icmp_checksum = 0;
        pIcmp->icmp_timestamp = ::clock();
        pIcmp->icmp_sequence = nSeq++;
        pIcmp->icmp_checksum = checksum((uint16_t*)buff, sizeof(ICMP_HDR) + 32);
        nRet = (long)::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32,
                0, (sockaddr *)&dest, sizeof(dest));
        if(nRet == -1)
        {
            printf(" sendto() failed: %d \n", errno);
            return -1;
        }
        nRet = (long)::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
        if(nRet == -1)
        {
            printf(" recvfrom() failed: %d\n", errno);
            return -1;
        }
        // 下面开始解析接收到的ICMP封包
        auto nTick = ::clock();
        if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))
        {
            printf(" Too few bytes from %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 0 // IP头解析
        IPHeader * header = (IPHeader*)recvBuf;
        struct in_addr a;
        a.s_addr = header->ipSource;
        printf("source ip %s\n", inet_ntoa(a));
        a.s_addr = header->ipDestination;
        printf("dest ip %s\n", inet_ntoa(a));
#endif
        if(pRecvIcmp->icmp_type != 0)// 回显
        {
            printf(" nonecho type %d recvd \n", pRecvIcmp->icmp_type);
            return -1;
        }
        if(pRecvIcmp->icmp_id != pID)
        {
            printf(" someone else's packet! \n");
            return -1;
        }
        printf(" %d bytes from %s:", (int)nRet, inet_ntoa(from.sin_addr));
        printf(" icmp_seq = %d. ", pRecvIcmp->icmp_sequence);
        printf(" time: %d ms", (int)nTick - (int)pRecvIcmp->icmp_timestamp);
        printf(" \n");
    }
    return 0;
}
uint16_t checksum(uint16_t* buff, int size)
{
    unsigned long cksum = 0;
    while(size>1)
    {
        cksum += *buff++;
        size -= sizeof(uint16_t);
    }
    // 是奇数
    if(size)
    {
        cksum += *(uint8_t*)buff;
    }
    // 将32位的chsum高16位和低16位相加,然后取反
    while(cksum >> 16)
        cksum = (cksum >> 16) + (cksum & 0xffff);
    return (uint16_t)(~cksum);
}

bool SetTimeout(SOCKET s, int nTime, bool bRecv)
{
    int ret = ::setsockopt(s, SOL_SOCKET, bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));
    return ret != -1;
}

原文链接: https://www.cnblogs.com/leaving/p/5268203.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/230085

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午2:32
下一篇 2023年2月13日 下午2:32

相关推荐