C++实现udp分包和组包

目录

  • udp开发中的几个问题
  • udp分包和组包策略
  • C++实现udp分包
  • C++实现udp组包

udp开发中的几个问题

1、udp数据是怎么发送的

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的。不会使用块的合并优化算法,由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息)和结束标志, 即面向消息的通信是有消息保护边界的。
因此UDP是不会出现粘包的,但是会丢包。

2、tcp的处理方式

TCP是面向连接的,面向流的可靠性传输。TCP会将多个间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包发送,这样一个数据包里就可能含有多个消息的数据,面向流的通信是无消息保护边界的,也就是TCP粘包。接收端需要自己完成数据的拆包和组包,解决粘包问题。

3、udp的MTU值

MTU = 1500 - IP头(20) - UDP头(8) = 1472(Bytes)(局域网)
MTU = 576 - 20 - 8 = 548(Internet互联网标准)
如果发送数据包大于MTU,则会出现严重丢包,甚至sendto返回失败。

4、udp大于MTU值怎么处理

1、UDP发送的数据报大小 <= MTU时,发送端直接发送,接收端无需组包。
2、UDP发送的数据报大小 > MTU时,发送端必须进行拆包发送,接收端收到包以后组包。

udp分包和组包策略

1、给每个整包分配一个唯一的序列号sequence,组包时根据序列号判断分包属于哪个整包。
2、每个子包分配一个index序号,用于接收端按顺序组包。
3、udp包是独立的,因此分包后,每个包都要有可识别的公共包头。
4、等所有分包都接收完成再进行组包。
5、udp是不可靠传输,如果分包的其中一个子包丢了,那么整个包将被丢弃(重传依赖其他响应机制)。
6、udp是无序的,发送端按序发,接收端收到包可能是乱序的,组包时要按顺序组包。
7、udp支持一对多通信,如果同时接收多个客户端的消息,多个客户端的消息会交叉到达,需要单独处理每个客户端的消息。

6 和 7 是实现的难点。

C++实现udp分包

写个udp客户端,往指定udp服务端发送数据,for循环连续发送多个包,发送间隔依据机器性能,性能好的机器可以无间隔发送(nosleep)还能保证不丢包。
可启动多个客户端同时向服务端发送数据,测试组包。

客户端实现:

#ifndef UDPCLIENT_H
#define UDPCLIENT_H

#include "SocketInclude.h"
class UdpClient
{
public:
    UdpClient();
    ~UdpClient();
    int CreateUdpCli(uint32_t serverIp, uint16_t _uListenPort);
    int dealUdpSendData();
private:
    UdpClientDef*    pUdpClientDef;
};
#endif //UDPCLIENT_H
#include "UdpClient.h"
#include 

UdpClient::UdpClient()
{
    pUdpClientDef = new UdpClientDef;
}

UdpClient::~UdpClient()
{
    delete pUdpClientDef;
}

//本端是客户端,udp客户端建链
int UdpClient::CreateUdpCli(uint32_t serverIp,uint16_t _uListenPort)
{
    return CreateUdpClient(pUdpClientDef,serverIp,_uListenPort);
}

//处理udp数据发送
int UdpClient::dealUdpSendData()
{
    static unsigned int sequence_whole = 0;//整包的序号
    //原始数据包
    uint32_t dataLength = 5000;
    void *sendbuff = new char[dataLength];
    CommMsgHdr* pHead = (CommMsgHdr*)sendbuff;

    pHead->uMsgType = 631;
    pHead->uTotalLen = dataLength;

    //开始分包
    int packetNum = dataLength / UDP_PAYLOAD;//分包数量
    int lastPaketSize = dataLength % UDP_PAYLOAD;//最后一片包的大小
    int sequence = 0;//当前发送的包序号
    if (lastPaketSize != 0)
    {
        packetNum = packetNum + 1;
    }

    //分包的包头
    MergeHdr tMergeHdr;
    tMergeHdr.uAllPktSize = dataLength;//负载总大小
    tMergeHdr.uPieces = packetNum;//分包数量
    tMergeHdr.uSequence = sequence_whole++;
    tMergeHdr.msgHead.uMsgType = 635;

    unsigned char piecebuff[UDP_PAYLOAD + sizeof(MergeHdr)];
    while (sequence < packetNum)
    {
        memset(piecebuff,0,UDP_PAYLOAD + sizeof(MergeHdr));
        if (sequence < (packetNum-1))//不是最后一片
        {
            tMergeHdr.uCurPktSize = sizeof(MergeHdr) + UDP_PAYLOAD;//当前包大小
            tMergeHdr.uIndex = sequence + 1;//当前包序号
            tMergeHdr.uOffset = sequence * UDP_PAYLOAD;//当前包偏移
            tMergeHdr.msgHead.uTotalLen  = tMergeHdr.uCurPktSize;
            memcpy(piecebuff, &tMergeHdr, sizeof(MergeHdr));
            memcpy(piecebuff+sizeof(MergeHdr), (char*)sendbuff+tMergeHdr.uOffset, UDP_PAYLOAD);

            ssize_t send_len = ::sendto(pUdpClientDef->fd, (const char*)piecebuff, tMergeHdr.uCurPktSize, 0, (struct sockaddr*) &pUdpClientDef->remote_addr,sizeof(struct sockaddr));
            if(send_len!=tMergeHdr.uCurPktSize)
            {
                printf("udp send failed,errno=[%d]\n",errno);
            }

            sequence ++;
        }
        else//最后一片
        {
            tMergeHdr.uCurPktSize = sizeof(MergeHdr)+(dataLength - sequence * UDP_PAYLOAD);
            tMergeHdr.uIndex = sequence + 1;
            tMergeHdr.uOffset = sequence * UDP_PAYLOAD;
            tMergeHdr.msgHead.uTotalLen  = tMergeHdr.uCurPktSize;
            memcpy(piecebuff, &tMergeHdr, sizeof(MergeHdr));
            memcpy(piecebuff+sizeof(MergeHdr), (char*)sendbuff+tMergeHdr.uOffset, dataLength - sequence*UDP_PAYLOAD);
            ssize_t send_len = ::sendto(pUdpClientDef->fd, (const char*)piecebuff, tMergeHdr.uCurPktSize, 0, (struct sockaddr*) &pUdpClientDef->remote_addr,sizeof(struct sockaddr));
            if(send_len!=tMergeHdr.uCurPktSize)
            {
                printf("udp send failed,errno=[%d]\n",errno);
            }

            sequence ++;
        }
    }
    delete[] (char *)(sendbuff), sendbuff = NULL;
    return 0;
}

结构体定义:

#ifndef SOCKETINCLUE_H
#define SOCKETINCLUE_H

#include 
#include 
#include 
#include  // for open
#include  // for close
#include 
#include 
#include 
#include 

#define UDP_MTU             1472
#define MAX_EPOLL_EVENTS    1024         //epoll监听最大事件数量,对应最大连接socket数量

#pragma pack(push, 1)

//业务包头
struct CommMsgHdr
{
    uint16_t uMsgType;
    uint32_t uTotalLen;
};

//分包包头
struct MergeHdr
{
    CommMsgHdr  msgHead;//公共包头
    unsigned int uSequence;//整包的序号
    unsigned int uCurPktSize;//当前包的大小(sizeof(MergeHdr)+负载长度)
    unsigned int uAllPktSize;//数据的总大小
    unsigned int uPieces;//数据被分成包的个数
    unsigned int uIndex;//数据包当前的帧号
    unsigned int uOffset;//数据包在整个数据中的偏移
};
#define UDP_PAYLOAD         ( UDP_MTU - sizeof(MergeHdr) )    //互联网udp有效负载

//单个Sequence的所有包
struct pkt_merge
{
    void* mergebuff        = nullptr ;  //合并后的数据
    int  uAllPktSize       = 0;     //接收总长度
    unsigned int uSequence = 0;     //整包的序号
    unsigned int uPieces;           //数据被分成包的个数

    std::map<unsigned int,void*> mRcvData;//暂存接收的包
};

//udp客户端
typedef struct _UdpClientDef_{
    int32_t             fd;              //fd,如果是服务端则统一使用服务端的fd,根据地址区分不同客户端
    struct sockaddr_in  remote_addr;     //对端地址
    std::map<int,pkt_merge*> m_pkt_merge;//,需要组包时,暂存包
}UdpClientDef;

//udp服务端
typedef struct _UdpServerDef_{
    int32_t fd;                         //fd
    struct sockaddr_in local_addr;      //本端地址
}UdpServerDef;

#pragma pack(pop)

int32_t CreateUdpClient(UdpClientDef* udp, uint32_t remoteIp, int32_t remotePort);
int32_t CreateUdpServer(UdpServerDef *udp, int32_t plocalPort);

#endif // SOCKETINCLUE_H

#include "SocketInclude.h"

//创建udp套接字
int32_t CreateUdpClient(UdpClientDef* udp, uint32_t remoteIp, int32_t remotePort)
{
    if (udp == NULL)		return -1;
    udp->fd = -1;

    udp->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    fcntl(udp->fd, F_SETFL, O_NONBLOCK);//设置非阻塞
    if(udp->fd < 0)
    {
        printf("[CreateUdpClient] create udp socket failed,errno=[%d],remoteIp=[%u],remotePort=[%d]",errno,remoteIp,remotePort);
        return -1;
    }

    udp->remote_addr.sin_family = AF_INET;
    udp->remote_addr.sin_port = htons(remotePort);
    udp->remote_addr.sin_addr.s_addr = remoteIp;
    return 0;
}

int32_t CreateUdpServer(UdpServerDef *udp, int32_t plocalPort)
{
    if (udp == NULL)		return -1;
    udp->fd = -1;

    udp->local_addr.sin_family = AF_INET;
    udp->local_addr.sin_port = htons(plocalPort);
    udp->local_addr.sin_addr.s_addr = INADDR_ANY;

    udp->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(udp->fd < 0)
    {
        printf("[CreateUdpServer] create udp socket failed,errno=[%d],plocalPort=[%d]",errno,plocalPort);
        return -1;
    }

    //2.socket参数设置
    int opt = 1;
    setsockopt(udp->fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//chw
    fcntl(udp->fd, F_SETFL, O_NONBLOCK);//设置非阻塞

    if (bind(udp->fd, (struct sockaddr*) &udp->local_addr,sizeof(struct sockaddr_in)) < 0)
    {
        close(udp->fd);
        printf("[CreateUdpServer] Udp server bind failed,errno=[%d],plocalPort=[%d]",errno,plocalPort);
        return -2;
    }

    return 0;
}

调用方式:

UdpClient mUdpClient;
uint32_t serverIp = inet_addr("127.0.0.1");
mUdpClient.CreateUdpCli(serverIp,9090);

for(int index=0;index<ui->lineEdit->text().toInt();index++)
{
        mUdpClient.dealUdpSendData();
        usleep(1000 * 10);
}

C++实现udp组包

写个udp服务端,绑定端口,使用epoll监听接收。

#ifndef UDPSERVER_H
#define UDPSERVER_H

#include "SocketInclude.h"
using namespace std;
class UdpServer
{
public:
    UdpServer();
    ~UdpServer();
    bool StartUp(uint16_t _uListenPort);//启动服务
private:
    static void* ThreadCallBack(void *arg);//创建线程函数
    void DealUdpThread();//udp线程处理函数
    void TryMergePkt(int index, unsigned int seq);//尝试组包
    void DelMergePkt(int index, unsigned int seq);//释放包
    void SetUdpEpollFlag(int fd, bool flag);

private:
    UdpServerDef*    pUdpServerDef;    //udp服务端对象
    pthread_t        m_threadId;    //udp线程ID
    int              m_epoll_fd;    //epollfd
    bool             m_bIsRunning;  //线程是否运行
    vector<UdpClientDef> m_vUdpClientDef;
};
#endif //UDPSERVER_H

#include "UdpServer.h"
#include 
#include 
#include 

UdpServer::UdpServer()
{
    pUdpServerDef = new UdpServerDef;
    m_epoll_fd = epoll_create(1);
    m_bIsRunning = true;
}

UdpServer::~UdpServer()
{
    m_bIsRunning = false;
}

bool UdpServer::StartUp(uint16_t _uListenPort)
{
    if(CreateUdpServer(pUdpServerDef,_uListenPort) == 0)
    {
        SetUdpEpollFlag(pUdpServerDef->fd,true);
    }

    if(pthread_create(&m_threadId, nullptr, ThreadCallBack, this))
    {
        printf("[UdpServer::StartUp] create thread failed.");
        return false;
    }

    return true;
}

//TCP服务监听线程,处理接入监听,客户端断开/错误管理
void* UdpServer::ThreadCallBack(void *arg)
{
    UdpServer *tm = static_cast<UdpServer *>(arg);
    if(tm)
        tm->DealUdpThread();
    return nullptr;
}

void UdpServer::DealUdpThread()
{
    const int kEpollDefaultWait = 1;//超时时长,单位ms
    struct epoll_event alive_events[MAX_EPOLL_EVENTS];

    void* bigBuffer = NULL;
    int  mergeRcvLen = 0;
    static unsigned int sequence = 1;
    while (m_bIsRunning)
    {
        int num = epoll_wait(m_epoll_fd, alive_events, MAX_EPOLL_EVENTS, kEpollDefaultWait);
        for (int i = 0; i < num; ++i)
        {
            int fd = alive_events[i].data.fd;
            int events = alive_events[i].events;

            if ( events & EPOLLIN )
            {
                char recv_buffer[UDP_MTU];
                memset(recv_buffer,0,UDP_MTU);
                ssize_t recv_len = 0;
                socklen_t src_len = sizeof(struct sockaddr_in);
                struct sockaddr_in SrcAddr;
                memset(&SrcAddr, 0, src_len);

                //1.开始接收
                struct sockaddr_in remote_addr;
                if ((recv_len = recvfrom(fd, recv_buffer, UDP_MTU, 0,	(struct sockaddr*) &SrcAddr, &src_len)) > 0)
                {
                    remote_addr.sin_port = SrcAddr.sin_port;
                    remote_addr.sin_addr.s_addr = SrcAddr.sin_addr.s_addr;

                    //判断是否已记录该客户端
                    bool isExist = false;
                    for(int index=0;index<m_vUdpClientDef.size();index++)
                    {
                        if(m_vUdpClientDef[index].remote_addr.sin_addr.s_addr == remote_addr.sin_addr.s_addr
                        && m_vUdpClientDef[index].remote_addr.sin_port        == remote_addr.sin_port)
                        {
                            isExist = true;
                            break;
                        }
                    }
                    if(!isExist)
                    {
                        UdpClientDef tUdpClientDef;
                        tUdpClientDef.fd = fd;
                        tUdpClientDef.remote_addr = remote_addr;
                        m_vUdpClientDef.push_back(tUdpClientDef);
                    }
                }
                else
                    continue;

                //1.不需要组包
                if( ((CommMsgHdr *)recv_buffer)->uMsgType != 635)
                {
                    //直接分发处理
                }

                //2.需要组包,处理错序
                for(int index=0;index<m_vUdpClientDef.size();index++)
                {
                    if(m_vUdpClientDef[index].remote_addr.sin_addr.s_addr == remote_addr.sin_addr.s_addr
                    && m_vUdpClientDef[index].remote_addr.sin_port        == remote_addr.sin_port)
                    {
                        MergeHdr* tMergeHdr = (MergeHdr*)recv_buffer;

                        if(m_vUdpClientDef[index].m_pkt_merge.count(tMergeHdr->uSequence) > 0)
                        {
                            //已存在该Sequence的包
                            m_vUdpClientDef[index].m_pkt_merge[tMergeHdr->uSequence]->uAllPktSize += tMergeHdr->uCurPktSize - sizeof(MergeHdr);;
                        }
                        else
                        {
                            //新Sequence的包
                            //建议新增定时器,在一定时间内没有收完包,则丢弃包
                            pkt_merge *tpkt_merge = new pkt_merge;
                            tpkt_merge->uSequence = tMergeHdr->uSequence;
                            tpkt_merge->uAllPktSize = tMergeHdr->uCurPktSize - sizeof(MergeHdr);
                            tpkt_merge->uPieces = tMergeHdr->uPieces;
                            tpkt_merge->mergebuff = new char[tMergeHdr->uAllPktSize];

                            m_vUdpClientDef[index].m_pkt_merge[tMergeHdr->uSequence] = tpkt_merge;
                        }
                        //todo,如果已存在key,存在泄漏
                        void* buff = new char[UDP_MTU];
                        memcpy(buff,recv_buffer,UDP_MTU);
                        m_vUdpClientDef[index].m_pkt_merge[tMergeHdr->uSequence]->mRcvData[tMergeHdr->uIndex] = buff;

                        TryMergePkt(index,tMergeHdr->uSequence);
                    }
                }

#if 0         //3.需要组包,不处理错序,此时如果同时接收两个客户端的数据,则可能出现错序
              if( ((CommMsgHdr *)recv_buffer)->uMsgType == 635)
                {
                    MergeHdr* tMergeHdr = (MergeHdr*)recv_buffer;
                    //根据sequence按顺序接收
                    if(sequence == tMergeHdr->uIndex)
                    {
                        sequence++;
                        if(bigBuffer == nullptr)
                            bigBuffer = new char[tMergeHdr->uAllPktSize];
                        mergeRcvLen += tMergeHdr->uCurPktSize - sizeof(MergeHdr);

                        memcpy((char*)bigBuffer+tMergeHdr->uOffset, recv_buffer + sizeof(MergeHdr),
                               tMergeHdr->uCurPktSize - sizeof(MergeHdr));
                        if ((tMergeHdr->uPieces == tMergeHdr->uIndex)
                                && (mergeRcvLen == tMergeHdr->uAllPktSize))
                        {
                            //组包完成
                            CommMsgHdr* pMsg = (CommMsgHdr*)bigBuffer;
                            printf("pMsg.uMsgType=%d\n",pMsg->uMsgType);
                            printf("pMsg.uTotalLen=%d\n",pMsg->uTotalLen);
                            printf("remote_addr.sin_port=%d\n",ntohs(remote_addr.sin_port));

                            mergeRcvLen = 0;
                            delete[] (char *)(bigBuffer);
                            bigBuffer = NULL;
                            sequence = 1;
                        }
                    }
                    //如果出现错序或乱序,丢弃包
                    else
                    {
                        mergeRcvLen = 0;
                        delete[] (char *)(bigBuffer);
                        bigBuffer = NULL;
                        sequence = 1;
                        printf(" miss-sequence \n");
                    }
                }
#endif
            }
        }

    }
}

//尝试组包
void UdpServer::TryMergePkt(int index, unsigned int seq)
{
    pkt_merge *tpkt_merge = m_vUdpClientDef[index].m_pkt_merge[seq];
    if(tpkt_merge->uPieces > tpkt_merge->mRcvData.size())
    {
        //还没收完所有的包,不组包
        auto last_ite = tpkt_merge->mRcvData.end();
        last_ite --;
        //如果此时收到的 分包序列号 >= 包的总数,则说明中间有丢包,此时丢弃包
        if(last_ite->first >= tpkt_merge->uPieces)
        {
            DelMergePkt(index, seq);
            printf("error,miss sequence\n");
        }
    }
    else if(tpkt_merge->uPieces < tpkt_merge->mRcvData.size())
    {
        //收到包的数量大于分片数量,出现异常,丢弃包
        printf("error,recv too many bags\n");
        DelMergePkt(index, seq);
    }
    else
    {
        //已经收完所有分包,开始组包
        auto ite = tpkt_merge->mRcvData.begin();
        while(ite != tpkt_merge->mRcvData.end())
        {
            MergeHdr* tMergeHdr = (MergeHdr*)ite->second;
            memcpy((char*)tpkt_merge->mergebuff+tMergeHdr->uOffset, (char*)ite->second + sizeof(MergeHdr),
                   tMergeHdr->uCurPktSize - sizeof(MergeHdr));
            ++ ite;
        }

        ite --;
        MergeHdr* tMergeHdr = (MergeHdr*)ite->second;
        if ((tpkt_merge->uPieces == tMergeHdr->uIndex)
                && (tpkt_merge->uAllPktSize == tMergeHdr->uAllPktSize))
        {
            //组包完成,分发处理
            CommMsgHdr* pMsg = (CommMsgHdr*)tpkt_merge->mergebuff;
            printf("pMsg.uMsgType=%d\n",pMsg->uMsgType);
            printf("pMsg.uTotalLen=%d\n",pMsg->uTotalLen);

            //释放资源
            DelMergePkt(index, seq);
        }
    }
}

void UdpServer::DelMergePkt(int index, unsigned int seq)
{
    pkt_merge *tpkt_merge = m_vUdpClientDef[index].m_pkt_merge[seq];

    auto ite_2 = tpkt_merge->mRcvData.begin();
    while(ite_2 != tpkt_merge->mRcvData.end())
    {
        delete[] (char *)(ite_2->second);
        ite_2++;
    }
    delete[] (char *)(tpkt_merge->mergebuff);
    tpkt_merge->mRcvData.clear();
    m_vUdpClientDef[index].m_pkt_merge.erase(m_vUdpClientDef[index].m_pkt_merge.find(tpkt_merge->uSequence));
}

//设置epoll监听udp套接字,只监听EPOLLIN事件
void UdpServer::SetUdpEpollFlag(int fd, bool flag)
{
    struct epoll_event evt;
    evt.events = EPOLLIN;
    evt.data.fd = fd;
    if(flag)
        epoll_ctl(m_epoll_fd,EPOLL_CTL_ADD,fd,&evt);
    else
        epoll_ctl(m_epoll_fd,EPOLL_CTL_DEL,fd,&evt);
}

你可能感兴趣的:(C/C++,udp,c++,网络)