Примеры работы с client socket в delphi. Программирование серверов на основе сокетов в дельфи. Алгоритм работы сокетного сервера

Новости 02.07.2020
Новости

В данной статье будут рассмотрены базовые свойства и функции компонентов Delphi: TClientSocket и TServerSocket – использующихся для работы с сетью по протоколу TCP\IP.

Внимание! Если вы используете версию Delphi выше 6.0, то предварительно вам необходимо установить Sockets Components, во всех версиях Delphi это делается следующим образом:

  • Заходим в диалог Install Packages… : (Главное меню) Component -> Install Packages;
  • Щелкаем по кнопке Add… , после чего находим папку Bin вашего Delphi (н-р: C:\Program Files\Borland\Delphi 7\bin , либо C:\Program Files\Embarcadero\RAD Studio\7.0\Bin);
  • В найденной папке Bin уже ищем файлик dclsockets[тут циферки] .bpl , жмем ОК ;
  • Радуемся, потому что теперь на вкладке панели компонентов на вкладке Internet у нас появились два замечательных компонента TServerSocket и TClientSocket.

При разработке любых сетевых приложений обычно разработку всегда начинают с сервера (конечно если у вас есть команда, дизайнеры интерфейсов могут приступать к работе над клиентом). Для реализации сервера, удивительно, необходимо использовать TServerSocket.

Основные свойства:

  • Active – поле типа boolean, при установлении в true – сервер запускается, можно использовать, как присвоение конкретных значений, так и вызывая функции ServerSocket1.Open (… Active:=true;) или ServerSocket1.Close (… Active:=false).
  • Port – порт на котором будет прослушивать (принимать клиентов) сервер, любое не занятое другими серверами в системе значение в пределах диапазона integer .

Основные события:

  • OnListen – вызывается при установлении сервера в режим прослушивания, может испоьзоватся, когда нам нужно определить время реального старта сервера.
  • OnClientRead – вызывается в момент принятия данных от клиента.
  • OnClientError
  • OnClientConnect – вызывается во время присоединения нового клиента к серверу.
  • OnClientDisconnect – обратное событие событию, OnClientConnect

у каждой функции события есть атрибут Socket: TCustomWinSocket , в него передаеться указатель на объект сокета с которым мы в данный момент работаем, если нам нужно что-то ответить или что-то сделать с клиентом, который вызвал событие, нам необходимо использовать именно этот объект, во всех остальных случаях мы используем ServerSocket1.Socket , аналогичная ситуация и с клиентским компонентом.

Readonly свойства и функции:

  • – возвращает количество активных соединений.
  • ServerSocket1.Socket.Connections – массив объектов типа TCustomWinSocket, массив всех объектов связанных с клиентами, счет индексов начинается с 0, длинна массива равна ServerSocket1.Socket.ActiveConnections .
  • Функции и свойства, применимые к элементам массива ServerSocket1.Socket.Connections и передаваемому атрибуту Socket в функцию события сервера:
  • Socket.LocalHost
  • Socket.LocalAdress – возвращает IP сервера.
  • Socket.RemoteHost
  • Socket.RemoteAdress – возвращает IP клиента.
  • Socket.ReceiveText – возвращает принятое от клиента текстовое сообщение, после чего очищает буфер, можно использовать только 1 раз, за 1 прием.
  • Socket.SendText(Text) – отсылает клиенту текстовое сообщение Text типа string .

Для компонента TClientSocket все практически тоже самое, только наоборот + основное визуальное отличие сервера от клиента, сервер в системе может быть запущен 1 на 1 значение порта, в количестве клиентов вас ограничивает только оперативная память.

Основные свойства:

  • Active – поле типа boolean, при установлении в true – клиент пытается соединится с сервером, можно использовать, как присвоение конкретных значений, так и вызывая функции ClientSocket1.Open (… Active:=true;) или ClientSocket1.Close (… Active:=false).
  • Port – порт по которому клиент сможет присоединиться к серверу, любое значение в пределах диапазона integer .
  • Adress – IPv4 адрес сервера типа string по шаблону 255.255.255.255, с которым будет соединяться клиент.

Основные события:

  • OnRead – вызывается в момент принятия данных от севера.
  • OnError – вызывается при возникновении ошибки в передаче данных.
  • OnConnecting – вызывается во время присоединения клиента к серверу.
  • OnDisconnect – обратное событие событию, OnConnecting , вызывается во время отсоединения от сервера клиента.

Readonly свойства и функции:

  • ClientSocket1.Socket.SendText() string
  • Socket.LocalHost – возвращает имя клиента в сети.
  • Socket.LocalAdress – возвращает IP клиента.
  • Socket.RemoteHost – возвращает имя сервера в сети.
  • Socket.RemoteAdress – возвращает IP сервера.
  • Socket.ReceiveText – возвращает принятое от сервера текстовое сообщение, после чего очищает буфер, можно использовать только 1 раз, за 1 прием.
  • Socket.SendText(Text) – отсылает серверу текстовое сообщение Text типа string .

Предоставленной информации вполне достаточно, чтобы реализовать небольшой серверный чат, удовлетворяющий техническому заданию: internet_sockets.doc (Word Doc 97-2003, 26.5 Kb).

Эта статья написана в Воскресенье, Октябрь 10th, 2010 at 1:24 в разделе . Вы можете подписаться на обновления комментариев к статье - . Вы можете


Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). В отличие от предыдущей статьи на тему сокетов, здесь мы разберем создание серверных приложений.

Следует сразу заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором Вы одновременно запустите и сервер, и клиент. При этом нужно в качестве имени компьютера, к которому надо подключиться, использовать хост-имя localhost или IP-адрес - 127.0.0.1.

Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.

Алгоритм работы сокетного сервера

Что же позволяет делать сокетный сервер?.. По какому принципу он работает?.. Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество Вы можете указать сами (или вообще убрать это ограничение, как это сделано по умолчанию). Для каждого подключенного клиента сервер открывает отдельный сокет, по которому Вы можете обмениваться данными с клиентом. Также отличным решением является создание для каждого подключения отдельного процесса (Thread).

Ниже следует примерная схема работы сокетного сервера в Дельфи-приложениях:

Разберем схему подробнее:

  1. Определение св-в Port и ServerType - чтобы к серверу могли нормально подключаться клиенты, нужно, чтобы порт, используемый сервером точно совпадал с портом, используемым клиентом (и наоборот). Свойство ServerType определяет тип подключения (подробнее см.ниже);
  2. Открытие сокета - открытие сокета и указанного порта. Здесь выполняется автоматическое начало ожидания подсоединения клиентов (Listen);
  3. Подключение клиента и обмен данными с ним - здесь подключается клиент и идет обмен данными с ним. Подробней об этом этапе можно узнать ниже в этой статье и в статье про сокеты (клиентская часть);
  4. Отключение клиента - Здесь клиент отключается и закрывается его сокетное соединение с сервером;
  5. Закрытие сервера и сокета - По команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов.
  6. Следует заметить, что пункты 3-4 повторяются многократно, т.е. эти пункты выполняются для каждого нового подключения клиента.

Примечание:

Документации по сокетам в Дельфи на данный момент очень мало, так что, если Вы хотите максимально глубоко изучить эту тему, то советую просмотреть литературу и электронную документацию по Unix/Linux-системам - там очень хорошо описана теория работы с сокетами. Кроме того, для этих ОС есть множество примеров сокетных приложений (правда, в основном на C/C++ и Perl).

Краткое описание компонента TServerSocket

Здесь мы познакомимся с основными свойствами, методами и событиями компонента TServerSocket.

Свойства Методы События:

Socket класс TServerWinSocket, через который Вы имеете доступ к открытым сокетным каналам. Далее мы рассмотрим это свойство более подробно, т.к. оно, собственно и есть одно из главных. Тип: TServerWinSocket; ServerType тип сервера. Может принимать одно из двух значений: stNonBlocking - синхронная работа с клиентскими сокетами. При таком типе сервера Вы можете работать с клиентами через события OnClientRead и OnClientWrite. stThreadBlocking - асинхронный тип. Для каждого клиентского сокетного канала создается отдельный процесс (Thread). Тип: TServerType; ThreadCacheSize количество клиентских процессов (Thread), которые будут кэшироваться сервером. Здесь необходимо подбирать среднее значение в зависимости от загруженности Вашего сервера. Кэширование происходит для того, чтобы не создавать каждый раз отдельный процесс и не убивать закрытый сокет, а оставить их для дальнейшего использования. Тип: Integer; Active показатель того, активен в данных момент сервер, или нет. Т.е., фактически, значение True указывает на то, что сервер работает и готов к приему клиентов, а False - сервер выключен. Чтобы запустить сервер, нужно просто присвоить этому свойству значение True. Тип: Boolean; Port номер порта для установления соединений с клиентами. Порт у сервера и у клиентов должны быть одинаковыми. Рекомендуются значения от 1025 до 65535, т.к. от 1 до 1024 - могут быть заняты системой. Тип: Integer; Service строка, определяющая службу (ftp, http, pop, и т.д.), порт которой будет использован. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам. Тип: string; Open Запускает сервер. По сути, эта команда идентична присвоению значения True свойству Active; Close Останавливает сервер. По сути, эта команда идентична присвоению значения False свойству Active. OnClientConnect возникает, когда клиент установил сокетное соединение и ждет ответа сервера (OnAccept); OnClientDisconnect возникает, когда клиент отсоединился от сокетного канала; OnClientError возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка; OnClientRead возникает, когда клиент передал берверу какие-либо данные. Доступ к этим данным можно получить через пеаедаваемый параметр Socket: TCustomWinSocket; OnClientWrite возникает, когда сервер может отправлять данные клиенту по сокету; OnGetSocket в обработчике этого события Вы можете отредактировать параметр ClientSocket; OnGetThread в обработчике этого события Вы можете определить уникальный процесс (Thread) для каждого отдельного клиентского канала, присвоив параметру SocketThread нужную подзадачу TServerClientThread; OnThreadStart, OnThreadEnd возникает, когда подзадача (процесс, Thread) запускается или останавливается, соответственно; OnAccept возникает, когда сервер принимает клиента или отказывает ему в соединении; OnListen возникает, когда сервер переходит в режим ожидания подсоединения клиентов.

TServerSocket.Socket (TServerWinSocket)

Итак, как же сервер может отсылать данные клиенту? А принимать данные? В основном, если Вы работаете через события OnClientRead и OnClientWrite, то общаться с клиентом можно через параметр ClientSocket (TCustomWinSocket). Про работу с этим классом можно прочитать в статье про клиентские сокеты, т.к. отправка/посылка данных через этот класс аналогична - методы (Send/Receive)(Text,Buffer,Stream). Также и при работе с TServerSocket.Socket. Однако, т.к. здесь мы рассматриваем сервер, то следует выделить некоторые полезные свойства и методы:

ActiveConnections (Integer) количество подключенных клиентов; ActiveThreads (Integеr) количество работающих процессов; Connections (array) - массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда:


ServerSocket1.Socket.Connections.SendText("Hello!");

отсылает первому подключенному клиенту сообщение "Hello!". Команды для работы с элементами этого массива - также (Send/Receive)(Text,Buffer, Stream); IdleThreads (Integer) количество свободных процессов. Такие процессы кэшируются сервером (см. ThreadCacheSize); LocalAddress, LocalHost, LocalPort соответственно - локальный IP-адрес, хост-имя, порт; RemoteAddress, RemoteHost, RemotePort соответственно - удаленный IP-адрес, хост-имя, порт; Методы Lock и UnLock соответственно, блокировка и разблокировка сокета.

Практика и примеры

А теперь рассмотрим вышеприведенное на конкретном примере. Скачать уже готовые исходники можно, щелкнув здесь.

Итак, рассмотрим очень неплохой пример работы с TServerSocket (этот пример - наиболее наглядное пособие для изучения этого компонента). В приведенных ниже исходниках демонстрируется протоколирование всех важных событий сервера, плюс возможность принимать и отсылать текстовые сообщения:

Пример 1. Протоколирование и изучение работы сервера, посылка/прием сообщений через сокеты.


{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1} procedure TForm1.Button1Click(Sender: TObject); begin {Определяем порт и запускаем сервер} ServerSocket1.Port:= 1025; {Метод Insert вставляет строку в массив в указанную позицию} Memo2.Lines.Insert(0, "Server starting"); ServerSocket1.Open; end ; procedure TForm1.Button2Click(Sender: TObject); begin {Останавливаем сервер} ServerSocket1.Active:= False; Memo2.Lines.Insert(0, "Server stopped"); end ; procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер "прослушивает" сокет на наличие клиентов} Memo2.Lines.Insert(0, "Listening on port " + IntToStr(ServerSocket1.Port)); end ; procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер принимает клиента} Memo2.Lines.Insert(0, "Client connection accepted"); end ; procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент подсоединяется} Memo2.Lines.Insert(0, "Client connected"); end ; procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент отсоединяется} Memo2.Lines.Insert(0, "Client disconnected"); end ; procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin {Произошла ошибка - выводим ее код} Memo2.Lines.Insert(0, "Client error. Code = " + IntToStr(ErrorCode)); end ; procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); begin {От клиента получено сообщение - выводим его в Memo1} Memo2.Lines.Insert(0, "Message received from client"); Memo1.Lines.Insert(0, "> " + Socket.ReceiveText); end ; procedure TForm1.ServerSocket1ClientWrite(Sender: TObject; Socket: TCustomWinSocket); begin {Теперь можно слать данные в сокет} Memo2.Lines.Insert(0, "Now can write to socket"); end ; procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); begin Memo2.Lines.Insert(0, "Get socket"); end ; procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin Memo2.Lines.Insert(0, "Get Thread"); end ; procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0, "Thread end"); end ; procedure TForm1.ServerSocket1ThreadStart(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0, "Thread start"); end ; procedure TForm1.Button3Click(Sender: TObject); var i: Integer; begin {Посылаем ВСЕМ клиентам сообщение из Edit1} for i:= 0 to ServerSocket1.Socket.ActiveConnections - 1 do ServerSocket1.Socket.Connections[i].SendText(Edit1.Text); Memo1.Lines.Insert(0, "end;

Приемы работы с TServerSocket (и просто с сокетами)

Хранение уникальных данных для каждого клиента.

Наверняка, если Ваш сервер будет обслуживать множество клиентов, то Вам потребуется хранить какую-либо информацию для каждого клиента (имя, и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к handle сокета, массивы клиентов, и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство - Data. На самом деле, Data - это всего-навсего указатель. Поэтому, записывая данные клиента в это свойство будьте внимательны и следуйте правилам работы с указателями (выделение памяти, определение типа, и т.д.)!

Посылка файлов через сокет

Здесь мы рассмотрим посылку файлов через сокет (по просьбе JINX-а) :-). Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере:


Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile))

Почему несколько блоков при передаче могут обьединяться в один

Это тоже по просьбе JINX-а:-). За это ему огромное спасибо! Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет - обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете - сеть, ограниченный трафик, и т.д.). Именно поэтому две команды:


ServerSocket1.Socket.Connections.SendText("Hello, "); ServerSocket1.Socket.Connections.SendText("world!");

совершенно идентичны одной команде:


ServerSocket1.Socket.Connections.SendText("Hello, world!");

И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток - TFileStream (либо поток в памяти - TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf. Определить размер полученного блока можно методом ReceiveLength. Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный):


Как следить за сокетом

Это вопрос сложный и требует долгого рассмотрения. Пока лишь замечу, что созданный Вашей программой сокет Вы можете промониторить всегда:-). Сокеты (как и большинство объектов в Windows) имеют свой дескриптор (handle), записанный в свойстве Handle. Так вот, узнав этот дескриптор Вы свободно сможете управлять любым сокетом (даже созданным чужой программой)! Однако, скорее всего, чтобы следить за чужим сокетом, Вам придется использовать исключительно функции WinAPI Sockets.

Эпилог

В этой статье отображены основные приемы работы с компонентом TServerSocket в Дельфи и несколько общих приемов для обмена данными по сокетам. Если у Вас есть вопросы - скидывайте их мне на E-mail: [email protected], а еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!


Введение


Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда ). В отличие от предыдущей статьи на тему сокетов, здесь мы разберем создание серверных приложений.

Следует сразу заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором Вы одновременно запустите и сервер, и клиент. При этом нужно в качестве имени компьютера, к которому надо подключиться, использовать хост-имя localhost или IP-адрес - 127.0.0.1 .

Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.

Алгоритм работы сокетного сервера


Что же позволяет делать сокетный сервер?.. По какому принципу он работает?.. Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество Вы можете указать сами (или вообще убрать это ограничение, как это сделано по умолчанию). Для каждого подключенного клиента сервер открывает отдельный сокет, по которому Вы можете обмениваться данными с клиентом. Также отличным решением является создание для каждого подключения отдельного процесса (Thread).

Ниже следует примерная схема работы сокетного сервера в Дельфи-приложениях:

Разберем схему подробнее:

  • Определение св-в Port и ServerType - чтобы к серверу могли нормально подключаться клиенты, нужно, чтобы порт, используемый сервером точно совпадал с портом, используемым клиентом (и наоборот). Свойство ServerType определяет тип подключения (подробнее см.ниже);
  • Открытие сокета - открытие сокета и указанного порта. Здесь выполняется автоматическое начало ожидания подсоединения клиентов (Listen );
  • Подключение клиента и обмен данными с ним - здесь подключается клиент и идет обмен данными с ним. Подробней об этом этапе можно узнать ниже в этой статье и в статье про сокеты (клиентская часть);
  • Отключение клиента - Здесь клиент отключается и закрывается его сокетное соединение с сервером;
  • Закрытие сервера и сокета - По команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов.

Следует заметить, что пункты 3-4 повторяются многократно, т.е. эти пункты выполняются для каждого нового подключения клиента.

Примечание : Документации по сокетам в Дельфи на данный момент очень мало, так что, если Вы хотите максимально глубоко изучить эту тему, то советую просмотреть литературу и электронную документацию по Unix/Linux-системам - там очень хорошо описана теория работы с сокетами. Кроме того, для этих ОС есть множество примеров сокетных приложений (правда, в основном на C/C++ и Perl).

Краткое описание компонента TServerSocket


Здесь мы познакомимся с основными свойствами, методами и событиями компонента TServerSocket .

Свойства

Socket - класс TServerWinSocket, через который Вы имеете доступ к открытым сокетным каналам. Далее мы рассмотрим это свойство более подробно, т.к. оно, собственно и есть одно из главных. Тип: TServerWinSocket ;
ServerType - тип сервера. Может принимать одно из двух значений: stNonBlocking - синхронная работа с клиентскими сокетами. При таком типе сервера Вы можете работать с клиентами через события OnClientRead и OnClientWrite . stThreadBlocking - асинхронный тип. Для каждого клиентского сокетного канала создается отдельный процесс (Thread). Тип: TServerType ;
ThreadCacheSize - количество клиентских процессов (Thread), которые будут кэшироваться сервером. Здесь необходимо подбирать среднее значение в зависимости от загруженности Вашего сервера. Кэширование происходит для того, чтобы не создавать каждый раз отдельный процесс и не убивать закрытый сокет, а оставить их для дальнейшего использования. Тип: Integer ;
Active - показатель того, активен в данных момент сервер, или нет. Т.е., фактически, значение True указывает на то, что сервер работает и готов к приему клиентов, а False - сервер выключен. Чтобы запустить сервер, нужно просто присвоить этому свойству значение True . Тип: Boolean ;
Port - номер порта для установления соединений с клиентами. Порт у сервера и у клиентов должны быть одинаковыми. Рекомендуются значения от 1025 до 65535, т.к. от 1 до 1024 - могут быть заняты системой. Тип: Integer ;
Service - строка, определяющая службу (ftp , http , pop , и т.д.), порт которой будет использован. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам. Тип: string ;

Open - Запускает сервер. По сути, эта команда идентична присвоению значения True свойству Active ;
Close - Останавливает сервер. По сути, эта команда идентична присвоению значения False свойству Active .

OnClientConnect - возникает, когда клиент установил сокетное соединение и ждет ответа сервера (OnAccept );
OnClientDisconnect - возникает, когда клиент отсоединился от сокетного канала;
OnClientError - возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка;
OnClientRead - возникает, когда клиент передал серверу какие-либо данные. Доступ к этим данным можно получить через передаваемый параметр Socket: TCustomWinSocket ;
OnClientWrite - возникает, когда сервер может отправлять данные клиенту по сокету;
OnGetSocket - в обработчике этого события Вы можете отредактировать параметр ClientSocket ;
OnGetThread - в обработчике этого события Вы можете определить уникальный процесс (Thread) для каждого отдельного клиентского канала, присвоив параметру SocketThread нужную подзадачу TServerClientThread;
OnThreadStart , OnThreadEnd - возникает, когда подзадача (процесс, Thread) запускается или останавливается, соответственно;
OnAccept - возникает, когда сервер принимает клиента или отказывает ему в соединении;
OnListen - возникает, когда сервер переходит в режим ожидания подсоединения клиентов.


TServerSocket.Socket (TServerWinSocket)


Итак, как же сервер может отсылать данные клиенту? А принимать данные? В основном, если Вы работаете через события OnClientRead и OnClientWrite , то общаться с клиентом можно через параметр ClientSocket (TCustomWinSocket). Про работу с этим классом можно прочитать в статье про клиентские сокеты, т.к. отправка/посылка данных через этот класс аналогична - методы (Send/Receive)(Text,Buffer,Stream). Также и при работе с TServerSocket.Socket. Однако, т.к. здесь мы рассматриваем сервер, то следует выделить некоторые полезные свойства и методы:

  • ActiveConnections (Integer ) - количество подключенных клиентов;
  • ActiveThreads (Integer ) - количество работающих процессов;
  • Connections (array ) - массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда:
    ServerSocket1.Socket.Connections.SendText("Hello!");
    отсылает первому подключенному клиенту сообщение "Hello!". Команды для работы с элементами этого массива - также (Send/Receive)(Text,Buffer, Stream);
  • IdleThreads (Integer ) - количество свободных процессов. Такие процессы кэшируются сервером (см. ThreadCacheSize );
  • LocalAddress , LocalHost , LocalPort - соответственно - локальный IP-адрес, хост-имя, порт;
  • RemoteAddress , RemoteHost , RemotePort - соответственно - удаленный IP-адрес, хост-имя, порт;
  • Методы Lock и UnLock - соответственно, блокировка и разблокировка сокета.

Практика и примеры


А теперь рассмотрим вышеприведенное на конкретном примере. Скачать уже готовые исходники можно, щелкнув .

Итак, рассмотрим очень неплохой пример работы с TServerSocket (этот пример - наиболее наглядное пособие для изучения этого компонента). В приведенных ниже исходниках демонстрируется протоколирование всех важных событий сервера, плюс возможность принимать и отсылать текстовые сообщения:

Пример 1. Протоколирование и изучение работы сервера, посылка/прием сообщений через сокеты

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{Полный исходник смотри }
procedure TForm1.Button1Click(Sender: TObject); begin {Определяем порт и запускаем сервер} ServerSocket1.Port:= 1025; {Метод Insert вставляет строку в массив в указанную позицию} Memo2.Lines.Insert(0,"Server starting"); ServerSocket1.Open; end; procedure TForm1.Button2Click(Sender: TObject); begin {Останавливаем сервер} ServerSocket1.Active:= False; Memo2.Lines.Insert(0,"Server stopped"); end; procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер "прослушивает" сокет на наличие клиентов} Memo2.Lines.Insert(0,"Listening on port "+IntToStr(ServerSocket1.Port)); end; procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер принимает клиента} Memo2.Lines.Insert(0,"Client connection accepted"); end; procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент подсоединяется} Memo2.Lines.Insert(0,"Client connected"); end; procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент отсоединяется} Memo2.Lines.Insert(0,"Client disconnected"); end; procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin {Произошла ошибка - выводим ее код} Memo2.Lines.Insert(0,"Client error. Code = "+IntToStr(ErrorCode)); end; procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); begin {От клиента получено сообщение - выводим его в Memo1} Memo2.Lines.Insert(0,"Message received from client"); Memo1.Lines.Insert(0,"> "+Socket.ReceiveText); end; procedure TForm1.ServerSocket1ClientWrite(Sender: TObject; Socket: TCustomWinSocket); begin {Теперь можно слать данные в сокет} Memo2.Lines.Insert(0,"Now can write to socket"); end; procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); begin Memo2.Lines.Insert(0,"Get socket"); end; procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin Memo2.Lines.Insert(0,"Get Thread"); end; procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0,"Thread end"); end; procedure TForm1.ServerSocket1ThreadStart(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0,"Thread start"); end; procedure TForm1.Button3Click(Sender: TObject); var i: Integer; begin {Посылаем ВСЕМ клиентам сообщение из Edit1} for i:= 0 to ServerSocket1.Socket.ActiveConnections-1 do begin ServerSocket1.Socket.Connections[i].SendText(Edit1.Text); end; Memo1.Lines.Insert(0,"

Приемы работы с TServerSocket (и просто с сокетами)


Хранение уникальных данных для каждого клиента.


Наверняка, если Ваш сервер будет обслуживать множество клиентов, то Вам потребуется хранить какую-либо информацию для каждого клиента (имя, и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к handle сокета, массивы клиентов, и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство - Data . На самом деле, Data - это всего-навсего указатель. Поэтому, записывая данные клиента в это свойство будьте внимательны и следуйте правилам работы с указателями (выделение памяти, определение типа, и т.д.)!

Посылка файлов через сокет.


Здесь мы рассмотрим посылку файлов через сокет (по просьбе JINX-а) :-). Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере:

Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile) )

Почему несколько блоков при передаче могут обьединяться в один


Это тоже по просьбе JINX-а:-). За это ему огромное спасибо! Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет - обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете - сеть, ограниченный трафик, и т.д.). Именно поэтому две команды:
ServerSocket1.Socket.Connections.SendText("Hello, ");
ServerSocket1.Socket.Connections.SendText("world!");
совершенно идентичны одной команде:
ServerSocket1.Socket.Connections.SendText("Hello, world!");

И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток - TFileStream (либо поток в памяти - TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf . Определить размер полученного блока можно методом ReceiveLength . Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный):

Как следить за сокетом


Это вопрос сложный и требует долгого рассмотрения. Пока лишь замечу, что созданный Вашей программой сокет Вы можете промониторить всегда:-). Сокеты (как и большинство объектов в Windows) имеют свой дескриптор (handle), записанный в свойстве Handle. Так вот, узнав этот дескриптор Вы свободно сможете управлять любым сокетом (даже созданным чужой программой)! Однако, скорее всего, чтобы следить за чужим сокетом, Вам придется использовать исключительно функции WinAPI Sockets.


В этой статье отображены основные приемы работы с компонентом TServerSocket в Дельфи и несколько общих приемов для обмена данными по сокетам. Если у Вас есть вопросы - скидывайте их мне на E-mail: [email protected] , а еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!

Карих Николай (Nitro ). Московская область, г.Жуковский

Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). В отличие от предыдущей статьи на тему сокетов, здесь мы разберем создание серверных приложений.
Следует сразу заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором Вы одновременно запустите и сервер, и клиент. При этом нужно в качестве имени компьютера, к которому надо подключиться, использовать хост-имя localhost или IP-адрес - 127.0.0.1.
Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.
Алгоритм работы сокетного сервера
Что же позволяет делать сокетный сервер?.. По какому принципу он работает?.. Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество Вы можете указать сами (или вообще убрать это ограничение, как это сделано по умолчанию). Для каждого подключенного клиента сервер открывает отдельный сокет, по которому Вы можете обмениваться данными с клиентом. Также отличным решением является создание для каждого подключения отдельного процесса (Thread).
Ниже следует примерная схема работы сокетного сервера в Дельфи-приложениях:

Разберем схему подробнее: ·Определение св-в Port и ServerType - чтобы к серверу могли нормально подключаться клиенты, нужно, чтобы порт, используемый сервером точно совпадал с портом, используемым клиентом (и наоборот). Свойство ServerType определяет тип подключения (подробнее см.ниже); ·Открытие сокета - открытие сокета и указанного порта. Здесь выполняется автоматическое начало ожидания подсоединения клиентов (Listen); ·Подключение клиента и обмен данными с ним - здесь подключается клиент и идет обмен данными с ним. Подробней об этом этапе можно узнать ниже в этой статье и в статье про сокеты (клиентская часть); ·Отключение клиента - Здесь клиент отключается и закрывается его сокетное соединение с сервером; ·Закрытие сервера и сокета - По команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов.
Следует заметить, что пункты 3-4 повторяются многократно, т.е. эти пункты выполняются для каждого нового подключения клиента.
Примечание: Документации по сокетам в Дельфи на данный момент очень мало, так что, если Вы хотите максимально глубоко изучить эту тему, то советую просмотреть литературу и электронную документацию по Unix/Linux-системам - там очень хорошо описана теория работы с сокетами. Кроме того, для этих ОС есть множество примеров сокетных приложений (правда, в основном на C/C++ и Perl).
Краткое описание компонента TServerSocket
Здесь мы познакомимся с основными свойствами, методами и событиями компонента
Свойства
Socket - класс TServerWinSocket, через который Вы имеете доступ к открытым сокетным каналам. Далее мы рассмотрим это свойство более подробно, т.к. оно, собственно и есть одно из главных. Тип: TServerWinSocket;
ServerType - тип сервера. Может принимать одно из двух значений: stNonBlocking - синхронная работа с клиентскими сокетами. При таком типе сервера Вы можете работать с клиентами через события OnClientRead и OnClientWrite. stThreadBlocking - асинхронный тип. Для каждого клиентского сокетного канала создается отдельный процесс (Thread). Тип: TServerType;
ThreadCacheSize - количество клиентских процессов (Thread), которые будут кэшироваться сервером. Здесь необходимо подбирать среднее значение в зависимости от загруженности Вашего сервера. Кэширование происходит для того, чтобы не создавать каждый раз отдельный процесс и не убивать закрытый сокет, а оставить их для дальнейшего использования. Тип: Integer;
Active - показатель того, активен в данных момент сервер, или нет. Т.е., фактически, значение True указывает на то, что сервер работает и готов к приему клиентов, а False - сервер выключен. Чтобы запустить сервер, нужно просто присвоить этому свойству значение True. Тип: Boolean;
Port - номер порта для установления соединений с клиентами. Порт у сервера и у клиентов должны быть одинаковыми. Рекомендуются значения от 1025 до 65535, т.к. от 1 до 1024 - могут быть заняты системой. Тип: Integer;
Service - строка, определяющая службу (ftp, http, pop, и т.д.), порт которой будет использован. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам. Тип: string;
Методы
Open - Запускает сервер. По сути, эта команда идентична присвоению значения True свойству Active;
Close - Останавливает сервер. По сути, эта команда идентична присвоению значения False свойству Active.
События
OnClientConnect - возникает, когда клиент установил сокетное соединение и ждет ответа сервера (OnAccept);
OnClientDisconnect - возникает, когда клиент отсоединился от сокетного канала;
OnClientError - возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка;
OnClientRead - возникает, когда клиент передал берверу какие-либо данные. Доступ к этим данным можно получить через пеаедаваемый параметр Socket: TCustomWinSocket;
OnClientWrite - возникает, когда сервер может отправлять данные клиенту по сокету;
OnGetSocket - в обработчике этого события Вы можете отредактировать параметр ClientSocket;
OnGetThread - в обработчике этого события Вы можете определить уникальный процесс (Thread) для каждого отдельного клиентского канала, присвоив параметру SocketThread нужную подзадачу TServerClientThread;
OnThreadStart, OnThreadEnd - возникает, когда подзадача (процесс, Thread) запускается или останавливается, соответственно;
OnAccept - возникает, когда сервер принимает клиента или отказывает ему в соединении;
OnListen - возникает, когда сервер переходит в режим ожидания подсоединения клиентов.
TServerSocket.Socket (TServerWinSocket)
Итак, как же сервер может отсылать данные клиенту? А принимать данные? В основном, если Вы работаете через события OnClientRead и OnClientWrite, то общаться с клиентом можно через параметр ClientSocket (TCustomWinSocket). Про работу с этим классом можно прочитать в статье про клиентские сокеты, т.к. отправка/посылка данных через этот класс аналогична - методы (Send/Receive)(Text,Buffer,Stream). Также и при работе с TServerSocket.Socket. Однако, т.к. здесь мы рассматриваем сервер, то следует выделить некоторые полезные свойства и методы: ·ActiveConnections (Integer) - количество подключенных клиентов; ·ActiveThreads (Integеr) - количество работающих процессов; ·Connections (array) - массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда:·ServerSocket1.Socket.Connections.SendText("Hello!");·отсылает первому подключенному клиенту сообщение "Hello!". Команды для работы с элементами этого массива - также (Send/Receive)(Text,Buffer, Stream); ·IdleThreads (Integer) - количество свободных процессов. Такие процессы кэшируются сервером (см. ThreadCacheSize); ·LocalAddress, LocalHost, LocalPort - соответственно - локальный IP-адрес, хост-имя, порт; ·RemoteAddress, RemoteHost, RemotePort - соответственно - удаленный IP-адрес, хост-имя, порт; ·Методы Lock и UnLock - соответственно, блокировка и разблокировка сокета.
Практика и примеры
А теперь рассмотрим вышеприведенное на конкретном примере. Скачать уже готовые исходники можно, щелкнув здесь.
Итак, рассмотрим очень неплохой пример работы с TServerSocket (этот пример - наиболее наглядное пособие для изучения этого компонента). В приведенных ниже исходниках демонстрируется протоколирование всех важных событий сервера, плюс возможность принимать и отсылать текстовые сообщения:
Пример 1. Протоколирование и изучение работы сервера, посылка/прием сообщений через сокеты.

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

{Полный исходник смотри здесь}

procedure TForm1.Button1Click (Sender: TObject ) ;

begin

{Определяем порт и запускаем сервер}

ServerSocket1.Port := 1025 ;

{Метод Insert вставляет строку в массив в указанную позицию}

Memo2.Lines .Insert (0 ,"Server starting" ) ;

ServerSocket1.Open ;

end ;

procedure TForm1.Button2Click (Sender: TObject ) ;

begin

{Останавливаем сервер}

ServerSocket1.Active := False ;

Memo2.Lines .Insert (0 ,"Server stopped" ) ;

end ;

procedure TForm1.ServerSocket1Listen (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{Здесь сервер "прослушивает" сокет на наличие клиентов}

Memo2.Lines .Insert (0 ,"Listening on port " +IntToStr (ServerSocket1.Port ) ) ;

end ;

procedure TForm1.ServerSocket1Accept (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{Здесь сервер принимает клиента}

Memo2.Lines .Insert (0 ,"Client connection accepted" ) ;

end ;

procedure TForm1.ServerSocket1ClientConnect (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{Здесь клиент подсоединяется}

Memo2.Lines .Insert (0 ,"Client connected" ) ;

end ;

procedure TForm1.ServerSocket1ClientDisconnect (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{Здесь клиент отсоединяется}

Memo2.Lines .Insert (0 ,"Client disconnected" ) ;

end ;

procedure TForm1.ServerSocket1ClientError (Sender: TObject ;

Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;

var ErrorCode: Integer ) ;

begin

{Произошла ошибка - выводим ее код}

Memo2.Lines .Insert (0 ,"Client error. Code = " +IntToStr (ErrorCode) ) ;

end ;

procedure TForm1.ServerSocket1ClientRead (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{От клиента получено сообщение - выводим его в Memo1}

Memo2.Lines .Insert (0 ,"Message received from client" ) ;

Memo1.Lines .Insert (0 ,"> " +Socket.ReceiveText ) ;

end ;

procedure TForm1.ServerSocket1ClientWrite (Sender: TObject ;

Socket: TCustomWinSocket) ;

begin

{Теперь можно слать данные в сокет}

Memo2.Lines .Insert (0 ,"Now can write to socket" ) ;

end ;

procedure TForm1.ServerSocket1GetSocket (Sender: TObject ; Socket: Integer ;

var ClientSocket: TServerClientWinSocket) ;

begin

Memo2.Lines .Insert (0 ,"Get socket" ) ;

end ;

procedure TForm1.ServerSocket1GetThread (Sender: TObject ;

ClientSocket: TServerClientWinSocket;

var SocketThread: TServerClientThread) ;

begin

Memo2.Lines .Insert (0 ,"Get Thread" ) ;

end ;

procedure TForm1.ServerSocket1ThreadEnd (Sender: TObject ;

begin

Memo2.Lines .Insert (0 ,"Thread end" ) ;

end ;

procedure TForm1.ServerSocket1ThreadStart (Sender: TObject ;

Thread: TServerClientThread) ;

begin

Memo2.Lines .Insert (0 ,"Thread start" ) ;

end ;

procedure TForm1.Button3Click (Sender: TObject ) ;

var i: Integer ;

begin

{Посылаем ВСЕМ клиентам сообщение из Edit1}

for i:= 0 to ServerSocket1.Socket .ActiveConnections -1 do begin

ServerSocket1.Socket .Connections [ i] .SendText (Edit1.Text ) ;

end ;

Memo1.Lines .Insert (0 ,"< " +Edit1.Text ) ;

end ;

Далее мы будем рассматривать уже не примеры, а приемы работы с TServerSocket.
Приемы работы с TServerSocket (и просто с сокетами)
Хранение уникальных данных для каждого клиента.
Наверняка, если Ваш сервер будет обслуживать множество клиентов, то Вам потребуется хранить какую-либо информацию для каждого клиента (имя, и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к handle сокета, массивы клиентов, и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство - Data. На самом деле, Data - это всего-навсего указатель. Поэтому, записывая данные клиента в это свойство будьте внимательны и следуйте правилам работы с указателями (выделение памяти, определение типа, и т.д.)!
Посылка файлов через сокет.
Здесь мы рассмотрим посылку файлов через сокет (по просьбе JINX-а) :-). Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере:

{Посылка файла через сокет}

procedure SendFileBySocket(filename: string ) ;

var srcfile: TFileStream;

begin

{Открываем файл filename}

Srcfile:= TFileStream.Create (filename,fmOpenRead) ;

{Посылаем его первому подключенному клиенту}

ServerSocket1.Socket .Connections [ 0 ] .SendStream (srcfile) ;

{Закрываем файл}

Srcfile.Free ;

end ;

Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile))
Почему несколько блоков при передаче могут обьединяться в один
Это тоже по просьбе JINX-а:-). За это ему огромное спасибо! Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет - обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете - сеть, ограниченный трафик, и т.д.). Именно поэтому две команды:
ServerSocket1.Socket.Connections.SendText("Hello, ");
ServerSocket1.Socket.Connections.SendText("world!");
совершенно идентичны одной команде:
ServerSocket1.Socket.Connections.SendText("Hello, world!");
И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток - TFileStream (либо поток в памяти - TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf. Определить размер полученного блока можно методом ReceiveLength. Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный):

{Прием файла через сокет}

procedure TForm1.ClientSocket1Read (Sender: TObject ;

Socket: TCustomWinSocket) ;

var l: Integer ;

Buf: PChar ;

Src: TFileStream;

begin

{Записываем в l размер полученного блока}

L:= Socket.ReceiveLength ;

{Заказываем память для буфера}

GetMem (buf,l+1 ) ;

{Записываем в буфер полученный блок}

Socket.ReceiveBuf (buf,l) ;

{Открываем временный файл для записи}

Src:= TFileStream.Create ("myfile.tmp" ,fmOpenReadWrite) ;

{Ставим позицию в конец файла}

Src.Seek (0 ,soFromEnd) ;

{Записываем буфер в файл}

Src.WriteBuffer (buf,l) ;

{Закрываем файл}

Src.Free ;

{Освобождаем память}

FreeMem (buf) ;

end ;

Как следить за сокетом
Это вопрос сложный и требует долгого рассмотрения. Пока лишь замечу, что созданный Вашей программой сокет Вы можете промониторить всегда:-). Сокеты (как и большинство объектов в Windows) имеют свой дескриптор (handle), записанный в свойстве Handle. Так вот, узнав этот дескриптор Вы свободно сможете управлять любым сокетом (даже созданным чужой программой)! Однако, скорее всего, чтобы следить за чужим сокетом, Вам придется использовать исключительно функции WinAPI Sockets.
Эпилог
В этой статье отображены основные приемы работы с компонентом TServerSocket в Дельфи и несколько общих приемов для обмена данными по сокетам. Если у Вас есть вопросы - скидывайте их мне на E-mail: [email protected] , а еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!
Карих Николай (Nitro). Московская область, г.Жуковский

Доброго времени суток, уважаемые!
Вопрос у меня немного поверхностный, но важный (по крайней мере, для меня:)), больше похоже на "посоветуйте" -).

Есть задача, написать аналог HTTP(S) прокси-сервера, но с небольшой спецификой: запросы некоторых подключенных клиентов будут обрабатываться по-другому и обмен с такими клиентами будет происходить маленькими пакетами. Остальным клиентам ("не особо одаренным") должно быть также хорошо, как и с обычным прокси:). Основные требования, предъявляемые к такому серверу - быть устойчивым к капризным клиентам (это те, кто маленькими пакетами будет пуляться) и всем остальным клиентам, и держать максимально возможное (насколько позволят ОС+hardware и др.) количество одновременных соединений (например, 100:)). В то же время, сервер не должен получиться громоздким и слишком сложным.

Из арсенала имеются только стандартные средства Delphi (Indy и другие библиотеки отбрасываем).

Собственно вопрос заключается в том, что использовать в качестве базы и какая модель приложения наиболее пригодна (по вашему мнению) для описанной выше задачи.

Первое, что приходит в голову - TTCP, TSocket или голое API of WinSock.

С TSocket работал давно (последний раз лет 6-7 назад) - в то время очень не понравилось. Некоторые пакеты пропускались (хотя TCP считают "гарантированным"). Когда тут на форуме спросил в чем дело, мне тогда намекнули, если мне не изменяет память, на метод Nagle. Но проблему так и не решил (в те времена).

Вчера "тест-драйв" попытался устроить TCPServer`у. НаписАл на базе него простенький прокси для обработки GET запросов, который, в общем-то, даже работал, но мееееедленно. Реагировал на событие OnAccept, режим блокировки - bmThreadBlocking, читал и писАл из\в сокеты используя ReceiveBuf и SendBuf.

В общем, уважаемые, каким образом лучше организовать работу сервера (прием соединений на сервере и создание временных подключений внутри потока для получения данных с другого сервера и отдачи их обратно клиенту) и с помощью чего:
* ПисАть на голом WinSock API, потоки создавать самому, работать в блокирующем режиме
* ПисАть на голом WinSock API, работать в неблокирующем режиме, тогда, как я понимаю, потоки создавать не нужно будет
* Аналогично описанному выше, только с использванием в качестве опорной точки один из компонентов\классов (например, (T)TCP, (T)Socket)
* Применять "фишку" Delphi - режим ThreadBlocking
* что-либо другое

Заранее благодарю всех дочитавших до конца и ответивших!


DVM ( 2010-05-11 18:43 )

чем Indy то не угодил?


kernel ( 2010-05-11 18:50 )


> DVM © (11.05.10 18:43)
> чем Indy то не угодил?

Лицензией, тяжестью исправления (в том смысле, что исправлять много приходится, чтобы что-либо добавить\изменить на более низком уровне), потом, помню, были проблемы с убиванием потоков (http://сайт/view/6-1236577141/).


DVM ( 2010-05-11 19:34 )


> kernel © (11.05.10 18:50)

Если хочешь максимум контроля над сервером - делай все сам на WinSock. Я бы наверное выбрал блокирующий режим и что-то типа пула потоков.


Slym ( 2010-05-11 20:37 )

kernel © (11.05.10 18:07)
НаписАл на базе него простенький прокси для обработки GET запросов

Твоя задача сводится
1. принять заголовок у клиента, выдрать от туда host:port заказаного сервера
2. сконектиться по host:port и полностью возможно с минимальными изменениями отправить принятые в п1. от клиента данные
3. обеспечить прозрачное тунеллирование трафика клиент/сервер
4. (ОПЦИЯ) затормозить особо ретивые конекты наример тупым sleep (при буфере сокета в 8кб + sleep(1000) получим 8кб/сек максимум)


Сергей М. ( 2010-05-11 22:02 )


> пакеты пропускались (хотя TCP считают "гарантированным").
> Когда тут на форуме спросил в чем дело, мне тогда намекнули,
> если мне не изменяет память, на метод Nagle

Похоже ты уже и сам не помнишь о чем был твой вопрос.
Используется или не используется Nagle - на "пропуск пакетов" он повлиять никак не может в принципе .


kernel ( 2010-05-13 14:08 )

Благодарю всех за ответы!


> DVM © (11.05.10 19:34)

Все что нужно из контроля - общение с клиентом с заданными размерами пакетов (маленькими пакетами для вредных клиентов и большими - для остальных) с возможностью их "переработки" и подсчет отправленных\полученных данных. Все это, если я не ошибаюсь, успешно выполняют и обычные сокетообразные компоненты. Или все равно лучше применить чистый WinSock? :) Тут, наверное, больше интересует у кого из них (сок. компоненты vs WinSock API) производительность будет побольше. Или в этом плане разницы никакой нет?

> Slym © (11.05.10 20:37)

Ну как работает прокси мне известно. Делал именно так, как Вы описали.

> Сергей М. © (11.05.10 22:02)

Действительно подзабыл. Нашел те старые исходники на этих компонентах, там скорее всего была проблема в том, что в короткий промежуток времени сервер сам цеплялся к клиентам и отправлял всем клиентам команды. Для того чтобы всем все успешно отправилось, необходимо было ставить задержку около 1 сек. (по крайней мере у меня работало только не < ~1 сек.) после отсылки команды каждому ПК. Вот тут мне про Nagle скорее всего и намекнули.


DVM ( 2010-05-13 16:25 )


> kernel © (13.05.10 14:08)


> Тут, наверное, больше интересует у кого из них (сок. компоненты
> vs WinSock API) производительность будет побольше. Или
> в этом плане разницы никакой нет?

Компоненты они же на базе WinSock построены. Разницы между ними и чистым Winsock практически нет. По крайней мере это не то место, где следует искать разницу.


DVM ( 2010-05-13 16:26 )


> kernel © (13.05.10 14:08)

Другое дело, что вокруг WinSock можно написать свои простенькие обертки, больше подходящие к конкретной задаче - в этом их удобство.


kernel ( 2010-05-14 21:46 )

Спасибо за ответы, DVM !
Сделал свой класс на основе ServerSocket, все работает быстро и замечательно [тьфу-тьфу-тьфу], контроля полностью хватает.
PS: собст-но это сообщение и отправил через пробный прокси:)


kernel ( 2010-05-18 17:36 )

Всем привет! Чтобы не создавать отдельный топик, спрошу здесь (т.к. разговор связан с этой темой).

Организовал нужный мне функционал на базе TServerSocket (режим - stThreadBlocking) с созданием отдельных потоков для каждого установленного соединения. Дошел до момента, где мне необходимо делать туннелирование между [клиент] <-> ["прокси-сервер"] <-> [запрашиваемый сервер] для работы SSL. Механизм такой: клиент посылает мне запрос CONNECT (HTTP/1.0), затем после того, как я (прокси) отправляю заголовок "200 OK - соединение установлено", в цикле, пока любое из двух соединений не разорвется (клиент или TargetHost), ловлю данные в буфер от клиента и тут же передаю запрашиваемому серверу, пока от клиента не начнет приходить ноль байт. Затем то же самое делаю, но наоборот, ловлю данные в буфер от запрашиваемого сервера и тут же передаю клиенту. И это все повторяется до тех пор, пока кто-либо из них не разорвет соединение.

...
const BufSize = 128;
...
ClientStream: TWinSocketStream;
TargetConnection: TTcpClient;
Buf: array of Byte;
...
ClientStream:= TWinSocketStream.Create(ClientSocket, 60000);
...
while (КлиентИПунктНазначенияПодключены) do begin
ClientWait:= (ClientStream.WaitForData(100));
if ClientWait then begin
while (ClientSocket.Connected) and (TargetConnection.Connected) and (RecLen > 0) do begin
TargetConnection.SendBuf(Buf, RecLen);
if (not ClientStream.WaitForData(10)) then Break;
RecLen:= ClientStream.Read(Buf, BufSize);
end;
end;

ServerWait:= TargetConnection.WaitForData(100);
if ServerWait then begin
while (ClientSocket.Connected) and TargetConnection.Connected) and (RecLen > 0) do begin
ClientStream.Write(Buf, RecLen);
if (not ClientSocket.Connected) or (not TargetConnection.Connected) then Break;
if (not TargetConnection.WaitForData(10)) then Break;
RecLen:= TargetConnection.ReceiveBuf(Buf, BufSize);
end;
end;
end;

Все бы хорошо (и даже отрабатываются несколько проходов общего цикла верно), но практически сразу общий цикл замирает (точнее, не замирает, а бесконечно крутится) и наблюдается странная картина: ClientStream.WaitForData(100) в начале цикла начинает все время возвращать True, в то время как количество принятых байт (RecLen:= ClientStream.Read ... ) с этого сокета становится все время равно нулю. При этом, оба соединения не дисконнэктятся (хотя с чего бы им разрываться, если данные "недопередались").

Вопрос: Что я делаю не так? :)


kernel ( 2010-05-18 17:40 )

То есть, проще говоря, через некоторое время цикл "крутится", а данные не хочет отдавать ни один сокет:/



Рекомендуем почитать

Наверх