简体   繁体   English

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

[英]Receiving string from c++ UDP buffer

I am attempting to retrieve a string inside a struct from a UDP buffer.我试图从 UDP 缓冲区中检索结构内的字符串。 The idea here is to first memcpy the the header and use the header.dataSize to tell recv how large the string is, then memcpy from buffer into a new string variable.这里的想法是首先 memcpy 标头并使用 header.dataSize 告诉 recv 字符串有多大,然后 memcpy 从缓冲区到一个新的字符串变量。 The string is serialized data that will then be de-serialized into a struct.该字符串是序列化的数据,然后将被反序列化为一个结构体。

The problem I am encountering is that when the receive side attempts to memcpy the string, it giving me an error for invalid pointer.我遇到的问题是,当接收端尝试 memcpy 字符串时,它给我一个无效指针的错误。

Error:错误:

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

Am i using memcpy wrong ?我使用 memcpy 错了吗? Or is there a better way to copy the specified size of data ?或者有没有更好的方法来复制指定大小的数据? I tried memccpy using 0 as the termination char but that didn't work either.我尝试使用 0 作为终止字符的 memccpy 但这也不起作用。

Send code:发送代码:

// 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;
}

Receive Code:接收代码:

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";}
            else{std::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;
}

My structs:我的结构:

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;
};
        std::string serData;
        memcpy(&serData, buffer, sizeof(inHdr.dataSize));

A std::string is just an ordinary class. std::string只是一个普通的类。 A very typical std::string looks like:一个非常典型的std::string看起来像:

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

That's a capsule summary of what a std::string , more or less.这是std::string或多或少的概括总结。 Actual details vary, but this is what it is, in so many words.实际细节各不相同,但这就是它的全部内容。 Hopefully this makes it clear why scribbling over these pointers and data, with what's effectively random junk, is not going to accomplish anything useful, and guarantees a crash.希望这能说明为什么在这些指针和数据上乱涂乱画,实际上是随机的垃圾,不会完成任何有用的事情,并保证崩溃。

The correct way to do this would be:正确的方法是:

  1. Use std::string 's resize() method to resize the size of the string's data.使用std::stringresize()方法来调整字符串数据的大小。

  2. With C++17 you can use its data() method to get a pointer to the string's internal buffer.使用 C++17,您可以使用它的data()方法来获取指向字符串内部缓冲区的指针。 With earlier C++ standard getting the address of the first character in the string, &serData[0] , will do the trick.使用较早的 C++ 标准获取字符串中第一个字符的地址, &serData[0]可以解决问题。

  3. And finally, now, after all this work, you actually have a valid buffer to memcpy() your data into.最后,现在,在完成所有这些工作之后,您实际上有一个有效的缓冲区可以将您的数据放入memcpy()

But an even better approach is to forget memcpy and construct std::string with the data in the first place, using its constructor:但更好的方法是忘记memcpy并首先使用数据构造std::string ,使用其构造函数:

std::string serData{buffer, buffer+inHdr.dataSize};

That's it.就是这样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM