PDA

Просмотр полной версии : [Delphi] Задержка в WinSock между send() и recv()


GlooK
29.09.2009, 04:22
Возникла проблемка, которую я решил, но решение думаю не совсем верное.

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

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

Код

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;


Решение нашел такое:

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):

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;

slesh
29.09.2009, 09:08
Так это дело даже не в сети. потому что в винде идет полюбому буферизация и если неуспел считать то уже будут склеиваться данные.
Вообще всё зависит от реализации сервера.
Типичные сервера первым пакетом шлют хидер а вторым данные. А нетепичные сервера - шлют сразу всё вместе или хидер разбивают.
по этому когда шлеш данные то вписывай поле
Connection: Close
Как тока сделал send то в цикле гоняй recv пока будут идти данные.
Как только он вернул <=0 то выходи из цикла.
Также желательно замутить таймаут на recv чтобы небыло подвисаний на сверхглючных серваках (вернее самописном вебсервере где он не разрывает соединение потому что не воспринимает Connection: Close)

GlooK
29.09.2009, 14:52
Про Connection: Close не знал. Спасибо.
Просто я делал эти запросы через браузер, и он этого не вписывал.

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


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;


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

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

slesh
29.09.2009, 15:03
наоборот строки поставь

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

GlooK
29.09.2009, 15:18
Не получается.
Сейчас все запросы сразу посылаются. При снифе идет сразу четыре запроса GET.
Ответ только на первом.

Вот пример отправляемого запроса:

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.

slesh
29.09.2009, 15:27
вообще покажи полностью исходник.
Судя по тому что ты показывал у тебя дискриптор сокета - глобальная переменная.
Вот он и затирается скорее всего.

GlooK
29.09.2009, 15:39
вообще покажи полностью исходник.
Судя по тому что ты показывал у тебя дискриптор сокета - глобальная переменная.
Вот он и затирается скорее всего.
Сорцы отправил в личку.
А сокет действительно глобальная переменная :)

slesh
29.09.2009, 15:48
советую глянуть исходнки моего httpsender'a там всё нормально работало.
И там уже есть готовая функция для отправки и получения данных

GlooK
29.09.2009, 15:53
советую глянуть исходнки моего httpsender'a там всё нормально работало.
И там уже есть готовая функция для отправки и получения данных

Спасибо за советы. Уже качаю :)

GlooK
29.09.2009, 18:26
HTTP Sender архив битый. Перезалей плиз.

Кстати, при приеме данных, максимальное число принятых данных cntread не превышает 2048. Т.е. текст обрезается.

while (true) do
begin
cntread := Recv(hSocket, hArray, 5000, 0);
if (cntread > 0) then hOutput := hOutput + copy(hArray, 1, cntread) else break;
end;
end;


Нашел правильное решение. Думаю уже конечное.


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);
if (iRead < 2048) then break;
end;
result := hOutput;
end;


Только вот вопрос: почему 2048?

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