Конференция "Сети" » Сокеты. Ограничение коннектов к серверу [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]
    Та я про ее реализацию, а не про затраты программиста :)
  • Цукор5 (13.12.11 01:10) [20]

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


    А ему, клиенту, какая разница? Он ведь не живой )))
    Так правильно я сделал или нет? Обеспечивает ли мой код работу только с одним клиентом?
  • Цукор5 (13.12.11 01:12) [21]

    > Ты немного не правильно представляешь себе ситуацию. Когда
    > соединение устанавливается через слушающий сокет - для него
    > создается отдельный сокет, хэндл которого тебе и возвращает
    > accept. После чего слушающий можно закрывать и работать
    > с установленным соединением.


    Все я правильно понимаю. Мне не нужно закрывать слушающий сокет ибо сервер будет работать 24/7.  Мн нужно ждать новых клиентов и даже работать с ними, если "очередь" пуста.
  • Anatoly Podgoretsky © (13.12.11 09:48) [22]

    > Да, я действительно делаю Accept

    Для чего?
  • Rouse_ © (13.12.11 10:23) [23]

    > Мне не нужно закрывать слушающий сокет ибо сервер будет
    > работать 24/7.

    Так тыж сам сказал что тебе нужно только одно соединение. Вопрос, нафига тебе сервер, если соединение уже установлено? Не, ну если нужно - значит нужно...
  • Сергей М. © (13.12.11 11:03) [24]

    > клиенту, какая разница? Он ведь не живой


    Как это какая ? И как это не живой ?

    Ты ж его запустил в ларек и он справедливо полагает что сейчас его обслужат, раз уж он вошел).. А ты и не собираешься его обслуживать - ты другим, перед этим вошедшим клиентом в этот момент занят - вновь вошедшему ты попросту молча даешь пинка).. Зачем тогда пускал его ?))
  • Сергей М. © (13.12.11 11:16) [25]
    В схеме по умолчанию происходит вот что:

    // момент 0
    Listen(ListeningServerSocket, 5);
    ..
    // момент 1

    в этот момент из 10-ти потециальных клиентов, попытавшихся законнектиться, подтверждение коннекта УЖЕ получат первые пятеро из них (ВНЕ зависимости от того что ни одна Accept() сервером еще не выполнена ) и они будут ждать обслуживания, остальные же пять получат отлуп.
    ..
    // момент 2
    // здесь для обслуживания очередного клиента из пяти УЖЕ законнекченных создается ассоциированное с ним отдельное кл.гнездо, без которого собственно обслуживание этого клиента не возможно

    NewClientConnectionSocket := Accept(ListeningServerSocket, ..);
    ..
    // момент 3
    // очередь уменьшилась на 1, теперь 1 из 5-ти клиентов, получивших ранее отлуп, при повторной попытке может успешно законнектиться, оставшиеся 4 по прежнему получат отлуп
  • Сергей М. © (13.12.11 11:20) [26]
    Accept() выгребает из очереди УЖЕ законнекченных клиентов (если таковые там имеются) очередного клиента, создает и ассоциирует с ним сокет.

    Если очередь пуста, Accept() ждет ее пополнения (в блок. режиме) либо запускает операцию ожидания (в неблок. режиме) и возвращает управление.
 
Конференция "Сети" » Сокеты. Ограничение коннектов к серверу [D7, WinXP]
Есть новые Нет новых   [134435   +13][b:0][p:0.002]