- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
О чём не пишут в книгах по Delphi - А. Григорьев
Шрифт:
Интервал:
Закладка:
Если выходной буфер сокета равен нулю, данные сразу копируются в сеть, но успешное завершение функции и в этом случае не гарантирует успешную доставку. Использовать нулевой выходной буфер для TCP-сокетов не рекомендуется, т.к. это снижает производительность при последовательной отправке данных небольшими порциями. При буферизации эти порции накапливаются в буфере, а потом отправляются одним большим пакетом, требующим одного подтверждения от клиента. Если же буферизация не осуществляется, то будет отправлено несколько мелких пакетов, каждый со своим заголовком и своим подтверждением от клиента, что приведет к снижению производительности.
Функция recv копирует пришедшие данные из входного буфера сокета в буфер, заданный параметром Buf, но не более len байтов. Скопированные данные удаляются из буфера сокета. При этом все полученные данные сливаются в один поток, поэтому получатель может самостоятельно выбирать, какой объем данных считывать за один раз. Если за один раз была скопирована только часть пришедшего пакета, оставшаяся часть не пропадает, а будет скопирована при следующем вызове recv. Функция recv возвращает число байтов, скопированных в буфер. Если на момент ее вызова входной буфер сокета пуст, она ждет, когда там что-то появится, затем копирует полученные данные и лишь после этого возвращает управление вызвавшей ее программе. Если recv возвращает 0, это значит, что удаленный сокет корректно завершил соединение. Если соединение завершено некорректно (например, из-за обрыва кабеля или сбоя удаленного компьютера), функция завершается с ошибкой (т.е. возвращает SOCKET_ERROR).
Теперь рассмотрим, какие действия при использовании TCP должен выполнить сервер. Как мы уже говорили, сервер должен перевести сокет в режим ожидания соединения. Это делается с помощью функции listen, имеющей следующий прототип:
function listen(s: TSocket; backlog: Integer): Integer;
Параметр s задает сокет, который переводится в режим ожидания подключения. Этот сокет должен быть привязан к адресу, т.е. функция bind должна быть вызвана для него явно. Для сокета, находящегося в режиме ожидания, создается очередь подключений. Размер этой очереди определяется параметром backlog, если он равен SOMAXCONN, очередь будет иметь максимально возможный размер. В MSDN отмечается, что узнать максимально допустимый размер очереди стандартными средствами нельзя. Функция возвращает ноль при успешном завершении и SOCKET_ERROR — в случае ошибки.
Когда клиент вызывает функцию connect, и по указанному в ней адресу имеется сокет, находящийся в режиме ожидания подключения, то информация о клиенте помещается в очередь подключений этого сокета. Успешное завершение connect говорит о том, что на стороне сервера подключение добавлено в очередь. Однако для того, чтобы соединение было действительно установлено, сервер должен выполнить еще некоторые действия: извлечь из очереди соединений информацию о соединении и создать сокет для его обслуживания. Эти операции выполняются с помощью функции accept, имеющей следующий прототип:
function accept(s: TSocket; addr: PSockAddr; addrlen: PInteger) : TSocket;
Параметр s задает сокет, который находится в режиме ожидания соединения и из очереди которого извлекается информация о соединении. Выходной параметр addr позволяет получить адрес клиента, установившего соединение. Здесь должен быть передан указатель на буфер, в который этот адрес будет помещен. Параметр addrlen содержит указатель на переменную, в которой хранится длина этого буфера: до вызова функции эта переменная должна содержать фактическую длину буфера, задаваемого параметром addr, после вызова — количество байтов буфера, реально понадобившихся для хранения адреса клиента. Очевидно, что в случае TCP и входное, и выходное значение этой переменной должно быть равно SizeOf(TSockAddr). Эти параметры передаются как указатели, а не как параметры-переменные, что было бы более естественно для Delphi, потому что библиотека сокетов допускает для этих указателей нулевые значения, если сервер не интересует адрес клиента. В данном случае разработчики модуля WinSock сохранили полную функциональность, предоставляемую библиотекой.
В случае ошибки функция accept возвращает значение INVALID_SOCKET. При успешном завершении возвращается дескриптор сокета. созданного библиотекой сокетов и предназначенного для обслуживания данного соединения. Этот сокет уже привязан к адресу и соединен с сокетом клиента, установившего соединение, и его можно использовать в функциях recv и send без предварительного вызова каких-либо других функций. Уничтожается этот сокет обычным образом, с помощью closesocket.
Исходный сокет, определяемый параметром s, остается в режиме прослушивания. Если сервер поддерживает одновременное соединение с несколькими клиентами, то функция accept может быть вызвана многократно. Каждый раз при этом будет создаваться новый сокет, обслуживающий одно конкретное соединение: протокол TCP и библиотека сокетов гарантируют, что данные, посланные клиентами, попадут в буферы соответствующих сокетов и не будут перемешаны.
Для получения целостной картины кратко повторим все сказанное. Для установления соединения сервер должен, во-первых, создать сокет с помощью функции socket, а во-вторых, привязать его к адресу с помощью функции bind. Далее сокет должен быть переведен в режим ожидания с помощью listen, а потом с помощью функции accept создается новый сокет, обслуживающий соединение, установленное клиентом. После этого сервер может обмениваться данными с клиентом. Клиент же должен создать сокет, при необходимости привязки к конкретному порту вызвать bind, и затем вызвать connect для установления соединения. После успешного завершения этой функции клиент может обмениваться данными с сервером. Это иллюстрируют листинги 2.11 и 2.12.
Листинг 2.11. Код сервераvar
S, AcceptedSock: TSocket;
Addr: TSockAddr;
Data: TWSAData;
Len: Integer;
begin
WSAStartup($101, Data);
S := socket(AF_INET, SOCK_SТREAМ, 0);
Addr.sin_family := FF_INET;
Addr.sin_port := htons(3030);
Addr.sin_addr.S_addr := INADDR_ANY;
FillChar(Addr.sin_zero, SizeOf(Addr.sin_zero), 0);
bind(S, Addr, SizeOf(TSockAddr));
listen(S, SOMAXCONN);
Len := SizeOf(TSockAddr);
AcceptedSock := accept(S, @Addr, @Len);
{
Теперь Addr содержит адрес клиента, с которым установлено соединение, а AcceptedSock - дескриптор, обслуживающий это соединение. Допустимы следующие действия:
send(AcceptedSock, ...) - отправить данные клиенту
recv(AcceptedSock, ...) - получить данные от клиента
accept(...) - установить соединение с новым клиентом
}
Здесь сокет сервера привязывается к порту с номером 3030. В общем случае разработчик сервера сам должен выбрать порт из диапазона 1024–65 535.
Листинг 2.12. Код клиентаvar
S: TSocket;
Addr: TSockAddr;
Data: TWSAData;
begin
WSAStartup($101, Data);
S := socket(AF_INET, SOCK_STREAM, 0);
Addr.sin_family := AF_INET;
Addr.sin_port := htons(3030);
Addr.sin_addr.S_addr := inet_addr(...);
FillChar(Addr.sin_zero, SizeOf(Addr.sin_zero), 0);
connect(S, Addr, SizeOf(TSockAddr));
{
Теперь соединение установлено. Допустимы следующие действия:
send(S, ...) - отправить данные серверу
recv(S, ...) — получить данные от сервера
}
В приведенном коде для краткости опущены проверки результатов функций с целью обнаружения ошибок. При написании серьезных программ этим пренебрегать нельзя. Блок-схема действии клиента и сервера приведена на рис. 2.3.
Если на момент вызова функции accept очередь соединений пуста, то нить, вызвавшая ее, блокируется до тех пор, пока какой-либо клиент не подключится к серверу. С одной стороны, это удобно: сервер может не вызывать функцию accept в цикле до тех пор, пока она не завершится успехом, а вызвать ее один раз и ждать, когда подключится клиент. С другой стороны, это создает проблемы тем серверам, которые должны взаимодействовать с несколькими клиентами. Действительно, пусть функция accept успешно завершилась и в распоряжении программы оказались два сокета: находящийся в режиме ожидания новых подключений и созданный для обслуживания уже существующего подключения. Если вызвать accept, то программа не сможет продолжить работу до тех пор, пока не подключится еще один клиент, а это может произойти через очень длительный промежуток времени или вообще никогда не случится. Из-за этого программа не сможет обрабатывать вызовы уже подключившегося клиента. С другой стороны, если функцию acсept не вызывать, сервер не сможет обнаружить подключение новых клиентов. Средства для решения этой проблемы есть как у стандартных сокетов, так и у сокетов Windows, и далее мы их рассмотрим. Но существует довольно популярный способ ее решения средствами не библиотеки сокетов, а операционной системы. Он заключается в использовании отдельной нити для обслуживания каждого из клиентов. Каждый раз, когда клиент подключается, функция accept передает управление программе, возвращая новый сокет. Здесь сервер может породить новую нить, которая предназначена исключительно для обмена данными с новым клиентом. Старая нить после этого снова вызывает accept для старого сокета, а новая — функции recv и send для нового сокета. Такой метод решает заодно и проблемы, связанные с тем, что функции send и recv также могут блокировать работу программы и помешать обмену данными с другими клиентами. В данном случае будет блокирована только одна нить, обменивающаяся данными с одним из клиентов, а остальные нити продолжат свою работу. Далее мы рассмотрим пример сервера, работающего по такой схеме.

