О чём не пишут в книгах по Delphi
Шрифт:
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);
// Перевод элементов управления в состояние "Сервер работает"
LabelPortNumber.Enabled := False;
EditРоrtNumber.Enabled := False;
BtnStartServer.Enabled := False;
LabelServerState.Caption := 'Сервер работает';
except
on EConvertError do
// Это исключение может возникнуть только в одном месте
// при вызове StrToInt(EditPortNumber.Text)
MessageDlg('"' + EditPortNumber.Text + '"не является целым числом',
mtError, [mbOK], 0);
on ERangeError do
// это исключение может возникнуть только в одном месте -
// при присваивании значения номеру порта
MessageDlg('Номер порта должен находиться в диапазоне 1-65535',
mtError, [mbOK], 0);
end;
end;
Слушающая" нить
TListenThread
состоит из бесконечного ожидания подключения клиента. Каждый раз при подключении клиента библиотека сокетов создаёт новый сокет, и для работы с ним создается новая нить типа TClientThread
(листинг 2.20).
Поделиться:
Популярные книги
Камень Книга седьмая
7. Камень
Фантастика:
фэнтези
боевая фантастика
6.22
рейтинг книги
Я сделаю это сама
1. Магический XVIII век
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Кровь на эполетах
3. Штуцер и тесак
Фантастика:
альтернативная история
7.60
рейтинг книги
Сыночек в награду. Подари мне любовь
1. Суровые отцы
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Кодекс Крови. Книга ХII
12. РОС: Кодекс Крови
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Найди меня Шерхан
3. Ямпольские-Демидовы
Любовные романы:
современные любовные романы
короткие любовные романы
7.70
рейтинг книги
Идеальный мир для Лекаря 4
4. Лекарь
Фантастика:
фэнтези
юмористическая фантастика
аниме
5.00
рейтинг книги
Релокант. По следам Ушедшего
3. Релокант в другой мир
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Мир-о-творец
8. Помещик
Фантастика:
альтернативная история
5.00
рейтинг книги
Протокол "Наследник"
1. Гибрид
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга VII
7. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
4.75
рейтинг книги
Попаданка в семье драконов
Попаданка в академии драконов
Любовные романы:
любовно-фантастические романы
7.37
рейтинг книги
Новые горизонты
5. Гибрид
Фантастика:
попаданцы
технофэнтези
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Скрываясь в тени
2. Теневой путь
Фантастика:
боевая фантастика
7.84