-
Задача, которую я решаю, требует ограничить количество коннектов к серверу до одного. Т.е. только один клиент может работать с моим сервером одновременно.
Сделал, но боюсь что не совсем правильно (использую критическую секцию). Взгляните на код. Может это надо делать как-то иначе? Заранее спасибо за критику в мой адрес.
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 немного тяжеловат для такой простой задачи
Ну чем он тяжеловат ?
Только тем что колбек-функцию нужно объявить, реализовать и задействовать ? Так эти полтора десятка строк только на пользу, если не исключено будущее расширение функ-ти сервера)
-
Та я про ее реализацию, а не про затраты программиста :)
-
> Запустил клиента и тут же пинком выгнал его без объяснения
> ему причин в отказе обслуживания.Зачем, спрашивается, тогда
> вообще нужно было его запускать ?
А ему, клиенту, какая разница? Он ведь не живой )))
Так правильно я сделал или нет? Обеспечивает ли мой код работу только с одним клиентом?
-
> Ты немного не правильно представляешь себе ситуацию. Когда
> соединение устанавливается через слушающий сокет - для него
> создается отдельный сокет, хэндл которого тебе и возвращает
> accept. После чего слушающий можно закрывать и работать
> с установленным соединением.
Все я правильно понимаю. Мне не нужно закрывать слушающий сокет ибо сервер будет работать 24/7. Мн нужно ждать новых клиентов и даже работать с ними, если "очередь" пуста.
-
> Да, я действительно делаю Accept
Для чего?
-
> Мне не нужно закрывать слушающий сокет ибо сервер будет
> работать 24/7.
Так тыж сам сказал что тебе нужно только одно соединение. Вопрос, нафига тебе сервер, если соединение уже установлено? Не, ну если нужно - значит нужно...
-
> клиенту, какая разница? Он ведь не живой
Как это какая ? И как это не живой ?
Ты ж его запустил в ларек и он справедливо полагает что сейчас его обслужат, раз уж он вошел).. А ты и не собираешься его обслуживать - ты другим, перед этим вошедшим клиентом в этот момент занят - вновь вошедшему ты попросту молча даешь пинка).. Зачем тогда пускал его ?))
-
В схеме по умолчанию происходит вот что:
// момент 0
Listen(ListeningServerSocket, 5);
..
// момент 1
в этот момент из 10-ти потециальных клиентов, попытавшихся законнектиться, подтверждение коннекта УЖЕ получат первые пятеро из них (ВНЕ зависимости от того что ни одна Accept() сервером еще не выполнена ) и они будут ждать обслуживания, остальные же пять получат отлуп.
..
// момент 2
// здесь для обслуживания очередного клиента из пяти УЖЕ законнекченных создается ассоциированное с ним отдельное кл.гнездо, без которого собственно обслуживание этого клиента не возможно
NewClientConnectionSocket := Accept(ListeningServerSocket, ..);
..
// момент 3
// очередь уменьшилась на 1, теперь 1 из 5-ти клиентов, получивших ранее отлуп, при повторной попытке может успешно законнектиться, оставшиеся 4 по прежнему получат отлуп
-
Accept() выгребает из очереди УЖЕ законнекченных клиентов (если таковые там имеются) очередного клиента, создает и ассоциирует с ним сокет.
Если очередь пуста, Accept() ждет ее пополнения (в блок. режиме) либо запускает операцию ожидания (в неблок. режиме) и возвращает управление.