Что такое Socket (Сокет) в сетевом программировании
Сокеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Следует различать клиентские и серверные сокеты. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты. Программный интерфейс сокетов описан в стандарте POSIX.1 и в той или иной мере поддерживается всеми современными операционными системами.
Сокет на сленге системных администраторов означает комбинацию IP-адреса и номера порта, например 10.10.10.10:80.
Сокеты и бла-бла-бла.
Для того чтобы не было недоразумений, я сразу оговорюсь, что написанное ниже рассчитано на тех, кто кодит на с/с++ (MSVC++ в Windows-системах и gсс/g++ в никсах). Я также предполагаю, что у читателей есть хотя бы минимальный набор знаний об устройстве и функционировании компьютерных сетей. Необязателен, но желателен справочник по Windows API 32 под рукой или доступ к MSDN (юниксоидам в этом плане повезло — man pages не могут быть «не под рукой» ;)). Еще я хотел бы сделать предупреждение: представленный ниже материал не претендует на полноту освещения затронутых в нем тем, а также на абсолютную точность.
И наконец, перед тем, как мы окунемся в омут с головой, я дам еще один совет: дружище, выучи все-таки английский! Он тебе очень пригодится. Ведь когда ты захочешь стать гуру сетевого программирования, тебе придется прочесть очень много RFC -документов, а ошибки перевода и неправильного толкования технических спецификаций являются «бомбами замедленного действия»!
У каждой уважающей себя современной операционной системы есть средства для взаимодействия с другими компьютерами. Самым распространенным среди программистов средством для упомянутых целей являются сокеты. Сокеты — это API (Application Programming Interface — Интерфейс Программирования Приложений) для работы с уровнями OSI. Сокеты настолько гибки, что позволяют работать почти с любым из уровней модели OSI. Хочешь — формируй IP-пакеты руками и займись хакингом, отправляя «неправильные» пакеты, которые будут вводить сервера в ступор, хочешь — займись более благоразумным делом и создай новый удобный голосовой чат, хочешь — игрульку по сети гоняй, не хочешь — твое право, но этот случай мы в данном руководстве не рассматриваем… 🙂
Сокеты и бла-бла-бла…
Когда мы создаем сокет (socket — гнездо), мы получаем возможность доступа к нужному нам уровню OSI. Ну а дальше мы можем использовать соответствующие вызовы для взаимодействия с ним. Для того чтобы понять сокеты, можно провести аналогию с телефонным аппаратом и телефонной трубкой. Сокеты устроены таким образом, что они могут взаимодействовать с ОС на любом уровне OSI, скрывая ту часть реализации, которой мы не интересуемся (тебя же не волнует, как работает телефон, когда ты набираешь 03). Телефоны и сокеты бывают разные: бывают старые телефоны с дисковым набором и бывают низкоуровневые сокеты для работы с Ethernet-фреймами, бывают супер-модные цифровые телефоны и бывают сокеты для работы с верхними уровнями стека протоколов… и т.д. Причем вызовы для всех типов сокетов одни и те же, что, имхо, очень удобно. Когда мы создаем сокет, мы также заставляем систему организовать два канала: входящий (это как громкоговоритель у телефона) и исходящий (микрофон). Осуществляя чтение и запись в эти каналы, мы приказываем системе взять на себя дальнейшую судьбу данных, т.е. передать и проследить, чтоб данные дошли вовремя, в нужной последовательности, не искаженные и т.п. Система должна давать (и дает) максимум гарантий (для каждого уровня OSI — гарантии свои), что данные будут переданы правильно. Наша задача — поместить их в очередь, а на другом конце — прочитать из входящей очереди и обработать должным образом. Все остальное — нам ни к чему. Еще один плюс — сокеты переносимы. То есть изначально концепция сокетов была разработана в Berkeley, поэтому классическая реализация сокетов называется Berkeley sockets или BSD sockets (BSD == Berkeley Software Distribution). В дальнейшем, почти все ОС тем или иным образом унаследовали эту реализацию. В каждой ОС степень поддержки сокетов разная, но точно могу сказать: в современных операционных системах MS и *nix — сокеты поддерживаются настолько, насколько нам, геймдевелоперам, они могут понадобиться. Больше нам и не нужно, потому что мы не кодим под экзотические ОС, потому что, в свою очередь, геймеры (они наша целевая аудитория) на таковых не сидят. Однако по мере изучения мы будем придерживаться классической реализации BSD sockets, и стараться по минимуму использовать системно-зависимый код.
Пример кода клиент- серверного сокета на языке С++
Жизненный цикл сервера для языка С++ можно представить так:
1.Initialize Winsock. 2.Create a socket. Создание сокета ориентированного на соединение (функция socket()). 3.Bind the socket. Назначение сокету адреса привязки (функция bind()). 4.Listen on the socket for a client. Перевод сокета в режим ожидания запроса (функция listen()). 5.Accept a connection from a client. Прием поступающих запросов(функция accept()). 6.Receive and send data. 7.Disconnect. Закрытие сокета (функция close()).
Жизненный цикл клиента можно представить так:
1.Initialize Winsock. 2.Create a socket. 3.Connect to the server. 4.Send and receive data. 5.Disconnect.
Листинг клиента
#include #include #include //#include #pragma comment(lib, "Ws2_32.lib") int main(int argc, char **argv) { WSADATA wsaData; int iResult; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2,2), &wsaData); //для инициализации вызываем WSAStartup. MAKEWORD(2,2) указываем версию библиотеки для использования if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); return 1; } //Creating a Socket for the Client struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory( &hints, sizeof(hints) );/*заполняем нулями стркутуру hints, если этого не сделать то струкутура будет заполнена случайными цифрами*/ hints.ai_family = AF_UNSPEC;// hints.ai_socktype = SOCK_STREAM;// hints.ai_protocol = IPPROTO_TCP;// #define DEFAULT_PORT "27015" // Resolve the server address and port iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed: %d\n", iResult); WSACleanup(); return 1; } SOCKET ConnectSocket = INVALID_SOCKET; // Attempt to connect to the first address returned by // the call to getaddrinfo ptr=result; // Create a SOCKET for connecting to server ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // Connect to server. iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } // Should really try the next address returned by getaddrinfo // if the connect call failed // But for this simple example we just free the resources // returned by getaddrinfo and print an error message freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } #define DEFAULT_BUFLEN 512 int recvbuflen = DEFAULT_BUFLEN; char *sendbuf = "this is a test"; char recvbuf[DEFAULT_BUFLEN]; // Send an initial buffer iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("send failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Bytes Sent: %ld\n", iResult); // shutdown the connection for sending since no more data will be sent // the client can still use the ConnectSocket for receiving data iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // Receive data until the server closes the connection do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("Bytes received: %d\n", iResult); else if (iResult == 0) printf("Connection closed\n"); else printf("recv failed: %d\n", WSAGetLastError()); } while (iResult > 0); // shutdown the send half of the connection since no more data will be sent iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // cleanup closesocket(ConnectSocket); WSACleanup(); return 0; }
Листинг сервера
#include #include #include #pragma comment(lib, "Ws2_32.lib") int main() { WSADATA wsaData; int iResult; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); return 1; } #define DEFAULT_PORT "27015" struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory(&hints, sizeof (hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; // Resolve the local address and port to be used by the server iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed: %d\n", iResult); WSACleanup(); return 1; } SOCKET ListenSocket = INVALID_SOCKET; // Create a SOCKET for the server to listen for client connections ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // Setup the TCP listening socket iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed: %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) { printf( "Listen failed with error: %ld\n", WSAGetLastError() ); closesocket(ListenSocket); WSACleanup(); return 1; } SOCKET ClientSocket; ClientSocket = INVALID_SOCKET; // Accept a client socket ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } #define DEFAULT_BUFLEN 512 char recvbuf[DEFAULT_BUFLEN]; int iSendResult; int recvbuflen = DEFAULT_BUFLEN; // Receive until the peer shuts down the connection do { iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); if (iResult > 0) { printf("Bytes received: %d\n", iResult); // Echo the buffer back to the sender iSendResult = send(ClientSocket, recvbuf, iResult, 0); if (iSendResult == SOCKET_ERROR) { printf("send failed: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } printf("Bytes sent: %d\n", iSendResult); } else if (iResult == 0) printf("Connection closing. \n"); else { printf("recv failed: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } } while (iResult > 0); // shutdown the send half of the connection since no more data will be sent iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } // cleanup closesocket(ClientSocket); WSACleanup(); return 0; }
Сокеты
— это один конец двустороннего канала связи между двумя программами, работающими в сети. Соединяя вместе два сокета, можно передавать данные между разными процессами (локальными или удаленными). Реализация сокетов обеспечивает инкапсуляцию протоколов сетевого и транспортного уровней.
Первоначально сокеты были разработаны для UNIX в Калифорнийском университете в Беркли. В UNIX обеспечивающий связь метод ввода-вывода следует алгоритму open/read/write/close. Прежде чем ресурс использовать, его нужно открыть, задав соответствующие разрешения и другие параметры. Как только ресурс открыт, из него можно считывать или в него записывать данные. После использования ресурса пользователь должен вызывать метод Close(), чтобы подать сигнал операционной системе о завершении его работы с этим ресурсом.
Когда в операционную систему UNIX были добавлены средства межпроцессного взаимодействия (Inter-Process Communication, IPC) и сетевого обмена, был заимствован привычный шаблон ввода-вывода. Все ресурсы, открытые для связи, в UNIX и Windows идентифицируются дескрипторами. Эти дескрипторы, или описатели (handles), могут указывать на файл, память или какой-либо другой канал связи, а фактически указывают на внутреннюю структуру данных, используемую операционной системой. Сокет, будучи таким же ресурсом, тоже представляется дескриптором. Следовательно, для сокетов жизнь дескриптора можно разделить на три фазы: открыть (создать) сокет, получить из сокета или отправить сокету и в конце концов закрыть сокет.
Интерфейс IPC для взаимодействия между разными процессами построен поверх методов ввода-вывода. Они облегчают для сокетов отправку и получение данных. Каждый целевой объект задается адресом сокета, следовательно, этот адрес можно указать в клиенте, чтобы установить соединение с целью.
Типы сокетов
Существуют два основных типа сокетов — потоковые сокеты и дейтаграммные.
Потоковые сокеты (stream socket)
Потоковый сокет — это сокет с установленным соединением, состоящий из потока байтов, который может быть двунаправленным, т, е. через эту конечную точку приложение может и передавать, и получать данные.
Потоковый сокет гарантирует исправление ошибок, обрабатывает доставку и сохраняет последовательность данных. На него можно положиться в доставке упорядоченных, сдублированных данных. Потоковый сокет также подходит для передачи больших объемов данных, поскольку накладные расходы, связанные с установлением отдельного соединения для каждого отправляемого сообщения, может оказаться неприемлемым для небольших объемов данных. Потоковые сокеты достигают этого уровня качества за счет использования протокола . TCP обеспечивает поступление данных на другую сторону в нужной последовательности и без ошибок.
Для этого типа сокетов путь формируется до начала передачи сообщений. Тем самым гарантируется, что обе участвующие во взаимодействии стороны принимают и отвечают. Если приложение отправляет получателю два сообщения, то гарантируется, что эти сообщения будут получены в той же последовательности.
Однако, отдельные сообщения могут дробиться на пакеты, и способа определить границы записей не существует. При использовании TCP этот протокол берет на себя разбиение передаваемых данных на пакеты соответствующего размера, отправку их в сеть и сборку их на другой стороне. Приложение знает только, что оно отправляет на уровень TCP определенное число байтов и другая сторона получает эти байты. В свою очередь TCP эффективно разбивает эти данные на пакеты подходящего размера, получает эти пакеты на другой стороне, выделяет из них данные и объединяет их вместе.
Потоки базируются на явных соединениях: сокет А запрашивает соединение с сокетом В, а сокет В либо соглашается с запросом на установление соединения, либо отвергает его.
Если данные должны гарантированно доставляться другой стороне или размер их велик, потоковые сокеты предпочтительнее дейтаграммных. Следовательно, если надежность связи между двумя приложениями имеет первостепенное значение, выбирайте потоковые сокеты.
Сервер электронной почты представляет пример приложения, которое должно доставлять содержание в правильном порядке, без дублирования и пропусков. Потоковый сокет рассчитывает, что TCP обеспечит доставку сообщений по их назначениям.
Дейтаграммные сокеты (datagram socket)
Дейтаграммные сокеты иногда называют сокетами без организации соединений, т. е. никакого явного соединения между ними не устанавливается — сообщение отправляется указанному сокету и, соответственно, может получаться от указанного сокета.
Потоковые сокеты по сравнению с дейтаграммными действительно дают более надежный метод, но для некоторых приложений накладные расходы, связанные с установкой явного соединения, неприемлемы (например, сервер времени суток, обеспечивающий синхронизацию времени для своих клиентов). В конце концов на установление надежного соединения с сервером требуется время, которое просто вносит задержки в обслуживание, и задача серверного приложения не выполняется. Для сокращения накладных расходов нужно использовать дейтаграммные сокеты.
Использование дейтаграммных сокетов требует, чтобы передачей данных от клиента к серверу занимался . В этом протоколе на размер сообщений налагаются некоторые ограничения, и в отличие от потоковых сокетов, умеющих надежно отправлять сообщения серверу-адресату, дейтаграммные сокеты надежность не обеспечивают. Если данные затерялись где-то в сети, сервер не сообщит об ошибках.
Кроме двух рассмотренных типов существует также обобщенная форма сокетов, которую называют необрабатываемыми или сырыми.
Сырые сокеты (raw socket)
Главная цель использования сырых сокетов состоит в обходе механизма, с помощью которого компьютер обрабатывает TCP/IP. Это достигается обеспечением специальной реализации стека TCP/IP, замещающей механизм, предоставленный стеком TCP/IP в ядре — пакет непосредственно передается приложению и, следовательно, обрабатывается гораздо эффективнее, чем при проходе через главный стек протоколов клиента.
По определению, — это сокет, который принимает пакеты, обходит уровни TCP и UDP в стеке TCP/IP и отправляет их непосредственно приложению.
При использовании таких сокетов пакет не проходит через фильтр TCP/IP, т.е. никак не обрабатывается, и предстает в своей сырой форме. В таком случае обязанность правильно обработать все данные и выполнить такие действия, как удаление заголовков и разбор полей, ложится на получающее приложение — все равно, что включить в приложение небольшой стек TCP/IP.
Однако нечасто может потребоваться программа, работающая с сырыми сокетами. Если вы не пишете системное программное обеспечение или программу, аналогичную анализатору пакетов, вникать в такие детали не придется. Сырые сокеты главным образом используются при разработке специализированных низкоуровневых протокольных приложений. Например, такие разнообразные утилиты TCP/IP, как trace route, ping или arp, используют сырые сокеты.
Работа с сырыми сокетами требует солидного знания базовых протоколов TCP/UDP/IP.
Порты
Порт определен, чтобы разрешить задачу одновременного взаимодействия с несколькими приложениями. По существу с его помощью расширяется понятие IP-адреса. Компьютер, на котором в одно время выполняется несколько приложений, получая пакет из сети, может идентифицировать целевой процесс, пользуясь уникальным номером порта, определенным при установлении соединения.
Сокет состоит из IP-адреса машины и номера порта, используемого приложением TCP. Поскольку IP-адрес уникален в Интернете, а номера портов уникальны на отдельной машине, номера сокетов также уникальны во всем Интернете. Эта характеристика позволяет процессу общаться через сеть с другим процессом исключительно на основании номера сокета.
За определенными службами номера портов зарезервированы — это широко известные номера портов, например порт 21, использующийся в FTP. Ваше приложение может пользоваться любым номером порта, который не был зарезервирован и пока не занят. Агентство Internet Assigned Numbers Authority (IANA) ведет перечень широко известных номеров портов.
Обычно приложение клиент-сервер, использующее сокеты, состоит из двух разных приложений — клиента, инициирующего соединение с целью (сервером), и сервера, ожидающего соединения от клиента.
Например, на стороне клиента, приложение должно знать адрес цели и номер порта. Отправляя запрос на соединение, клиент пытается установить соединение с сервером:
Если события развиваются удачно, при условии что сервер запущен прежде, чем клиент попытался с ним соединиться, сервер соглашается на соединение. Дав согласие, серверное приложение создает новый сокет для взаимодействия именно с установившим соединение клиентом:
Теперь клиент и сервер могут взаимодействовать между собой, считывая сообщения каждый из своего сокета и, соответственно, записывая сообщения.
Работа с сокетами в .NET
Поддержку сокетов в .NET обеспечивают классы в пространстве имен System.Net.Sockets — начнем с их краткого описания.
Класс | Описание |
---|---|
MulticastOption | Класс MulticastOption устанавливает значение IP-адреса для присоединения к IP-группе или для выхода из нее. |
NetworkStream | Класс NetworkStream реализует базовый класс потока, из которого данные отправляются и в котором они получаются. Это абстракция высокого уровня, представляющая соединение с каналом связи TCP/IP. |
TcpClient | Класс TcpClient строится на классе Socket, чтобы обеспечить TCP-обслуживание на более высоком уровне. TcpClient предоставляет несколько методов для отправки и получения данных через сеть. |
TcpListener | Этот класс также построен на низкоуровневом классе Socket. Его основное назначение — серверные приложения. Он ожидает входящие запросы на соединения от клиентов и уведомляет приложение о любых соединениях. |
UdpClient | UDP — это протокол, не организующий соединение, следовательно, для реализации UDP-обслуживания в .NET требуется другая функциональность. |
SocketException | Это исключение порождается, когда в сокете возникает ошибка. |
Socket | Последний класс в пространстве имен System.Net.Sockets — это сам класс Socket. Он обеспечивает базовую функциональность приложения сокета. |
Класс Socket
Класс Socket играет важную роль в сетевом программировании, обеспечивая функционирование как клиента, так и сервера. Главным образом, вызовы методов этого класса выполняют необходимые проверки, связанные с безопасностью, в том числе проверяют разрешения системы безопасности, после чего они переправляются к аналогам этих методов в Windows Sockets API.
Прежде чем обращаться к примеру использования класса Socket, рассмотрим некоторые важные свойства и методы этого класса:
Свойство или метод | Описание |
---|---|
AddressFamily | Дает семейство адресов сокета — значение из перечисления Socket.AddressFamily. |
Available | Возвращает объем доступных для чтения данных. |
Blocking | Дает или устанавливает значение, показывающее, находится ли сокет в блокирующем режиме. |
Connected | Возвращает значение, информирующее, соединен ли сокет с удаленным хостом. |
LocalEndPoint | Дает локальную конечную точку. |
ProtocolType | Дает тип протокола сокета. |
RemoteEndPoint | Дает удаленную конечную точку сокета. |
SocketType | Дает тип сокета. |
Accept() | Создает новый сокет для обработки входящего запроса на соединение. |
Bind() | Связывает сокет с локальной конечной точкой для ожидания входящих запросов на соединение. |
Close() | Заставляет сокет закрыться. |
Connect() | Устанавливает соединение с удаленным хостом. |
GetSocketOption() | Возвращает значение SocketOption. |
IOControl() | Устанавливает для сокета низкоуровневые режимы работы. Этот метод обеспечивает низкоуровневый доступ к лежащему в основе классу Socket. |
Listen() | Помещает сокет в режим прослушивания (ожидания). Этот метод предназначен только для серверных приложений. |
Receive() | Получает данные от соединенного сокета. |
Poll() | Определяет статус сокета. |
Select() | Проверяет статус одного или нескольких сокетов. |
Send() | Отправляет данные соединенному сокету. |
SetSocketOption() | Устанавливает опцию сокета. |
Shutdown() | Запрещает операции отправки и получения данных на сокете. |
Сокеты в Python для начинающих
В далеком для меня 2010 году я писал статью для начинающих про сокеты в Python. Сейчас этот блог канул в небытие, но статья мне показалась довольно полезной. Статью нашел на флешке в либровском документе, так что это не кросспост, не копипаст — в интернете ее нигде нет.
Что это
Для начала нужно разобраться что такое вообще сокеты и зачем они нам нужны. Как говорит вики, сокет — это программный интерфейс для обеспечения информационного обмена между процессами. Но гораздо важнее не зазубрить определение, а понять суть. Поэтому я тут постараюсь рассказать все как можно подробнее и проще.
Существуют клиентские и серверные сокеты. Вполне легко догадаться что к чему. Серверный сокет прослушивает определенный порт, а клиентский подключается к серверу. После того, как было установлено соединение начинается обмен данными.
Рассмотрим это на простом примере. Представим себе большой зал с множеством небольших окошек, за которыми стоят девушки. Есть и пустые окна, за которыми никого нет. Те самые окна — это порты. Там, где стоит девушка — это открытый порт, за которым стоит какое-то приложение, которое его прослушивает. То есть, если, вы подойдете к окошку с номером 9090, то вас поприветствуют и спросят, чем могут помочь. Так же и с сокетами. Создается приложение, которое прослушивает свой порт. Когда клиент устанавливает соединение с сервером на этом порту именно данное приложение будет ответственно за работу этим клиентом. Вы же не подойдете к одному окошку, а кричать вам будут из соседнего 🙂
После успешной установки соединения сервер и клиент начинают обмениваться информацией. Например, сервер посылает приветствие и предложение ввести какую-либо команду. Клиент в свою очередь вводит команду, сервер ее анализирует, выполняет необходимые операции и отдает клиенту результат.
Сервер
Сейчас создайте два файла — один для сервера, а другой для клиента.
В Python для работы с сокетами используется модуль socket:
import socket
Прежде всего нам необходимо создать сокет:
sock = socket.socket()
Здесь ничего особенного нет и данная часть является общей и для клиентских и для серверных сокетов. Дальше мы будем писать код для сервера. Это вполне логично — зачем нам писать клиентское приложение, если некуда подключаться 🙂
Теперь нам нужно определиться с хостом и портом для нашего сервера. Насчет хоста — мы оставим строку пустой, чтобы наш сервер был доступен для всех интерфейсов. А порт возьмем любой от нуля до 65535. Следует отметить, что в большинстве операционных систем прослушивание портов с номерами 0 — 1023 требует особых привилегий. Я выбрал порт 9090. Теперь свяжем наш сокет с данными хостом и портом с помощью метода bind, которому передается кортеж, первый элемент (или нулевой, если считать от нуля) которого — хост, а второй — порт:
sock.bind(('', 9090))
Теперь у нас все готово, чтобы принимать соединения. С помощью метода listen мы запустим для данного сокета режим прослушивания. Метод принимает один аргумент — максимальное количество подключений в очереди. Напряжем нашу бурную фантазию и вспомним про зал с окошками. Так вот этот параметр определяет размер очереди. Если он установлен в единицу, а кто-то, явно лишний, пытается еще подстроится сзади, то его пошлют 🙂 Установим его в единицу:
sock.listen(1)
Ну вот, наконец-то, мы можем принять подключение с помощью метода accept, который возвращает кортеж с двумя элементами: новый сокет и адрес клиента. Именно этот сокет и будет использоваться для приема и посылке клиенту данных.
conn, addr = sock.accept()
Вот и все. Теперь мы установили с клиентом связь и можем с ним «общаться». Т.к. мы не можем точно знать, что и в каких объемах клиент нам пошлет, то мы будем получать данные от него небольшими порциями. Чтобы получить данные нужно воспользоваться методом recv, который в качестве аргумента принимает количество байт для чтения. Мы будем читать порциями по 1024 байт (или 1 кб):
while True: data = conn.recv(1024) if not data: break conn.send(data.upper())
Как мы и говорили для общения с клиентом мы используем сокет, который получили в результате выполнения метода accept. Мы в бесконечном цикле принимаем 1024 байт данных с помощью метода recv. Если данных больше нет, то этот метод ничего не возвращает. Таким образом мы можем получать от клиента любое количество данных.
Дальше в нашем примере для наглядности мы что-то сделаем с полученными данными и отправим их обратно клиенту. Например, с помощью метода upper у строк вернем клиенту строку в верхнем регистре.
Теперь можно и закрыть соединение:
conn.close()
Собственно сервер готов. Он принимает соединение, принимает от клиента данные, возвращает их в виде строки в верхнем регистре и закрывает соединение. Все просто 🙂 В итоге у вас должно было получиться следующее:
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket sock = socket.socket() sock.bind(('', 9090)) sock.listen(1) conn, addr = sock.accept() print 'connected:', addr while True: data = conn.recv(1024) if not data: break conn.send(data.upper()) conn.close()
Клиент
Думаю, что теперь будет легче. Да и само клиентское приложение проще — нам нужно создать сокет, подключиться к серверу послать ему данные, принять данные и закрыть соединение. Все это делается так:
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket sock = socket.socket() sock.connect(('localhost', 9090)) sock.send('hello, world!') data = sock.recv(1024) sock.close() print data
Думаю, что все понятно, т.к. все уже разбиралось ранее. Единственное новое здесь — это метод connect, с помощью которого мы подключаемся к серверу. Дальше мы читаем 1024 байт данных и закрываем сокет.
- Python
- Программирование
Что такое сокеты в программировании
Для обеспечения сетевых коммуникаций используются сокеты. Сокет это конечная точка сетевых коммуникаций. Каждый использующийся сокет имеет тип и ассоциированный с ним процесс. Сокеты существуют внутри коммуникационных доменов. Домены это абстракции, которые подразумевают конкретную структуру адресации и множество протоколов, которое определяет различные типы сокетов внутри домена. Примерами коммуникационных доменов могут быть: UNIX домен , Internet домен , и т.д.
В Internet домене сокет — это комбинация IP адреса и номера порта, которая однозначно определяет отдельный сетевой процесс во всей глобальной сети Internet. Два сокета, один для хоста-получателя, другой для хоста-отправителя, определяют соединение для протоколов, ориентированных на установление связи, таких, как TCP.
- Создание сокета
- Привязка к локальным именам
- Установление связи
- Передача данных
- Закрывание сокетов
- Пример функции, для установления WWW коннекции
Создание сокета
Для создания сокета используется системный вызов socket.
s = socket(domain, type, protocol);
Этот вызов основывается на информации о коммуникационном домене и типе сокета. Для использования особенностей Internet, значения параметров должны быть следующими: communication domain — AF_INET (Internet протоколы). type of the socket — SOCK_STREAM; Этот тип обеспечивает последовательный, надежный, ориентированный на установление двусторонней связи поток байтов.
Выше был упомянут сокет с типом stream. Краткое описание других типов сокетов приведено ниже:
Datagram socket — поддерживает двусторонний поток данных. Не гарантируется, что этот поток будет последовательным, надежным, и что данные не будут дублироваться. Важной характеристикой данного сокета является то, что границы записи данных предопределены. Raw socket — обеспечивает возможность пользовательского доступа к низлежащим коммуникационным протоколам, поддерживающим сокет-абстракции. Такие сокеты обычно являются датаграм- ориентированными.
Функция socket создает конечную точку для коммуникаций и возвращает файловый дескриптор, ссылающийся на сокет, или -1 в случае ошибки. Данный дескриптор используется в дальнейшем для установления связи.
Для создания сокета типа stream с протоколом TCP , обеспечивающим коммуникационную поддержку, вызов функции socket должен быть следующим:
s = socket(AF_INET, SOCK_STREAM, 0);
Привязка к локальным именам
Сокет создается без имени. Пока с сокетом не будет связано имя, удаленные процессы не имеют возможности ссылаться на него и, следовательно, на данном сокете не может быть получено никаких сообщений. Коммуникационные процессы используют для данных целей ассоциации. В Internet домене ассоциация складывается из локального и удаленного адреса и из локального и удаленного порта. В большинстве доменов ассоциация должна быть уникальной.
В Internet домене связывание сокета и имени может быть весьма сложным, но, к счастью, обычно нет необходимости специально привязывать адрес и номер порта к сокету, так как функции connect и send автоматически свяжут данный сокет с подходящим адресом, если это не было сделано до их вызова.
Для связывания сокета с адресом и номером порта используют системный вызов bind:
bind(s, name, namelen);
Привязываемое имя (name) это строка байт переменной длины, которая интерпретируется поддерживаемым протоколом. Интерпретация может различаться в различных коммуникационных доменах.
Установление связи
Со стороны клиента связь устанавливается с помощью стандартной функции connect:
error = connect(s, serveraddr, serveraddrlen);
которая инициирует установление связи на сокете, используя дескриптор сокета s и информацию из структуры serveraddr , имеющей тип sockaddr_in , которая содержит адрес сервера и номер порта на который надо установить связь. Если сокет не был связан с адресом, connect автоматически вызовет системную функцию bind.
Connect возвращает 0, если вызов прошел успешно. Возвращенная величина -1 указывает на то, что в процессе установления связи произошла некая ошибка. В случае успешного вызова функции процесс может работать с дескриптором сокета, используя функции read и write, и закрывать канал используя функцию close.
Со стороны сервера процесс установления связи сложнее. Когда сервер желает предложить один из своих сервисов, он связывает сокет с общеизвестным адресом, ассоциирующимся с данным сервисом, и пассивно слушает этот сокет. Для этих целей используется системный вызов listen:
error = listen(s, qlength);
где s это дескриптор сокета, а qlength это максимальное количество запросов на установление связи, которые могут стоять в очереди, ожидая обработки сервером; это количество может быть ограничено особенностями системы.
Когда сервер получает запрос от клиента и принимает решение об установлении связи, он создает новый сокет и связывает его с ассоциацией, эквивалентной ‘слушающему сокету’. Для Internet домена это означает тот же самый номер порта. Для этой цели используется системный вызов accept:
newsock = accept(s, clientaddr, clientaddrlen);
Сокет, ассоциированный клиентом, и сокет, который был возвращен функцией accept, используются для установления связи между сервером и клиентом.
Процесс установления связи показан на рисунке 1.
Рис. 1: Взаимодействие клиента и сервера
Передача данных
Когда связь установлена, с помощью различных функций может начаться процесс передачи данных. При наличии связи, пользователь может посылать и получать сообщения с помощью функций read и write:
write(s, buf, sizeof(buf)); read(s, buf, sizeof(buf));
Вызовы send и recv практически идентичны read и write, за исключением того, что добавляется аргумент флагов.
send(s, buf, sizeof(buf), flags); recv(s, buf, sizeof(buf), flags);
- MSG_OOB — Посылать/получать данные, характерные для сокетов типа stream.
- MSG_PEEK — Просматривать данные без чтения. когда указывается в recv, любые присутствующие данные возвращаются пользователю, но сами данные остаются как «непрочитанные». Следующий read или recv вызванный на данном сокете вернет прочитанные в прошлый раз данные.
- MSG_DONTROUTE — посылать данные без маршрутизации пакетов. (Используется только процессами, управляющими таблицами маршрутизации.)
Закрывание сокетов
Когда взаимодействующие модули решают прекратить передачу данных и закрыть сеанс связи, они обмениваются трехсторонним рукопожатием с сегментами, содержащими установленный бит «От отправителя больше нет данных» (этот бит еще называется FIN бит).
Если сокет больше не используется, процесс может закрыть его с помощью функции close, вызвав ее с соответствующим дескриптором сокета:
Если данные были ассоциированы с сокетом, обещающим доставку (сокет типа stream), система будет пытаться осуществить передачу этих данных. Тем не менее, по истечении довольно таки длительного промежутка времени, если данные все еще не доставлены, они будут отброшены. Если пользовательский процесс желает прекратить любую передачу данных, он может сделать это с помощью вызова shutdown на данном сокете для его закрытия. Вызов shutdown вызывает «моментальное» отбрасывание всех стоящих в очереди данных. Формат вызова следующий:
- 0 — если пользователь больше не желает читать данные
- 1 — если данные больше не будут посылаться
- 2 — если данные не будут ни посылаться ни получаться
Пример функции, для установления WWW коннекции
/* ----------------------------------------------------------- MakeConnection Function allocates a socket and estabishes a connection with remote host. Default port number 80. Input : WWW server name (with port number, if it is not 80) Output : file descriptor on success -1 on error ----------------------------------------------------------- */ int MakeConnection(unsigned char* ServerName)< int s; struct sockaddr_in ssin; struct hostent* hp; int PortNum; unsigned char strHlp[STRNGLEN], *pch; /* use default port number - 80 or specific number from the server name */ strcpy(strHlp,ServerName); pch = strchr(strHlp,':'); if(pch==NULL)< PortNum = 80; >else < pch[0] = '\0'; pch++; PortNum = atoi(pch); if(PortNum==0)< PortNum = 80; >> /* get host by name - resolve host name into IP address */ if( (hp=gethostbyname(strHlp)) == NULL ) < return -1; >bzero(&ssin, sizeof(ssin)); bcopy(hp->h_addr, &ssin.sin_addr, hp->h_length); ssin.sin_family = hp->h_addrtype; ssin.sin_port = htons(PortNum); /* allocate a socket */ if((s=socket(AF_INET, SOCK_STREAM, 0))==-1) < return -1; >/* make a connection */ if(connect(s, &ssin, sizeof(ssin), 0)==-1) < return -1; >return s; /* socket descriptor */ >