4

I am trying to send a map composed of an int and another map over TCP socket in linux.

the map is of the form

map<int, map<string, double>>

Following this SO link i tried to do

unsigned char* mybytemap =  reinterpret_cast<unsigned char*>(&my_map);

then to send the buffer i used write() function as follows:

int size = sizeof(mybytemap);
char temp[10];
sprintf(temp, "%d", size);
write(sockfd, temp, strlen(temp));     //send the size for the client
write(sockfd, mybytemap, sizeof(mybytemap));

On the client side:

char temp[10];
n  = read(sockfd, temp, 10);
size = stoi(temp);    //Got Size Of buffer

unsigned char * buf;
if(size != 0)
{
    buf = new unsigned char[size];
    int current=0;
    while(current<size)
    {
        n = read(sockfd,(unsigned char*)(buf)+current, min(1024,size-current));
        if (n <= 0)
        {
            cout<<"ERROR reading from socket when receiving"<<endl;
            break;
        }
        current+=n;
    }
}

map<int, map<string, double>> *test = reinterpret_cast< map<int, map<string, double>>* > (buf);

vector<int> ks;
for(map<int, map<string, double>>::iterator it = test->begin(); it != test->end(); ++it)
{
    ks.push_back(it->first);
    cout<<"id: "<<it->first<<endl;
}

but the Map isn't received correctly and code crashes when trying to access the data. how to fix that?

Do i need to XML the map? and if so can someone guide me on how to do that?

EYakoumi
  • 407
  • 6
  • 17
  • 2
    you need to read about de-/serialization. You cannot simply dump the bits & bytes of an object into a file and then read it later. a `std::map` may use pointers internally, and having the values of such pointers in a file is completely useless – 463035818_is_not_an_ai May 20 '19 at 11:23
  • 2
    Use a library like json to serialize the data. – stark May 20 '19 at 11:23
  • How do you know that map on the sending and receiving end are binary compatible? Also you take a size of a pointer(`sizeof(mybytemap)`), the answer in linked question does it differently. This is wrong on such a fundamental level that a correct answer would need to properly write everything from scratch. If you can afford it, I'd suggest properly serlializing the map, sending it over your channel, and deserializing on the other end. Possibly you could use some library for that. – luk32 May 20 '19 at 11:23
  • 1
    Try [Boost.Serialization](https://www.boost.org/doc/libs/release/libs/serialization/). IIRC, it should be able to handle all STL types by default. – Daniel Langr May 20 '19 at 11:50

2 Answers2

3

answer in link in your question is not applicable to map (map is not pod)

general idea how you can to encode\decode map : encode size of map and then for each key/value encode size of key, then encode key, then encode size of value, then encode value. Following code assumes you encode size_t into sizeof(size_t) bytes

template<class T>
std::string encode(T value){
   // data to bytes
}

template<class T> 
T decode(std::string bytes){
  // bytes to data
}

template<class K, class V>
std::string encode_map(std::map<K, V> data){
  std::string result;
  result.append(encode<size_t>(data.size()));
  for(auto iter: data){
    std::string first = encode<K>(iter.first);
    result.append(encode<size_t>( first.size() ));
    result.append(first);
    std::string second = encode<V>(iter.second);
    result.append(encode<size_t>( second.size() ));
    result.append(encode<V>(iter.second));
  }
  return result;
}

template<class K, class V>
std::map<K, V> decode_map(std::string bytes){
  size_t index = 0;
  size_t size = decode<size_t>(std::string(bytes.begin()+index, bytes.begin()+index+sizeof(size_t) ) );
  index += sizeof(size_t);
  std::map<K, V> result;
  for(size_t i = 0; i<size; i++){
    size_t next_size = decode<size_t>(std::string(bytes.begin()+index, bytes.begin()+index+sizeof(size_t) ) );
    index += sizeof(size_t);
    K key = decode<K>(std::string(bytes.begin()+index, bytes.begin()+index+next_size ) );
    index += next_size;
    next_size = decode<size_t>(std::string(bytes.begin()+index, bytes.begin()+index+sizeof(size_t) ) );
    index += sizeof(size_t);
    V value = decode<V>(std::string(bytes.begin()+index, bytes.begin()+index+next_size ) );
    index += next_size;
    result[key] = value;
  }
  return result;
}
Andrew Kashpur
  • 736
  • 5
  • 13
2

As recommended, you need to perform so-called serialization. You can employ Boost.Serialization as a serialization library. It can handle all STL types. An illustrative example:

std::map<int, std::map<std::string, double>> m1;
// ... (fill m1)

// serialize into a buffer (string):
std::string buffer;
boost::iostreams::back_insert_device<std::string> inserter(buffer);
boost::iostreams::stream<boost::iostreams::back_insert_device<std::string>> ostr(inserter);
boost::archive::binary_oarchive oa(ostr);
oa << m1;
ostr.flush();

// ... (here you can send the contents of buffer as plain bytes-chars via socket)

// deserialize into new map:
boost::iostreams::basic_array_source<char> device(buffer.data(), buffer.size());
boost::iostreams::stream<boost::iostreams::basic_array_source<char>> istr(device);
boost::archive::binary_iarchive ia(istr);
std::map<int, std::map<std::string, double>> m2;
ia >> m2;

Full live demo (with all headers): https://wandbox.org/permlink/NyZeVTrFI0p8RcmY

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • I tried your solution and the map is serialized into the string as the demo showed. but when i try to convert the resulting string into a char* (using c_str()), the conversion stops at the first '\0' char and doesn't continue. will find a way to overcome that but as part of serialization it worked – EYakoumi May 20 '19 at 13:03
  • what i did is i replaced all \0 with a \n... hoping that \n is never used as in client side i am replacing all \n to \0 to revert to original – EYakoumi May 20 '19 at 13:12
  • 1
    @EYakoumi `\0` character just represents a byte in a serialized string with a zero value. What is wrong with that? Why do you need to convert it to some other value? Simply send is via socket as a byte array. – Daniel Langr May 20 '19 at 14:00
  • when i tried converting the resulting string into char* using string.c_str() function, the result stops at the first occurrence of \0 (for example lets say the string is "123\0456" the resulting char* of that will be only "123". that's why i had to replace all the \0. (unless there is another way to convert the string) – EYakoumi May 22 '19 at 07:10
  • @EYakoumi `std::string::c_str` does not convert anything. It just gives you a pointer to the `char` array (start of the stored "string"). Same as `std::string::data`. You can use this pointer to send the data (chars/bytes) via a socket. Something as `write(sockfd, buffer.data(), buffer.size()); `. – Daniel Langr May 22 '19 at 10:31