C++ Exercises(十六)---Ethernet帧包结构解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

图1是一个假想的帧包结构,



图2是解包后的结果。

复制代码
/////////////////////////////
///帧信息类 
/////////////////////////////
class CFrame
{
public:
    CFrame(void);
    ~CFrame(void);
    void setSerialNumber(int nSN);
    void setPreCode(const string& strPreCode);
    void setPreBoundCode(const string& strBoundCode);
    void setEtherType(const string& strEtherType);
    void setData(char* strData,int len);
    void setCRC(const string& strCRC);
    void setFrameState(bool isValid);
    void setDstAddress(const string& desAddress);
    void setSrcAddress(const string& srcAddress);
private:
    int nFrameSN;// 帧序号
    string strPreCode;//前导码
    string strPreBoundCode;//帧前定界符
    string strDstAddress;//目的地址
    string strSrcAddress;//源地址
    string strEtherType;//帧类型
    string strData;//数据域
    string strCRC;//CRC校验码
    bool bIsValid;//是否正确的帧
    friend ostream& operator << (ostream& out,const CFrame& frame);
};

CFrame::CFrame(void)
{
    this->nFrameSN = -1;
    this->bIsValid = false;
    this->strEtherType = "";
    this->strCRC = "";
    this->strData = "";
    this->strDstAddress = "";
    this->strPreBoundCode = "";
    this->strPreCode = "";
    this->strSrcAddress = "";
}

CFrame::~CFrame(void)
{
}

void CFrame::setSerialNumber(int nSN)
{
    this->nFrameSN = nSN;
}

void CFrame::setPreCode(const string& strPreCode)
{
    this->strPreCode = strPreCode;
}
void CFrame::setPreBoundCode(const string& strBoundCode)
{
    this->strPreBoundCode = strBoundCode;
}
void CFrame::setEtherType(const string& strEtherType)
{
    this->strEtherType = strEtherType;
}
void CFrame::setData(char* strData,int len)
{
    this->strData = string(strData,len);
}

void CFrame::setCRC(const string& strCRC)
{
    this->strCRC = strCRC;
}

void CFrame::setFrameState(bool isValid)
{
    this->bIsValid = isValid;
}
void CFrame::setDstAddress(const string& desAddress)
{
    this->strDstAddress = desAddress;
}
void CFrame::setSrcAddress(const string& srcAddress)
{
    this->strSrcAddress = srcAddress;
}

复制代码
 

复制代码

/////////////////////////////
///帧解析器类
///////////////////////////

class CFrameParser
{
public:
    CFrameParser(void);
    CFrameParser(const char* pFilePath);
    CFrameParser(const string& strFilePath);
    ~CFrameParser(void);
    bool DoParser();//实际的解析动作
private:
    string strInputFile;//帧数据文件
    vector<CFrame> vecFrames;//帧包列表
    
};


CFrameParser::CFrameParser(void)
{
}

CFrameParser::~CFrameParser(void)
{
}

CFrameParser::CFrameParser(const char* pFilePath):strInputFile(pFilePath)
{

}
CFrameParser::CFrameParser(const string& strFilePath):strInputFile(strFilePath)
{

}

bool CFrameParser::DoParser()
{    // 检测输入文件是否存在,并可以按所需的权限和方式打开
    ifstream file(this->strInputFile.c_str(), ios::in|ios::binary|ios::_Nocreate);
    if (!file.is_open())
    {
        cout << "无法打开帧封装包文件,请检查文件是否存在并且未损坏" << endl;
        return false;
    }
    
    // 变量声明及初始化
    int nSN = 1;                        // 帧序号
    int nCheck = 0;                        // 校验码
    int nCurrDataOffset = 22;            // 帧头偏移量
    int nCurrDataLength = 0;            // 数据字段长度
    bool bParseCont = true;                // 是否继续对输入文件进行解析
    int nFileEnd = 0;                    // 输入文件的长度
    // 计算输入文件的长度
    file.seekg(0, ios::end);            // 把文件指针移到文件的末尾
    nFileEnd = file.tellg();            // 取得输入文件的长度
    file.seekg(0, ios::beg);            // 文件指针位置初始化
    cout.fill('0');                        // 显示初始化
    cout.setf(ios::uppercase);            // 以大写字母输出
    // 定位到输入文件中的第一个有效帧
    // 从文件头开始,找到第一个连续的“AA-AA-AA-AA-AA-AA-AA-AB”
    while ( true )
    {        
        for (int j = 0; j < 7; j++)                // 找个连续的xaa
        {            
            if (file.tellg() >= nFileEnd)        // 安全性检测
            {
                cout<<"没有找到合法的帧"<<endl;
                file.close();
                return false;
            }
            // 看当前字符是不是xaa,如果不是,则重新寻找个连续的xaa
            if (file.get() != 0xaa)                
            {
                j = -1;
            }
        }
        if (file.tellg() >= nFileEnd)            // 安全性检测
        {
            cout<<"没有找到合法的帧"<<endl;
            file.close();
            return false;
        }
        if (file.get() == 0xab)                    // 判断个连续的xaa之后是否为xab
        {
            break;
        }
    }
    // 将数据字段偏移量定位在上述二进制串之后字节处,并准备进入解析阶段
    nCurrDataOffset = static_cast<int>(file.tellg()) + 14;
    file.seekg(-8,ios::cur);

    // 主控循环
    while ( bParseCont ) // 当仍然可以继续解析输入文件时,继续解析
    {
        // 检测剩余文件是否可能包含完整帧头
        if (static_cast<int>(file.tellg())+ 14 > nFileEnd)// 从目的字段到类型字段总共14字节
        {
            cout<<endl<<"没有找到完整帧头,解析终止"<<endl;
            file.close();
            return false;
        }
        CFrame frame;
        int c;                        // 读入字节
        int i = 0;                    // 循环控制变量                    
        int EtherType = 0;            // 由帧中读出的类型字段
        bool bAccept = true;        // 是否接受该帧
        // 输出帧的序号
        frame.setSerialNumber(nSN);
        // 输出前导码,只输出,不校验
        string tmpPreCode="";
        for (i = 0; i <7; i++)                    // 输出格式为:AA AA AA AA AA AA AA
        {
            c = file.get();
            string hexCode = util::ConvertToHex(c);
            tmpPreCode.append(hexCode);
            if (i!=6)
            {
                tmpPreCode.append(1,' ');
            }
        }
        frame.setPreCode(tmpPreCode);
        // 输出帧前定界符,只输出,不校验
        cout << endl << "帧前定界符:\t";        
        cout.width(2);                            // 输出格式为:AB
        c = file.get();
        string tmpBoundCode = util::ConvertToHex(c);
        frame.setPreBoundCode(tmpBoundCode);
        string tmpDesAddress;
        // 输出目的地址,并校验
        for (i = 1; i <=6; i++)                    // 输出格式为:xx-xx-xx-xx-xx-xx
        {
            c = file.get();
            string desAddr = util::ConvertToHex(c);
            tmpDesAddress.append(desAddr);
            if (i!=6)
            {
                tmpDesAddress.append(1,'-');
            }
            if (i == 1)                            // 第一个字节,作为“余数”等待下一个bit
            {
                nCheck = c;
            }
            else                                // 开始校验
            {
                util::CRC::checkCRC(nCheck, c);
            }
        }
        frame.setDstAddress(tmpDesAddress);
        string tmpSrcAddress;
        // 输出源地址,并校验
        for (i = 1; i <=6; i++)                    // 输出格式为:xx-xx-xx-xx-xx-xx
        {
            c = file.get();
            string srcAddr = util::ConvertToHex(c);
            tmpSrcAddress.append(srcAddr);
            if (i!=6)
            {
                tmpSrcAddress.append(1,'-');
            }
            util::CRC::checkCRC(nCheck, c);                // 继续校验
        }
        frame.setSrcAddress(tmpSrcAddress);
        //// 输出类型字段,并校验                            
        // 输出类型字段的高位
        c = file.get();
        util::CRC::checkCRC(nCheck, c);                    // CRC校验
        EtherType = c;
        // 输出类型字段的低位
        c = file.get();                        
        util::CRC::checkCRC(nCheck,c);                        // CRC校验
        EtherType <<= 8;                        // 转换成主机格式
        EtherType |= c;
        string tmpType = util::ConvertToType(EtherType);
        frame.setEtherType(tmpType);
        // 定位下一个帧,以确定当前帧的结束位置
        while ( bParseCont )
        {
            for (int i = 0; i < 7; i++)                    //找下一个连续的个xaa
            {                
                if (file.tellg() >= nFileEnd)            //到文件末尾,退出循环
                {
                    bParseCont = false;
                    break;
                }
                // 看当前字符是不是xaa,如果不是,则重新寻找个连续的xaa
                if (file.get() != 0xaa)
                {
                    i = -1;
                }
            }
            // 如果直到文件结束仍没找到上述比特串,将终止主控循环的标记bParseCont置为true
            bParseCont = bParseCont && (file.tellg() < nFileEnd);        
            // 判断个连续的xaa之后是否为xab
            if (bParseCont && file.get() == 0xab)        
            {
                break;
            }
        }
        // 计算数据字段的长度
        nCurrDataLength =                                
            bParseCont ?                                // 是否到达文件末尾
            (static_cast<int>(file.tellg()) - 8 - 1 - nCurrDataOffset) :    // 没到文件末尾:下一帧头位置- 前导码和定界符长度- CRC校验码长度- 数据字段起始位置
        (static_cast<int>(file.tellg()) - 1 - nCurrDataOffset);        // 已到达文件末尾:文件末尾位置- CRC校验码长度- 数据字段起始位置
        // 以文本格式数据字段,并校验
        char* pData = new char[nCurrDataLength];    // 创建缓冲区
        file.seekg(bParseCont ? (-8 - 1 -nCurrDataLength) : ( -1 - nCurrDataLength), ios::cur);
        file.read(pData, nCurrDataLength);                // 读入数据字段
        frame.setData(pData,nCurrDataLength);
        int nCount = 50;                                // 每行的基本字符数量
        for (i = 0; i < nCurrDataLength; i++)            // 输出数据字段文本    
        {
            util::CRC::checkCRC(nCheck, (int)pData[i]);            // CRC校验
        }
        delete[] pData;                                    //释放缓冲区空间
        // 输出CRC校验码,如果CRC校验有误,则输出正确的CRC校验码
        cout << endl <<"CRC校验";
        c = file.get();                                // 读入CRC校验码
        int nTmpCRC = nCheck;
        util::CRC::checkCRC(nCheck, c);                        // 最后一步校验
        string strCRC = util::ConvertToHex(c);
        frame.setCRC(strCRC);
        if ((nCheck & 0xff) != 0)                    // CRC校验无误
        {
            bAccept = false;                        // 将帧的接收标记置为false
        }
        //    如果数据字段长度不足字节或数据字段长度超过字节,则将帧的接收标记置为false    
        if (nCurrDataLength < 46 ||    nCurrDataLength > 1500 )                            
        {
            bAccept = false;
        }
        frame.setFrameState(bAccept);
        vecFrames.push_back(frame);
        nSN++;                                    // 帧序号加
        nCurrDataOffset = static_cast<int>(file.tellg()) + 22;    // 将数据字段偏移量更新为下一帧的帧头结束位置
    }
    // 关闭输入文件
    file.close();
    return true;
}
复制代码
 

复制代码
namespace util
{//实用工具
    class CRC
    {
    public:
    ////////////////////////////////////////////////////////////////////////////////
        // CRC校验,在上一轮校验的基础上继续作位CRC校验
        // 
        //    输入参数:
        //        chCurrByte    低位数据有效,记录了上一次CRC校验的余数
        //        chNextByte    低位数据有效,记录了本次要继续校验的一个字节
        //
        //    传出参数:
        //        chCurrByte    低位数据有效,记录了本次CRC校验的余数
        ////////////////////////////////////////////////////////////////////////////////
        static void checkCRC(int &chCurrByte, int chNextByte)
        {
            // CRC循环:每次调用进行次循环,处理一个字节的数据。
            for (int nMask = 0x80; nMask > 0; nMask >>= 1)
            {
                if ((chCurrByte & 0x80) != 0)        // 首位为1:移位,并进行异或运算            {    
                    chCurrByte <<= 1;                // 移一位
                    if ( (chNextByte & nMask) != 0)    // 补一位
                    {
                        chCurrByte |= 1;
                    }
                    chCurrByte ^= 7;                // 首位已经移出,仅对低位进行异或运算,的二进制为,0111
                }
                else                                // 首位为0,只移位,不进行异或运算
                {        
                    chCurrByte <<= 1;                // 移一位
                    if ( (chNextByte & nMask) != 0)    // 补一位
                    {
                        chCurrByte |= 1;
                    }
                }
            }
        }
    };
    char mappingTable[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    string ConvertToHex(int ch)
    {
        int high = ch/16;
        int low = ch%16;
        string result;
        result.append(1,mappingTable[high]);
        result.append(1,mappingTable[low]);
        return result;
    }
    string ConvertToType(int ch)
    {
        string result;
        int num,i;
        for (i=0;i<4;++i)
        {
            num = ch & 0x000F;
            ch>>=4;
            result.append(1,mappingTable[num]);
            if (i==1)
            {
                result.append(1,' ');
            }
        }
        
        for (i=0;i<=1;++i)
        {
            swap(result[i],result[4-i]);
        }
        return result;
    }
}
复制代码



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/01/03/1367516.html,如需转载请自行联系原作者
目录
相关文章
|
2天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
1月前
|
设计模式 安全 数据库连接
【C++11】包装器:深入解析与实现技巧
本文深入探讨了C++中包装器的定义、实现方式及其应用。包装器通过封装底层细节,提供更简洁、易用的接口,常用于资源管理、接口封装和类型安全。文章详细介绍了使用RAII、智能指针、模板等技术实现包装器的方法,并通过多个案例分析展示了其在实际开发中的应用。最后,讨论了性能优化策略,帮助开发者编写高效、可靠的C++代码。
36 2
|
9天前
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
15 0
|
9天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
37 0
|
1月前
|
机器学习/深度学习 自然语言处理 数据管理
GraphRAG核心组件解析:图结构与检索增强生成
【10月更文挑战第28天】在当今数据科学领域,自然语言处理(NLP)和图数据管理技术的发展日新月异。GraphRAG(Graph Retrieval-Augmented Generation)作为一种结合了图结构和检索增强生成的创新方法,已经在多个应用场景中展现出巨大的潜力。作为一名数据科学家,我对GraphRAG的核心组件进行了深入研究,并在此分享我的理解和实践经验。
73 0
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
50 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
90 4

推荐镜像

更多