С++ Чтение из сокета в std::string

Я пишу программу на С++, которая использует сокеты c. Мне нужна функция для получения данных, которые я хотел бы вернуть в виде строки. Я знаю, что это не сработает:

std::string Communication::recv(int bytes) {
    std::string output;
    if (read(this->sock, output, bytes)<0) {
        std::cerr << "Failed to read data from socket.\n";
    }
    return output;
}

Потому что функция read()* принимает в качестве аргумента указатель на массив символов. Каков наилучший способ вернуть строку здесь? Я знаю, что теоретически мог бы прочитать данные в массив символов, а затем преобразовать их в строку, но мне это кажется расточительным. Есть ли способ лучше?

* На самом деле я не возражаю против использования чего-то другого, кроме read(), если есть более подходящая альтернатива

Вот весь код на pastebin, срок действия которого истекает через неделю. Если к тому времени у меня не будет ответа, я опубликую его повторно: http://pastebin.com/HkTDzmSt

[ОБНОВЛЕНИЕ]

Я также пытался использовать &output[0], но вывод содержал следующее:

jello!
[insert a billion bell characters here]

"желе!" были данные, отправленные обратно в сокет.


person 735Tesla    schedule 22.01.2014    source источник
comment
c_str() является константой, вы не можете этого сделать. просто прочитайте в буфер, а затем скопируйте в строку.   -  person billz    schedule 22.01.2014
comment
@billz Правильно, я отказываюсь от своего предложения.   -  person Borgleader    schedule 22.01.2014
comment
Это вообще правильная форма? Если нет, то я должен сделать что-то по-другому?   -  person 735Tesla    schedule 22.01.2014
comment
Хотя @billz &output[0] может сработать. Немного схематично, но это позволяет избежать копии памяти.   -  person Eric Fortin    schedule 22.01.2014
comment
@EricFortin, почему &output[0], а не только &output?   -  person 735Tesla    schedule 22.01.2014
comment
output[0] возвращает ссылку на первый символ. Вам нужен указатель для записи сокета, поэтому вы берете адрес этой ссылки. Вы можете сделать это, поскольку символы в строке непрерывны в памяти. &output будет указывать на строковый объект, а не на буфер, в который вы можете писать.   -  person Eric Fortin    schedule 22.01.2014
comment
@EricFortin Я попробовал ваше предложение, и оно сработало (почти). На выходе последовала целая куча непечатаемых символов.   -  person 735Tesla    schedule 22.01.2014
comment
@ 735Tesla Вам все еще нужно проверить, сколько байтов возвращено функцией read.   -  person billz    schedule 22.01.2014
comment
@ 735Tesla Вы ожидаете от своего сокета только символы ascii? std::string может содержать \0 в строке, а также любые байты, которые вы можете вставить. Они могут быть недоступны для печати, они могут быть UTF8 и т. д..   -  person Eric Fortin    schedule 22.01.2014
comment
@billz не ограничивает ли третий аргумент функции количество прочитанных байтов до указанного целого числа?   -  person 735Tesla    schedule 22.01.2014
comment
@EricFortin Я использую netcat для проверки сокета. Все, что я отправил в программу, должно было быть jello!\n   -  person 735Tesla    schedule 22.01.2014
comment
Третий параметр — это max, который можно прочитать. Вы можете читать меньше (т.е. короткое чтение). Этот ИМО является менее интересным аспектом. Что происходит, когда вы случайно вставляете в необработанную строку больше, чем выделено или о чем строковый объект знает в своей переменной длины?   -  person Duck    schedule 22.01.2014
comment
@ 735Tesla Либо вы отправляете \0 с другого конца, либо добавляете его самостоятельно из своего клиентского кода, если хотите распечатать.   -  person Eric Fortin    schedule 22.01.2014
comment
@Duck Поместит ли это кучу нулевых байтов в конец строки?   -  person 735Tesla    schedule 22.01.2014
comment
@EricFortin Я напишу программу на Python, чтобы протестировать ее, потому что это будет очень просто   -  person 735Tesla    schedule 22.01.2014
comment
@ 735Tesla, нет, не сам по себе. Но если вы пойдете по этому пути, вы можете поставить его туда самостоятельно.   -  person Duck    schedule 22.01.2014
comment
@EricFortin Я написал программу на Python, чтобы протестировать свою, и у меня все еще та же проблема: puu.sh/6tAnc.png puu.sh/6tApn.png   -  person 735Tesla    schedule 22.01.2014
comment
@EricFortin первое изображение - это сервер python, а второе - результат, который я получаю, когда запускаю свою программу. Часть о закрытии сокета - это то, что он печатает, когда закрывает сокет, а не данные, которые он получил.   -  person 735Tesla    schedule 22.01.2014


Ответы (1)


Вот некоторые функции, которые должны помочь вам достичь того, чего вы хотите. Предполагается, что вы получите только символ ascii с другого конца сокета.

std::string Communication::recv(int bytes) {
    std::string output(bytes, 0);
    if (read(this->sock, &output[0], bytes-1)<0) {
        std::cerr << "Failed to read data from socket.\n";
    }
    return output;
}

or

std::string Communication::recv(int bytes) {
    std::string output;
    output.resize(bytes);

    int bytes_received = read(this->sock, &output[0], bytes-1);
    if (bytes_received<0) {
        std::cerr << "Failed to read data from socket.\n";
        return "";
    }

    output[bytes_received] = 0;
    return output;
}

При печати строки обязательно используйте cout << output.c_str(), поскольку строка перезаписывает operator<<, и пропускайте непечатаемые символы, пока они не достигнут размера. В конечном счете, вы также можете изменить размер в конце функции до полученного размера и иметь возможность использовать обычный cout.

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

person Eric Fortin    schedule 22.01.2014
comment
Я все еще получаю тот же результат, что и в моем последнем комментарии. Интересно то, что символы колокольчика примерно равны длине желе! вычитается из 1024 (параметр, который я сейчас использую как bytes) - person 735Tesla; 22.01.2014
comment
@ 735Tesla Сначала вы должны отправить длину своей строки. Затем при получении вы сначала читаете эту длину, чтобы знать, сколько читать дальше. Используя фиксированную длину для получения, вы ничего не ждете и/или читаете мусор в свою строку. - person François Moisan; 22.01.2014
comment
@FrançoisMoisan Спасибо. Я не думал об этом. Есть ли способ динамически получать ввод? - person 735Tesla; 22.01.2014
comment
Попробуйте напечатать выходную строку следующим образом: cout ‹‹ output.c_str() ‹‹ endl; Оператор перезаписи строки‹‹ и пропуск байтов, равных 0, и продолжается до тех пор, пока не будет достигнут размер. - person Eric Fortin; 22.01.2014
comment
@EricFortin Это сработало! Спасибо! Не могли бы вы добавить это к своему ответу? - person 735Tesla; 22.01.2014
comment
Зачем читать только bytes-1 в буфер размером bytes? Зачем устанавливать output[bytes_received]=0 даже если bytes_received < 0? Вопросы, вопросы... - person mvds; 17.11.2014
comment
Спасибо за указание на отсутствующий возврат в случае ошибки. Что касается того, почему читать на 1 байт меньше, чем bytes, это должно было обеспечить строку байтов с нулевым завершением (в начале я сказал, что принимаю только символы ascii). Мы также можем изменить размер до bytes+1 и прочитать bytes байт. - person Eric Fortin; 17.11.2014