Interested Article - Сокеты Беркли

Сокеты Беркли интерфейс программирования приложений (API), представляющий собой библиотеку для разработки приложений на языке C с поддержкой межпроцессного взаимодействия (IPC), часто применяемый в компьютерных сетях .

Сокеты Беркли (также известные как API сокетов BSD ) впервые появились как API в операционной системе 4.1BSD Unix (выпущенной в 1982 году) . Тем не менее, только в 1989 году Калифорнийский университет в Беркли смог начать выпускать версии операционной системы и сетевой библиотеки без лицензионных ограничений AT&T , действующих в защищённой авторским правом Unix.

API сокетов Беркли сформировал фактически стандарт абстракции для сетевых сокетов. Большинство прочих языков программирования использует интерфейс, схожий с API языка Си.

API Интерфейса транспортного уровня (TLI), основанный на STREAMS, представляет собой альтернативу сокетному API. Тем не менее, API сокетов Беркли значительно преобладает в популярности и количестве реализаций.

Интерфейс сокета Беркли

Интерфейс сокета Беркли API , позволяющий реализовывать взаимодействие между компьютерами или между процессами на одном компьютере. Данная технология может работать со множеством различных устройств ввода-вывода и драйверов , несмотря на то, что их поддержка зависит от реализации операционной системы . Подобная реализация интерфейса лежит в основе TCP/IP , благодаря чему считается одной из фундаментальных технологий, на которых основывается Интернет . Технология сокетов впервые была разработана в Калифорнийском университете Беркли для применения на UNIX -системах. Все современные операционные системы имеют ту или иную реализацию интерфейса сокетов Беркли, так как это стало стандартным интерфейсом для подключения к сети Интернет.

Программисты могут получать доступ к интерфейсу сокетов на трёх различных уровнях, наиболее мощным и фундаментальным из которых является уровень сырых сокетов . Довольно небольшое число приложений нуждается в ограничении контроля над исходящими соединениями, реализуемыми ими, поэтому поддержка сырых сокетов задумывалась быть доступной только на компьютерах, применяемых для разработки на основе технологий, связанных с Интернетом. Впоследствии в большинстве операционных систем была реализована их поддержка, включая Windows .

Заголовочные файлы

Программная библиотека сокетов Беркли включает в себя множество связанных заголовочных файлов.

<sys/socket.h>
Базовые функции сокетов BSD и структуры данных.
<netinet/in.h>
Семейства адресов/протоколов PF_INET и PF_INET6. Широко используются в сети Интернет, включают в себя IP-адреса, а также номера портов TCP и UDP.
<sys/un.h>
Семейство адресов PF_UNIX/PF_LOCAL. Используется для локального взаимодействия между программами, запущенными на одном компьютере. В компьютерных сетях не применяется.
<arpa/inet.h>
Функции для работы с числовыми IP-адресами.
<netdb.h>
Функции для преобразования протокольных имен и имен хостов в числовые адреса. Используются локальные данные аналогично DNS.

Структуры

  • sockaddr — обобщённая структура адреса, к которой, в зависимости от используемого семейства протоколов, приводится соответствующая структура, например:
struct sockaddr_in stSockAddr;
...
bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in));
  • sockaddr_in
  • sockaddr_in6
  • in_addr
  • in6_addr

Функции

socket()

socket() создаёт конечную точку соединения и возвращает дескриптор . socket() принимает три аргумента:

  • domain , указывающий семейство протоколов создаваемого сокета. Этот параметр задает правила использования именования и формат адреса. Например:
    • PF_INET для сетевого протокола IPv4 или
    • PF_INET6 для IPv6 .
    • PF_UNIX для локальных сокетов (используя файл).
  • type (тип) один из:
    • SOCK_STREAM надёжная потокоориентированная служба (TCP) (сервис) или потоковый сокет
    • SOCK_DGRAM служба датаграмм (UDP) или датаграммный сокет
    • SOCK_SEQPACKET надёжная служба последовательных пакетов
    • SOCK_RAW Сырой сокет — сырой протокол поверх сетевого уровня.
  • protocol определяет используемый транспортный протокол. Самые распространённые — это IPPROTO_TCP , IPPROTO_SCTP , IPPROTO_UDP , IPPROTO_DCCP . Эти протоколы указаны в <netinet/in.h>. Значение « 0 » может быть использовано для выбора протокола по умолчанию из указанного семейства ( domain ) и типа ( type ).

Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

Прототип

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

gethostbyname() и gethostbyaddr()

Функции gethostbyname() и gethostbyaddr() возвращают указатель на объект типа struct hostent , описывающий интернет-узел по имени или по адресу, соответственно. Эта структура содержит или информацию, полученную от сервера имен или произвольные поля из строки в /etc/hosts. Если локальный сервер имен не запущен, то эти подпрограммы просматривают /etc/hosts. Функции принимают следующие аргументы:

  • name , определяющий имя хоста. Например: www.wikipedia.org
  • addr , определяющий указатель на struct in_addr , содержащую адрес хоста.
  • len , определяющий длину в байтах addr .
  • type , определяющий тип области адресов хоста. Например: PF_INET

Функции возвращают NULL-указатель в случае ошибки. В этом случае может быть проверена дополнительная целая h_errno для выявления ошибки или неправильного или неизвестного хоста. В противном случае возвращается корректная struct hostent * .

Прототипы

struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);

connect()

connect() Устанавливает соединение с сервером. Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.

Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.

Прототип

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

bind()

bind() связывает сокет с конкретным адресом. Когда сокет создается при помощи socket() , он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

  • sockfd — дескриптор, представляющий сокет при привязке
  • serv_addr — указатель на структуру sockaddr , представляющую адрес, к которому привязываем.
  • addrlen — поле socklen_t , представляющее длину структуры sockaddr .

Возвращает 0 при успехе и −1 при возникновении ошибки.

Прототип

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

listen()

listen() подготавливает привязываемый сокет к принятию входящих соединений (так называемое «прослушивание»). Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET . Принимает два аргумента:

  • sockfd — корректный дескриптор сокета.
  • backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.

После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

Прототип

#include <sys/socket.h>
int listen(int sockfd, int backlog);

accept()

accept() используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

  • sockfd — дескриптор слушающего сокета на принятие соединения.
  • cliaddr — указатель на структуру sockaddr , для принятия информации об адресе клиента.
  • addrlen — указатель на socklen_t , определяющее размер структуры, содержащей клиентский адрес и переданной в accept() . Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

Прототип

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

Дополнительные параметры для сокетов

После создания сокета можно задавать для него дополнительные параметры. Вот некоторые из них:

  • TCP_NODELAY отключает алгоритм Нейгла ;
  • SO_KEEPALIVE включает периодические проверки на наличие 'признаков жизни', если это поддерживается ОС.

Блокирующие и неблокирующие сокеты

Сокеты Беркли могут работать в одном из двух режимов: блокирующем или неблокирующем. Блокирующий сокет не возвращает контроль, пока не отошлёт (или пока не получит) все данные, указанные для операции. Это верно лишь для Linux-систем. В других системах, например во FreeBSD, вполне естественно для блокирующего сокета посылать не все данные (но можно поставить в send() или recv() флаг MSG_WAITALL). Приложение должно проверять возвращаемое значение для отслеживания того, сколько байт было послано/получено и, соответственно, перепосылать необработанную на данный момент информацию . Это может привести к проблемам, если сокет продолжает «слушать»: программа может повиснуть из-за того, что сокет ждет данных, которые могут никогда не прибыть.

Сокет обычно указывается блокирующим или неблокирующим при помощи функций fcntl() или ioctl() .

Передача данных

Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write , но есть специальные функции для передачи данных через сокеты:

Нужно обратить внимание, что при использовании протокола TCP (сокеты типа SOCK_STREAM ) есть вероятность получить меньше данных, чем было передано, так как ещё не все данные были приняты, поэтому нужно либо дождаться, когда функция recv возвратит 0 байт, либо выставить флаг MSG_WAITALL для функции recv , что заставит её дождаться окончания передачи. Для остальных типов сокетов флаг MSG_WAITALL ничего не меняет (например, в UDP весь пакет = целое сообщение). См. также главу «Блокирующие и неблокирующие сокеты».

Высвобождение ресурсов

Система не освобождает ресурсы, выделенные при вызове socket() , пока не произойдет вызова close() . Это особенно важно в случае, если вызов connect() прошёл неудачно и может быть повторен. Каждый вызов socket() должен иметь соответствующий вызов close() во всех возможных путях исполнения. Необходимо добавлять заголовочный файл <unistd.h> для поддержки функции закрытия.

Результатом выполнения системного вызова close() является только обращение к интерфейсу для закрытия сокета, а не закрытие самого сокета. Это является командой для ядра закрыть сокет. Иногда на серверной стороне сокет может перейти в режим ожидания TIME_WAIT длительностью до 4 минут.

Пример клиента и сервера, использующих TCP

TCP реализует концепцию соединения. Процесс создаёт TCP-сокет вызовом функции socket() с параметрами PF_INET или PF_INET6 , а также SOCK_STREAM (Потоковый сокет) и IPPROTO_TCP .

Сервер

Создание простейшего TCP-сервера состоит из следующих шагов:

  • Создание TCP-сокетов вызовом функции socket() .
  • Привязывание сокета к прослушиваемому порту вызовом функции bind() . Перед вызовом bind() программист должен объявить структуру sockaddr_in , очистить её (при помощи memset() ), затем sin_family ( PF_INET или PF_INET6 ) и заполнить поля sin_port (прослушиваемый порт, указать в виде последовательности байтов ). Преобразование short int в порядок байтов может быть выполнено при помощи вызова функции htons() (сокращение от «от хоста в сеть»).
  • Подготовка сокета к прослушиванию на предмет соединений (создание прослушиваемого сокета) при помощи вызова listen() .
  • Принятие входящих соединений через вызов accept() . Это блокирует сокет до получения входящего соединения, после чего возвращает дескриптор сокета для принятого соединения. Первоначальный дескриптор остаётся прослушиваемым дескриптором, а accept() может быть вызван вновь для этого сокета в любое время (пока он открыт).
  • Соединение с удаленным хостом, которое может быть создано при помощи send() и recv() или write() и read() .
  • Итоговое закрытие каждого открытого сокета, который больше не нужен, происходит при помощи close() . Необходимо отметить, что если были любые вызовы fork() , то каждый процесс должен закрыть известные ему сокеты (ядро отслеживает количество процессов, имеющих открытый дескриптор), а кроме того, два процесса не должны использовать один и тот же сокет в одно время.
/* Код сервера на языке Си */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define port	1100

int main(void) {
	struct sockaddr_in stSockAddr;
	int i32SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (i32SocketFD == -1) {
		perror("ошибка при создании сокета");
		exit(EXIT_FAILURE);
	}

	memset(&stSockAddr, 0, sizeof (stSockAddr));

	stSockAddr.sin_family = PF_INET;
	stSockAddr.sin_port = htons(port);
	stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(i32SocketFD, (struct sockaddr*) &stSockAddr, sizeof (stSockAddr)) == -1) {
		perror("Ошибка: связывания");

		close(i32SocketFD);
		exit(EXIT_FAILURE);
	}

	if (listen(i32SocketFD, 10) == -1) {
		perror("Ошибка: прослушивания");

		close(i32SocketFD);
		exit(EXIT_FAILURE);
	}

	for (;;) {
		int i32ConnectFD = accept(i32SocketFD, 0, 0);

		if (i32ConnectFD < 0) {
			perror("Ошибка: принятия");
			close(i32SocketFD);
			exit(EXIT_FAILURE);
		}

		/* выполнение операций чтения и записи ... */

		shutdown(i32ConnectFD, SHUT_RDWR);

		close(i32ConnectFD);
	}

	return 0;
}

Клиент

Создание TCP-клиента происходит следующим образом:

  • Создание TCP-сокета вызовом socket() .
  • Соединение с сервером при помощи connect() , передача структуры sockaddr_in с sin_family с указанными PF_INET или PF_INET6 , sin_port для указания порта прослушивания (в байтовом порядке), и sin_addr для указания IPv4 или IPv6 адреса прослушиваемого сервера (также в байтовом порядке).
  • Взаимодействие с сервером при помощи send() и recv() или write() и read() .
  • Завершение соединения и сброс информации при вызове close() . Аналогично, если были какие-либо вызовы fork() , каждый процесс должен закрыть ( close() ) сокет.
/* Код клиента на языке Си */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
	struct sockaddr_in stSockAddr;
	int i32Res;
	int i32SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (i32SocketFD == -1) {
		perror("Ошибка: невозможно создать сокет");
		return EXIT_FAILURE;
	}

	memset(&stSockAddr, 0, sizeof (stSockAddr));

	stSockAddr.sin_family = PF_INET;
	stSockAddr.sin_port = htons(1100);
	i32Res = inet_pton(PF_INET, "192.168.1.3", &stSockAddr.sin_addr);

	if (i32Res < 0) {
		perror("Ошибка: первый параметр не относится к категории корректных адресов");
		close(i32SocketFD);
		return EXIT_FAILURE;
	} else if (!i32Res) {
		perror("Ошибка: второй параметр не содержит корректного IP-адреса");
		close(i32SocketFD);
		return EXIT_FAILURE;
	}

	if (connect(i32SocketFD, (struct sockaddr*) &stSockAddr, sizeof (stSockAddr)) == -1) {
		perror("Ошибка: соединения");
		close(i32SocketFD);
		return EXIT_FAILURE;
	}

	/* выполнение операций чтения и записи ... */

	shutdown(i32SocketFD, SHUT_RDWR);

	close(i32SocketFD);
	return 0;
}

Пример клиента и сервера, использующих UDP

UDP основывается на протоколе без установления соединений, то есть протокол, не гарантирующий доставку информации. UDP-пакеты могут приходить не в указанном порядке, дублироваться и приходить более одного раза, или даже не доходить до адресата вовсе. Из-за этих минимальных гарантий UDP значительно уступает протоколу TCP. Отсутствие установки соединений означает отсутствие потоков или соединений между двумя хостами, так как вместо этого данные прибывают в датаграммах ( Датаграммный сокет ).

Адресное пространство UDP, область номеров UDP-портов (в терминологии ISO — TSAP) полностью отделены от TCP-портов.

Сервер

Код может создавать UDP-сервер на порту 7654 следующим образом:

int sock = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );

struct sockaddr_in sa;
int bound;
ssize_t recsize;
socklen_t *address_len=NULL;

sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons( 7654 );

bound = bind( sock, ( struct sockaddr* )&sa, sizeof( struct sockaddr ) );

if ( bound < 0 )
  fprintf( stderr, "bind(): ошибка %s\n", strerror( errno ) );

bind() связывает сокет с парой адрес/порт.

while( 1 )
  {
    printf( "recv test....\n" );
    recsize = recvfrom( sock, ( void* )Hz, 100, 0, ( struct sockaddr* )&sa, address_len );

    if ( recsize < 0 )
      fprintf( stderr, "Ошибка %s\n", strerror( errno ) );

    printf( "recsize: %d\n ", recsize );
    sleep( 1 );
    printf( "datagram: %s\n", Hz );
  }

Такой бесконечный цикл получает все UDP-датаграммы, приходящие на порт 7654, при помощи recvfrom() . Функция использует параметры:

  • сокет,
  • указатель на буфер данных,
  • размер буфера,
  • флаги (аналогично при чтении или других сокетных функциях получения),
  • адресная структура отправителя,
  • длина адресной структуры отправителя.

Клиент

Простейшая демонстрация отправки UDP-пакета, содержащего «Привет!» на адрес 127.0.0.1, порт 7654, выглядит примерно так:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h> /* для вызова close() для сокета */

int main( void )
  {
    int sock;
    struct sockaddr_in sa;
    int bytes_sent;
    const char* buffer = "Привет!";
    int buffer_length;

    buffer_length = strlen( buffer ) + 1;

    sock = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );

    if ( sock == -1 )
      {
         printf("Ошибка создания сокета");
         return 0;
      }

    sa.sin_family = PF_INET;
    sa.sin_addr.s_addr = htonl( 0x7F000001 );
    sa.sin_port = htons( 7654 );

    bytes_sent =
      sendto(
        sock,
        buffer,
        strlen( buffer ) + 1,
        0,
        ( struct sockaddr* )&sa,
        sizeof( struct sockaddr_in )
      );

    if ( bytes_sent < 0 )
      printf( "Ошибка отправки пакета: %s\n", strerror( errno ) );

    close( sock );
    return 0;
  }

См. также

Примечания

  1. Uresh Vahalia. UNIX internals: the new frontiers. — Upper Saddle River, New Jersey 07458: Prentice Hall PTR, 2003. — 844 с. — ISBN 0-13-101908-2 .
  2. . Дата обращения: 12 декабря 2008. 10 апреля 2011 года.

Ссылки

Определение стандарта «де юре» интерфейса сокетов, содержащееся в стандарте POSIX , более известное как:

  • IEEE Std. 1003.1-2001 Standard for Information Technology — Portable Operating System Interface (POSIX).
  • Open Group Technical Standard: Base Specifications, Issue 6, December 2001.
  • ISO/IEC 9945:2002
  • — информация об этих стандартах, а также о текущей работе над ними.
  • RFC3493 и RFC3542 — описание IPv6 -расширения базового API сокетов.
  • Unix Manual Pages
  • (рус.) на сайте IBM.
  • — 2007
  • — Microsoft’s documentation.
  • — 1996
  • Linux Journal , 1998
Источник —

Same as Сокеты Беркли