Сокеты

Сокет - устройство двунаправленной связи, которое может использоваться для взаимодействия с другим процессом на одной и той же машине или с процессом, запущенным на других машинах. Программы Интернета такие как Telnet, rlogin, FTP, talk , и World Wide Web используют сокеты.

Например, можно получить WWW-страницу от сервера Web, используя программу Telnet , так как они обе используют сокеты для сетевого взаимодействия. Для открытия подключения с сервером WWW на www.codesourcery.com, необходимо использовать telnet www.codesourcery.com 80. Константа 80 определяет подключение к Web серверу. Если после того, как подключение будет установлено, передать команду get /, то Web серверу через сокеты будет отправлено сообщение, на которое он ответит, передав исходный текст домашней HTML страницы и затем закроет подключение.

Пример:

	% telnet www.codesourcery.com 80
	Trying 206.168.99.1...
	Connected to merlin.codesourcery.com (206.168.99.1).
	Escape character is '^]'.
	GET /
	<html>
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
	...

Основы сокетов

При создании сокета, необходимо определить три параметра: стиль взаимодействия, пространство имен, и пртокол. Стиль взаимодействия контролирует, как сокет обрабатывает передаваемые данные, и определяет количество партнеров взаимодействия. Через сокеты данные передаются блоками (пакетами). Стиль взаимодействия определяет, как эти пакеты будут обработаны и как они передаются от отправителя к получателю.

Пространство имен определяет, как записаны адреса сокета ( socket addresses ). Адрес сокета идентифицирует один конец подключения сокета. Например, адреса сокета в локальном пространстве имен являются обычными именами файлов. В пространстве имен Интернет адрес сокета состоит из Интернет адреса ( IP адрес) главного компьютера, присоединенного к сети и номера порта, который идентифицирует сокет среди множества сокетов на том же главном компьютере.

Протокол определяет, как передаются данные. Существуют следующие виды протоколов: TCP/IP , первичные сетевые протоколы, используемые Интернетом; сетевой протокол AppleTalk ; локальный UNIX протокол взаимодействия. Не все комбинации стилей, пространств имен и протоколов поддерживаются.

Системные вызовы

Виды системных вызовов:

Сокеты представляются дескрипторами файлов.

Создание и уничтожение сокетов

С помощью функций socket и close создаются и уничтожаются сокеты. При создании сокета, необходимо определить три параметра сокета: пространство имен, стиль взаимодействия и протокол.

Для указания пространства имен используются константы, начинающиеся с PF_ (сокращение "семейство протокола"). Например, PF_LOCAL или PF_UNIX определяют локальное пространство имен, и PF_INET определяет Интернет пространство имен.

Второй параметр, стиль взаимодействия, представляет собой константу, начинающиюся с SOCK_ . SOCK_STREAM опеределяет стиль взаимодейтсвия соединение, SOCK_DGRAM - стиль датаграмы.

Третий параметр, протокол, определяет механизм нижнего уровня для передачи и получения данных. Для каждой комбинации пространство имен - стиль взаимоделйствия существует свой протокол.

Для каждой пары существует лучший протокол, поэтому можно указать 0, что соответсвует этому протоколу. Если команда socket выполнена успешно, в качестве результата возвращается дескриптор файла для сокета. С помощью команд read и write , можно читать и записывать данные в сокет.

Вызов connect

Для создания соединение между двумя сокетами, клиент вызывает connect , передавая адрес сокета сервера для подключения. Клиент - процесс, инициализирующий соединение, а сервер - процесс, ожидающий разрешения соединения. Клиент посылает запрос connect , чтобы инициализировать соединение между локальным сокетом и сокетом сервера, переданным в качестве второго параметра. В качестве третьего параметра передается длина, в байтах, адресной структуры, на которую указывает второй параметр.

Отправка данных

Любая техника записи в дескриптор файла, может использоваться при записи в сокет. Функция send , определенная для дескрипторов файлов сокета, аналогична функции write с несколькими дополнительными параметрами.

Серверы

Цикл жизни сервера состоит из создания сокета, привязки сокета к адресу, вызова listen , разрешающего соединение с сокетом, вызова accept , принимающего входящие соединения, и затем закрытия сокета. Данные не читаются и не записываются непосредственно через сокет сервера; вместо этого, каждый раз когда программа принимает новое соединение, Linux создает отдельный сокет, используется при передаче данных по этому соединению. В этом разделе рассматриваются вызовы bind, listen и accept .

С помощью команды bind адрес сервера должен быть привязан к сокету. Первый параметр команды - дескриптор файла сокета. Второй параметр - указатель на структуру адреса сервера; формат которого зависит от семейства адреса. Третий параметр - длина структуры адреса, в байтах.

Когда адрес связан с сокетом стиля соединение, необходимо вызвать listen , чтобы указать, что это - сервер. Первый параметр команды - дескриптор файла сокета. Второй параметр определяет, длину очереди ожидающих соединений. Если очередь заполнена, дополнительные соединения будут отвергнуты. Это не ограничивает общее количество соединений, которые сервер может обработать; это ограничивает только число клиентов, пытающихся соединиться и не получивших подтверждение.

С помощью команды accept сервер принимает запрос на соединение от клиента. Первый параметр вызова - дескриптор файла сокета. Второй параметр указывает на структуру адреса сокета, в которой хранится адрес клиентского сокета. Третий параметр - длина, в байтах, структуры адреса сокета. Сервер может использовать адрес клиента, чтобы определить, требуется ли действительно взаимодействовать с клиентом.

Вызов accept создает новый сокет для взаимодействия с клиентом и возвращает соответствующий дескриптор файла. Оригинальный сокет сервера продолжает принимать новые клиентские соединения.

Для чтения данных из сокета, без удаления их из входной очереди, используется команда recv . В качестве параметров передаются теже аргументы, что и в команде read , плюс дополнительный параметр FLAGS . Флаг MSG_PEEK указывает, что данные должны быть прочитаны, но не удалены из входной очереди.

Локальные сокеты

Сокеты, подключающие процессы на одном компьютере могут использовать локальное пространство имен, представляющий собой синоним для PF_LOCAL и PF_UNIX . Они называются локальными сокетами или сокетами UNIX-домена. Адреса этих сокетов, определяемые именами файлов, используются только при создании соединения.

Имя сокета указывается в структуре sockaddr_un . Если в AF_LOCAL установлено поле sun_family , это указывает на то, что адрс в докальном пространстве имен. Поле Sun_path указывает, что используется имя файла; максимальная длина поля - 108 байт. Для вычисления длины struct sockaddr_un используется макрокоманда SUN_LEN . Может использоваться любое имя файла, но для процесса должно быть установлено право на запись в каталог. Чтоб соединениться с сокетом, процесса должен иметь право на чтения файла. Хотя различные компьютеры могут совместно использовать одну файловую систему, только процессы, запущенные на этом компьютере, могут взаимодействовать используя сокеты локального пространства имен.

Единственный допустимый протокол для локального пространства имен - 0. Поскольку он находится в файловой системе, локальный сокет представлен как файл.

Например, обратите внимание на начальную s:

	% ls -l /tmp/socket
	srwxrwx--x 1 user group 0 Nov 13 19:18 /tmp/socket

Вызов unlink удаляет локальный сокет, при завершении работы с ним.

Пример использования локальных сокетов

В листинге 5.10 представлена программа сервера, в которой создается локальный сокет и слушает запросы на соединения с сервером. При получении запроса на соединение, сервер читает текстовые сообщения, передаваемые через соединение и печатает их. Если одно из этих сообщений - "выход", программа сервера удаляет сокет и завершается. Программа socket-server предполагает, что путь к сокету передается через параметр командной строки.

Листинг 5.10 (socket-server.c)

	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <sys/socket.h>
	#include <sys/un.h>
	#include <unistd.h>
	/* 	Чтение текста из сокета и вывод его на печать. Продолжение цикла до закрытия сокета. 
	 *	В качестве результата возвратит не ноль, если клиент передал сообщение "quit", иначе 0 */
	int server (int client_socket)
	{
		while (1) {
			int length;
			char* text;
			/* Сначала из сокета прочитать длину текстого сообщения. Если в качестве результата возвратиться 0,
			   то клиент закрыл соединение */
			if (read (client_socket, &ength, sizeof (length)) == 0)
				return 0;
			/* выделить место в буфере для хранения текста */
			text = (char*) malloc (length);
			/* Чтение текста и распечатка */
			read (client_socket, text, length);
			printf ("%s\n", text);
			/* Освободить буфер */
			free (text);
			/* Если от клиента поступило сообщение "quit", завершить работу */
			if (!strcmp (text, "quit"))
				return 1;
		}
	}
	int main (int argc, char* const argv[])
	{
		const char* const socket_name = argv[1];
		int socket_fd;
		struct sockaddr_un name;
		int client_sent_quit_message;
		/* Создать сокет */
		socket_fd = socket (PF_LOCAL, SOCK_STREAM, 0);
		/* Определить что это сервер */
		name.sun_family = AF_LOCAL;
		strcpy (name.sun_path, socket_name);
		bind (socket_fd, &name, SUN_LEN (&name));
		/* Слушать (ожидать) соединения */
		listen (socket_fd, 5);
		/* Неоднократно принимать соединения, создавая для каждого клиента server().
		   Продолжать до получения от клиента сообщения "quit" */
		do {
			struct sockaddr_un client_name;
			socklen_t client_name_len;
			int client_socket_fd;
			/* Принимать соединение */
			client_socket_fd = accept (socket_fd, &client_name, &client_name_len);
			client_sent_quit_message = server (client_socket_fd);
			/* Закрыть соединение с нашей стороны */
			close (client_socket_fd);
		}
		while (!client_sent_quit_message);
		/* Удалить файл сокета */
		close (socket_fd);
		unlink (socket_name);
		return 0;
	}

Клиент-программа, представленная в листинге 5.11, соединяется с локальным сокетом и посылает сообщения. Путь к сокету и сообщения передаtтся через командную строку.

Листинг 5.11(socket-client.c)

	#include <stdio.h>
	#include <string.h>
	#include <sys/socket.h>
	#include <sys/un.h>
	#include <unistd.h>
	/* Записать TEXT в сокет, переданный дескриптором файла SOCKET_FD */
	void write_text (int socket_fd, const char* text)
	{
		/* Записать в строку количество байт, включая NUL-терминал */
		int length = strlen (text) + 1;
		write (socket_fd, &length, sizeof (length));
		/* Записать строку */
		write (socket_fd, text, length);
	}
	int main (int argc, char* const argv[])
	{
		const char* const socket_name = argv[1];
		const char* const message = argv[2];
		int socket_fd;
		struct sockaddr_un name;
		/* Создать сокет */
		socket_fd = socket (PF_LOCAL, SOCK_STREAM, 0);
		/* Сохранить имя сервера в адресе сокета */
		name.sun_family = AF_LOCAL;
		strcpy (name.sun_path, socket_name);
		/* Соединиться с сокетом */
		connect (socket_fd, &name, SUN_LEN (&name));
		/* Записать текст командной строки в сокет */
		write_text (socket_fd, message);
		close (socket_fd);
		return 0;
	}

Перед передачей сообщения, посылается размер сообщения в байтах в качестве переменной length. Сервер сохраняет размер сообщения, для выделения памяти под сообщение. Чтобы выполнить этот пример, необходимо запустить сервер-программу в одном окне, определить путь к сокету.

Например, /tmp/socket .

	% ./socket-server /tmp/socket

В другом окне запустить клиент-программу несколько раз, опеределяя один и тот же путь сокета и посылая клиенту сообщение:

	% ./socket-client /tmp/socket "Hello, world."
	% ./socket-client /tmp/socket "This is a test."

Сервер-программа получает и печатает эти сообщения. Для закрытия соединения, клиент посылает сообщение "quit":

	% ./socket-client /tmp/socket "quit"

Сервер-программа завершена.

Internet-Domain сокеты

Cокеты UNIX-domain использоваться только для взаимодействия двух процессов только на одном компьютере. Сокеты Internet, используются для соединения нескольких процессов на различных машинах, подключенных к сети.

Для соединения процессов через Интернет сокеты используют пространство имен Интернет указываемое с помощью PF_INET . Большинство протоколов являются TCP/IP . Интернет протокол ( IP), протокол нижнего уровня, отправляет пакеты через Интернет, разбивая на меньшие пакеты, в случае необходимости. Он гарантирует только доставку "лучшего усилия", так что пакеты могут быть потеряны или переупорядочены во время транспортировки. Каждый компьютер имеет IP адрес. Протокол управления передачей ( TCP ), который следует за IP протоколом, обеспечивает надежное подключение. Это позволяет установить между компьютерами соединение, наподобие телефонного и гарантирует доставку данных в парвильном порядке.

Интернет адрес сокета состоит из двух частей: номера компьютера и номера порта. Эта информация хранится в переменной структуры sockaddr_in . Для идентификации того, что это адрес Интернет пространства имен, необходимо установить поле sin_family в AF_INET . В поле Sin_addr хранится Интернет адрес компьютера, как 32-разрядное целое число IP . Каждому сокету на одном компьютере присваивается номер порта. Поскольку различные машины сохраняют многобайтовые значения в различном порядке байта, используют htons , чтобы преобразовать число порта к сетевому порядку байтов.

Команда gethostbyname преобразовывает удобочитаемые имена хоста, числа со стандартной точечной нотацией (типа 10.0.0.1) или DNS имена (такие как www.codesourcery.com) в 32-разрядные IP адреса. В качестве результата возвращается указатель на структуру struct hostent ; в поле h_addr хранится IP адрес главного компьютера.

Листинг 5.12 иллюстрирует использование Internet-domain сокетов. Программа получает домашнюю страницу от Web сервера, имя хоста которого определено в командной строке.

Листинг 5.12(socket-inet.c)

	#include <stdlib.h>
	#include <stdio.h>
	#include <netinet/in.h>
	#include <netdb.h>
	#include <sys/socket.h>
	#include <unistd.h>
	#include <string.h>
	/* Печать содержимого домашней страницы. 
	 * В качестве результата передать флаг успешного завершения процесса.*/
	void get_home_page (int socket_fd)
	{
		char buffer[10000];
		ssize_t number_characters_read;
		/* Передать команду HTTP GET для домашней страницы */
		sprintf (buffer, "GET /\n");
		write (socket_fd, buffer, strlen (buffer));
		/* Читать данные из сокета. Не все данные могут быть возвращены одновременно, 
		 * продолжать попытку до завершения процесса */
		while (1) {
			number_characters_read = read (socket_fd, buffer, 10000);
			if (number_characters_read == 0)
				return;
			/* Записать данные в стандартный вывод */
			fwrite (buffer, sizeof (char), number_characters_read, stdout);
		}
	}
	int main (int argc, char* const argv[])
	{
		int socket_fd;
		struct sockaddr_in name;
		struct hostent* hostinfo;
		/* Создать сокет */
		socket_fd = socket (PF_INET, SOCK_STREAM, 0);
		/* Сохранить адрес сервера в адрессе сокета */
		name.sin_family = AF_INET;
		/* Преобразовать строку в число */
		hostinfo = gethostbyname (argv[1]);
		if (hostinfo == NULL)
			return 1;
		else
			name.sin_addr = *((struct in_addr *) hostinfo->h_addr);
		/* Web сервер использует 80 порт */
		name.sin_port = htons (80);
		/* Установить соединение с Web сервером */
		if (connect (socket_fd, &name, sizeof (struct sockaddr_in)) == -1) {
			perror ("connect");
			return 1;
		}
		/* Получить домашнюю страницу */
		get_home_page (socket_fd);
		return 0;
	}

Имя хоста Web сервера задается в командно строке (без "http: //"). Команда gethostbyname преобразовывает имя хоста в числовой IP адрес и затем подключает поток (TCP) сокета к порту 80 на главном компьютере. Серверы используют Гипертекстовый Транспортный Протокол ( HTTP ), поэтому передается команда HTTP GET , сервер в качестве ответа передает текст домашней страницы.

Для отображения страницы www.codesourcery.com, необходимо задать следующуе команду

	% ./socket-inet www.codesourcery.com
	<html>
	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
	...

Пары сокетов

Как упоминалось ранее, функция pipe , создает два дескриптора файлов для начала и конца канала. Каналы ограничены, потому что дескрипторы файлов используются только связанными процессами и потому что взаимодействие однонаправлено. Функция socketpair создает два дескриптора файлов для двух сокетов, подключенных на одном компьютере. Эти дескрипторы файлов разрешают двухстороннее взаимодействие двух связанных процессов. Первые три параметра команды - идентичны параметрам команды socket : они определяют домен, стиль подключения и протокол. Последний параметр - массив с двумя целыми числами, в котором хранятся характеристики файлов этих двух сокетов. При использовании команды socketpair , необходимо определить PF_LOCAL как пространство имен.