// клиента. Если бы протокол допускал отправку сервером нескольких
// ответов подряд, при чтении очередной порции данных могло бы
// оказаться, что начало порции принадлежит одной строке, конец -
// следующей, а признак конца строки нужно искать где-то в середине
if Buf[BufStart + Portion - 1] = #0 then
begin
EditReply.Text := PChar(@Buf[0]);
Break;
end;
Inc(BufStart, BufStep);
until False;
end;
Реакция на кнопку Отсоединитьсясовсем простая: нужно разорвать соединение и закрыть сокет (листинг 2.18).
Листинг 2.18. Реакция на нажатие кнопки Отсоединиться
procedure TSimpleClientForm.BtnDisconnectClick(Sender: TObject);
begin
shutdown(FSocket, SD_BOTH);
closesocket(FSocket);
OnDisconnect;
end;
Откомпилируем наши примеры и посмотрим, что получилось. Пока у нас один клиент работает с одним сервером, все вполне предсказуемо: клиент передает сообщения, сервер на них отвечает. Попытаемся подключиться вторым клиентом, не отключая первый, и посмотрим, что будет. Само подключение с точки зрения клиента проходит нормально, хотя сервер находится в своем внутреннем цикле и не вызывает 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);
// Перевод элементов управления в состояние "Сервер работает"
Читать дальше
Конец ознакомительного отрывка
Купить книгу