Форум: "Сети";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2014.12.28;
Скачать: [xml.tar.bz2];




Вниз

Сервер. Сокеты, потоки. 


kernel ©   (2010-05-11 18:07) [0]

Доброго времени суток, уважаемые!
Вопрос у меня немного поверхностный, но важный (по крайней мере, для меня :)), больше похоже на "посоветуйте" -).

Есть задача, написать аналог HTTP(S) прокси-сервера, но с небольшой спецификой: запросы некоторых подключенных клиентов будут обрабатываться по-другому и обмен с такими клиентами будет происходить маленькими пакетами. Остальным клиентам ("не особо одаренным") должно быть также хорошо, как и с обычным прокси :). Основные требования, предъявляемые к такому серверу - быть устойчивым к капризным клиентам (это те, кто маленькими пакетами будет пуляться) и всем остальным клиентам, и держать максимально возможное (насколько позволят ОС+hardware и др.) количество одновременных соединений (например, 100 :)). В то же время, сервер не должен получиться громоздким и слишком сложным.

Из арсенала имеются только стандартные средства Delphi (Indy и другие библиотеки отбрасываем).

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

Первое, что приходит в голову - TTCP[Server\Client], T[Server\Client]Socket или голое API of WinSock.

С T[Server\Client]Socket работал давно (последний раз лет 6-7 назад) - в то время очень не понравилось. Некоторые пакеты пропускались (хотя TCP считают "гарантированным"). Когда тут на форуме спросил в чем дело, мне тогда намекнули, если мне не изменяет память, на метод Nagle. Но проблему так и не решил (в те времена).

Вчера "тест-драйв" попытался устроить TCPServer`у. НаписАл на базе него простенький прокси для обработки GET запросов, который, в общем-то, даже работал, но мееееедленно. Реагировал на событие OnAccept, режим блокировки - bmThreadBlocking, читал и писАл из\в сокеты используя ReceiveBuf и SendBuf.

В общем, уважаемые, каким образом лучше организовать работу сервера (прием соединений на сервере и создание временных подключений внутри потока для получения данных с другого сервера и отдачи их обратно клиенту) и с помощью чего:
* ПисАть на голом WinSock API, потоки создавать самому, работать в блокирующем режиме
* ПисАть на голом WinSock API, работать в неблокирующем режиме, тогда, как я понимаю, потоки создавать не нужно будет
* Аналогично описанному выше, только с использванием в качестве опорной точки один из компонентов\классов (например, (T)TCP[Server\Client], (T)[Server\Client]Socket)
* Применять "фишку" Delphi - режим ThreadBlocking
* что-либо другое

Заранее благодарю всех дочитавших до конца и ответивших!



DVM ©   (2010-05-11 18:43) [1]

чем Indy то не угодил?



kernel ©   (2010-05-11 18:50) [2]


> DVM ©   (11.05.10 18:43) [1]
> чем Indy то не угодил?

Лицензией, тяжестью исправления (в том смысле, что исправлять много приходится, чтобы что-либо добавить\изменить на более низком уровне), потом, помню, были проблемы с убиванием потоков (http://delphimaster.net/view/6-1236577141/).



DVM ©   (2010-05-11 19:34) [3]


> kernel ©   (11.05.10 18:50) [2]

Если хочешь максимум контроля над сервером - делай все сам на WinSock. Я бы наверное выбрал блокирующий режим и что-то типа пула потоков.



Slym ©   (2010-05-11 20:37) [4]

kernel ©   (11.05.10 18:07)
НаписАл на базе него простенький прокси для обработки GET запросов

Твоя задача сводится
1. принять заголовок у клиента, выдрать от туда host:port заказаного сервера
2. сконектиться по host:port и полностью возможно с минимальными изменениями отправить принятые в п1. от клиента данные
3. обеспечить прозрачное тунеллирование трафика клиент/сервер
4. (ОПЦИЯ) затормозить особо ретивые конекты наример тупым sleep (при буфере сокета в 8кб + sleep(1000) получим 8кб/сек максимум)



Сергей М. ©   (2010-05-11 22:02) [5]


> пакеты пропускались (хотя TCP считают "гарантированным").
>  Когда тут на форуме спросил в чем дело, мне тогда намекнули,
>  если мне не изменяет память, на метод Nagle


Похоже ты уже и сам не помнишь о чем был твой вопрос.
Используется или не используется Nagle - на "пропуск пакетов" он повлиять никак не может в принципе.



kernel ©   (2010-05-13 14:08) [6]

Благодарю всех за ответы!


> DVM ©   (11.05.10 19:34) [3]

Все что нужно из контроля - общение с клиентом с заданными размерами пакетов (маленькими пакетами для вредных клиентов и большими - для остальных) с возможностью их "переработки" и подсчет отправленных\полученных данных. Все это, если я не ошибаюсь, успешно выполняют и обычные сокетообразные компоненты. Или все равно лучше применить чистый WinSock? :) Тут, наверное, больше интересует у кого из них (сок. компоненты vs WinSock API)  производительность будет побольше. Или в этом плане разницы никакой нет?

> Slym ©   (11.05.10 20:37) [4]

Ну как работает прокси мне известно. Делал именно так, как Вы описали.

> Сергей М. ©   (11.05.10 22:02) [5]

Действительно подзабыл. Нашел те старые исходники на этих компонентах, там скорее всего была проблема в том, что в короткий промежуток времени сервер сам цеплялся к клиентам и отправлял всем клиентам команды. Для того чтобы всем все успешно отправилось, необходимо было ставить задержку около 1 сек. (по крайней мере у меня работало только не < ~1 сек.) после отсылки команды каждому ПК. Вот тут мне про Nagle скорее всего и намекнули.



DVM ©   (2010-05-13 16:25) [7]


> kernel ©   (13.05.10 14:08) [6]


>  Тут, наверное, больше интересует у кого из них (сок. компоненты
> vs WinSock API)  производительность будет побольше. Или
> в этом плане разницы никакой нет?

Компоненты они же на базе WinSock построены. Разницы между ними и чистым Winsock практически нет. По крайней мере это не то место, где следует искать разницу.



DVM ©   (2010-05-13 16:26) [8]


> kernel ©   (13.05.10 14:08) [6]

Другое дело, что вокруг WinSock можно написать свои простенькие обертки, больше подходящие к конкретной задаче - в этом их удобство.



kernel ©   (2010-05-14 21:46) [9]

Спасибо за ответы, DVM!
Сделал свой класс на основе ServerSocket, все работает быстро и замечательно [тьфу-тьфу-тьфу], контроля полностью хватает.
PS: собст-но это сообщение и отправил через пробный прокси :)



kernel ©   (2010-05-18 17:36) [10]

Всем привет! Чтобы не создавать отдельный топик, спрошу здесь (т.к. разговор связан с этой темой).

Организовал нужный мне функционал на базе TServerSocket (режим - stThreadBlocking) с созданием отдельных потоков для каждого установленного соединения. Дошел до момента, где мне необходимо делать туннелирование между [клиент] <-> ["прокси-сервер"] <-> [запрашиваемый сервер] для работы SSL. Механизм такой: клиент посылает мне запрос CONNECT (HTTP/1.0), затем после того, как я (прокси) отправляю заголовок "200 OK - соединение установлено", в цикле, пока любое из двух соединений не разорвется (клиент или TargetHost), ловлю данные в буфер от клиента и тут же передаю запрашиваемому серверу, пока от клиента не начнет приходить ноль байт. Затем то же самое делаю, но наоборот, ловлю данные в буфер от запрашиваемого сервера и тут же передаю клиенту. И это все повторяется до тех пор, пока кто-либо из них не разорвет соединение.

...
const BufSize = 128;
...
ClientStream : TWinSocketStream;
TargetConnection: TTcpClient;
Buf: array [0..BufSize-1] of Byte;
...
ClientStream := TWinSocketStream.Create(ClientSocket, 60000);
...
while (КлиентИПунктНазначенияПодключены) do begin
ClientWait := (ClientStream.WaitForData(100));
if ClientWait then begin
 RecLen := ClientStream.Read(Buf, BufSize);
 while (ClientSocket.Connected) and (TargetConnection.Connected) and (RecLen > 0) do begin
  TargetConnection.SendBuf(Buf, RecLen);
  if (not ClientSocket.Connected) or (not TargetConnection.Connected) then Break;
  if (not ClientStream.WaitForData(10)) then Break;
  RecLen := ClientStream.Read(Buf, BufSize);
 end;
end;

ServerWait := TargetConnection.WaitForData(100);
if ServerWait then begin
 RecLen := TargetConnection.ReceiveBuf(Buf, BufSize);
 while (ClientSocket.Connected) and TargetConnection.Connected) and (RecLen > 0) do begin
  ClientStream.Write(Buf, RecLen);
  if (not ClientSocket.Connected) or (not TargetConnection.Connected) then Break;
  if (not TargetConnection.WaitForData(10)) then Break;
  RecLen := TargetConnection.ReceiveBuf(Buf, BufSize);
 end;
end;
end;

...


Все бы хорошо (и даже отрабатываются несколько проходов общего цикла верно), но практически сразу общий цикл замирает (точнее, не замирает, а бесконечно крутится) и наблюдается странная картина: ClientStream.WaitForData(100) в начале цикла начинает все время возвращать True, в то время как количество принятых байт (RecLen := ClientStream.Read ...) с этого сокета становится все время равно нулю. При этом, оба соединения не дисконнэктятся (хотя с чего бы им разрываться, если данные "недопередались").

Вопрос: Что я делаю не так? :)



kernel ©   (2010-05-18 17:40) [11]

То есть, проще говоря, через некоторое время цикл "крутится", а данные не хочет отдавать ни один сокет :/



Slym ©   (2010-05-19 08:06) [12]

procedure TPortMapClientThread.DoTunneling(Peer1,Peer2:TCustomWinSocket);
 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[0..4095] of char;
 r:integer;
begin
 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;
     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;
     end;
   end else
     exit;
 end;
end;

USAGE:
DoTunneling(ClientSocket,TargetConnection);



kernel ©   (2010-05-19 15:48) [13]

Спасибо, Slym! Немного позже попробую даный код и отпишусь здесь :)



kernel ©   (2010-05-19 20:35) [14]

Еще раз огромное спасибо, Slym! Первые тесты туннелирования пока дали успешный результат.
PS: FPortMap.ClientTimeout поставил = 60000 [мс] для пробы. Хотя в Инди HTTP прокси, например, таймаут на каждое подключение при туннелировании = 100 мс. Какое оптимальное значение можно поставить? :)



Slym ©   (2010-05-20 07:58) [15]

kernel ©   (19.05.10 20:35) [14]
Какое оптимальное значение можно поставить?

10сек вполне хватит



kernel ©   (2010-05-20 18:53) [16]


> Slym ©   (20.05.10 07:58) [15]

Спасибо! :)




Форум: "Сети";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2014.12.28;
Скачать: [xml.tar.bz2];




Наверх





Память: 0.79 MB
Время: 0.023 c
2-1386759097      Йа                    2013-12-11 14:51  2014.12.28  
Delphi, Excel 2007, установить параметры автофильтра


6-1273586835      kernel                2010-05-11 18:07  2014.12.28  
Сервер. Сокеты, потоки.


15-1400963402     Юрий                  2014-05-25 00:30  2014.12.28  
С днем рождения ! 25 мая 2014 воскресенье


15-1396545086     Павиа                 2014-04-03 21:11  2014.12.28  
Выбор ноутбука


15-1401363729     В поисках железяк     2014-05-29 15:42  2014.12.28  
ИБП обязательно должен быть мощнее БП?