Как я могу отправить std :: vector <std :: string> через сокет UNIX? - PullRequest
4 голосов
/ 17 апреля 2010

Для моего приложения мне нужно иметь возможность отправить std::vector<std::string> через сокет UNIX (локальный) и получить копию вектора на другом конце сокета. Какой самый простой способ сделать это с O(1) сообщениями относительно размера вектора (т.е. без отправки сообщения для каждой строки в векторе)?

Поскольку это все на одном хосте, и так как я контролирую оба конца сокета, меня не волнуют проблемы, связанные с машиной, такие как конечность или представление вектора / строки.

Я бы не хотел использовать внешние библиотеки по разным причинам.

Ответы [ 5 ]

12 голосов
/ 17 апреля 2010

std :: string не мешает вам иметь nuls внутри вашей строки. Только когда вы пытаетесь использовать их с nul чувствительными API, вы сталкиваетесь с проблемами. Я подозреваю, что вы бы сериализовали массив, предварительно указав размер массива, а затем длину каждой строки в проводе.

...
long length = htonl( vec.size() );
write( socket, &length, sizeof(length) );
for ( int i = 0; i < vec.size(); ++i ) {
    length = htonl( vec[i].length() );
    write( socket, &length, sizeof(length) );
    write( socket, vec[i].data(), vec[i].length() );
}
...

Распаковка производится аналогично:

...
std::vector vectorRead;
long size = 0;
read( socket, &size, sizeof( size ) );
size = ntohl( size );
for ( int i = 0; i < size; ++i ) {
    std::string stringRead;
    long length = 0;
    read( socket, &length, sizeof( length ) );
    length = ntohl( length );
    while ( 0 < length ) {
        char buffer[1024];
        int cread;
        cread = read( socket, buffer, min( sizeof( buffer ), length ) );
        stringRead.append( buffer, cread );
        length -= cread;
    }
    vectorRead.push_back( stringRead );
}
...
3 голосов
/ 17 апреля 2010

Упаковка структур данных для передачи и приема обычно называется сериализацией .

Один вариант, который вы можете использовать: Библиотека сериализации Boost имеет возможность сериализации векторов STL.

Еще один вариант - бросить свой собственный - в этом случае это не должно быть сложно. Вы можете, например, объединить все строки вектора в одну строку (с разделением каждой составляющей NULL) и отправить этот буфер, а затем восстановить его аналогичным образом.

1 голос
/ 20 апреля 2010

Решение, которое я принял в итоге, заключалось в сериализации вектора строк в виде <string1>\0<string2>\0...<stringN>\0 (отправка длины вышеупомянутой строки заранее). Хотя Дэвид правильно указывает, что это не будет работать для случаев, когда std::string содержит ноль, я могу гарантировать, что это не будет иметь место для моего приложения.

1 голос
/ 17 апреля 2010

Я уверен, что меня за это кричат ​​фанаты C ++, но попробуйте writev(2) (a.k.a. scatter / collect I / O ). Однако вам все равно придется иметь дело с нулевыми разделителями на принимающей стороне.

0 голосов
/ 17 апреля 2010

Невозможно отправить вектор через сокет, даже на одном компьютере (или даже в том же процессе). Есть две проблемы с этим:

  1. vector и string поддерживают внутренние указатели на необработанную память. Это исключает отправку вектора <, string> другому процессу
  2. dtors вектора и строки захотят удалить этот указатель. Операции с сокетами сделают memcpy для вашего объекта (включая значения необработанных указателей), и вы получите двойное удаление.

Итак, правило таково: чтобы отправлять объекты через сокет, он должен быть в состоянии memcpy'd. Есть несколько способов сделать это

  1. Сериализация вектора Такие вещи, как ICE, хороши для генерации этих сериализаций http://www.zeroc.com/ Они имеют очевидные издержки
  2. Создайте что-нибудь с тем же интерфейсом, что и вектор и строка, но с возможностью memcpy'd
  3. Создание версий только для чтения того, что выглядит как вектор. Сторона отправки может быть обычным вектором, сторона recv может повторно интерпретировать_cast буфер recv в качестве реализации только для чтения

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

Номер 3 применяется для всех вариантов использования, в которых читатель редко изменяет содержимое буфера recv. Если читателю не нужны итераторы произвольного доступа, и он может работать с ForwardIterators, сериализация довольно проста: выделить один буфер, который может содержать все строки, плюс и целое число для каждой, обозначая длину плюс одно целое число для размера вектора.

Результат может быть reinterpret_cast'd для определенной пользователем структуры, которая является коллекцией только для чтения строк только для чтения. Так что без особых проблем вы можете по крайней мере получить O (1) на стороне чтения.

Чтобы получить O (1) на отправляющей стороне, вам нужно использовать метод 2. Я сделал это, зная, что мое приложение никогда не будет использовать больше, чем строки длины X, и что вектор никогда не будет содержать более Y предметов. Хитрость в том, что для исправления емкости мне никогда не придется идти в кучу памяти. Недостатком является то, что вы отправляете всю емкость каждой строки, а не только то, что было использовано. Однако во многих случаях просто отправлять все гораздо быстрее, чем пытаться сжать его, особенно если вы находитесь на одной машине - в этом случае вы можете просто поместить эту структуру в общую память и уведомить приложение recv о том, что оно просто ищет.

Возможно, вы захотите взглянуть на boost interprocess, чтобы получить больше идей о том, как создавать контейнеры, которые можно переносить через сокеты без сериализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...