003 后台运行的设置与包的设计与实现

本文介绍了如何在服务器程序中设置后台运行,避免控制台影响用户体验,并详细解释了TCP粘包问题的成因及解决方案,通过包头和包体格式设计来确保数据包的准确接收。同时,文章还讨论了服务器缓冲区的处理策略,以实现数据的高效复用。
摘要由CSDN通过智能技术生成

1 后台运行设置

为什么要设置后台运行?

因为这是个服务器程序,运行起来不设置后台运行的话会弹出一个控制台,会严重影响体验

后台运行怎么设置

方式一 控制项:

  • 项目属性--》链接器--》入口点:mainCRTStartup

  • 项目属性--》链接器--》所有选项--》子系统--》选中windows

方式二源码:

#pragma comment(linker, "subsystem:windows /entry:WinMainCRTStartup") // 纯窗口界面 #pragma comment(linker, "subsystem:windows /entry:mainCRTStartup") // 命令行里面可以开启窗口 #pragma comment(linker, "subsystem:console /entry:WinMainCRTStartup")// 窗口界面可以创建console #pragma comment(linker, "subsystem:console /entry:mainCRTStartup")// 纯后台

2 接收数据包的设计

在上一节以及完成了基本的网络通信架构,现在就要接收数据了,接收数据会有一些问题

2.1 为什么要设计包

2.1.1tcp传输过程中容易发生粘包

tcp通信协议工作方式:

  • 客户端发送一个数据包,服务器应答一个数据包

  • 有时候tcp为了提高通信效率,client可以不用等待对方回应可以反反复复的发送数据包,server就会一个一个回应,client有一个没有收到就会报错

  • 当客户端发送命令很频繁的时候,有时候server‘端就会收到多个数据包,这时如何区分这些数

什么是粘包?粘包就是连续向对端发送两个或者两个以上的数据包,对端在一次收取中收到的数据包数量可能大于1个,当大于1个时,可能是几个(包括一个)包加上某个包的部分,或者干脆几个完整的包在一起。当然,也可能收到的数据只是一个包的部分,这种情况一般也叫作半包。

客户端发送命令频繁,server一次接收多次发送数据包,如何区分这些数据包,来了一些数据需要准确识别这些数据。

2.2 解决方案:包头加包体格式

包的结构:
public:
    WORD sHead; // 包头 固定位0xFEFF
    DWORD nLength; // 包长度 从命令号到和校验
    WORD sCmd;// 命令号 -- 根据业务选择
    std::string strData; // 包数据
    WORD sSum;// 校验值

00 0000 00 .... 00

包头+包长度+命令号+包数据+校验

包头常用值:FFFE、FEFE(这些值不易再数据里面出现--经验值)

因为recv到的是一个缓冲区数据所以,需要传入这个缓冲区地址,以及缓冲区长度,再根据包结构,先找到包头两个字节,指针加2(因为包头占两个字节),在把后四个字节当作lenght(命令号的长度+正式数据长度+和校验长度)解析出来值,指针加4(length占四个字节),然后把后面两个字节当作命令号解析,指针再加两个字节,然后就是包数据了利用(前面解析出来lenght的值 - 4就可以算出缓冲区的长度了)然后memcpy 长度就是length-4, 然后指针再加length-4,然后把后面两个字节当作sum,最后对数据进行和校验,与接收到的sum值进行对比,正确就返回index的值(解析了多少缓冲区长度)

解包接口
// 数据解析 len:输入作为缓冲区长度,输出:本次解析的数据长度
    CPacket(const BYTE* pData, size_t& nSize)
    {
        size_t index = 0;
        for (; index < nSize; index++)
        {
        
            if (*(WORD*)(pData + index) == 0xFEFF)
            {
                index += 2;
                break;
            }
        }
        // 包数据可能不全
        if (index + 8 > nSize)
        {
            nSize = 0;
            return;
        }
        nLength = *(DWORD*)(pData + index); index += 4;
        //cmd + data + sum  + head + length
        // 包数据不全
        if (nLength + index > nSize)
        {
            nSize = 0;
            return;
        }
        sCmd = *(WORD*)(pData + index); index += 2;
        if (nLength > 4)
        {
            strData.resize(nLength - 4);
            memcpy((void*)strData.c_str(), pData + index, nLength - 4); index += nLength - 4;
        }
        sSum = *(WORD*)(pData + index); index += 2;
        DWORD sum = 0;
        for (int i = 0; i < strData.size(); i++)
        {
            sum += BYTE(strData[i]) & 0xFF;
        }
        if (sSum == sum)
        {
            nSize = index;
            return;
        }
        nSize = 0;
    }

封包接口

在下一节发送数据时介绍

3 服务器缓冲区的处理

可复用缓冲区处理逻辑如下:

假设有一个buffer有4096字节,在一次recv后,往buffer里面填数据,然后开始解析数据,解析完了后,需要把buffer减去用掉的长度,还有把buffer往前移,把用掉的buffer覆盖,第二次接收数据时,buffer的首地址需要加上上次没用完的数据长度,buffer的总长度也得减掉上次没用完的长度

参考代码:
  • 定义一个静态变量index来累加每次的recv的数据长度

  • recv时buffer缓冲区的地址应该是buffer+index,buffer缓冲区长度为BUFFER_SIZE - index

  • 让解析数据包的长度为index,但是又不能传index进去,因为会改变index的值,所以需要让len = index

  • 解析完数据后,把缓冲区,往前移覆盖用掉的缓冲区

  • 让index减去用掉的长度

    // 循环接收处理数据,存放在m_packet里面
        int DealCommand()
        {
            if (m_socket_client == INVALID_SOCKET)
            {
                return false;
            }
            char* buffer = new char[BUFFER_SIZE];
            // 这里有个bug,可能上传有没有处理完的数据,再次DealCommand时候就会丢失数据
            // 解决方案初始化放在构造函数里面,因为这是给可重复利用的缓冲区
            memset(buffer, 0, BUFFER_SIZE);
    ​
            // 缓冲区当前所有数据长度
            static int index = 0;
            while (true)
            {
                size_t len = recv(m_socket_client, buffer + index, BUFFER_SIZE - index, 0);
                if (len <= 0)
                {
                    return -1;
                }
                // 每次接收数据后,index加上这个长度
                index += len;
                // 让每次解析都是全部的数据长度,如果直接传index进去解析,会导致index值被修改
                len = index;
                // len是个引用 本次解析的数据长度
                m_packet = CPacket((BYTE*)buffer, len);
                if (len > 0)
                {
                    // 解析完了后,把buffer往前移
                    memmove(buffer, buffer + len, BUFFER_SIZE - len);
                    // index 减去用掉的长度
                    index -= len;
                    return m_packet.sCmd;
                }
            }
        }

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值