[Delphi] Задержка в WinSock между send() и recv()

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by GlooK, 29 Sep 2009.

  1. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    Возникла проблемка, которую я решил, но решение думаю не совсем верное.

    Конструкция указанная в примере работала нормально,
    до тех пор пока на запрос не возвращалось коротких ответов.
    Но когда ответ приходит небольшой (например, 400 Bad Request, где одни хидеры), то возникает косяк.
    Заключает он в том, что ответ не успевает приняться и следующий ответ от сервера включает в себя два(!) ответа.
    В ответе идут хидеры 400 бед реквеста и 200 ок вместе.

    Это не позволяет нормально анализировать принятые данные.

    Код
    Code:
    function fWSWrite(hInput: string):string;
    var
    hOutput: array[0..5000] of char;
    begin
    Send(hSocket, hInput[1], length(hInput), 0);
    FillChar(hOutput, SizeOf(hOutput), 0);
    Recv(hSocket, hOutput, 5000, 0);
    result := hOutput;
    end;
    
    Решение нашел такое:
    Code:
    function fWSWrite(hInput: string):string;
    var
    hOutput: array[0..5000] of char;
    begin
    Send(hSocket, hInput[1], length(hInput), 0);
    sleep(1000);
    FillChar(hOutput, SizeOf(hOutput), 0);
    Recv(hSocket, hOutput, 5000, 0);
    result := hOutput;
    end;
    
    Но считаю, что это не совсем корректно.
    В MSDN ничего о задержки при отправке/приеме не увидел.

    Подскажите, пожалуйста, верное решение?

    UPD: Нашел вариант по лучше (через select):
    Code:
    function fWSWrite(hInput: string):string;
    var
    hOutput: string;
    hArray: array[0..5000] of char;
    rdfs: tfdset;
    tmout: timeval;
    ievnt: integer;
    cntread: integer;
    begin
    cntread := 1;
    Send(hSocket, hInput[1], length(hInput), 0);
    FillChar(hArray, SizeOf(hArray), 0);
    while (cntread > 0) do
    begin
    FD_ZERO(rdfs);
    FD_SET(hSocket, rdfs);
    tmout.tv_sec := 0;
    tmout.tv_usec := 500000;
    ievnt := select(0, @rdfs, nil, nil, @tmout);
    if (ievnt <= 0) then break;
    cntread := Recv(hSocket, hArray, 5000, 0);
    hOutput := hOutput + copy(hArray, 1, cntread);
    end;
    result := hOutput;
    end;
    
     
    #1 GlooK, 29 Sep 2009
    Last edited: 29 Sep 2009
  2. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    Так это дело даже не в сети. потому что в винде идет полюбому буферизация и если неуспел считать то уже будут склеиваться данные.
    Вообще всё зависит от реализации сервера.
    Типичные сервера первым пакетом шлют хидер а вторым данные. А нетепичные сервера - шлют сразу всё вместе или хидер разбивают.
    по этому когда шлеш данные то вписывай поле
    Connection: Close
    Как тока сделал send то в цикле гоняй recv пока будут идти данные.
    Как только он вернул <=0 то выходи из цикла.
    Также желательно замутить таймаут на recv чтобы небыло подвисаний на сверхглючных серваках (вернее самописном вебсервере где он не разрывает соединение потому что не воспринимает Connection: Close)
     
  3. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    Про Connection: Close не знал. Спасибо.
    Просто я делал эти запросы через браузер, и он этого не вписывал.

    Тогда код получается такой (без таймаута)?

    Code:
    function fWSWrite(hInput: string):string;
    var
    hOutput: string;
    hArray: array[0..5000] of char;
    cntread: integer;
    begin
    cntread := 1;
    Send(hSocket, hInput[1], length(hInput), 0);
    FillChar(hArray, SizeOf(hArray), 0);
    while (cntread > 0) do
    begin
    cntread := Recv(hSocket, hArray, 5000, 0);
    hOutput := hOutput + copy(hArray, 1, cntread);
    end;
    result := hOutput;
    end;
    
    И если реализовывать таймаут, то обязательно переводить сокеты в неблокирующий режим?

    Буду рад примерам из собственных реализаций:)
     
  4. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    наоборот строки поставь
    Code:
    while (true) do
    begin
    cntread := Recv(hSocket, hArray, 5000, 0);
    if (cntread > 0) then hOutput := hOutput + copy(hArray, 1, cntread) else break;
    end;
    end;
    
    насчет таймаутов я в своей статейке про юзанье winsock описывал как это делать. и там не нужно было переводить в неблокируемый режим. там юзалось setsockopt
     
  5. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    Не получается.
    Сейчас все запросы сразу посылаются. При снифе идет сразу четыре запроса GET.
    Ответ только на первом.

    Вот пример отправляемого запроса:
    Code:
    GET url HTTP/1.1
    Host: url
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: ru,en-us;q=0.7,en;q=0.3
    Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 300
    Connection: close
    Referer: url
    
    P.S. Во всех запросах изменил Connection: keep-alive на Connection: close.
     
  6. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    вообще покажи полностью исходник.
    Судя по тому что ты показывал у тебя дискриптор сокета - глобальная переменная.
    Вот он и затирается скорее всего.
     
  7. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    Сорцы отправил в личку.
    А сокет действительно глобальная переменная :)
     
  8. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    советую глянуть исходнки моего httpsender'a там всё нормально работало.
    И там уже есть готовая функция для отправки и получения данных
     
    1 person likes this.
  9. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    Спасибо за советы. Уже качаю :)
     
  10. GlooK

    GlooK Elder - Старейшина

    Joined:
    20 Apr 2007
    Messages:
    172
    Likes Received:
    53
    Reputations:
    10
    HTTP Sender архив битый. Перезалей плиз.

    Кстати, при приеме данных, максимальное число принятых данных cntread не превышает 2048. Т.е. текст обрезается.
    Code:
    while (true) do
    begin
    cntread := Recv(hSocket, hArray, 5000, 0);
    if (cntread > 0) then hOutput := hOutput + copy(hArray, 1, cntread) else break;
    end;
    end;
    
    Нашел правильное решение. Думаю уже конечное.

    Code:
    function fWSWrite(hInput: string):string;
    var
    hOutput: string;
    hArray: array[0..5000] of char;
    iRead: integer;
    begin
    hOutput := '';
    Send(hSocket, hInput[1], length(hInput), 0);
    FillChar(hArray, SizeOf(hArray), 0);
    while (TRUE) do
    begin
    iRead := Recv(hSocket, hArray, 5000, 0);
    if (iRead > 0) then hOutput := hOutput + copy(hArray, 1, iRead);
    [B]if (iRead < 2048) then break;[/B]
    end;
    result := hOutput;
    end;
    
    Только вот вопрос: почему 2048?

    P.S. Среда Turbo Delphi Lite (Portable)