Читаем О чём не пишут в книгах по Delphi полностью

Откомпилируем наши примеры и посмотрим, что получилось. Пока у нас один клиент работает с одним сервером, все вполне предсказуемо: клиент передает сообщения, сервер на них отвечает. Попытаемся подключиться вторым клиентом, не отключая первый, и посмотрим, что будет. Само подключение с точки зрения клиента проходит нормально, хотя сервер находится в своем внутреннем цикле и не вызывает accept, для второго клиента. Впрочем, как мы знаем, для успешного выполнения функции connect на стороне клиента достаточно, чтобы сокет сервера находился в режиме прослушивания. Теперь попытаемся отправить что-то серверу со второго клиента. Сама отправка проходит успешно, но при попытке получить ответ клиент "зависает", т.к. функция recv блокирует нить до прихода данных, а данные не приходят, потому что сервер не обрабатывает сообщения от этого клиента. Отсоединим первый клиент от сервера, чтобы сервер вернулся к выполнению функции accept. Мы видим, что сервер немедленно обнаружил подключение второго клиента, а также то, что клиент прислал ему сообщение. Соответственно, сервер отвечает на это сообщение, и второй клиент "отвисает" — теперь с ним можно нормально работать.

Простейший сервер и эксперименты с ним, конечно, очень познавательны, но на практике хотелось бы иметь такой сервер, который может работать одновременно с несколькими клиентами. Чтобы добиться этого, сделаем так же, как при написании UDP-чата: вынесем в отдельные нити работу с блокирующими функциями (пример MultithreadedServer на компакт-диске). Нам понадобится одна нить для выполнения функции accept и по одной нити на работу с каждым подключившимся клиентом. Инициализация выполняется при нажатии кнопки Запустить (листинг 2.19). После инициализации библиотеки сокетов, создания сокета и перевода его в режим прослушивания она создает нить типа TListenThread, передает ей дескриптор сокета и больше с сокетами не работает — дальнейшая роль главной нити заключается только в обработке сообщений. Благодаря этому сервер может иметь нормальный пользовательский интерфейс.

Листинг 2.19. Инициализация многонитевого сервера

// Реакция на кнопку Запустить

procedure TServerForm.BtnStartServerClick(Sender: TObject);

var

 // Сокет, который будет "слушать"

 ServerSocket: TSocket;

 // Адрес, к которому привязывается слушающий сокет

 ServerAddr: TSockAddr;

begin

 // Формирyем адрес для привязки.

 FillChar(ServerAddr.sin_zero, SizeOf(ServerAddr.sin_zero), 0);

 ServerAddr.sin_family := AF_INET;

 ServerAddr.sin_addr.S_addr := ADDR_ANY;

 try

  ServerAddr.sin_port := htons(StrToInt(EditPortNumber.Text));

  if ServerAddr.sin_port = 0 then

  begin

   MessageDlg('Номер порта должен находиться в диапазоне 1-65535',

    mtError, [mbOK], 0);

   Exit;

  end;

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

  ServerSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  if ServerSocket = INVALID_SOCKET then

  begin

   MessageDlg('Ошибка при создании сокета: '#13#10 + GetErrorString,

    mtError, [mbOK], 0);

   Exit;

  end;

  // Привязка сокета к адресу

  if bind(ServerSocket, ServerAddr, SizeOf(ServerAddr)) = SOCKET_ERROR then

  begin

   MessageDlg('Ошибка при привязке сокета к адресу: '#13#10 +

    GetErrorString, mtError, [mbOK], 0);

   closesocket(ServerSocket);

   Exit;

  end;

  // Перевод сокета в режим прослушивания

  if listen(ServerSocket, SOMAXCONN) = SOCKET_ERROR then

  begin

   MessageDlg('Ошибка при переводе сокета в режим просушивания:'#13#10 +

    GetErrorString, mtError, [mbOK], 0);

   closesocket(ServerSocket);

   Exit;

  end;

  // Запуск нити, обслуживающей слушающий сокет

  TListenThread.Create(ServerSocket);

  // Перевод элементов управления в состояние "Сервер работает"

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже