Сокет Berkeley ( BSD ) — это интерфейс прикладного программирования (API) для сокетов домена Интернета и сокетов домена Unix , используемый для межпроцессного взаимодействия (IPC). Обычно он реализуется как библиотека подключаемых модулей. Он появился в операционной системе Unix 4.2BSD , выпущенной в 1983 году.
Сокет — это абстрактное представление ( дескриптор ) локальной конечной точки сетевого пути связи. API сокетов Беркли представляет его как файловый дескриптор ( дескриптор файла ) в философии Unix , которая обеспечивает общий интерфейс для ввода и вывода в потоки данных.
Сокеты Berkeley с небольшими изменениями превратились из фактического стандарта в компонент спецификации POSIX . Термин сокеты POSIX по сути является синонимом сокетов Berkeley , но их также называют сокетами BSD , что подтверждает первую реализацию в Berkeley Software Distribution .
Сокеты Беркли появились в операционной системе 4.2BSD Unix , выпущенной в 1983 году, как программный интерфейс. Однако только в 1989 году Калифорнийский университет в Беркли смог выпустить версии операционной системы и сетевой библиотеки, свободные от лицензионных ограничений проприетарного Unix корпорации AT&T .
Все современные операционные системы реализуют версию интерфейса сокета Беркли. Он стал стандартным интерфейсом для приложений, работающих в Интернете . Даже реализация Winsock для MS Windows, созданная независимыми разработчиками, точно следует стандарту.
API сокетов BSD написано на языке программирования C. Большинство других языков программирования предоставляют похожие интерфейсы, обычно написанные как библиотека-обертка на основе API C. [1]
По мере того, как сокетный API Беркли развивался и в конечном итоге привел к сокетному API POSIX, [2] некоторые функции были объявлены устаревшими или удалены и заменены другими. POSIX API также разработан для повторного входа и поддерживает IPv6.
API интерфейса транспортного уровня (TLI) на основе STREAMS предлагает альтернативу API сокета. Многие системы, которые предоставляют API TLI, также предоставляют API сокета Беркли.
Системы, отличные от Unix, часто предоставляют API сокетов Беркли с уровнем трансляции для собственного сетевого API. Plan 9 [3] и Genode [4] используют API файловой системы с управляющими файлами, а не дескрипторами файлов.
Интерфейс сокета Berkeley определен в нескольких файлах заголовков. Имена и содержимое этих файлов немного различаются в зависимости от реализации. В общем, они включают:
API сокетов Беркли обычно предоставляет следующие функции:
Функция socket() создает конечную точку для связи и возвращает файловый дескриптор для сокета. Она использует три аргумента:
Функция возвращает -1, если произошла ошибка. В противном случае она возвращает целое число, представляющее вновь назначенный дескриптор.
bind() связывает сокет с адресом. Когда сокет создается с помощью socket() , ему присваивается только семейство протоколов, но не назначается адрес. Эта ассоциация должна быть выполнена до того, как сокет сможет принимать соединения с других хостов. Функция имеет три аргумента:
bind() возвращает 0 в случае успеха и -1 в случае ошибки.
После того, как сокет был связан с адресом, listen() подготавливает его для входящих подключений. Однако это необходимо только для потокоориентированных (ориентированных на соединение) режимов данных, т. е. для типов сокетов ( SOCK_STREAM , SOCK_SEQPACKET ). listen() требует два аргумента:
После принятия соединения оно удаляется из очереди. В случае успеха возвращается 0. В случае ошибки возвращается -1.
Когда приложение прослушивает потоковые соединения от других хостов, оно уведомляется о таких событиях (ср. функцию select() ) и должно инициализировать соединение с помощью функции accept() . Она создает новый сокет для каждого соединения и удаляет соединение из очереди прослушивания. Функция имеет следующие аргументы:
accept() возвращает новый дескриптор сокета для принятого соединения или значение -1 , если произошла ошибка. Вся дальнейшая коммуникация с удаленным хостом теперь происходит через этот новый сокет.
Сокеты датаграмм не требуют обработки с помощью accept(), поскольку получатель может немедленно ответить на запрос, используя прослушивающий сокет.
connect() устанавливает прямую связь с определенным удаленным хостом, идентифицированным по его адресу, через сокет, идентифицированный по его файловому дескриптору.
При использовании протокола , ориентированного на соединение , это устанавливает соединение. Некоторые типы протоколов не требуют соединения, в частности, протокол пользовательских дейтаграмм . При использовании с протоколами, не требующими соединения, connect определяет удаленный адрес для отправки и получения данных, позволяя использовать такие функции, как send и recv . В этих случаях функция connect предотвращает прием дейтаграмм из других источников.
connect() возвращает целое число, представляющее код ошибки: 0 представляет успех, а –1 представляет ошибку. Исторически сложилось так, что в системах, производных от BSD, состояние дескриптора сокета не определено, если вызов connect не удается (как указано в спецификации Single Unix), поэтому переносимые приложения должны немедленно закрыть дескриптор сокета и получить новый дескриптор с помощью socket(), в случае, если вызов connect() не удается. [5]
Функции gethostbyname() и gethostbyaddr() используются для разрешения имен и адресов хостов в системе доменных имен или других механизмах разрешения локального хоста (например, поиск /etc/hosts). Они возвращают указатель на объект типа struct hostent , который описывает хост протокола Интернета . Функции используют следующие аргументы:
Функции возвращают указатель NULL в случае ошибки, в этом случае можно проверить внешнее целое число h_errno , чтобы узнать, является ли это временным сбоем или недействительным или неизвестным хостом. В противном случае возвращается допустимая структура hostent * .
Эти функции не являются строго компонентом API сокета BSD, но часто используются в сочетании с функциями API для поиска хоста. Эти функции теперь считаются устаревшими интерфейсами для запросов к системе доменных имен. Были определены новые функции, которые полностью не зависят от протокола (поддерживая IPv6). Эти новые функции — getaddrinfo() и getnameinfo() , и они основаны на новой структуре данных addrinfo . [6]
Эта пара функций появилась в то же время, что и API сокетов BSD в 4.2BSD (1983), [7] в том же году, когда впервые была создана DNS. Ранние версии не запрашивали DNS и выполняли только поиск /etc/hosts. Версия 4.3BSD (1984) добавила DNS грубым способом. Текущая реализация с использованием Name Service Switch происходит от Solaris и более поздней NetBSD 1.4 (1999). [8] Первоначально определенная для NIS+ , NSS делает DNS лишь одним из многих вариантов поиска этими функциями, и его использование может быть отключено даже сегодня. [9]
API сокетов Беркли представляет собой общий интерфейс для сетевого взаимодействия и межпроцессного взаимодействия, поддерживающий использование различных сетевых протоколов и архитектур адресов.
Ниже приведен список выборки семейств протоколов (со стандартным символьным идентификатором), определенных в современной реализации Linux или BSD :
Сокет для связи создается с помощью socket()
функции, путем указания желаемого семейства протоколов ( PF_ -идентификатор) в качестве аргумента.
Первоначальная концепция дизайна интерфейса сокета различала типы протоколов (семейства) и конкретные типы адресов, которые каждый из них может использовать. Предполагалось, что семейство протоколов может иметь несколько типов адресов. Типы адресов определялись дополнительными символическими константами с использованием префикса AF вместо PF . Идентификаторы AF предназначены для всех структур данных, которые имеют дело с типом адреса, а не с семейством протоколов. Однако эта концепция разделения протокола и типа адреса не нашла поддержки реализации, и константы AF определялись соответствующим идентификатором протокола, оставляя различие между константами AF и PF как технический аргумент без практических последствий. Действительно, существует много путаницы в правильном использовании обеих форм. [11]
Спецификация POSIX.1—2008 не определяет никаких PF -констант, а только AF -константы [12]
Необработанные сокеты предоставляют простой интерфейс, который обходит обработку стеком TCP/IP хоста. Они позволяют реализовать сетевые протоколы в пространстве пользователя и помогают в отладке стека протоколов. [13] Необработанные сокеты используются некоторыми службами, такими как ICMP , которые работают на уровне Интернета модели TCP/IP.
Розетки Berkeley могут работать в одном из двух режимов: блокирующем и неблокирующем.
Блокирующий сокет не возвращает управление, пока не отправит (или не получит) некоторые или все данные, указанные для операции. Для блокирующего сокета нормально не отправлять все данные. Приложение должно проверить возвращаемое значение, чтобы определить, сколько байт было отправлено или получено, и оно должно повторно отправить любые данные, которые еще не обработаны. [14] При использовании блокирующих сокетов следует уделить особое внимание accept(), поскольку он может все еще блокироваться после указания читаемости, если клиент отключается во время фазы подключения.
Неблокируемый сокет возвращает все, что находится в буфере приема, и немедленно продолжает работу. Если программы, использующие неблокируемые сокеты, написаны неправильно, они особенно подвержены условиям гонки из-за различий в скорости сетевого соединения. [ необходима цитата ]
Сокет обычно устанавливается в блокирующий или неблокирующий режим с помощью функций fcntl и ioctl .
Операционная система не освобождает ресурсы, выделенные сокету, пока сокет не будет закрыт. Это особенно важно, если вызов connect не удается и будет повторен.
Когда приложение закрывает сокет, уничтожается только интерфейс сокета. Ядро отвечает за внутреннее уничтожение сокета. Иногда сокет может войти в состояние TIME_WAIT на стороне сервера на срок до 4 минут. [15]
В системах SVR4 использование close()
может привести к отбрасыванию данных. Использование shutdown()
или SO_LINGER может потребоваться в этих системах для гарантии доставки всех данных. [16]
Transmission Control Protocol (TCP) — это протокол , ориентированный на соединение , который предоставляет множество функций исправления ошибок и производительности для передачи потоков байтов. Процесс создает сокет TCP, вызывая функцию socket()
с параметрами для семейства протоколов ( PF INET , PF_INET6 ), режима сокета для потоковых сокетов ( SOCK_STREAM ) и идентификатора протокола IP для TCP ( IPPROTO_TCP ).
Создание TCP-сервера включает в себя следующие основные шаги:
Следующая программа создает TCP-сервер, прослушивающий порт номер 1100:
#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 sa ; int SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( SocketFD == -1 ) { perror ( " невозможно создать сокет " ) ; exit ( EXIT_FAILURE ) ; } memset ( & sa , 0 , sizeof sa ) ; sa.sin_family = AF_INET ; sa.sin_port = htons ( 1100 ) ; sa.sin_addr . s_addr = htonl ( INADDR_ANY ); if ( bind ( SocketFD ,( struct sockaddr * ) & sa , sizeof sa ) == -1 ) { perror ( "связывание не удалось" ); close ( SocketFD ); exit ( EXIT_FAILURE ); } if ( listen ( SocketFD , 10 ) == -1 ) { perror ( "прослушивание не удалось" ); close ( SocketFD ); exit ( EXIT_FAILURE ); } for (;;) { int ConnectFD = accept ( SocketFD , NULL , NULL ); if ( ConnectFD == -1 ) { perror ( "принять не удалось" ); close ( SocketFD ); exit ( EXIT_FAILURE ); } /* выполнение операций чтения и записи ... read(ConnectFD, buff, size) */ if ( shutdown ( ConnectFD , SHUT_RDWR ) == -1 ) { perror ( "выключение не удалось" ); close ( ConnectFD ); close ( SocketFD ); exit ( EXIT_FAILURE ); } close ( ConnectFD ); } закрыть ( SocketFD ); вернуть EXIT_SUCCESS ; }
Программирование клиентского приложения TCP включает следующие этапы:
sockaddr_in
структуры с sin_family
установленным значением AF_INET , установленным на порт, который прослушивает конечная точка (в сетевом порядке байтов), и установленным на IP-адрес прослушивающего сервера (также в сетевом порядке байтов).sin_port
sin_addr
#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 sa ; int res ; int SocketFD ; SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( SocketFD == -1 ) { perror ( "невозможно создать сокет" ) ; exit ( EXIT_FAILURE ) ; } memset ( & sa , 0 , sizeof sa ); sa.sin_family = AF_INET ; sa.sin_port = htons ( 1100 ) ; res = inet_pton ( AF_INET , " 192.168.1.3 " , & sa.sin_addr ) ; если ( connect ( SocketFD , ( struct sockaddr * ) & sa , sizeof sa ) == -1 ) { perror ( "connect failed" ); close ( SocketFD ); exit ( EXIT_FAILURE ); } /* выполнение операций чтения и записи ... */ close ( SocketFD ); return EXIT_SUCCESS ; }
Протокол пользовательских дейтаграмм (UDP) — это протокол без установления соединения без гарантии доставки. Пакеты UDP могут приходить не по порядку, несколько раз или вообще не приходить. Благодаря этой минимальной конструкции, UDP имеет значительно меньше накладных расходов, чем TCP. Отсутствие соединения означает, что нет понятия потока или постоянного соединения между двумя хостами. Такие данные называются дейтаграммами ( сокетами дейтаграмм ).
Адресное пространство UDP, пространство номеров портов UDP (в терминологии ISO — TSAP ), полностью отделено от пространства портов TCP.
Приложение может настроить UDP-сервер на порту 7654 следующим образом. Программа содержит бесконечный цикл, который получает UDP-датаграммы с помощью функции recvfrom() .
#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() для сокета */ #include <stdlib.h> int main ( void ) { int sock ; struct sockaddr_in sa ; char buffer [ 1024 ]; ssize_t recsize ; socklen_t fromlen ; memset ( & sa , 0 , sizeof sa ); са . sin_family = AF_INET ; са . грех_адрес . s_addr = htonl ( INADDR_ANY ); са . sin_port = htons ( 7654 ); fromlen = sizeof sa ; sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); если ( bind ( sock , ( struct sockaddr * ) & sa , sizeof sa ) == -1 ) { perror ( "ошибка привязки не удалась" ); закрыть ( sock ); выйти ( EXIT_FAILURE ); } for (;;) { recsize = recvfrom ( sock , ( void * ) buffer , sizeof buffer , 0 , ( struct sockaddr * ) & sa , & fromlen ); if ( recsize < 0 ) { fprintf ( stderr , "%s \n " , strerror ( errno )); exit ( EXIT_FAILURE ); } printf ( "recsize: %d \n " , ( int ) recsize ); sleep ( 1 ); printf ( "датаграмма: %.*s \n " , ( int ) recsize , buffer ); } }
Ниже представлена клиентская программа для отправки UDP-пакета, содержащего строку «Hello World!», на адрес 127.0.0.1 через порт 7654.
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> int main ( void ) { int sock ; struct sockaddr_in sa ; int bytes_sent ; char buffer [ 200 ]; strcpy ( buffer , "hello world!" ); /* создаем Интернет, датаграмму, сокет с использованием UDP */ sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( sock == -1 ) { /* если сокет не удалось инициализировать, выходим */ printf ( "Error Creating Socket" ); exit ( EXIT_FAILURE ); } /* Обнуляем адрес сокета */ memset ( & sa , 0 , sizeof sa ); /* Адрес - IPv4 */ sa . sin_family = AF_INET ; /* Адрес IPv4 - это uint32_t, преобразуем строковое представление октетов в соответствующее значение */ sa . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); /* сокеты — это беззнаковые короткие порты, htons(x) гарантирует, что x находится в сетевом порядке байтов, установить порт на 7654 */ sa . sin_port = htons ( 7654 ); bytes_sent = sendto ( sock , buffer , strlen ( buffer ), 0 ,( struct sockaddr * ) & sa , sizeof sa ); if ( bytes_sent < 0 ) { printf ( "Ошибка отправки пакета: %s \n " , strerror ( errno )); exit ( EXIT_FAILURE ); } close ( sock ); /* закрыть сокет */ return 0 ; }
В этом коде buffer — это указатель на отправляемые данные, а buffer_length указывает размер данных.
{{cite web}}
: Отсутствует или пусто |title=
( помощь )Юридически стандартное определение интерфейса Sockets содержится в стандарте POSIX, известном как:
Информация об этом стандарте и текущей работе над ним доступна на веб-сайте Остина.
Расширения IPv6 для API базовых сокетов задокументированы в RFC 3493 и RFC 3542.