-
Доброго времени суток, уважаемые! Начал поднимать незавершенный проект, очень похожий на прокси. Все, что нужно вроде бы уже готово, но вот остановился я на ограничении пропускной способности, т.к. с этим сервером будет работать промышленный компьютер, для которого пакеты будут немного перерабатываться и отсылаться с более медленной скоростью. Ну так вот в чем проблема. Для каждого соединения с "прокси" создается свой поток, в этом потоке учавствуют две основные переменные: одна переменная связывает клиента с собой (сервером), вторая образует связь сервер<->запрашиваемый сервер. Проблемы касаются именно второй переменной и выявляются, например, при скачивании большого файла. Ее тип (второй переменной) - TClientSocket. Алгоритм примерно такой: клиент запрашивает нужный файл у прокси, прокси соединяется с удаленным сервером и затем в цикле передает данные (файл) клиенту до тех пор, пока Socket.ReceiveBuf удаленного соединения возвращает больше нуля, т.е. пока есть принятые данные. Внутри цикла еще работает ограничение пропускной способности по такому типу: смотрим сколько скачали за секунду, если достигнут разрешенный предел, уходим в Sleep. Но в какой-то момент почему-то ReceiveBuf становится равен нулю, т.е. будто удаленный сервер все что хотел сказать уже сказал, но файл не был передан до конца. Т.е. удаленный сервер не хочет больше отдавать файл, хотя соединение с ним остается установленным, поток также не сообщает о том, что уже убит и клиент упорно ждет, когда же ему отдадут недостающийся кусок файла. Затем устает ждать и говорит "пока". Т.е. вся проблема именно в том, что ReceiveBuf не хочет ничего толкать в буфер и утверждает, что ничего ему не приходило (Receive Length = 0). Из-за чего такое может быть? В коде проблем не наблюдается, все работает как нужно, но до каких-то пор. Вообще я был уверен, что проблема появилась именно тогда, когда начал удерживать пропускную способность с помощью слипов. Но сейчас, пока писАл это сообщение, что-то уверенность в этом пропала (забыл уже, т.к. проект давно не щупал). В общем, завтра еще раз проверю, убрав ограничение проп. спос. Но вопрос остается открытым :)
-
> (Receive Length = 0). > Из-за чего такое может быть?
If the socket is connection oriented and the remote side has shut down the connection gracefully, a recv will complete immediately with zero bytes received.
> удерживать пропускную способность с помощью слипов
Дурная затея.
-
> If the socket is connection oriented and the remote side > has shut down the connection gracefully, a recv will complete > immediately with zero bytes received.
Но я так понял, что "remote side has shut down" не происходит, т.к. соединение между удаленным сервером остается установленным. Или это не показатель?
> Дурная затея.
Сам так думал потом, но порывшись в интернете ничего другого в голову не пришло.
-
> соединение между удаленным сервером остается установленным
На основании чего сделано такое умозаключение ?
> ничего другого в голову не пришло
Обычный таймер.
-
> На основании чего сделано такое умозаключение ?
Socket.Connected возвращает True
> Обычный таймер.
Таймер есть, он раз в секунду обнуляет счетчик. А слипы срабатывают только при достижении счетчиком предела. Или я что-то не так понял?
-
> Socket.Connected возвращает True
Режим какой - блокирующий ?
> Или я что-то не так понял? >
С заданным интервалом, скажем 1 секунду, тикает таймер
При каждом тике от удаленного сервера запрашивается ровно столько, сколько требуется для обеспечения заданной скорости, скажем 10 кб. Все что получено тут же отдается клиенту. При этом как раз и обеспечивается требуемой скорости отдачи. Никакая буферизация при этом не требуется.
-
> Режим какой - блокирующий ?
stThreadBlocking
> При каждом тике от удаленного сервера запрашивается ровно > столько, сколько требуется для обеспечения заданной скорости, > скажем 10 кб. Все что получено тут же отдается клиенту. > При этом как раз и обеспечивается требуемой скорости отдачи. > Никакая буферизация при этом не требуется.
Примерный алгоритм понял, буду думать, как создавать таймеры для каждого подключения (кстати, таймер же можно, наверное, создать прямо внутри потока нужного подключения?).
-
> kernel (05.12.2010 20:45:04) [4]
А команда Disconnect поступала?
-
> stThreadBlocking
никак не вяжется с
> Socket.ReceiveBuf удаленного соединения
потому что с удаленным сервером твой прокси связывается через TClientSocket, у которого НЕТ такого свойства. У него есть ctNonBlocking и ctBlocking
-
> А команда Disconnect поступала?
От меня (т.е. от прокси) не поступала. Если только удаленный сервер сам решил разорвать.
> никак не вяжется с
Ага, виноват. Не у того сокета подглядел режим. ClientType := ctBlocking;
-
> kernel © (05.12.10 21:16) [9]
Ну тогда все верно: с твоей стороны не было Socket.Disconnect - значит Connected будет True до второго пришествия.
-
> Ну тогда все верно: с твоей стороны не было Socket.Disconnect > - значит Connected будет True до второго пришествия.
Т.е. пока я сам его не отрублю?
Тогда вытекает другой вопрос. Почему удаленный сервер отцепляется? Причем это не зависит от самого удаленного сервера (пробовал скачивать файлы с разных серверов). Я так понимаю, он должен это делать в какой-то момент, а я его должен попросить отдать файл дальше.
-
> Т.е. пока я сам его не отрублю?
Он уже "отрубился" по инициативе партнера - об этом тебе сказал ReceiveBuf = 0 Остается только освободить ресурсы, занятые сокетом на ТВОЕЙ стороне УЖЕ несуществующего соединения - вызовом Socket.Disconnect либо ClientSocketComponent.Close
> Почему удаленный сервер отцепляется?
А это ты у него спроси) Может ему элементарно не нравится то что ты ему перед этим послал..
-
> А это ты у него спроси)
Он не говорит.
> Может ему элементарно не нравится то что ты ему перед этим > послал..
Перед этим я ему только подготовленный "headers" запрашиваемого документа (файла) посылал. Затем получаю от него данные.
-
В любом случае спасибо всем ответившим. Завтра буду пробовать ковырять дальше (у меня сейчас ночь), отрублю временно ограничение ПС. Попытаю в таком состоянии его.
-
> Он не говорит
И не обязан, если прикладным протоколом это не предусмотрено.
> подготовленный "headers" запрашиваемого документа (файла) > посылал
Значит недопослал или послал что-то не то.
Опять же надо смотреть на конкретный прикладной протокол и то как ты его соблюдаешь.
-
> Значит недопослал или послал что-то не то.
Сейчас посмотрел еще раз, заголовок посылает в моем случае Opera, а я лишь читаю его и вытаскиваю для себя оттуда хост, порт и URI.
-
Протокол - HTTP, надо понимать ?
-
HTTP(S)
-
Т.е. браузер шлет твоему прокси CONNECT-запрос, ты его парсишь, коннектишься к удал.серверу и ретранслируешь потом через себя инф.обмен между браузером и удал.сервером, выполняя при этом шейпинг входящего (для браузера) трафика, так ?
-
В случае, если запрос GET или POST, то просто читаю из заголовка хост, порт и документ и отсылаю этот заголовок удаленному серверу - это для HTTP. В случае с HTTPS (ssl) ко мне приходит запрос CONNECT и я в таком случае цепляюсь к нужному хосту и порту (443 обычно) и просто делаю непосредственную связь между двумя сокетами.
-
У меня ограничение трафика на слипах работает...
type
TShaper=record
Speed:DWORD;
TotalCount,Count,Time:DWORD;
end;
procedure TPortMapClientThread.DoTunneling(Peer1,Peer2:TCustomWinSocket);
procedure ShapeIt(var Shaper:TShaper;Count: Integer);
var OverTime:integer;
begin
if Shaper.TotalCount=0 then
begin
Shaper.TotalCount:=Count;
Shaper.Count:=Count;
Shaper.Time:=GetTickCount;
exit;
end;
Inc(Shaper.TotalCount,Count);
Inc(Shaper.Count,Count);
if Shaper.Count>Shaper.Speed then
begin
OverTime:=Round(Shaper.Count/Shaper.Speed*1000-(GetTickCount-Shaper.Time));
if OverTime>0 then
begin
Sleep(OverTime);
Shaper.Count:=0;
Shaper.Time:=GetTickCount;
end;
end;
end;
function SendBufFully(Peer:TCustomWinSocket;var Buf; Count: Integer):boolean;
var pBuf:PByte;
s:integer;
begin
result:=false;
pBuf:=@Buf;
while count>0 do
begin
s:=Peer.SendBuf(pBuf^,Count);
if s=0 then exit;
inc(pBuf,s);
dec(Count,s);
end;
result:=true;
end;
var
FDSet: TFDSet;
TimeVal: TTimeVal;
Buf:array[byte] of char;
InShaper,OutShaper:TShaper;
Buf:array[0..4095] of char;
r:integer;
begin
InShaper.TotalCount:=0;
InShaper.Speed:=2048;
OutShaper.TotalCount:=0;
OutShaper.Speed:=2048;
TimeVal.tv_sec := FPortMap.ClientTimeout div 1000;
TimeVal.tv_usec := (FPortMap.ClientTimeout mod 1000) * 1000;
while Peer1.Connected and Peer2.Connected do
begin
FD_ZERO(FDSet);
FD_SET(Peer1.SocketHandle, FDSet);
FD_SET(Peer2.SocketHandle, FDSet);
if select(0, @FDSet, nil, nil, @TimeVal)>0 then
begin
if FD_ISSET(Peer1.SocketHandle, FDSet) then
begin
r:=Peer1.ReceiveBuf(Buf,Length(Buf));
if r=0 then exit;
if not SendBufFully(Peer2,Buf,r) then exit;
ShapeIt(InShaper,r);
end;
if FD_ISSET(Peer2.SocketHandle, FDSet) then
begin
r:=Peer2.ReceiveBuf(Buf,Length(Buf));
if r=0 then exit;
if not SendBufFully(Peer1,Buf,r) then exit;
ShapeIt(OutShaper,r);
end;
end else
exit;
end;
end;
procedure TPortMapClientThread.ClientExecute;
begin
try
RemoteSocket.Open('',FPortMap.RemoteHost,'',FPortMap.RemotePort);
DoTunneling(ClientSocket,RemoteSocket);
finally
RemoteSocket.Close;
ClientSocket.Close;
end;
end;
-
Всем снова привет! Уже больше года этой проблеме и до сих пор не могу решить ее. Проект был отложен и вчера снова взялся за него.
Запустил Wireshark, смотрю пакеты и в момент обрыва наблюдаю, что удаленный сервер (откуда я качаю файл) в одном из последних пакетов TCP выставляет флаги Fin, Push и ACK. Т.е., как я понимаю, он разрывает связь и, видимо, просит меня что-то "протолкнуть" -) (Push). Только вот понять я не могу, почему он это делает...
-
Slym, спасибо за код. Переделал свой код на тот же самый, что и Ваш, единственное отличие - один сокет TClientWinSocket, второй - TServerClientWinSocket. Но проблема полностью осталась :(
-
Может быть есть какой-то баг в D2006? И еще, каким-либо боком это не может быть связано с методом Nagle (либо его отсутствием)?
-
kernel © (27.03.11 14:43) [23] Переделал свой код на тот же самый, что и Ваш, единственное отличие - один сокет TClientWinSocket, второй - TServerClientWinSocketэто зачем? все было вполне универсально т.к.: TServerClientWinSocket = class(TCustomWinSocket)
TClientWinSocket = class(TCustomWinSocket) учи матчасть!
-
kernel © (27.03.11 14:43) [23] Переделал свой код на тот же самый Автоваз в свое время тоже фиат переделывал... показывай, может у тебя ручник к педале газа приделан :) передельщик
-
Ну так это потому что у меня весь сервер на TServerSocket построен и где-то внутри из него вытекает TServerClientWinSocket (т.е. предопределенно вытекает из TServerSocket). А внутри потока уже TClientSocket используется (ThreadConnection). А вот собственно тот проблемный кусок кода. Общение происходит в одну сторону, т.к. в этом месте уже идет принятие чего-либо после GET\POST запроса. Хотя и двухстороннюю передачу тоже делал, ничего не прибавилось и не убавилось :) InShaper.TotalCount:=0;
InShaper.Speed:=2048;
OutShaper.TotalCount:=0;
OutShaper.Speed:=2048;
TimeVal.tv_sec := SocketsTimeout div 1000;
TimeVal.tv_usec := (SocketsTimeout mod 1000) * 1000;
while ClientSocket.Connected and ThreadConnection.Socket.Connected do begin
FD_ZERO(FDSet);
FD_SET(ClientSocket.SocketHandle, FDSet);
FD_SET(ThreadConnection.Socket.SocketHandle, FDSet);
if select(0, @FDSet, nil, nil, @TimeVal) > 0 then begin
if FD_ISSET(ThreadConnection.Socket.SocketHandle, FDSet) then begin
RecLen := ThreadConnection.Socket.ReceiveBuf(SockBuffer, Length(SockBuffer));
if RecLen = 0 then Break;
if not WriteBufferToSocket(ClientSocket, SockBuffer, RecLen) then Break;
ShapeIt(InShaper, RecLen);
end;
end else Break;
end;
ThreadConnection.Close;
ThreadConnection.Free;
-
kernel © (27.03.11 21:54) [27] Ну так это потому что у меня весь сервер на TServerSocket построен и где-то внутри из него вытекает TServerClientWinSocket (т.е. предопределенно вытекает из TServerSocket). А внутри потока уже TClientSocket используется (ThreadConnection). аналогично... TServerSocket и TClientSocket - это "компонентная" обертка над ними внутри юзается WinSocket
kernel © (27.03.11 21:54) [27] есть подозрение что сервак тебе шлет Bad request или иную ошибку возвращает и рвет соединение... проверяй снифером что отсылаешь серверу и что возвращается в первых пакетах от него
-
Неа, никакого Bad request не наблюдается. Я передал заголовок на удаленный сервер и далее все, что к прокси приходит отдаю сразу клиенту. Файл принимается до каких-то пор, затем связь обрывается "на пустом месте" - самому прокси ничего не приходит, однако на более низком уровне наблюдаются всякие TCP ZeroWindow, TCP Windows Update.. FIN PUSH и т.п. ...
В общем, не знаю уже с ним что делать. Думаю полностью сменить подход к написанию этого прокси (хотя вроде все максимум аккуратно делал) и "с нуля" все начинать делать и уже, наверное, не на Delphi :( А то уже больше года прошло и с места проект не сдвинулся...
-
скомпилься на 7 дельфе, если глюк повторяется тогда я низнаю в чем проблема
-
- просто удаленный хост ненавязчиво вам намекает, что у вас слишком медленный канал, вы слишком долго занимаете его пул подключений(далеко не резиновый), и ваш приоритет сравнялся с плинтусом - переходите на режим докачки по частям, либо целиком кэшируйте файл(ы)...
И версия Delphi никакого отношения к этому не имеет.
-
Спасибо большое всем еще раз! Тоже склоняюсь к тому, что написАл han_malign. Только вот вопрос, как теперь ограничивать пропускную способность. Не редко бывает, что сервер не разрешает докачку файла и окно, как я понимаю, будет расти до каких-то пор, пока не разорвется соединение.
> han_malign, > либо целиком кэшируйте файл(ы)
Что именно имеется ввиду?
-
> > > han_malign, > > либо целиком кэшируйте файл(ы) > > Что именно имеется ввиду? > >
быстро скачать и медленно отдавать
-
Ок, все понял. Спасибо. А в случае, если потребуется ограничивать пропускную способность ради того, чтобы канал интернета не занимать, как в этой ситуации поступить можно?
-
...точнее, чтобы не загружать канал интернета
|