Конференция "Сети" » Сервер. Сокеты, потоки. [WinXP]
 
  • kernel © (11.05.10 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 © (11.05.10 18:43) [1]
    чем Indy то не угодил?
  • kernel © (11.05.10 18:50) [2]

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

    Лицензией, тяжестью исправления (в том смысле, что исправлять много приходится, чтобы что-либо добавить\изменить на более низком уровне), потом, помню, были проблемы с убиванием потоков (http://pda.delphimaster.net/?id=1236577141&n=4).
  • DVM © (11.05.10 19:34) [3]

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

    Если хочешь максимум контроля над сервером - делай все сам на WinSock. Я бы наверное выбрал блокирующий режим и что-то типа пула потоков.
  • Slym © (11.05.10 20:37) [4]
    kernel ©   (11.05.10 18:07)
    НаписАл на базе него простенький прокси для обработки GET запросов

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

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


    Похоже ты уже и сам не помнишь о чем был твой вопрос.
    Используется или не используется Nagle - на "пропуск пакетов" он повлиять никак не может в принципе.
  • kernel © (13.05.10 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 © (13.05.10 16:25) [7]

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


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

    Компоненты они же на базе WinSock построены. Разницы между ними и чистым Winsock практически нет. По крайней мере это не то место, где следует искать разницу.
  • DVM © (13.05.10 16:26) [8]

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

    Другое дело, что вокруг WinSock можно написать свои простенькие обертки, больше подходящие к конкретной задаче - в этом их удобство.
  • kernel © (14.05.10 21:46) [9]
    Спасибо за ответы, DVM!
    Сделал свой класс на основе ServerSocket, все работает быстро и замечательно [тьфу-тьфу-тьфу], контроля полностью хватает.
    PS: собст-но это сообщение и отправил через пробный прокси :)
  • kernel © (18.05.10 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 © (18.05.10 17:40) [11]
    То есть, проще говоря, через некоторое время цикл "крутится", а данные не хочет отдавать ни один сокет :/
  • Slym © (19.05.10 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 © (19.05.10 15:48) [13]
    Спасибо, Slym! Немного позже попробую даный код и отпишусь здесь :)
  • kernel © (19.05.10 20:35) [14]
    Еще раз огромное спасибо, Slym! Первые тесты туннелирования пока дали успешный результат.
    PS: FPortMap.ClientTimeout поставил = 60000 [мс] для пробы. Хотя в Инди HTTP прокси, например, таймаут на каждое подключение при туннелировании = 100 мс. Какое оптимальное значение можно поставить? :)
  • Slym © (20.05.10 07:58) [15]
    kernel ©   (19.05.10 20:35) [14]
    Какое оптимальное значение можно поставить?

    10сек вполне хватит
  • kernel © (20.05.10 18:53) [16]

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

    Спасибо! :)
 
Конференция "Сети" » Сервер. Сокеты, потоки. [WinXP]
Есть новые Нет новых   [134436   +25][b:0][p:0.002]