Как закрыть сокет delphi

Обновлено: 05.07.2024

Здесь интересно применён класс TMultiReadExclusiveWriteSynchronizer. Он используется для предотвращения попытки закрыть сокет и уничтожить объект из другой нити пула (fClosingLock.BeginRead). Все операции с сокетом проходят как операции чтения для этого объекта синхронизации, кроме операции создания и операции закрытия сокета — они являются операциями записи и потому могут выполняться только при монопольном владении ресурсом.
Во всём же остальном работа с сокетами в данной процедуре совершенно обыкновенная.
Единственное что в этой процедуре стоит рассмотреть дополнительно — это подключение нового клиента к серверу, метод DoAccept.

Здесь ключевым моментом является использование WSAAccept. Эта функция позволяет отклонять подключение клиентов таким образом, что клиент на самом деле получает событие FD_CONNECT.
Это предочтительный путь для организации так называемых чёрных списков.
Идём далее. Расмотрим организацию ввода вывода. Сделаем это на примере операции чтения.

Суть этой процедуры в том, что она вызывает чтение или запись в сокет до того момента, когда выделенный буфер не окажется заполненным. Интересный момент в данном случае, это определение типа операции по ссылке на оверлапед структуру. Эту ссылку предоставляет очередь и необходимо лишь сравнить её с соответсвующими полями класса, в которых храняться структуры для чтения и записи.
Так же примечательно, то что если операция чтения/записи выполнилась мгновенно, то она всё равно попадает в очередь, однако это можно настроить через апи.
Стоит так же рассмотреть создание класса сокета и внедрение в очередь.

В конструкторе клиентского сокета создаётся непосрественно сокет (WSASocket), регестрируется в очереди (CreateIoCompletionPort), асоциируется с событием и вызывает асинхронную функцию подключения(WSAConnect). Сам факт подключения ожидается в потоке который был рассмотрен первым(поток ожидания событий в сокетах). Тот в свою очередь поставит это событие в очередь.

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

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

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

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

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

Описание свойств и методов компонента TClientSocket

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

Active - показывает, открыт сокет или нет. Тип: Boolean . Соответственно, True - открыт, а False - закрыт. Это свойство доступно для записи;
Host - строка ( Тип: string ), указывающая на хост-имя компьютера, к которому следует подключиться;
Address - строка ( Тип: string ), указывающая на IP-адрес компьютера, к которому следует подключиться. В отличие от Host , здесь может содержаться лишь IP. Отличие в том, что если Вы укажете в Host символьное имя компьютера, то IP адрес, соответствующий этому имени будет запрошен у DNS;
Port - номер порта ( Тип: Integer (Word) ), к которому следует подключиться. Допустимые значения - от 1 до 65535 ;
Service - строка ( Тип: string ), определяющая службу ( ftp , http , pop , и т.д.), к порту которой произойдет подключение. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам;
ClientType - тип соединения. ctNonBlocking - асинхронная передача данных, т.е. посылать и принимать данные по сокету можно с помощью OnRead и OnWrite . ctBlocking - синхронная (одновременная) передача данных. События OnRead и OnWrite не работают. Этот тип соединения полезен для организации обмена данными с помощью потоков (т.е. работа с сокетом как с файлом);
Open - открытие сокета (аналогично присвоению значения True свойству Active );
Close - закрытие сокета (аналогично присвоению значения False свойству Active );

На этом все методы компонента TClientSocket исчерпываются. А Вы спросите: "А как же работать с сокетом? Как тогда пересылать данные?". Об этом Вы узнаете чуть дальше.

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

Легче всего (и полезней) изучать любые методы программирования на практике. Поэтому далее приведены примеры с некоторыми комментариями:

Пример 1. Простейшая сокетная программа

procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin

ClientSocket1.Close;
end;

Если Вы думаете, что данный пример программы совершенно бесполезен и не может принести никакой пользы, то глубоко ошибаетесь. Приведенный код - простейший пример сканера портов (PortScanner). Суть такой утилиты в том, чтобы проверять, включен ли указанный порт и готов ли он к приему/передаче данных. Именно на таком принципе основан PortScanner из программы NetTools Pro.

procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin

Socket.SendText('Hello!');
ListBox1.Items.Add('< Hello!');
end;

procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
begin

ListBox1.Items.Add('> '+Socket.ReceiveText);
end;

procedure Button2Click(Sender: TObject);
begin

ClientSocket1.Socket.SendText(Edit3.Text);
ListBox1.Items.Add('< '+Edit3.Text);
end;

Работа с сокетным потоком

"А как еще можно работать с сокетом?", - спросите Вы. Естественно, приведенный выше метод - не самое лучшее решение. Самих методов организации работы с сокетами очень много. Я приведу лишь еще один дополнительный - работа через поток. Наверняка, многие из Вас уже имеют опыт работы, если не с потоками (stream), то с файлами - точно. Для тех, кто не знает, поток - это канал для обмена данными, работа с которым аналогична работе с обычным файлом. Нижеприведенный пример показывает, как организовать поток для работы с сокетом:

Пример 3. Поток для работы с сокетом
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var c: Char;
MySocket: TWinSocketStream;
begin

MySocket := TWinSocketStream.Create(Socket,60000);

while not MySocket.WaitForData(100) do
Application.ProcessMessages;

MySocket.Read(c,1);

MySocket.Write(c,1);

MySocket.Free;

end;

ПРИМЕЧАНИЕ: Для использования потока не забудьте установить свойство ClientType в ctBlocking .

Посылка/прием сложных данных

Методы TClientSocket.Socket (TCustomWinSocket, TClientWinSocket) :

  • SendBuf (var Buf; Count: Integer) - Посылка буфера через сокет. Буфером может являться любой тип, будь то структура (record), либо простой Integer . Буфер указывается параметром Buf , вторым параметром Вы должны указать размер пересылаемых данных в байтах ( Count );
  • SendText (const S: string) - Посылка текстовой строки через сокет. Этот метод рассматривался в примере 2 (см.выше);
  • SendStream (AStream: TStream) - Посылка содержимого указанного потока через сокет. Пересылаемый поток должен быть открыт. Поток может быть любого типа - файловый, из ОЗУ, и т.д. Описание работы непосредственно с потоками выходит за рамки данной статьи;

Всем перечисленным методам соответствуют методы Receive. Их описание можно посмотреть в справочном файле по Дельфи (VCL help).

Авторизация на сервере

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

Эпилог

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

Здесь интересно применён класс TMultiReadExclusiveWriteSynchronizer. Он используется для предотвращения попытки закрыть сокет и уничтожить объект из другой нити пула (fClosingLock.BeginRead). Все операции с сокетом проходят как операции чтения для этого объекта синхронизации, кроме операции создания и операции закрытия сокета — они являются операциями записи и потому могут выполняться только при монопольном владении ресурсом.
Во всём же остальном работа с сокетами в данной процедуре совершенно обыкновенная.
Единственное что в этой процедуре стоит рассмотреть дополнительно — это подключение нового клиента к серверу, метод DoAccept.

Здесь ключевым моментом является использование WSAAccept. Эта функция позволяет отклонять подключение клиентов таким образом, что клиент на самом деле получает событие FD_CONNECT.
Это предочтительный путь для организации так называемых чёрных списков.
Идём далее. Расмотрим организацию ввода вывода. Сделаем это на примере операции чтения.

Суть этой процедуры в том, что она вызывает чтение или запись в сокет до того момента, когда выделенный буфер не окажется заполненным. Интересный момент в данном случае, это определение типа операции по ссылке на оверлапед структуру. Эту ссылку предоставляет очередь и необходимо лишь сравнить её с соответсвующими полями класса, в которых храняться структуры для чтения и записи.
Так же примечательно, то что если операция чтения/записи выполнилась мгновенно, то она всё равно попадает в очередь, однако это можно настроить через апи.
Стоит так же рассмотреть создание класса сокета и внедрение в очередь.

В конструкторе клиентского сокета создаётся непосрественно сокет (WSASocket), регестрируется в очереди (CreateIoCompletionPort), асоциируется с событием и вызывает асинхронную функцию подключения(WSAConnect). Сам факт подключения ожидается в потоке который был рассмотрен первым(поток ожидания событий в сокетах). Тот в свою очередь поставит это событие в очередь.

Пожалуйста, выделяйте текст программы тегом [сode=pas] . [/сode] . Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Указывайте точные версии Delphi и используемых сетевых библиотек.

'> Правила работы с соединениями в Indy 10 , Возможные варианты создания и закрытия соединений в Indy 10



Когда я работаю с Indy9, то делаю так(в версии 10 должно быть также):

А ты пробовал сменить порт?

Добавлено 01.10.07, 03:42
попробуй ещё все вызовы

if IdTCPClient1.Connected then IdTCPClient1.Disconnect; Порт железно свободный (проверял netstat -a).
Попробовал заменить все дисконнекты на
if IdTCPClient1.Connected then IdTCPClient1.Disconnect;
После двухчасового тестирования ошибка повторилась. Может ли быть проблема в постоянных обрывах связи (физической)? Ну была похожая. Так то же и не придумал что с этим делать.
Для того, что бы уж точно он решил, что связь разорвана я просто пытался читать из буфера что-нибудь в OnDisconnect и обрабатывал EIdConnClosedGracefully.
Примерно так:
procedure TiaKiosk.DoOnClientDisconnectedEvent(Sender: TObject);



VladDV, а за свой код ты 100% уверен?

Добавлено 02.10.07, 04:38
Просто когда я столкнулся с этим, проблема оказалась в самом коде. Попробуй потрассировать программу - проверь код

To Felan: Спасибо за вариант. Я приблизительно сейчас также делаю, только принцип немного другой. При попытке подключения отдельно слежу за исключением Already connected. При его возникновении закрываю соединение немного извратным способом:

Затем выжидаю некоторое время (около 10 сек) и пытаюсь подключиться снова. Как правило после этого подключение проходит нормально (уже тестирую три часа). Почему выжидаю время. На одном форуме наткнулся на такой ответ по данной проблеме (привожу как есть, на английском):

>this is a normal situation, if you are running blocking sockets.
>There is a solution, to send NOOP messages to the server all
>ten seconds, and capture error (try . except . end)
>and try to reconnect.

К сожалению ссылка на сайт утеряна
Чтобы пока не вносить изменения в протокол (не реализовывать команду noop), я решил принудительно обрывать соединение, выжидать и снова подключаться. Использование стандартного Disconnect не помогло. Вычитал на официальном сайте Indy, что исключение генерит IOHandler, потому пытаюсь закрывать его.
Это конечно корявое решение, но лучше, чем его отсутствие. Его можно считать временной заплаткой.

To Аэтерос Дельфийский: На 100% никогда нельзя быть уверенным Код уже достаточно большой, не исключено, что и там глюк. Но я уже несколько раз его просмотрел, пока ничего не увидел. Трассировать тоже не реально, т.к. ошибку приходится ждать не один час.

А в чем у тебя проблема в коде была? Хотя бы направление, в каком нужно мыслить?

Добавлено 02.10.07, 05:39
Для лучшего понимания ситуация выкладываю код обработчика таймера. Поскольку проект является закрытым, весь код выложить не смогу. Оставляю только то, что относится к организации связи на клиенте (код сильно не судите, сетевым программированием я занялся совсем недавно )

Читайте также: