Создайте новое приложение. Сразу же подключите в раздел uses модуль WinSock. Мы пока будем использовать функции только первой версии библиотеки, поэтому заголовочного файла из состава Delphi будет достаточно.

Теперь поместите на форму только одну кнопку, с помощью которой будет происходить запуск TCP-сервера на компьютере. По нажатии этой кнопки нужно написать код из листинга 4.1.

Листинг 4.1. Запуск ТСР-сервера

procedure TForml bStartServerClick(Sender: TObject); var
wData: WSADATA;

sServerListen, sClient TSOCKET. localaddr. clientaddr SockAddr_In; iSize: Integer: si: TCPClientThread. begin // Загрузка WinSock

if WSAStartup(MAKEWORDQ.l). wData) <> 0 then begin
MessageBoxCO, 'He могу загрузить WinSock' 'Ошибка' 0);
exit;
end.

// Создание сокета

sServerListen := socket(AF_INET, SOCK_STREAM. IPPROTOJP);

if sServerListen = INVALID_SOCKET then begin MessageBox(0, 'Ошибка создания сокета' 'Ошибка'. 0); exit:

end.

// Заполнение структуры адреса

localaddr.sin_addr.s_addr •= htonl(INADDR_ANY);
localaddr.sin_family := AF_INET;
localaddr.sin_port .= htons(5050);

// Связывание сокета с локальным адресом

if bmd(sServerListen. localaddr sizeof(localaddr)) = SOCKETJRROR then begin
TestWinSockError('Bind'). exit:
end;

// Прослушивание

if TestFuncError(listen(sServerListen. 4). 'Listen') then exit;
MessageBox(0, 'Сервер запущен' 'Внимание!!!' 0);

while (true) do begin iSize -= sizeof(clientaddr). // Прием нового соединения

sClient :•>
accept (sServerListen. @cl ientaddr. @iSize):
if sClient = INVALID_SOCKET then
begin продолжение &
Листинг 4.1 (продолжение)
TestWinSockError('accept'): break;
end:

// Соединение принято, создаем поток si := TCPClientThread Create(true); si.Sock := sClient: si.Resume:

end:
closesocket(sServerListen):
end:

В самом начале мы загружаем сетевую библиотеку Windows с помощью функции WSAStartup. Нам достаточно будет версии 1.1, поэтому в качестве первого параметра передается значение MAKEWORD(1.1). Если не удается загрузить библиотеку, то выдаем на экран соответствующее сообщение.

Теперь мы должны создать сокет. Для этого вызывается функция socket, а результат сохраняется в переменной sServerListen. Если результат равен SOCKETERROR, то произошла ошибка, и мы просто выводим соответствующее сообщение, а в реальном приложении я рекомендую получить код ошибки и проанализировать ее. Таким образом, пользователь сможет легко устранить причину ошибки и перезапустить сервер.

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

Теперь заполняем структуру SockAddr_In. Здесь важно правильно указать поле si n_port. В качестве порта будем использовать 5050, но если просто присвоить это число параметру sin_port, то результат может оказаться не таким, как мы предполагаем. Это связано с тем, что при передаче в сети (маршрутизаторами, серверами) используется другой порядок байтов, отличный от принятого в процессорах Intel. Число надо преобразовывать с помощью функции htons, которая описывается следующим образом:

function htons(
hostshort: u_short ): u_short, stdcall:

В качестве единственного параметра передается число, в котором надо изменить порядок байтов на принятый в сетях TCP/IP. Функция возвращает в качестве результата преобразованное число.

Теперь связываем структуру SockAddr_In с локальным адресом при помощи функции bind. В нашем случае полученная ошибка анализируется с использованием процедуры TestWinSockError. В универсальной процедуре проверяются все коды сетевых ошибок (листинг 4.2).

Листинг 4.2. Проверка ошибок

procedure TestWinSockError(S:String). var iErr:Integer. sFu"n Err-String: begin sFullErr = 'Неизвестная ошибка' iErr := WSAGetLastErrorO:

case iErr of
WSANOTINITIALISED: sFullErr :=

'Нужно сначала вызвать функцию WSASturtup. а потом создавать сокет': WSAENETDOWN: sFul1 Err := 'Связь нарушена, возможные причины -

отошел кабель или отключились от Интернета' WSAEADDRINUSE: sFullErr := 'Указанный адрес уже используется': WSAEFAULT sFullErr := 'Параметры name nnamelen не соответствуют выбранной адресации Параметр namelen может быть меньше необходимого значения, a name содержать некорректные данные': WSAEINPROGRESS: sFullErr := 'Выполняется операция в блокирующем режиме

Вы уже запустили на выполнение какую-то функцию и нужно дождаться завершения ее работы': WSAEINVAL: sFullErr := 'Сокет уже связан с адресом'; WSAENOBUFS: sFullErr := 'Недостаточно буферов, слишком много соединений';

WSAENOTSOCK: sFullErr := 'Неверный дескриптор сокета':

WSAEISCONN: sFullErr := 'Сокет уже подключен':

WSAEMFILE: sFullErr := 'Нет больше доступных дескрипторов'

// Проверка остальных сетевых ошибок end:

MessageBox(0. PChar('Ошибка вфункции '+S+' '+sFullErr) 'Ошибка' 0):

end:

Процедуру TestWinSockError можно было вызвать и после ошибки выполнения функции socket, но я не стал этого делать, чтобы показать разные методы обработки ошибок. Чуть позже мы увидим еще один способ.

Давайте отвлечемся от создания сервера и разберем процедуру TestWinSockError. Здесь только один строковый параметр, через который нужно передавать название последней выполненной сетевой функции, где произошла ошибка.

Сначала мы присваиваем переменной sFullErr сообщение "Неизвестная ошибка". Если в дальнейшем не будет найден соответствующий код ошибки, то в сообщении будет именно этот текст.

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

Снова возвращаемся к созданию сервера После удачного выполнения функции bind вызывается функция 1 isten. Ее вызов довольно интересен:

if TestFuncError(listen(sServerListen. 4).- 'Listen') then exit:

В вызове используется функция TestFuncError, в первом параметре которой передается результат выполнения функции listen, а во втором - строковое название функции. Функция TestFuncError (листинг 4.3) проверяет результат работы сетевой функции, переданный в качестве первого параметра, с константой S0CKET_ ERROR. Если произошла ошибка, то выводится текстовое описание ошибки с помощью уже знакомой нам TestWinSockError и возвращается результат true.

Листинг 4.3. Проверка результата выполнения функции

function TestFuncError(iErr:Integer;
FuncName:String). Boolean, begin
Result := false;
if iErr = SOCKETJRROR then
begin
TestWinSockError(FuncName);
Result := true;
end;
end:

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

После начала прослушивания можно запускать бесконечный цикл, в котором будем принимать входящие соединения. Для этого применяется функция accept, ожидающая соединения со стороны клиента. Эта функция блокирует выполнение программы, пока к указанному порту (в данном случае 5050) не подключится клиентская программа. Это главный недостаток нашего приложения, потому что после этого окно не будет откликаться на сообщения пользователя. Конечно же, вызов этой функции можно было бы убрать в отдельный поток, но это будет не совсем верно, и чуть позже мы увидим более хороший вариант. А пока смиримся с таким недостатком блокировки.

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

Если при соединении ошибок нет, то создадим новый поток, в котором будет происходить работа с клиентом. Создание потока осуществляется таким образом:

si .= TCPClientThread.Create(true);
si.Sock := sClient: si.Resume;

В данном случае задан новый экземпляр объекта потока ТСРС1 тentThread (который мы рассмотрим позже) в приостановленном состоянии. Об этом говорит значение параметра true. После этого записываем в свойство потока Sock значение сокета клиента, подключенного к нам, и запускаем поток на выполнение Методом Resume.

4.10. Создание ТСР-сервера || Оглавление || 4.10.2. Получение и передача сетевых данных


Delphi в шутку и всерьез: что умеют хакеры



Новости за месяц

  • Декабрь
    2021
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31