-
Задача, которую я решаю, требует ограничить количество коннектов к серверу до одного. Т.е. только один клиент может работать с моим сервером одновременно. Сделал, но боюсь что не совсем правильно (использую критическую секцию). Взгляните на код. Может это надо делать как-то иначе? Заранее спасибо за критику в мой адрес.
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);
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;
-
"ограничить количество коннектов к серверу до одного" и "только один клиент может работать с моим сервером одновременно" - две разные разницы.
Приведенный тобой код не имеет ничего общего с "ограничить количество коннектов к серверу до одного"
-
Хорошо, еще раз попробую объяснить. Необходимо, чтобы сервер после коннекта с каким-то клиентом блокировал остальные коннекты до тех пор, пока не отработает с текущим клиентом.
Аналогия с очередью и продавщицой. Она может отпускать покупателей ТОЛЬКО по одному. И никак иначе. Вот и мне так надо.
-
> Вот и мне так надо
Тогда придется выкинуть accept() и задействовать WSAAccept()
И перед как слушать установить для слушающего сокета (setsockopt) опцию SO_CONDITIONAL_ACCEPT
Только в этом случае ты сможешь блокировать все ненужные попытки коннекта.
-
Спасибо, посмотрю. А с accept вообще никак не получится? Мне казалось, что таки мой код блокирует стороннего клиента. Я ошибался?
-
2 Сергей М. © (12.12.11 17:57) [3] Еще небольшое уточнение. Мне не нужны клиенты, которые пытались соединиться когда сервер работал с клиентом. По аналогии с продавщицей. Если она отпускает клиента, то все остальные могут разойтись или настойчиво стоять и ждать. А вот когда она отпустила клиента, то любой другой (и не важно каким он был в очереди. хоть последним) может к ней попасть вновь.
-
> с accept вообще никак не получится?
никак.
> По аналогии с продавщицей
Продавщица - это твой сервер. Его дело обслужить или выгнать тех кто УЖЕ вошел в ларек. А в твой ларек охранник УЖЕ пустил кучу клиентов в кол-ве backlog, которые торчали у входа в ожидании когда ларек откроют.
Так вот accept() это охранник, тупо действующий по штатной инструкции (запускать в ларек не более чем backlog клиентов), в то время как "продвинутый" охранник WSAAccept() кроме (или вместо) штатной инструкции способен адекватно реагировать еще и на пожелания продавца.
-
> код блокирует стороннего клиента
не блокирует. он УЖЕ вперся в ларек - и теперь его пинками прогоняют оттуда.
-
Нет ну зачем-же так категорично? Достаточно просто не выполнять accept в случае если есть уже одно подключение и никаких заморочек с WSAAccept не потребуется. Т.е. грубо - есть соединение, слушающй сокет закрываем (соединение то уже установлено). При дисконнекте, опять начинаем слушать порт...
-
2 Сергей М. © (12.12.11 20:34) [7]
Несколько вопросов.
1) WSAAccept из WinSock 2. Я так подозреваю, что надо все ф-ции переводить на WinSock 2?
2) он УЖЕ вперся в ларек - и теперь его пинками прогоняют оттуда Да, я действительно делаю Accept и тут же закрываю сокет клиента. Основная задача: поток ClientThread запускать в единственном экземпляре.
Кстати, судя по логам мой код "выгоняет" сторонних клиентов, когда поток ClientThread работает. Не знаю правда, что будет когда два клиента будут на старте сразу.
-
2 Rouse_ © (12.12.11 21:51) [8]
> не выполнять accept в случае если есть уже одно подключение > и никаких заморочек с WSAAccept не потребуется.
тогда Select и FD_IsSet будут срабатывать пока ClientThread жив. Вот я и сделал Accept и CloseSocket, чтобы не было бесконечных попыток.
Ключевой вопрос. Насколько правильно использовать критическую секцию в моих целях? Может ли ClientThread запуститься в нескольких экземплярах?
-
> слушающй сокет закрываем
При первом же accept() в ларек УЖЕ вошли backlog клиентов. Одного соизволили обслужить, получив от accept() хэндл кл.гнезда, остальных backlog - 1 вытолкали в шею закрытием слушающего гнезда. Они чем виноваты ?)
> Цукор5 (12.12.11 21:56) [9] > надо все ф-ции переводить на WinSock 2?
что значит "переводить" ?
-
> тогда Select и FD_IsSet будут срабатывать пока ClientThread > жив.
Ты немного не правильно представляешь себе ситуацию. Когда соединение устанавливается через слушающий сокет - для него создается отдельный сокет, хэндл которого тебе и возвращает accept. После чего слушающий можно закрывать и работать с установленным соединением.
-
> я и сделал Accept и CloseSocket
Ну правильно) Запустил клиента и тут же пинком выгнал его без объяснения ему причин в отказе обслуживания. Зачем, спрашивается, тогда вообще нужно было его запускать ?
WSAAccept как раз и позволит обойтись с клиентом максимально коррректно - не пускать его вообще если обслужить его по каким-то причинам в этот момент невозможно.
-
> WSAAccept как раз и позволит обойтись с клиентом максимально > коррректн
Серег, нафига ты его на асинхронку то подбиваешь? :)
-
> Rouse_ © (12.12.11 22:11) [14]
Саш, ну вот где ты увидел у меня хоть какое-то упоминаниа асинхронки ?) WSAAccept() точно так же как и Accept() расчудесно работает и в блок. и в неблок. режимах. Но при этом позволяет запустить и соотв-но обслужить только "желанных" клиентов - остальные увидят на дверях ларька табличку "Closed" )
-
> WSAAccept() точно так же как и Accept() расчудесно работает > и в блок. и в неблок. режимах.
Это да, только WSAAccept немного тяжеловат для такой простой задачи, бо тащит за собой немного лишнего (ну эт я по памяти по сурсам W2k помню) :)
-
Ну можно еще попробовать указать 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.
Но не факт что это будет работать всегда и везде ожидаемым образом.
-
> WSAAccept немного тяжеловат для такой простой задачи
Ну чем он тяжеловат ? Только тем что колбек-функцию нужно объявить, реализовать и задействовать ? Так эти полтора десятка строк только на пользу, если не исключено будущее расширение функ-ти сервера)
-
Та я про ее реализацию, а не про затраты программиста :)
|