Лабораторная работа № 1
Клиент-серверное приложение для передачи сообщений с использованием протоколов TCP и UDP
1.1 Цель работы
Целью лабораторной работы является ознакомление студентов с основной структурой клиент-серверного приложения на примере написания программ передачи/приема текстовых сообщений с использованием сокетов в блокирующем режиме.
1.2 Задание на лабораторную работу
В первой лабораторной работе необходимо разработать 2 клиентсерверных приложения на основе протоколов TCP и UDP. Клиент должен подключиться к серверу и отправить ему текстовое сообщение; сервер, получив сообщение, должен вывести его на экран.
1.3 Методические указания
1.3.1 Понятие сокета
Сокеты (sockets) представляют собой высокоуровневый унифицированный интерфейс взаимодействия с телекоммуникационными протоколами. Приложение просто пишет данные в сокет, их дальнейшая буферизация, отправка и транспортировка осуществляется используемым стеком протоколов и сетевой аппаратурой. Чтение данных из сокета происходит аналогичным образом.
В программе сокет идентифицируется дескриптором - это просто переменная типа int(в ОС Windows тип SOCKET см. таблицу 1.1). Программа получает дескриптор от операционной системы при создании сокета, а затем передаёт его сервисам socket API для выполнения того или иного действия.
1.3.2 Обзор сокетов
Существует два вида сокетов - синхронные (блокируемые) и асинхронные (неблокируемые). Синхронные сокеты задерживают управление на время выполнения операции, а асинхронные возвращают его немедленно, продолжая выполнение в фоновом режиме.
Сокеты позволяют работать с множеством протоколов и являются удобным средством межпроцессорного взаимодействия, но в рамках данного лабораторного практикума речь будет идти только о сокетах семейства протоколов TCP/IP, использующихся для обмена данными между узлами сети Интернет.
Независимо от вида, сокеты делятся на два типа - потоковые и датаграммные. Потоковые сокеты работают с установкой соединения, обеспечивая надежную идентификацию обоих сторон и гарантируют целостность и успешность доставки данных. Датаграмные сокеты работают без установки соединения и не обеспечивают контроля успешности доставки данных, зато они значительно быстрее потоковых.
Выбор того или иного типа сокетов определяется транспортным протоколом, на котором работает сервер, клиент не может по своему желанию установить с датаграммным сервером потоковое соединение.
Датаграммные сокеты опираются на протокол UDP, а потоковые - на
TCP.
Для работы с библиотекой Winsock 2.х в исходный текст программы необходимо включить директиву "#include <winsock2.h>", а в командной строке линкера указать "ws2_32.lib". В Microsoft Visual Studio для этого достаточно нажать <Alt-F7>, перейти к закладке "Link" и к списку библиотек,
перечисленных в строке "Object/Library modules", добавить "ws2_32.lib",
отделив ее от остальных символом пробела.
Перед началом использования функций библиотеки Winsock, ее необходимо подготовить к работе вызовом функции "int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)", передав в старшем байте слова wVersionRequested номер требуемой версии, а в младшем - номер подверсии.
Аргумент lpWSAData должен указывать на структуру WSADATA, в которую при успешной инициализации будет занесена информация о производителе библиотеки. Если инициализация проваливается, функция возвращает ненулевое значение.
Для работы с сокетами в *nix никаких дополнительных действий не требуется, достаточно подключить необходимые заголовочные файлы.
1.3.3 Атрибуты сокета
С каждым сокетом связываются три атрибута: домен, тип и протокол. Эти атрибуты задаются при создании сокета и остаются неизменными на протяжении всего времени его существования. Для создания сокета используется функция socket, имеющая следующий прототип:
|
Таблица 1.1 |
Описание функции в *nix |
Описание функции в Windows |
|
|
#include <sys/types.h> |
#include <winsock2.h> |
#include <sys/socket.h> |
|
int socket ( |
SOCKET socket ( |
int domain, |
int domain, |
int type, |
int type, |
int protocol |
int protocol |
); |
); |
Домен определяет пространство адресов, в котором располагается сокет, и множество протоколов, которые используются для передачи данных. Мы будем использовать домен Internet, задаваемый константой AF_INET (префикс AF означает "address family" - "семейство адресов"). Сокеты, размещённые в этом домене, могут использоваться для работы в любой IPсети. Существуют и другие домены (AF_IPX для протоколов Novell, AF_INET6 для новой модификации протокола IP - IPv6 и т. д.).
Тип сокета определяет способ передачи данных по сети. Чаще других применяются:
-SOCK_STREAM. Передача потока данных с предварительной установкой соединения. Обеспечивается надёжный канал передачи данных, при котором фрагменты отправленного блока не теряются, не переупорядочиваются и не дублируются.
-SOCK_DGRAM. Передача данных в виде отдельных сообщений (датаграмм). Предварительная установка соединения не требуется. Обмен данными происходит быстрее, но является ненадёжным: сообщения могут теряться, дублироваться и переупорядочиваться. Допускается передача сообщения нескольким получателям (multicasting) и широковещательная передача (broadcasting).
-SOCK_RAW. Этот тип присваивается низкоуровневым (т. н. "сырым") сокетам.
Обратите внимание, что не все домены допускают задание произвольного типа сокета. Например, совместно с доменом Unix используется только тип SOCK_STREAM. С другой стороны, для Internet-домена можно задавать любой из перечисленных типов. В этом случае для реализации SOCK_STREAM используется протокол TCP, для реализации SOCK_DGRAM
-протокол UDP, а тип SOCK_RAW используется для низкоуровневой работы с протоколами IP, ICMP и т. д.
Наконец, последний атрибут определяет протокол, используемый для передачи данных. Протокол часто однозначно определяется по домену и типу сокета. В этом случае в качестве третьего параметра функции socket можно передать 0, что соответствует протоколу по умолчанию. Тем не менее, иногда (например, при работе с низкоуровневыми сокетами) требуется задать
протокол явно. Числовые идентификаторы протоколов зависят от выбранного домена; их можно найти в документации
1.3.4 Адреса
Прежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене (эту процедуру называют именованием сокета). Иногда связывание осуществляется неявно (внутри функций connect и accept), но выполнять его необходимо во всех случаях. Вид адреса зависит от выбранного вами домена. В Internet-домене адрес задаётся комбинацией IPадреса и 16-битного номера порта.
Для явного связывания сокета с некоторым адресом используется функция bind. Её прототип имеет вид:
|
Таблица 1.2 |
|
|
|
|
Описание функции в *nix |
Описание функции в Windows |
|
|
|
|
#include <sys/types.h> |
#include <winsock2.h> |
|
#include <sys/socket.h> |
||
|
||
int bind( |
int bind( |
|
int sockfd, |
SOCKET sockfd, |
|
struct sockaddr *addr, |
struct sockaddr *addr, |
|
int addrlen |
int addrlen |
|
); |
); |
В качестве первого параметра передаётся дескриптор сокета, который мы хотим привязать к заданному адресу. Второй параметр, addr, содержит указатель на структуру с адресом, а третий - длину этой структуры. Посмотрим, что она собой представляет.
|
Таблица 1.3 |
Описание структуры в *nix |
Описание структуры в Windows |
|
|
struct sockaddr {
unsigned short sa_family; // Семейство адресов char sa_data[14]; // 14 байтов для хранения адреса
};
Поле sa_family содержит идентификатор домена, тот же, что и первый параметр функции socket. В зависимости от значения этого поля по-разному интерпретируется содержимое массива sa_data. Разумеется, работать с этим массивом напрямую не очень удобно, поэтому вы можете использовать вместо sockaddr структуру вида sockaddr_in. При передаче в функцию bind указатель на эту структуру приводится к указателю на sockaddr. Рассмотрим структуру
sockaddr_in.
|
Таблица 1.4 |
Описание структуры в *nix |
Описание структуры в Windows |
|
|
struct sockaddr_in {
short int sin_family; // Семейство адресов unsigned short int sin_port; // Номер порта struct in_addr sin_addr; // IP-адрес
unsigned char sin_zero[8]; // "Дополнение" до размера структуры sockaddr
};
Здесь поле sin_family соответствует полю sa_family в sockaddr, в sin_port
записывается номер порта, а в sin_addr - IP-адрес хоста. Поле sin_addr само является структурой, которая имеет вид:
|
Таблица 1.5 |
|
|
Описание структуры в *nix |
Описание структуры в Windows |
|
|
struct in_addr {
unsigned long s_addr; };
Раньше in_addr представляла собой объединение, содержащее гораздо большее число полей. Сейчас, когда в ней осталось всего одно поле, она продолжает использоваться для обратной совместимости.