从 C++ UDP 缓冲区接收字符串

Posted

技术标签:

【中文标题】从 C++ UDP 缓冲区接收字符串【英文标题】:Receiving string from c++ UDP buffer 【发布时间】:2021-12-14 02:22:43 【问题描述】:

我正在尝试从 UDP 缓冲区检索结构内的字符串。 这里的想法是首先 memcpy 标头并使用 header.dataSize 告诉 recv 字符串有多大,然后从缓冲区 memcpy 到一个新的字符串变量。该字符串是序列化的数据,然后将被反序列化为结构。

我遇到的问题是,当接收端尝试 memcpy 字符串时,它给了我一个无效指针的错误。

错误:

Data:
    
free(): invalid pointer
Aborted (core dumped)

我使用 memcpy 错了吗?或者有没有更好的方法来复制指定大小的数据?我尝试使用 0 作为终止字符的 memccpy,但这也不起作用。

发送代码:

// interface details
std::string destIp = "127.0.0.1";
static uint16_t listPort = 10'000;
static uint16_t destPort = 10'001;

// -----------------------------------------------
// Main
// -----------------------------------------------
int main()
    // initialize interface
    UDP* udp = new UDP(listPort, destIp, destPort);
    udp->init();

    // create data struct 
    SerialData data1;
    fillSerialData(data1);

    // create the out stream 
    std::ostringstream outStream;
    // serialize
    
        cereal::BinaryOutputArchive archive_out(outStream);
        archive_out(data1);
    
    // create message out struct
    SerialMessage send1;
    send1.hdr.domainId = 6;
    send1.hdr.msgId = 1;

    // copy archive into data location in string format
    send1.data = outStream.str();
    send1.hdr.dataSize = sizeof(send1.data);
    send1.hdr.timeStamp = getTimeStamp();

    // send the data
    int nbytes = udp->send(reinterpret_cast<uint8_t *>(&send1), sizeof(send1));

    // output to console. 
    std::cout << "\n\tSerialized Data:\n\t" << send1.data << std::endl << std::endl;
    std::cout << "\tbytes sent: " << nbytes << "\n\tdataSize: " << send1.hdr.dataSize << "\n\ttimeStamp: " << send1.hdr.timeStamp << "\n\n";

    return 0;


接收代码:

int main(int, char **)

    std::cout << "Hello, recv!\n";

    // initialize signal handler
    signal(SIGINT, signalHandler);

    // initialize udp interface
    UDP *udp = new UDP(listPort, destIp, destPort);
    udp->init();

    // create buffer to read data into
    int recvSize = 0;
    int bufSize = 65536;
    uint8_t *buffer = new uint8_t[bufSize];
    memset(buffer, 0, bufSize);

    // loop and recv data
    while (!killSignal)
    
        // receive message
        if ((recvSize = udp->recv(buffer, bufSize)) < 0)
            if (errno == EAGAIN)std::cout << "\n\treceive timeout";
            elsestd::cout << "\n\tERROR: " << strerror(errno) << "\n\n";
        
        else
            std::cout << "\n\tReceived Message, size: " << recvSize << '\n';

            // get incoming message info via header
            Header inHdr;
            memcpy(&inHdr, buffer, sizeof(Header));
            std::string serData;
            memcpy(&serData, buffer, sizeof(inHdr.dataSize));

            std::cout << "\tdID: " << (int)inHdr.domainId << "\n\tmID: " << (int)inHdr.msgId << "\n\tdataLength: " << inHdr.dataSize << "\n\ttimeStamp: " << inHdr.timeStamp << std::endl;
            std::cout << "\nData:\n\t" << serData << std::endl;

            // TODO - remove comment tabs below after serData is showing to be filled with the data from buffer.
            //      deserialization part is good to go.  
/*
            // create in stream
            std::istringstream inStream(sMsg.data);
            // create object to store data in. 
            SerialData data;
            // De-serialize
            
                cereal::BinaryInputArchive archive_in(inStream);
                archive_in(data);
            
            std::cout << "Data Retreived From Archive:\n" << std::endl;
            printSerializedMessageData(data);
*/
        
    

    // close interface
    udp->close();

    // clear memory
    delete[] buffer;

    return 0;

我的结构:

struct Header

    uint8_t domainId;
    uint8_t msgId;
    int msgCnt;
    uint16_t dataSize;
    uint64_t timeStamp;
;

struct Footer 

    uint32_t temp;
;
    
struct Target

    std::string type;
    double x, y, z;
    uint64_t timeStamp;
    
    template <class Archive>
    void serialize( Archive & ar )
        ar( CEREAL_NVP(type), CEREAL_NVP(x), CEREAL_NVP(y), CEREAL_NVP(z), CEREAL_NVP(timeStamp) );
    
;

struct SerialData

    int numTargets;
    std::vector<Target> tgt;

    template <class Archive>
    void serialize( Archive & ar )
        ar( CEREAL_NVP(numTargets), CEREAL_NVP(tgt) );
    
;

struct SerialMessage

    Header hdr;
    std::string data;
    Footer ftr;
;

【问题讨论】:

无关:UDP* udp = new UDP(listPort, destIp, destPort); 似乎已泄露。您可能无需动态分配就可以逃脱并使用UDP udp(listPort, destIp, destPort); 【参考方案1】:
        std::string serData;
        memcpy(&serData, buffer, sizeof(inHdr.dataSize));

std::string 只是一个普通的类。一个非常典型的std::string 看起来像:

class string 
   char *buffer;
   size_t size;
   size_t max_size;
;

这是std::string 或多或少的摘要。实际细节各不相同,但这就是它的本质,用很多话来说。希望这能说明为什么在这些指针和数据上乱涂乱画,实际上是随机的垃圾,不会完成任何有用的事情,并且会导致崩溃。

正确的做法是:

    使用std::stringresize()方法来调整字符串数据的大小。

    使用 C++17,您可以使用其data() 方法获取指向字符串内部缓冲区的指针。使用早期的 C++ 标准获取字符串中第一个字符的地址,&amp;serData[0],就可以解决问题。

    最后,现在,经过所有这些工作,您实际上有一个有效的缓冲区,可以将您的数据放入memcpy()

但更好的方法是忘记memcpy,并首先使用数据构造std::string,使用它的构造函数:

std::string serDatabuffer, buffer+inHdr.dataSize;

就是这样。

【讨论】:

嗨,山姆,感谢您的帮助。这确实消除了我的错误。但是我认为 std::string 是用缓冲区的错误内存位置构造的,因为它没有提取适当的数据。这就是我使用 memcpy 的原因。例如:如果我将“hello world”作为结构内的字符串发送 - 未序列化:使用字符串构造函数中的缓冲区位置,我不会得到它。 我只能对我看到的代码做出判断。我还没有看到你做了什么实际的改变,所以我不能对此发表评论,除了说只有一个buffer,无论它指向的东西是明确的memcpy-ed,还是被@复制987654336@ 的构造函数,那么只有可能复制的数据块。此外,还不清楚如何确定 memcpy 使用了正确的内存位置,因为它正在将垃圾复制到无法可靠检查的 std::string 上。听起来你在某个地方也有其他问题。

以上是关于从 C++ UDP 缓冲区接收字符串的主要内容,如果未能解决你的问题,请参考以下文章

从 Direct Input 和 GetDeviceState() (C++) 接收键状态

通过 UDP 的 C++ 类在 C# 中使用,都有哪些选项?

MessageBox 从 TCHAR 缓冲区打印额外的 unicode 字符

在 C++ 中通过 UDP 发送缓冲图像

Udp -内部缓冲区

c++接收问题为何cin能接收,scanf接收就出错?