Конференция "Сети" » Сокеты. Ограничение коннектов к серверу [D7, WinXP]
 
  • Цукор5 (12.12.11 15:52) [0]
    Задача, которую я решаю, требует ограничить количество коннектов к серверу до одного. Т.е. только один клиент может работать с моим сервером одновременно.

    Сделал, но боюсь что не совсем правильно (использую критическую секцию). Взгляните на код. Может это надо делать как-то иначе? Заранее спасибо за критику в мой адрес.


    type
     TServerSocket =  class (TObject)
     private
       ...
       FCountClientSocket:Integer;
     public
       ...
     end;

    function TServerSocket.ListenThread (ServerSocket:TSocket): Integer;
     var ClientSocket: TSocket;
     ClientAddr: TSockAddr;
     ClientAddrLen: Integer;
     P:PClientThread;
     FClientThreadID:DWord;
     //
     hTime:PTimeVal;
     SetR:TFDSet;
    begin
     FCountClientSocket:=0;
     repeat
       New(hTime);
       hTime^.tv_sec:=0;
       hTime^.tv_usec:=300000;
       FD_ZERO(SetR);
       FD_Set(ServerSocket,SetR);
       if Select(0,@SetR,nil,nil,hTime)<>0 then
       begin
         if FD_IsSet(ServerSocket,SetR) then
         begin
           ClientAddrLen  := SizeOf(ClientAddr);
           ClientSocket   := Accept(ServerSocket, @ClientAddr, @ClientAddrLen);
           if ClientSocket = INVALID_SOCKET then Break;
           if FCountClientSocket = 0 then
           begin
             // увеличиваем кол-во потоков клиентов
             EnterCriticalSection(FRTLBusy);
             try
               Inc(FCountClientSocket);
             finally
               LeaveCriticalSection(FRTLBusy);
             end;
             //
             // новый поток для работы с клиентом
             New(P);
             P^.SelfUnit     := Self;
             P^.ClientSocket := ClientSocket;
             P^.ClientAddr   := ClientAddr;
             CloseHandle(BeginThread(nil,0,@MsgClientThread,P,0,FClientThreadID));
           end else
           begin  // закрыть сокет, которые пытается подключиться
             Shutdown(ClientSocket,SD_BOTH);
             CloseSocket(ClientSocket);
           end;
         end;
       end;
       Dispose(hTime);
       //смотрим на Abort
       case WaitForSingleObject(FAbort,100) of
         WAIT_OBJECT_0 : Break;
       end;
     until False;
     Shutdown(ServerSocket, SD_BOTH);
     CloseSocket(ServerSocket);
     Result:=0;
    end;

    function TServerSocket.ClientThread(ClientSocket: TSocket;
     ClientAddr: TSockAddr): Integer;
    begin
     ... // работаем
     //
     Shutdown(ClientSocket,SD_BOTH);
     CloseSocket(ClientSocket);
     // уменьшаем кол-во потоков клиентов
     EnterCriticalSection(FRTLBusy);
     try
      Dec(FCountClientSocket);
     finally
       LeaveCriticalSection(FRTLBusy);
     end;
     Result:=0;
    end;

  • Сергей М. © (12.12.11 17:06) [1]
    "ограничить количество коннектов к серверу до одного" и "только один клиент может работать с моим сервером одновременно" - две разные разницы.

    Приведенный тобой код не имеет ничего общего с "ограничить количество коннектов к серверу до одного"
  • Цукор5 (12.12.11 17:19) [2]
    Хорошо, еще раз попробую объяснить.

    Необходимо, чтобы сервер после коннекта с каким-то клиентом блокировал остальные коннекты до тех пор, пока не отработает с текущим клиентом.

    Аналогия с очередью и продавщицой. Она может отпускать покупателей ТОЛЬКО по одному. И никак иначе. Вот и мне так надо.
  • Сергей М. © (12.12.11 17:57) [3]

    > Вот и мне так надо


    Тогда придется выкинуть accept() и задействовать WSAAccept()

    И перед как слушать установить для слушающего сокета (setsockopt) опцию SO_CONDITIONAL_ACCEPT

    Только в этом случае ты сможешь блокировать все ненужные попытки коннекта.
  • Цукор5 (12.12.11 18:22) [4]
    Спасибо, посмотрю.

    А с accept вообще никак не получится? Мне казалось, что таки мой код блокирует стороннего клиента. Я ошибался?
  • Цукор5 (12.12.11 18:56) [5]
    2 Сергей М. ©   (12.12.11 17:57) [3]

    Еще небольшое уточнение. Мне не нужны клиенты, которые пытались соединиться когда сервер работал с клиентом.

    По аналогии с продавщицей. Если она отпускает клиента, то все остальные могут разойтись или настойчиво стоять и ждать. А вот когда она отпустила клиента, то любой другой (и не важно каким он был в очереди. хоть последним) может к ней попасть вновь.
  • Сергей М. © (12.12.11 20:33) [6]

    > с accept вообще никак не получится?


    никак.


    > По аналогии с продавщицей


    Продавщица - это твой сервер. Его дело обслужить или выгнать тех кто УЖЕ вошел в ларек. А в твой ларек охранник УЖЕ пустил кучу клиентов в кол-ве backlog, которые торчали у входа в ожидании когда ларек откроют.

    Так вот accept() это охранник, тупо действующий по штатной инструкции (запускать в ларек не более чем backlog клиентов), в то время как "продвинутый" охранник WSAAccept() кроме (или вместо) штатной инструкции способен адекватно реагировать еще и на пожелания продавца.
  • Сергей М. © (12.12.11 20:34) [7]

    > код блокирует стороннего клиента


    не блокирует.
    он УЖЕ вперся в ларек - и теперь его пинками прогоняют оттуда.
  • Rouse_ © (12.12.11 21:51) [8]
    Нет ну зачем-же так категорично? Достаточно просто не выполнять accept в случае если есть уже одно подключение и никаких заморочек с WSAAccept не потребуется. Т.е. грубо - есть соединение, слушающй сокет закрываем (соединение то уже установлено). При дисконнекте, опять начинаем слушать порт...
  • Цукор5 (12.12.11 21:56) [9]
    2 Сергей М. ©   (12.12.11 20:34) [7]

    Несколько вопросов.

    1) WSAAccept из WinSock 2. Я так подозреваю, что надо все ф-ции переводить на WinSock 2?

    2) он УЖЕ вперся в ларек - и теперь его пинками прогоняют оттуда
    Да, я действительно делаю Accept и тут же закрываю сокет клиента. Основная задача: поток ClientThread запускать в единственном экземпляре.

    Кстати, судя по логам мой код  "выгоняет" сторонних клиентов, когда поток ClientThread  работает. Не знаю правда, что будет когда два клиента будут на старте сразу.
  • Цукор5 (12.12.11 22:03) [10]
    2 Rouse_ ©   (12.12.11 21:51) [8]

    > не выполнять accept в случае если есть уже одно подключение
    > и никаких заморочек с WSAAccept не потребуется.


    тогда Select и FD_IsSet будут срабатывать пока ClientThread  жив. Вот я и сделал Accept и  CloseSocket, чтобы не было бесконечных попыток.

    Ключевой вопрос. Насколько правильно использовать критическую секцию в моих целях? Может ли  ClientThread  запуститься в нескольких экземплярах?
  • Сергей М. © (12.12.11 22:04) [11]

    > слушающй сокет закрываем


    При первом же accept() в ларек УЖЕ вошли backlog клиентов.
    Одного соизволили обслужить, получив от accept() хэндл кл.гнезда, остальных backlog - 1 вытолкали в шею закрытием слушающего гнезда. Они чем виноваты ?)


    > Цукор5   (12.12.11 21:56) [9]
    > надо все ф-ции переводить на WinSock 2?


    что значит "переводить" ?
  • Rouse_ © (12.12.11 22:07) [12]

    > тогда Select и FD_IsSet будут срабатывать пока ClientThread
    >  жив.

    Ты немного не правильно представляешь себе ситуацию. Когда соединение устанавливается через слушающий сокет - для него создается отдельный сокет, хэндл которого тебе и возвращает accept. После чего слушающий можно закрывать и работать с установленным соединением.
  • Сергей М. © (12.12.11 22:09) [13]

    > я и сделал Accept и  CloseSocket


    Ну правильно)
    Запустил клиента и тут же пинком выгнал его без объяснения ему причин в отказе обслуживания.
    Зачем, спрашивается, тогда вообще нужно было его запускать ?

    WSAAccept как раз и позволит обойтись с клиентом максимально коррректно - не пускать его вообще если обслужить его по каким-то причинам в этот момент невозможно.
  • Rouse_ © (12.12.11 22:11) [14]

    > WSAAccept как раз и позволит обойтись с клиентом максимально
    > коррректн

    Серег, нафига ты его на асинхронку то подбиваешь? :)
  • Сергей М. © (12.12.11 22:20) [15]

    > Rouse_ ©   (12.12.11 22:11) [14]


    Саш, ну вот где ты увидел у меня хоть какое-то упоминаниа асинхронки ?)
    WSAAccept() точно так же как и Accept() расчудесно работает и в блок. и в неблок. режимах. Но при этом позволяет запустить и соотв-но обслужить только "желанных" клиентов - остальные увидят на дверях ларька табличку "Closed"
    )
  • Rouse_ © (12.12.11 22:23) [16]

    > WSAAccept() точно так же как и Accept() расчудесно работает
    > и в блок. и в неблок. режимах.

    Это да, только WSAAccept немного тяжеловат для такой простой задачи, бо тащит за собой немного лишнего (ну эт я по памяти по сурсам W2k помню) :)
  • Сергей М. © (12.12.11 22:30) [17]
    Ну можно еще попробовать указать 2-м параметром в Listen()  значение backlog=1.

    > backlog

    [in] The maximum length to which the queue of pending connections can GROW. If this value is SOMAXCONN, then the underlying service provider responsible for socket s will set the backlog to a maximum "reasonable" value.

    Но не факт что это будет работать всегда и везде ожидаемым образом.
  • Сергей М. © (12.12.11 22:33) [18]

    > WSAAccept немного тяжеловат для такой простой задачи


    Ну чем он тяжеловат ?
    Только тем что колбек-функцию нужно объявить, реализовать и задействовать ? Так эти полтора десятка строк только на пользу, если не исключено будущее расширение функ-ти сервера)
  • Rouse_ © (12.12.11 22:49) [19]
    Та я про ее реализацию, а не про затраты программиста :)
 
Конференция "Сети" » Сокеты. Ограничение коннектов к серверу [D7, WinXP]
Есть новые Нет новых   [134435   +16][b:0][p:0.002]