![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Серьезный TCP сервер.
Николас © (2006-09-22 09:15:00)Стоит задача в обслуживании большого колличества клиентов, 100-1000 одновременных TCP соединений с постоянным обменом данными... Причем связь с сервером будет осуществлятся по разным каналам, от GPRS до LAN. Сейчас это реализовано на WinAPI Winsock. На локальной машине все работает хорошо для одного клиента, но достаточно попробова подключиться по не очень хорошему каналу связи, то начинаются потери пакетов, реализация протокола не допускает потери даже одного пакета, поэтому должна быть надежная реализация сервера.
Основной сетевой код работает в основном потоке, осуществляет обработку событий сокета и отправляет пришедшие данные на обработку. Есть проблема в приеме данных, когда пришла только часть данных от одного клиента, то мы ждем остальных данных, функция обработки полученных данных вызвается пока не придут все данные и пока мы не удалим их из очереди сокета, это сильно нагружает процессор, вариант со SLeep(100) снимает нагрузку с процессора, но так же усыпляет весь поток на 100мс, что критично для данного способа, ведь остальные клиенты должнв продолжать работу...
Неужели придется создавать для каждого клиента отдельный поток ? Есть у вас варианты для реализации? Может уже готовый компонент использовать?
Николас (2006-10-03 08:17:00) [1]vidiv НЕТ :) Представь 1000 клиентов которые постоянно обмениваются данными :)
vidiv (2006-09-29 20:18:00) [2]А если создать для всех соединений один отдельный поток и использовать функцию select ? Такой вариант не подходит?
y-soft (2006-09-25 08:32:00) [3]>Николас (24.09.06 23:26) [28]
Глянул навскидку Ваш код. Сейчас пока очень много работы, так что детально вникать пока не могу, постараюсь сделать это позже.
Но сразу могу сказать - класс, который Вы используете, написан неоптимально, реализация сводит на нет многие преимущества использования порта завершения :(
Что сразу же бросилось в глаза:
1. Сокеты для клиентских соединений создаются динамически, вызовом Accept, гораздо эффективнее создать сразу необходимое количество сокетов, а затем привязать из к слушающему вызовами AcceptEx... Появятся, правда, новые заботы - например, корректно закрывать клиентское соединение для повторного использования и учитывать состояние TCP WAIT_TIME...
2. Выгоднее все заботы о порте завершения переложить на систему. Один вызов BindIOCompletionCallback заменит множество строчек кода, и отпадет необходимость в потоках TAcceptThread и TWorkerThread
Продолжу позже - постараюсь описать алгоритм эффективного использования, а сейчас зовет труба :)
Николас (2006-09-24 23:26:00) [4]Перезалил сюда: http://rapidshare.de/files/34309493/IOCPDemo.rar.html
Все события кроме read обрабатываются нормально, потоки Accept и Worker создаются в зависимости от оклчисества процессоров, вообещем глянь код, не буду тут вставлять большие куски :)
y-soft (2006-09-24 17:30:00) [5]>Николас (24.09.06 14:04) [26]
Зря Вы выложили на Webfile.ru - при попытке скачать ничего не происходит
y-soft хотя бы помоги немного :)
Я же сказал - на разумные вопросы ответить готов
но не вызывается событие когда приходят данные :(
А AссeptEx вызывали для сокета? В очередь порта завершения его ставили?
Поставили ли в очередь порта слушающий сокет? Каким образом производите рассоединение?...
Приведите хотя бы общий порядок действий, а то трудно заниматься телепатией :)
А события завершения операций ввода/вывода приходят через функцию обратного вызова, точно так же, как и события соединения и рассоединения
Николас (2006-09-24 14:04:00) [6]y-soft хотя бы помоги немного :)
Тут пример набросал работы, но не вызывается событие когда приходят данные :(
http://webfile.ru/56873/IOCPDemo.rar
y-soft (2006-09-24 08:21:00) [7]>Николас (24.09.06 04:07) [24]
Не найдется у тя примера работы с IOCP на дельфи?
Увы, дать свой код будет неэтично по отношению к работодателю - ведь программированием занимаюсь не по вечерам в свое удовольствие, а за зарплату в рабочее время...
Но на разумные вопросы ответить готов
Николас (2006-09-24 04:07:00) [8]Сергей М. сейчас реализован пул нитей для исходящих пакетов, пропажи пакетов исчезли, думаю и для входящих подобное реализовать, но как это придумать пока в голову ничего не лезет :)
y-soft спасибо за разъяснения :) Не найдется у тя примера работы с IOCP на дельфи? Весь инет перерыл, нашел только у мелкософта на С++ :(
добрый, ага, WoW
Орион (2006-09-23 22:58:00) [9]> [22] добрый (23.09.06 21:07)
Откуда такая информация?
Или привеленные куски кода похожи на твой собственный эмулятор ВОВ?)))
добрый (2006-09-23 21:07:00) [10]автор пишет эмулятор сервера WoW? :) киддисы. когда же они успокоятся.
y-soft (2006-09-22 22:50:00) [11]>Николас (22.09.06 10:12) [7]
Сервер Lineage II к примеру под винду и держит несколько тысяч клиентов одновременно. Насколько я понял там используется IO Completion Port... кто-нибудь знает про это ?
Именно так. Где-то до 65000 соединений с помощью портов завершения держать можно при приемлемой затрате ресурсов. К тому же приложение станет масштабируемым. Все остальные модели работают гораздо хуже, а для блокирующих сокетов 1000 соединений это вообще реальный предел.
Обычно от этой технологии отпугивает кажущаяся сложность, но на самом деле ничего сложного нет, а при использовании функций системного пула потоков получается еще проще...
Про использование портов завершения с сокетами читайте у Оланда и Джонса, про функции системного пула потоков - у Рихтера...
Создавать 1000 потоков я думаю будет слишком жестоко...:(
Не только жестоко, но и весьма расточительно, даже если потоки основную часть времени будут находиться в ожидании. И медленнее из-за необходимости переключения контекстов. И загрузка процессора может подскакивать до 100% :(
При использовании портов завершения потоков может быть и всего пара вне зависимости от количества соединений, а при использовании BindIoCompletionCallback(...) даже и не придется заботиться об их создании и уничтожении...
Eraser (2006-09-22 16:52:00) [12]> [19] Сергей М. © (22.09.06 16:44)
> ибо с насаживанием идиотской концепции а-ля "всем нужна,
> без нее никак"
в висте над этим постарались. теперь, прежде чем запутить обычное не_managed приложение, система 10 раз спросит делать это или нет, при этом "сервис", который задает эти вопросы мне лично отключить так и не удалось, вернее его нету в разделе "сервисы" да и вообще нигде, по-моему это жестко прошито в каком-то системном процессе и не имеет опции "отключить". С .NET приложениями таких заморочек нет. Так что через пол годика начнется "бум". imho мелкомягкие своего добились.
Сергей М. (2006-09-22 16:44:00) [13]
> массовый преход на эту "переспективную" платфору не за горами
Не за горами и 9-й вал вопросов на эту тему, ибо с насаживанием идиотской концепции а-ля "всем нужна, без нее никак" все больше и больше батонокидателей оказываются зза барьером происходящего по сути.
Eraser (2006-09-22 16:38:00) [14]> [17] Сергей М. © (22.09.06 16:31)
я имел ввиду, что проще понять блокирующий режим, а не компоненты инди :)
> Есть TServerSocket, есть режим stThreadBlocking - куда уж
> проще исследовать именно его код, нежели перегруженный кучей
> идиотских наворотов "на перспективу" индейский код)
согласен, только вот, AFAIK, реализации под .NET netу у этих компонентов, а массовый преход на эту "переспективную" платфору не за горами.
Сергей М. (2006-09-22 16:31:00) [15]
> Eraser © (22.09.06 16:06) [16]
Насчет "проще понять" - не согласен.
Есть TServerSocket, есть режим stThreadBlocking - куда уж проще исследовать именно его код, нежели перегруженный кучей идиотских наворотов "на перспективу" индейский код)
Eraser (2006-09-22 16:06:00) [16]> [15] Сергей М. © (22.09.06 16:00)
ну вот, тем более, для начала инди - самое оно, с настройкам по-умолчанию. да и принцип работы блокирующих сокетов проще понять, чем не блокирующих.
Сергей М. (2006-09-22 16:00:00) [17]
> Eraser © (22.09.06 15:57) [14]
Да нахрен автору волокна, если он и в нитях-то плавает)...
Ему обычный пул нитей, возможно, к месту будет ...
Eraser (2006-09-22 15:57:00) [18]> [0] Николас (22.09.06 09:15)
присоединяюсь к тем, кто советует использовать Инди. В 10 версии есть возможность использовать вместо потоков - волокна, что может ощутимо добавить производительности мощному серверу.
Сергей М. (2006-09-22 11:12:00) [19]
> Николас (22.09.06 11:06) [12]
Вся обработка клиентских запросов здесь сводится к простейшему
> Memo1.Lines.Add('ReadFromServerSocket');
поэтому пока не вижу повода для волнений по сабжу ..
Николас (2006-09-22 11:06:00) [20]Сейчас код примерно такой:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, WinSock, StdCtrls ;
const
ServerPort = '8000';
ServerAddr = '127.0.0.1';
WM_SERVERSOCKETEVENT = WM_USER + 1 ;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure WMServerSocketEventHandler(var Msg: TMessage); message WM_SERVERSOCKETEVENT;
public
{ Public declarations }
procedure ReadFromServerSocket(Sock: Tsocket);
end;
var
Form1: TForm1;
ServerSock: TSocket;
world_sock_event_mask: integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Data:TWSAData;
Addr:TSockAddr;
Block:u_long;
begin
//Ñåòåâàÿ èíèöèàëèçàöèÿ
WSAStartup($0101, Data);
// Îáû÷íàÿ ïîñëåäîâàòåëü 237;îñòü äåéñòâèé ïî ñîçäàíèþ ñîêåòà,
// ïðèâÿçêå åãî ê àäðåñó è óñòàíîâëåíèþ íà ïðîñëóøèâàíèå
ServerSock:=Socket(AF_Inet,Sock_stream,0);
Addr.sin_family:=PF_Inet;
Addr.sin_addr.S_addr:=Inet_addr(PChar(ServerAddr));
Addr.sin_port:=HToNS(StrToInt(ServerPort));
FillChar(Addr.sin_zero,SizeOf(Addr.sin_zero),0);
Bind(ServerSock,Addr,SizeOf(Addr));
Listen(ServerSock,SOMaxConn);
world_sock_event_mask:=FD_Read or FD_Accept or FD_Close;
WSAAsyncSelect(ServerSock, Form1.Handle, WM_SERVERSOCKETEVENT, world_sock_event_mask);
// Block:=0;
// IOCtlSocket(ServerSock,FIONBIO,Block);
Memo1.Lines.Add('*** Server listen on port ' + ServerPort + ', IP ' + ServerAddr);
end;
procedure TForm1.WMServerSocketEventHandler(var Msg: TMessage);
var
Sock: TSocket;
SockError,AcceptResult: Integer;
Addr: TSockAddr;
Len: Integer;
SockEvent: Word;
begin
try
Sock:=TSocket(Msg.WParam);
//Ïðîâåðÿåì íà íàëè÷èå îøèáêè
SockError := WSAGetSelectError(Msg.lParam);
if SockError 0 then begin
Memo1.Lines.Add('SockError sock='+IntToStr(Sock)+', error='+IntToStr(SockError));
CloseSocket(Sock);
Exit ;
end;
SockEvent := WSAGetSelectEvent(Msg.lParam) ;
//Memo1.Lines.Add('DEBUG SockEvent=' + IntToStr(SockEvent));
case SockEvent of
FD_CONNECT: Memo1.Lines.Add('CONNECT');
FD_OOB: Memo1.Lines.Add('OOB');
FD_Read: ReadFromServerSocket(Sock);
FD_Accept: begin
AcceptResult:=Accept(Sock,@Addr,@Len);
Memo1.Lines.Add('*** New connection sock='+IntToStr(AcceptResult)); //+' Worldsock sock='+IntToStr(Sock)+' Addr='+inet_ntoa(Addr.sin_addr)+' port='+IntToStr(ntohs(Addr.sin_port))+' Len='+IntToStr(Len));
Sleep(1000);
end;
FD_Close: begin
Memo1.Lines.Add('*** Close connection sock=' + IntToStr(sock));
Shutdown(Sock,SD_Send);
CloseSocket(Sock);
end;
FD_Write: ;
end ;
except
Memo1.Lines.Add('WMWorldSocketEventHandler error');
end;
end;
procedure TForm1.ReadFromServerSocket(Sock: Tsocket);
var
ByteBuff: array of Byte ;
received_len: Integer;
begin
try
SetLength(ByteBuff, 6);
received_len:=Recv(Sock,ByteBuff[0], 6, 0);
Memo1.Lines.Add('ReadFromServerSocket');
except
end;
end;
end.