LINUX.ORG.RU

Сокеты. Основы. С++

 ,


2

2

Доброго времени суток!

Столкнулся с проблемой получения данных через сокеты, если мы не знаем размера передаваемого файла. В случае html-страниц нечто подобное (http://cboard.cprogramming.com/cplusplus-programming/147455-how-receive-large...) работает нормально, но как поступить в случае, если файл формируется по запросу на сервер или в случае, если надо с определенной страницы скачать определенную картинку? Желательно подобное сделать без использования libcurl и прочих сторонних библиотек.

★★

Последнее исправление: aido (всего исправлений: 1)

А в чем суть проблемы? Классическое «сервер вместо write(), shutdown(WR), read() делает write(), close()»?

anonymous
()
Ответ на: комментарий от anonymous

Не понятно, как читать из сокета данные, длина которых неизвестна.

Например, вот такая функция зависает (взято с некоторыми изменениями из Р. Лав «Системное программирование»):

int TExchange::Receive(){
	ssize_t ret;
	unsigned int length=128;
	buffer = new char[length];
	while (length != 0 && (ret = recv (sock, buffer, length,0)) != 0) {
		 if (ret == -1) {
			 if (errno == EINTR)
			 continue;
			 perror ("recv");
			 break;
		 }
		 length -= ret;
		 buffer += ret;
	} 
    return 0;
}

К тому же, что именно запрашивать в send-e, если хочется получить, например, главную страницу ru.wordpress.com, ведь адрес запрашиваемой страницы получается нулевым. Gросто «GET HTTP/1.1»?

aido ★★
() автор топика
Ответ на: комментарий от aido

Сервер отдает «файл», как и любую другую веб-страницу, через http, сперва идет http-заголовок, среди полей которого есть content-lenth, оно указывает на длину передаваемых данных. После заголовка будут идти собственно данные. Читать про http, там все очень просто.

staseg ★★★★★
()
Ответ на: комментарий от aido

Ну или не заполняет буфер эта функция.

aido ★★
() автор топика
Ответ на: комментарий от staseg

В смысле делать сначала пробный запрос, потом парсить заголовок на предмет content-length, затем выделять память и качать на этот раз полностью весь файл?

aido ★★
() автор топика
Ответ на: комментарий от aido

Не, это был ответ на вопрос про

К тому же, что именно запрашивать в send-e, если хочется получить, например, главную страницу ru.wordpress.com, ведь адрес запрашиваемой страницы получается нулевым. Gросто «GET HTTP/1.1»?]

Открываешь tcp-сокет, отправляешь get-запрос к своему «файлу», читаешь ответ: сперва будет идти http-заголовок, из которого ты поймешь длину, дальше прочитаешь сам файл, из этого же сокета.

staseg ★★★★★
()
Ответ на: комментарий от aido

<KO mode=on>когда будешь знать размер данных</KO>

shty ★★★★★
()

Если тебе нужно получить ровно один файл в рамках соединения, размер знать не нужно. Читаешь пока не получишь EOF. Если нужно получить несколько файлов, то нужно тем или иным образом передать размер каждого. В случае HTTP - Content-length, в общем случае TLV.

slovazap ★★★★★
()

В HTTP кроме content-length, есть «Transfer-Encoding: chunked». Позволяет передавать данные с неизвестной длиной.
Данные передаются порциями, вначале каждой порции указана её длина, в конце просто 0.
Подробней читай здесь

surefire ★★★
()
Ответ на: комментарий от staseg

Не факт. Ты качал когда-нибудь с sourceforge? Ни один браузер не показывает размер файла (и не просто так). Так что не всё так просто.

peregrine ★★★★★
()

Открой libcurl и посмотри как в нём сделано, на крайняк wget или еще что по меньше, если у тебя проблемы с чтением больших исходных кодов

anonymous
()

Если протокол имени себя (велосипед) то сначала идет заголовок известной длины в котором есть в т.ч. размер файла, затем сам файл.

Читаете заголовк (длина заранее известна - ноу проблем), выдергиваете из него длину файла, выделяете память и из того же сокета читаете файл.

AIv ★★★★★
()

Смотря, что мы хотим с этими данными делать. Если записать в файл - вычитываем в фиксированный буфер и записываем в файл такими же порциями. Если загрузить в память для обработки - используем динамически расширяемый буфер

annulen ★★★★★
()
Ответ на: комментарий от annulen

Загрузить в память для обработки.

Я до сих пор не пойму, в чем не верен алгоритм

В смысле делать сначала пробный запрос, потом парсить заголовок на предмет content-length, затем выделять память и качать на этот раз полностью весь файл?

aido ★★
() автор топика
Ответ на: комментарий от aido

В смысле делать сначала пробный запрос, потом парсить заголовок на предмет content-length

Учти, что у тебя может быть вполне легитимная ситуация (в самом худшем случае), что данные приходят по 1 байту, и в первой порции данных запросто может не быть всех заголовков. TCP - это поточный протокол, со всеми вытекающими проблемами.

Хинт: ХТТП заголовок заканчивается двумя переносами строки, считывай данные, пока не получишь полный заголовок, но помни о возможном переполнении, если тебе подсунут громадный заголовок размером в мегабайты или гигабайты ;)

anonymous
()
Ответ на: комментарий от aido

Если пробный запрос подразумевает два запроса, то алгоритм так себе. А задача простейшая
1. делаем буфер любого разумного размера
2. посылаем запрос и читаем ответ в буфер в цикле
3. когда в буфере найдено content-length, парсим его, выделяем буфер под тело и продолжаем читать в цикле.
4. после того как в нашем буфере встретится «/r/n/r/n», остаток копируем в новый буфер
5. читаем остаток запроса в новый буфер
...
6. PROFIT

elijah_sd
()
Ответ на: комментарий от aido

Перед recv() делай ioctl(sock, FIONREAD, &length) - чтобы узнать размер данных доступных для чтения, если <= 0, делай sleep(), а если и после ожидания ничего не пришло, можно предположить, что данные получены (естественно, нужно проверять результаты ф-ций). Вообще наверно лучше сделать через неблокирующие сокеты, с помощью select().

neversleep ★★
()
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от aido

Зачем тебе выделять память под файл? Он ведь на жесткий диск сразу же должен писаться через небольшого размера буффер.

trex6 ★★★★★
()
Ответ на: комментарий от aido

В твоем куске хз что происходит, и я честно говоря ничего не понимаю в этом их корявом хттп, в частности зачем им нужен content-length, кроме как для свистопердящих прогресс-баров:

#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define print(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__)
#define check(x) do { if (!(x)) perror(#x); } while (0)

int
main(int argc, char *argv[])
{
    const char *hostname = argv[1] ?: "ya.ru";
    const char *servname = "http";

    struct addrinfo hints = { }, *ai;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_ADDRCONFIG;

    int error = getaddrinfo(hostname, servname, &hints, &ai);
    if (error) return print("%s", gai_strerror(error)), 1;

    char address[INET6_ADDRSTRLEN];

    if (ai->ai_family == AF_INET) {
        struct sockaddr_in *v4 = (void *)ai->ai_addr;
        check(inet_ntop(ai->ai_family, &v4->sin_addr, address, sizeof(address)));
        print("connecting to %s port %d", address, ntohs(v4->sin_port));
    } else if (ai->ai_family == AF_INET6) {
        struct sockaddr_in6 *v6 = (void *)ai->ai_addr;
        check(inet_ntop(ai->ai_family, &v6->sin6_addr, address, sizeof(address)));
        print("connecting to %s port %d", address, ntohs(v6->sin6_port));
    } else {
        print("connecting to ???");
    }

    int sd;
    char q[] = "GET /\r\n";

    check(-1 != (sd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)));
    check(-1 != connect(sd, ai->ai_addr, ai->ai_addrlen));
    freeaddrinfo(ai);
    check(sizeof(q)-1 == write(sd, q, sizeof(q)-1));

    char *bytes = NULL;
    size_t len = 0, cap = 0;

    for (;;) {
        while (len + BUFSIZ >= cap)
            cap += (cap / 2) ?: 1;
        bytes = realloc(bytes, cap);

        ssize_t n = read(sd, &bytes[len], cap - len);
        print("read: %zd", n);

        if (n <= 0) {
            print("%s", n < 0 ? strerror(errno) : "EOF");
            break;
        } else {
            len += n;
        }
    }
    close(sd);

    fwrite(bytes, 1, len, stdout);

    return 0;
}

./a.out <url> выдает лог чтений и в конце содержимое накопленного буфера. Если у тебя был вопрос про буферизацию и ты не смог его сформулировать, то ответ начинается со слов for (;;).

зы: лор и некоторые другие выдают 400 Bad Request или схожую хрень (формально это тоже «файл»). Правильный запрос подбирай сам.

anonymous
()
Ответ на: комментарий от trex6

Этот «файл» может быть жсоном или другими промежуточными данными например, которые вовсе незачем напрямую класть в файл.

anonymous
()
Ответ на: комментарий от aido

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

anonymous
()
Ответ на: комментарий от anonymous

Ты студента не путай. Пусть сначала все в файлы складывает.

trex6 ★★★★★
()
Ответ на: комментарий от aido

сначала пробный запрос, потом парсить заголовок на предмет content-length

Распарсили, выяснили, что он отсутствует. Что дальше?

annulen ★★★★★
()

Выделяешь два буфера. Один расширяешь если потребуется, кусочками. Читаешь во второй, фиксированной длинны. Когда встречаешь EOF или заголовки протокола, то там уже сам пляшешь в плане оптимизации. Опираться на Content-Length - глупо, такая доверчивость рождает глупые уязвимости. Мне кажется этот заголовок только ради следования стандартам. Про уязвимость с «Content-Length: 99999999999999999999999999» уже рассказывали тут.

n0044h
()
Ответ на: комментарий от n0044h

Опираться на Content-Length - глупо, такая доверчивость рождает глупые уязвимости. Мне кажется этот заголовок только ради следования стандартам.

Глупо - такие советы давать. Ничего, что кроме как по Content-Length в HTTP/1.1 вы на практике никак больше не сможете определить окончание передачи тела ответа?

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

Пусть уж сразу 2 версию использует или SPDY. Вроде как на плюсах и сишке куча сетевых либ, нафига тс себе голову ломает... Из того же Qt можно взять годный сетевой api.

menangen ★★★★★
()
Ответ на: комментарий от menangen

ТС хочет парсить HTTP самостоятельно, без сторонних библиотек. Уже с первой версией затык, а вы вторую предлагаете, в которой кроме бинарного протокола ещё и SSL надо парсить. :) Да и много ли сейчас серверов поддерживают вторую версию?

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

Это потому что более одного файла можно послать или почему? И как это называется, websockets?

anonymous
()
Ответ на: комментарий от Sorcerer

Глупо - такие советы давать. Ничего, что кроме как по Content-Length в HTTP/1.1 вы на практике никак больше не сможете определить окончание передачи тела ответа?

Глупо - писать такие глупости с умным видом. Есть Transfer-Encoding: chunked, кроме того, сервер может закрыть соединение для обозначения конца файла и не передавть Content-Length.

И, конечно же, значение Content-Length может отличаться от реального в любую сторону при наличии бага в серверном процессе.

annulen ★★★★★
()
Ответ на: комментарий от n0044h

Мне кажется этот заголовок только ради следования стандартам.

Отнюдь, он необходим для передачи файлов без закрытия соединения, если не используется chunked encoding.

annulen ★★★★★
()
Ответ на: комментарий от staseg

он не всегда присутствует, если что.

FIL ★★★★
()
Ответ на: комментарий от annulen

Глупо - писать такие глупости с умным видом.

Все сказанные вами очевидности доказывают утверждение, что «Опираться на Content-Length - глупо»? Ну давайте тогда и на Transfer-Encoding: chunked не будем опираться, - а вдруг сервер нам десятичные длины кусков будет отправлять вместо шестнадцатиричных, а мы как лохи поведемся? Да вообще - требуем от HTTP-сервера отправлять в ответ на наш запрос чистый файл, без всяких заголовков, а затем закрывать соединение, потому что опираться на все эти заголовки - глупо.

Sorcerer ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.