Отправка двоичных сериализованных данных на основе C ++ на основе stl для передачи по сети с использованием сокетов. - PullRequest
5 голосов
/ 14 октября 2011

Мне нужно отправить несколько сложных объектов по сети на одноранговый узел. Я написал код для их сериализации, используя ostream и оператор << для каждого члена класса в объектах, которые должны быть сериализованы. Код, который я написал, успешно работает для сериализации и сетевой отправки (htonl (), htons () и т. Д. Правильно используется - я проверил вышеупомянутое, записав в ofstream (локальный файл) в двоичном формате (std :: ios :: bin) Моя следующая задача - записать эти двоичные данные через сетевой сокет - вот где у меня проблемы. </p>

У меня есть класс Socket, который переводит объекты std :: string в строки в стиле C перед отправкой их через сокет следующим образом:

int Socket::send (const std::string goodies) const
{
    status = ::send (socket, goodies.c_str(), goodies.size(), 0);
            return status;
}

тот же класс Socket, который я использую в приемнике, использует recv () для помещения входящего сообщения в std :: string перед передачей его в приложение десериализации:

int Socket::recv (std::string& goodies)
{
    char buf [1024];
    goodies = "";
    memset (buf, 0, 1025);

    int status = ::recv (socket, buf, 1024, 0);

    if (status < 0)
    {
        return -1;
    }
    else if (status == 0)
    {
        return 0;
    }
    else
    {
        goodies = buf;
        return status;
    }
}

Я делаю отправку, используя следующий код:

ostringstream os (std::ios::binary);
GiantObject giantComplexObjectWithWholeLoadOfOtherObjects;
// Initialize and set up 
// giantComplexObjectWithWholeLoadOfOtherObjects.

// The following call works well--I tested it by writing to ofstream locally (file)
// and checked it out using a hex dump.
// Now, of course, my intent is to send it over the network, so I write to
// ostream&:
giantComplexObjectWithWholeLoadOfOtherObjects.serialize (os);

std::string someStr = os.str(); // Get me the stream in std::string format

mySocket.send(someStr); // Does not work--send sent correctly, but recv received 0 bytes

Однако, если я попытаюсь:

std::string someStr ("Some silly string");
mySocket.send (someStr); // received by receiver (receiver is similar arch).

Таким образом, что-то не так с моим вызовом для отправки двоичного std :: string в сокет. Любая помощь очень ценится. Еще раз, я не хочу использовать Boost, protobuf и т. Д.

PS: я потратил значительное количество времени на просмотр старых постов здесь, и первый ответ, который получат эти типы вопросов, - это использование Boost. Пожалуйста - есть другие не-Boost, не-Protobuf способы, и я хочу понять эти другие способы. Я ценю то, что Boost и Protobuf привносят в таблицу, я просто хочу сделать это так, как я настроил код. Спасибо за ваше понимание.

Спасибо, что прочитали мой длинный пост.

Ответы [ 3 ]

8 голосов
/ 14 октября 2011

Я считаю, что одной из проблем является использование std :: string в качестве контейнера. Особенно линия goodies = buf;. Оператор присваивания std :: string из char * предполагает, что ввод заканчивается на первом NUL. Поскольку двоичные данные могут содержать произвольные символы NUL, они, скорее всего, прекратят работу раньше, чем вы ожидаете.

Для такого приложения я рекомендую использовать std::vector<char> или std::vector<unsigned char>. Поскольку память в буфере всегда смежна, вы можете получить доступ к базовому буферу, взяв адрес первого элемента.

Для принимающей стороны вы можете использовать std::copy для копирования данных. Или вы можете настроить его на использование векторного буфера и поменять местами после завершения приема. Это предполагает, что вы хотите, чтобы вкусности не изменялись, если статус <= 0. Если вы можете принять изменение положительных героев даже в этих случаях, вы можете изменить его размер до вызова <code>::recv() и избежать создания дополнительного вектора.

int Socket::send (const std::vector<char>& goodies) const {
    status = ::send (socket, &goodies[0], goodies.size(), 0);            
    return status; 

int Socket::recv (std::vector<char>& goodies)  {    
  std::vector<char> buffer(1024);
  int status = ::recv (socket, &buf[0], buf.size());
  if (status < 0) 
  {
     return -1;      
  }
  else if (status == 0)
  { 
     return 0;
  }
  else
  {
     buffer.resize(status);
     goodies.swap(buffer);
     return status;      
  } 
}
7 голосов
/ 14 октября 2011

Одна из проблем в приведенном выше заключается в том, что вы предполагаете, что «send» всегда будет отправлять goodies.size() байт за один вызов. Это не обязательно: вам нужно повторно звонить до тех пор, пока вы не отправите goodies.size() байтов. Кроме того, вы, вероятно, захотите взять goodies по const ref, а не по значению, чтобы избежать копирования вашего огромного объекта.

На стороне recv, опять же, у вас нет никаких гарантий, что recv вернет вам полный объект. Вам нужно будет повторно использовать данные в буфере до тех пор, пока вы не определите, что извлекли полный объект. Это означает, что при отправке вашего объекта вам необходимо будет использовать протокол, ориентированный на сообщения, например SCTP, который доставляет границы сообщений на уровне протокола, или использовать протокол потоковой передачи, но включать информацию об кадрах в ваш формат сообщения. Это позволит потребителю извлечь заголовок, описывающий длину данных, ожидаемых в объекте.

Именно поэтому, кстати, вы получите множество ответов, чтобы просто использовать существующую платформу, потому что они позаботятся о таких проблемах с кадрированием и сборкой сообщений.

1 голос
/ 14 октября 2011

Одна вещь, которая сразу же появляется, заключается в том, что вы выполняете и ввод, и вывод с помощью std::ostringstream, который, я считаю, предназначен только для вывода.Вы используете void ostringstream::str(string)?Или вы используете оператор извлечения?Я не уверен, что любой из них сработает, но я думаю, что первый сработает.Однако, если вы собираетесь выполнять ввод и вывод на stringstream, вам действительно нужно просто использовать stringstream (который устанавливает режим на ios_base::in | ios_base::out.

...