С сокеты: Сокеты в C# и .NET

Содержание

PHP: Сокеты — Manual

Change language: EnglishBrazilian PortugueseChinese (Simplified)FrenchGermanJapaneseRomanianRussianSpanishTurkishOther

  • Введение
  • Установка и настройка
  • Предопределённые константы
  • Примеры
  • Ошибки сокетов
  • Функции сокета
    • socket_accept — Принимает соединение на сокете
    • socket_addrinfo_bind — Создать и привязать к сокету из указанного addrinfo
    • socket_addrinfo_connect — Создать и подключиться к сокету из указанного addrinfo
    • socket_addrinfo_explain — Получить информацию о addrinfo
    • socket_addrinfo_lookup — Получить массив с содержимым getaddrinfo про указанное имя хоста
    • socket_bind — Привязывает имя к сокету
    • socket_clear_error — Очищает ошибку на сокете или последний код ошибки
    • socket_close — Закрывает экземпляр Socket
    • socket_cmsg_space — Вычислить размер буфера сообщения
    • socket_connect — Начинает соединение с сокетом
    • socket_create_listen — Открывает сокет на указанном порту для принятия соединений
    • socket_create_pair — Создаёт пару неразличимых сокетов и сохраняет их в массиве
    • socket_create — Создаёт сокет (конечную точку для обмена информацией)
    • socket_export_stream — Экспортировать сокет в поток, инкапсулирующий сокет
    • socket_get_option — Получает опции потока для сокета
    • socket_getopt — Псевдоним socket_get_option
    • socket_getpeername — Запрашивает удалённую сторону указанного сокета, в результате может быть возвращён хост/порт или путь в файловой системе Unix, в зависимости от типа сокета
    • socket_getsockname — Запрашивает локальную сторону указанного сокета, в результате можно получить хост/порт или путь в файловой системе Unix, в зависимости от типа сокета
    • socket_import_stream — Импортировать поток
    • socket_last_error — Возвращает последнюю ошибку на сокете
    • socket_listen — Прослушивает входящие соединения на сокете
    • socket_read — Читает строку максимальную длину байт из сокета
    • socket_recv — Получает данные из подсоединённого сокета
    • socket_recvfrom — Получает данные из сокета, независимо от того, подсоединён он или нет
    • socket_recvmsg — Прочитать сообщение
    • socket_select — Запускает системный вызов select() для заданных массивов сокетов с указанным тайм-аутом
    • socket_send — Отправляет данные в подсоединённый сокет
    • socket_sendmsg — Отправить сообщение
    • socket_sendto — Отправляет сообщение в сокет, независимо от того, подсоединён он или нет
    • socket_set_block — Устанавливает блокирующий режим на сокете
    • socket_set_nonblock — Устанавливает неблокирующий режим для файлового дескриптора fd
    • socket_set_option — Устанавливает опции для сокета
    • socket_setopt — Псевдоним socket_set_option
    • socket_shutdown — Завершает работу сокета на получение и/или отправку данных
    • socket_strerror — Возвращает строку, описывающую ошибку сокета
    • socket_write — Запись в сокет
    • socket_wsaprotocol_info_export — Экспорт структуры WSAPROTOCOL_INFO
    • socket_wsaprotocol_info_import — Импортирует сокет из другого процесса
    • socket_wsaprotocol_info_release — Высвобождает экспортированную структуру WSAPROTOCOL_INFO
  • Socket — Класс Socket
  • AddressInfo — Класс AddressInfo

There are no user contributed notes for this page.

Сокеты в 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 байт данных и закрываем сокет.

Как подключить Socket.io и основы работы с ним

Автор статьи: admin

В этой статья я разберу не большую JavaScript библиотеку для работы с WebSocket, которая называется Socket.io, покажу не большой пример и как подключить.

Также если вы не знаете что такое WebSocket, то посмотрите статью
«Что такое WebSocket и как он работает».

Как подключить Socket.io:

Перед тем как начать работать, нужно рассмотреть подключение Socket.io, это можно сделать двумя способами, через CDN или NPM.

CDN:

<script src=»https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js»></script>

NPM:

Тут стоит сказать, что через NPM, можно скачать Socket.io только для серверной части сайта.

На этом установка закончилась.

Работа с Socket.io:

Теперь перейдём к работе с этой библиотекой, скажу что я не буду делать для примера слишком большую программу.

Для начала сделаем серверную часть на Node.js.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// Создание сервера

const server = require(‘http’). createServer();

// Берём API socket.io

const io = require(‘socket.io’)(server);

 

// Подключаем клиенты

io.on(‘connection’, () => {

    // Выводим в консоль ‘connection’

    console.log(‘connection’)

    // Отправляем всем кто подключился сообщение привет

    io.emit(‘hello’, ‘Привет’)

    // Что делать при случае дисконнекта

    io.on(‘disconnect’, () => {

        // Выводи ‘disconnected’

        console.log(‘disconnected’);

    });

});

 

// Назначаем порт для сервера

server.listen(3000);

Давайте разберём этот не большой код, в начале мы подключаем библиотеку для сервера Node.js, потом уже API Socket.io.

Дальше пишем функцию, которая будет срабатывать, когда клиент подключен к серверу, внутри выводим в консоль «connection», потом отправляем клиенту «Привет», тут стоит заметить, что используем функция io.emit(), которая отправляет сообщения и в качестве первого параметра она использует грубо говоря заголовок сообщения, второй это само сообщение.

Примечание:

Первый параметр нужно запомнить, так как он будет использоваться для принятия сообщения на стороне клиента.

Потом отслеживаем десконект и при случаи этого выводим в консоль «disconnected», последние назначаем порт для подключения к серверу.

Теперь перейдём клинской части сайта, тут тоже нечего сложного нет, но в начале, мы создадим HTML документ.

<!DOCTYPE html>

<html lang=»ru»>

<head>

    <meta charset=»UTF-8″>

    <title>WebSocket</title>

</head>

<body>

    <div></div>

    <script src=»https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js»></script>

</body>

</html>

Самое интересное что есть в этом коде, это блок с текстом, куда будем его выводить, и скрипт с библиотекой Socket.io.

Вот клиентский код.

// Подключаем блок для вывода текста

let text = document. getElementById(«text»)

// Подключаемся к серверу

let socket = io(‘http://localhost:3000’);

// Отслеживаем подключение

socket.on(‘connect’, function () {

    // Выводим сообщение подключение

    text.innerHTML = «Подключение прошло успешно<br>»

    // Отслеживание сообщения от сервера со заголовком ‘hello’

    socket.on(‘hello’, function (data) {

        // Выводим сообщение от сервера

        text.innerHTML += «Сервер: » + data + «<br>»

    });

});

В начале мы подключаем блок и библиотеку Socket.io, дальше отслеживаем подключение к серверу, внутри пишем надпись «Подключение прошло успешно», и смотрит сообщение от сервера со заголовком «hello», выводим это на экран.

Вот что мне вывел сервер.

Вот что вывел клиент.

Как видите всё очень просто, нужно только запомнить две функции.

  • socket.on(header, function) — Для отслеживания сообщений, как от клиента, так и от сервера:
    • header — Заголовок сообщения;
    • function — Функция для назначения что делать если есть сообщение;
  • socket. emit(header, data) — Отправка сообщений ко всем клиентам или серверу:
    • header — Заголовок сообщения;
    • data — Само сообщение;

За счёт того что так мало команд, так легко работать с библиотекой.

Вывод:

В этой статье вы прочитали о JavaScript библиотеки Socket.io, как её подключить и работать с ней, если вас заинтересовала она, то зайдите посмотрите документацию.

Подписываетесь на соц-сети:

Оценка:

Количество оценивших: 2
Средняя оценка: 4,50

Загрузка…

Поделится:

Пока кнопок поделиться нет

Также рекомендую:

Программирование сокетов в Python (Руководство)

Фон

Розетки имеют долгую историю. Их использование originated с ARPANET в 1971 году и позднее стало API в операционной системе Berkeley Software Distribution (BSD), выпущенной в 1983 году, под названием https://en.wikipedia .org/wiki/Berkeley_sockets [Беркли сокеты].

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

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

Наиболее распространенным типом приложений сокетов являются клиент-серверные приложения, где одна сторона выступает в качестве сервера и ожидает подключения от клиентов. Это тип приложения, о котором я расскажу в этом руководстве. Более конкретно, мы рассмотрим API сокетов для сокетов Internet, иногда называемых сокетами Беркли или BSD. Также есть Unix сокеты домена, которые можно использовать только для связи между процессами на одном хосте.

Обзор Socket API

Https://docs.python.org/3/library/socket.html модуль[socket Python] предоставляет интерфейс для API сокетов Berkeley. Это модуль, который мы будем использовать и обсудить в этом руководстве.

Основные функции и методы API сокетов в этом модуле:

Python предоставляет удобный и непротиворечивый API, который отображается непосредственно на эти системные вызовы, их C-аналоги. Мы рассмотрим, как они используются вместе, в следующем разделе.

Как часть своей стандартной библиотеки, Python также имеет классы, которые облегчают использование этих низкоуровневых функций сокетов. Хотя это и не рассматривается в этом руководстве, см. Https://docs.python.org/3/library/socketserver.html[socketserver module], среду для сетевых серверов. Есть также много доступных модулей, которые реализуют высокоуровневые интернет-протоколы, такие как HTTP и SMTP. Для обзора см. Https://docs.python.org/3/library/internet.html[Internet Протоколы и поддержка].

TCP-сокеты

Как вы вскоре увидите, мы создадим объект сокета с помощью + socket.socket () + и определим тип сокета как + socket.SOCK_STREAM +. Когда вы делаете это, используется протокол по умолчанию Transmission Control Protocol (TCP). Это хороший вариант по умолчанию и, вероятно, то, что вы хотите.

Почему вы должны использовать TCP? Протокол управления передачей (TCP):

  • Надежен: отброшенные в сети пакеты обнаруживаются и повторно передаются отправителем.

  • Имеет порядок доставки данных: данные читаются вашим приложением в том порядке, в котором они были написаны отправителем.

Напротив, User Datagram Protocol (UDP) сокеты, созданные с помощью + socket.SOCK_DGRAM +, не являются надежными, и данные, считываемые получателем, могут быть вне приказ от отправителя пишет.

Почему это важно? Сети — это система доставки с максимальными усилиями. Нет никакой гарантии, что ваши данные достигнут пункта назначения или что вы получите то, что было отправлено вам.

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

На диаграмме ниже давайте рассмотрим последовательность вызовов API сокетов и поток данных для TCP:

Левый столбец представляет сервер. На правой стороне находится клиент.

Начиная с верхнего левого столбца, обратите внимание на API-вызовы, которые сервер делает для настройки «слушающего» сокета:

Разъем для прослушивания делает именно так, как он звучит. Он слушает соединения от клиентов. Когда клиент подключается, сервер вызывает + accept () +, чтобы принять или завершить соединение.

Клиент вызывает + connect () +, чтобы установить соединение с сервером и инициировать трехстороннее рукопожатие. Этап рукопожатия важен, поскольку он гарантирует, что каждая сторона соединения доступна в сети, другими словами, что клиент может достичь сервера и наоборот. Может случиться так, что только один хост, клиент или сервер может связаться с другим.

В середине находится раздел «туда-обратно», где данные обмениваются между клиентом и сервером с помощью вызовов + send () + и + recv () +.

Внизу клиент и сервер + close () + их соответствующие сокеты.

Эхо-клиент и сервер

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

Эхо-сервер

Вот сервер, + echo-server.py +:

#!/usr/bin/env python3

import socket

HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
PORT = 65432        # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s. accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
*Примечание:* Не беспокойтесь о понимании всего вышесказанного прямо сейчас. В этих нескольких строках кода много чего происходит. Это только отправная точка, чтобы вы могли увидеть базовый сервер в действии.

В конце этого руководства есть ссылка: #reference [reference section], которая содержит больше информации и ссылки на дополнительные ресурсы. Я буду ссылаться на эти и другие ресурсы на протяжении всего урока.

Давайте пройдемся по каждому вызову API и посмотрим, что происходит.

+ socket.socket () + создает объект сокета, который поддерживает тип менеджера context, так что вы можете использовать его в https://docs.python.org/3/reference/compound_stmts.html#with [+ с оператором +]. Нет необходимости вызывать + s. close () +:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().

Аргументы, передаваемые в https://docs.python.org/3/library/socket.html#socket.socket [+ socket () +], указывают ссылку: # socket-address-family [семейство адресов] и сокет тип. + AF_INET + — это семейство интернет-адресов для IPv4. + SOCK_STREAM + — это тип сокета для ссылки: # tcp-sockets [TCP], протокол, который будет использоваться для передачи наших сообщений в сети.

+ bind () + используется для связывания сокета с определенным сетевым интерфейсом и номером порта:

HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
PORT = 65432        # Port to listen on (non-privileged ports are > 1023)

# ...

s.bind((HOST, PORT))

Значения, передаваемые в + bind () +, зависят от ссылки: # socket-address-family [address family] сокета. В этом примере мы используем + socket.AF_INET + (IPv4). Так что ожидается 2 кортежа: + (хост, порт) +.

+ host + может быть именем хоста, IP-адресом или пустой строкой. Если используется IP-адрес, + host + должен быть строкой адреса в формате IPv4. IP-адрес + 127.0.0.1 + является стандартным IPv4-адресом для интерфейса loopback, поэтому только процессы на хосте смогут подключаться к серверу. Если вы передадите пустую строку, сервер будет принимать соединения на всех доступных интерфейсах IPv4.

+ port + должно быть целым числом из + 1 + -` + 65535 + ( + 0 + зарезервировано). Это TCP port номер для приема соединений от клиентов. Некоторые системы могут требовать привилегий суперпользователя, если порт <+ 1024 +`.

Вот примечание об использовании имен хостов с + bind () +:

_ «Если вы используете имя хоста в части хоста адреса сокета IPv4/v6, программа может показывать недетерминированное поведение, так как Python использует первый адрес, возвращенный из разрешения DNS. Адрес сокета будет по-разному преобразован в фактический адрес IPv4/v6, в зависимости от результатов разрешения DNS и/или конфигурации хоста. Для детерминированного поведения используйте числовой адрес в части хоста ». (Source) _

Я расскажу об этом позже в ссылке: # using-hostnames [Using Hostnames], но об этом стоит упомянуть здесь. А пока, просто поймите, что при использовании имени хоста вы можете увидеть разные результаты в зависимости от того, что возвращается из процесса разрешения имени.

Это может быть что угодно. При первом запуске приложения это может быть адрес + 10.1.2.3 +. В следующий раз это будет другой адрес, + 192.168.0.1 +. В третий раз это может быть + 172.16.7.8 + и так далее.

Продолжая пример с сервером, + listen () + позволяет серверу + accept () + соединений. Это делает его «слушающим» сокетом:

s.listen()
conn, addr = s. accept()

+ listen () + имеет параметр + backlog +. Он указывает количество неприемлемых подключений, которые система разрешит перед отказом в новых подключениях. Начиная с Python 3.5, это необязательно. Если не указано, выбирается значение по умолчанию + backlog +.

Если ваш сервер получает много запросов на соединение одновременно, увеличение значения + backlog + может помочь, установив максимальную длину очереди для ожидающих соединений. Максимальное значение зависит от системы. Например, в Linux см. Https://serverfault.com/questions/518862/will-increasing-net-core-somaxconn-make-a-difference/519152 [+/proc/sys/net/core/somaxconn + ].

+ accept () + link: # blocking-вызывает [блокирует] и ждет входящего соединения. Когда клиент подключается, он возвращает новый объект сокета, представляющий соединение, и кортеж, содержащий адрес клиента. Кортеж будет содержать + (хост, порт) + для соединений IPv4 или + (хост, порт, flowinfo, scopeid) + для IPv6. См. Ссылку: # socket-address-family [Семейства адресов сокетов] в справочном разделе для получения подробной информации о значениях кортежей.

Одна вещь, которую необходимо понять, это то, что теперь у нас есть новый объект сокета из + accept () +. Это важно, так как это сокет, который вы будете использовать для связи с клиентом. Он отличается от сокета прослушивания, который сервер использует для приема новых соединений:

conn, addr = s.accept()
with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024)
        if not data:
            break
        conn.sendall(data)

После получения объекта клиентского сокета + conn + из + accept () + бесконечный цикл + while + используется для циклического перемещения по ссылке: # blocking-call [blocking messages] to + conn.recv () + `. Это читает любые данные, которые клиент отправляет, и возвращает их обратно, используя `+ conn. sendall () +.

Если + conn.recv () + возвращает пустой https://docs.python.org/3/library/stdtypes.html#bytes-objects [+ bytes +] объект, + b '' +, затем клиент закрыл соединение и цикл прерывается. Оператор + with + используется с + conn + для автоматического закрытия сокета в конце блока.

Эхо-клиент

Теперь давайте посмотрим на клиента, + echo-client.py +:

#!/usr/bin/env python3

import socket

HOST = '127.0.0.1'  # The server's hostname or IP address
PORT = 65432        # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b'Hello, world')
    data = s.recv(1024)

print('Received', repr(data))

По сравнению с сервером клиент довольно прост. Он создает объект сокета, подключается к серверу и вызывает + s.sendall () + для отправки своего сообщения. Наконец, он вызывает + s.recv () +, чтобы прочитать ответ сервера, а затем распечатать его.

Запуск клиента и сервера Echo

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

*Примечание:* Если у вас возникли проблемы с запуском примеров или собственного кода из командной строки, прочитайте https://dbader.org/blog/how-to-make-command-line-commands-with-python [Как создавать свои собственные команды командной строки с использованием Python?] Если вы работаете в Windows, проверьте https://docs.python.org/3.6/faq/windows.html[Python Windows FAQ].

Откройте терминал или командную строку, перейдите в каталог, содержащий ваши сценарии, и запустите сервер:

Ваш терминал будет зависать. Это потому, что сервер является ссылкой: # blocking-Call [заблокирован] (приостановлено) в вызове:

Он ждет подключения клиента. Теперь откройте другое окно терминала или командную строку и запустите клиент:

$ . /echo-client.py
Received b'Hello, world'

В окне сервера вы должны увидеть:

$ ./echo-server.py
Connected by ('127.0.0.1', 64623)

В приведенном выше выводе сервер напечатал кортеж + addr +, возвращенный из + s.accept () +. Это IP-адрес клиента и номер порта TCP. Номер порта + 64623 +, скорее всего, будет другим, когда вы запустите его на своем компьютере.

Просмотр состояния сокета

Чтобы увидеть текущее состояние сокетов на вашем хосте, используйте + netstat +. Он доступен по умолчанию в macOS, Linux и Windows.

Вот вывод netstat из macOS после запуска сервера:

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *. *LISTEN

Обратите внимание, что + Local Address + равно + 127. 0.0.1.65432 +. Если бы + echo-server.py + использовал + HOST = '' + вместо + HOST = '127.0.0.1' +, netstat показал бы это:

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0 * .65432                *. *LISTEN

+ Local Address + равно +* . 65432 +, что означает, что все доступные хост-интерфейсы, которые поддерживают семейство адресов, будут использоваться для приема входящих соединений. В этом примере при вызове + socket () + было использовано + socket.AF_INET + (IPv4). Вы можете увидеть это в столбце + Proto +: + tcp4 +.

Я обрезал вывод выше, чтобы показать только эхо-сервер. Скорее всего, вы увидите гораздо больше результатов, в зависимости от того, на какой системе вы его используете. Обратите внимание на столбцы + Proto +, + Local Address + и + (state) +. В последнем примере выше netstat показывает, что эхо-сервер использует TCP-сокет IPv4 (+ tcp4 +), на порту 65432 на всех интерфейсах (+ *. 65432 +), и он находится в состоянии прослушивания (`+ LISTEN + `).

Другой способ увидеть это вместе с дополнительной полезной информацией — использовать + lsof + (список открытых файлов). Он доступен по умолчанию в macOS и может быть установлен в Linux с помощью диспетчера пакетов, если это еще не сделано:

$ lsof -i -n
COMMAND     PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
Python    67982 nathan    3u  IPv4 0xecf272      0t0  TCP *:65432 (LISTEN)

+ lsof + дает вам + COMMAND +, + PID + (идентификатор процесса) и + USER + (идентификатор пользователя) открытых интернет-сокетов при использовании с опцией + -i +. Выше процесс эхо-сервера.

+ netstat + и + lsof + имеют много доступных опций и различаются в зависимости от ОС, на которой вы их используете. Проверьте страницу + man + или документацию для обоих. Им определенно стоит потратить немного времени и узнать поближе. Вы будете вознаграждены. В macOS и Linux используйте + man netstat + и + man lsof +. Для Windows используйте + netstat/? +.

Вот типичная ошибка, которую вы увидите, когда будет предпринята попытка подключения к порту без прослушивающего сокета:

$ ./echo-client.py
Traceback (most recent call last):
  File "./echo-client.py", line 9, in <module>
    s.connect((HOST, PORT))
ConnectionRefusedError: [Errno 61] Connection refused

Либо указан неверный номер порта, либо сервер не работает. А может, на пути к файлу есть брандмауэр, блокирующий соединение, о котором легко забыть. Вы также можете увидеть ошибку + Время ожидания истекло +. Добавьте правило брандмауэра, которое позволяет клиенту подключаться к порту TCP!

Список ссылок: #errors [errors] в справочном разделе.

Отсутствие взаимопонимания

Давайте подробнее рассмотрим, как клиент и сервер взаимодействуют друг с другом:

При использовании интерфейса loopback (адрес IPv4 + 127.0.0.1 + или адрес IPv6 `+

1 `) данные никогда не покидают хост или не касаются внешняя сеть. На приведенной выше схеме интерфейс обратной связи находится внутри хоста. Это представляет внутреннюю природу интерфейса обратной связи и то, что соединения и данные, которые передают его, являются локальными для хоста. Вот почему вы также услышите интерфейс обратной связи и IP-адрес ` 127.0.0.1 ` или ` :: 1 +`, называемый «localhost».

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

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

Когда вы используете в своих приложениях IP-адрес, отличный от + 127.0.0.1 + или `+

1 +`, он, вероятно, связан с интерфейсом Ethernet, который подключен к внешней сети. Это ваши ворота к другим хостам за пределами вашего «локального» королевства:

Будь осторожен там. Это неприятный, жестокий мир. Обязательно прочитайте ссылку на раздел: # using-hostnames [Using Hostnames], прежде чем выходить из безопасных границ «localhost». Есть примечание о безопасности, которое применяется, даже если вы не используете имена хостов и используете только IP-адреса.

Обработка нескольких соединений

Эхо-сервер определенно имеет свои ограничения. Самое большое, что он обслуживает только одного клиента и затем выходит. Клиент echo также имеет это ограничение, но есть дополнительная проблема. Когда клиент делает следующий вызов, возможно, что + s.recv () + вернет только один байт, + b’H '+ из + b’Hello, world' +:

Аргумент + bufsize + для + 1024 +, использованный выше, является максимальным объемом данных, которые должны быть получены одновременно. Это не означает, что + recv () + вернет + 1024 + байт.

+ send () + также ведет себя так. + send () + возвращает количество отправленных байтов, которое может быть меньше размера передаваемых данных. Вы несете ответственность за проверку этого и вызываете + send () + столько раз, сколько необходимо для отправки всех данных:

_ «Приложения отвечают за проверку того, что все данные были отправлены; если были переданы только некоторые данные, приложение должно попытаться доставить оставшиеся данные ». (Source) _

Мы избежали необходимости делать это с помощью + sendall () +:

_ «В отличие от send (), этот метод продолжает отправлять данные из байтов до тех пор, пока не будут отправлены все данные или пока не произойдет ошибка. Никто не возвращается в случае успеха ». (Source) _

На данный момент у нас есть две проблемы:

  • Как мы обрабатываем несколько соединений одновременно?

  • Нам нужно вызывать + send () + и + recv () +, пока все данные не будут отправлены или получены.

Что мы делаем? Существует множество подходов к concurrency. В последнее время популярным подходом является использование Asynchronous I/O. + asyncio + была введена в стандартную библиотеку в Python 3.4. Традиционный выбор — использовать threads.

Проблема с параллелизмом состоит в том, что трудно понять правильно. Есть много тонкостей, которые нужно учитывать и оберегать. Все, что требуется для того, чтобы один из них проявил себя, и ваше приложение может внезапно выйти из строя не очень тонкими способами.

Я не говорю это, чтобы отпугнуть вас от обучения и использования параллельного программирования. Если ваше приложение нуждается в масштабировании, это необходимо, если вы хотите использовать более одного процессора или одно ядро. Тем не менее, для этого урока мы будем использовать нечто более традиционное, чем потоки, и о котором легче рассуждать. Мы собираемся использовать дедушку системных вызовов: https://docs.python.org/3/library/selectors.html#selectors.BaseSelector.select [+ select () +].

+ select () + позволяет проверять завершение ввода/вывода в нескольких сокетах. Таким образом, вы можете вызвать + select () +, чтобы увидеть, какие сокеты имеют ввод/вывод, готовый для чтения и/или записи. Но это Python, так что это еще не все. Мы собираемся использовать модуль selectors в стандартной библиотеке, чтобы использовать наиболее эффективную реализацию независимо от операционной системы, в которой мы работаем на:

_ «Этот модуль обеспечивает высокоуровневое и эффективное мультиплексирование ввода/вывода, построенное на примитивах выбранного модуля. Вместо этого пользователям рекомендуется использовать этот модуль, если они не хотят точно контролировать используемые примитивы уровня ОС ». (Source) _

Несмотря на то, что с помощью + select () + мы не можем работать одновременно, в зависимости от вашей рабочей нагрузки, этот подход все еще может быть достаточно быстрым. Это зависит от того, что ваше приложение должно делать, когда оно обслуживает запрос, и от количества клиентов, которые ему необходимо поддерживать.

https://docs.python.org/3/library/asyncio.html [+ asyncio +] использует однопотоковую совместную многозадачность и цикл обработки событий для управления задачами. С помощью + select () + мы напишем нашу собственную версию цикла событий, хотя и более просто и синхронно. При использовании нескольких потоков, даже если у вас есть параллелизм, в настоящее время мы должны использовать GIL с CPython и PyPy . Это эффективно ограничивает объем работы, которую мы можем выполнять параллельно.

Я говорю все это, чтобы объяснить, что использование + select () + может быть идеальным выбором. Не думайте, что вам нужно использовать + asyncio +, потоки или последнюю асинхронную библиотеку. Как правило, в сетевом приложении ваше приложение связано с вводом/выводом: оно может ожидать в локальной сети, конечных точках на другой стороне сети, на диске и т. Д.

Если вы получаете запросы от клиентов, которые инициируют работу с привязкой к процессору, посмотрите на модуль concurrent.futures. Он содержит класс ProcessPoolExecutor, который использует пул процессов для асинхронного выполнения вызовов.

В следующем разделе мы рассмотрим примеры сервера и клиента, которые решают эти проблемы. Они используют + select () + для одновременной обработки нескольких соединений и вызывают + send () + и + recv () + столько раз, сколько необходимо.

Многоканальный клиент и сервер

В следующих двух разделах мы создадим сервер и клиент, который будет обрабатывать несколько соединений, используя объект + selector +, созданный из модуля selectors.

Multi-Connection Server

Во-первых, давайте посмотрим на сервер множественных соединений + multiconn-server.py +. Вот первая часть, которая устанавливает сокет прослушивания:

import selectors
sel = selectors.DefaultSelector()
# ...
lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.bind((host, port))
lsock.listen()
print('listening on', (host, port))
lsock.setblocking(False)
sel.register(lsock, selectors.EVENT_READ, data=None)

Самое большое различие между этим сервером и сервером эха — это вызов + lsock.setblocking (False) + для настройки сокета в неблокирующем режиме. Вызовы, сделанные на этот сокет, больше не будут связывать: # blocking-Call [block] Когда он используется с + sel.select () +, как вы увидите ниже, мы можем ожидать события на одном или нескольких сокетах, а затем читать и записывать данные, когда они будут готовы.

+ sel.register () + регистрирует сокет, который будет отслеживаться с помощью + sel. select () + для интересующих вас событий. Для сокета прослушивания мы хотим прочитать события: + selectors.EVENT_READ +.

+ data + используется для хранения любых произвольных данных вместе с сокетом. Возвращается, когда возвращается + select () +. Мы будем использовать + data + для отслеживания того, что было отправлено и получено в сокете.

Далее идет цикл событий:

import selectors
sel = selectors.DefaultSelector()

# ...

while True:
    events = sel.select(timeout=None)
    for key, mask in events:
        if key.data is None:
            accept_wrapper(key.fileobj)
        else:
            service_connection(key, mask)

https://docs.python.org/3/library/selectors.html#selectors.BaseSelector.select [+ sel.select (timeout = None) +] ссылка: # blocking-call [blocks] до появления сокетов готов к вводу/выводу. Он возвращает список (ключ, события) кортежей, по одному для каждого сокета. + key + является SelectorKey + namedtuple +, который содержит атрибут + fileobj +. + key.fileobj + — объект сокета, а + mask + — маска события готовых операций.

Если + key.data + равно + None +, то мы знаем, что это из сокета прослушивания, и нам нужно + accept () + соединение. Мы вызовем нашу собственную функцию-оболочку + accept () +, чтобы получить новый объект сокета и зарегистрировать его с помощью селектора. Мы посмотрим на это через мгновение.

Если + key.data + не равно + None +, то мы знаем, что это клиентский сокет, который уже принят, и нам нужно его обслужить. Затем вызывается + service_connection () + и передается + key + и + mask +, которые содержат все, что нам нужно для работы с сокетом.

Давайте посмотрим, что делает наша функция + accept_wrapper () +:

def accept_wrapper(sock):
    conn, addr = sock. accept()  # Should be ready to read
    print('accepted connection from', addr)
    conn.setblocking(False)
    data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')
    events = selectors.EVENT_READ | selectors.EVENT_WRITE
    sel.register(conn, events, data=data)

Поскольку прослушивающий сокет был зарегистрирован для события + selectors.EVENT_READ +, он должен быть готов к чтению. Мы вызываем + sock.accept () +, а затем сразу же вызываем + conn.setblocking (False) +, чтобы перевести сокет в неблокирующий режим.

Помните, что это главная цель в этой версии сервера, так как мы не хотим, чтобы он связывал: # blocking-Call [block]. Если он блокируется, то весь сервер останавливается, пока не вернется. Это означает, что другие сокеты остаются в ожидании. Это ужасное состояние зависания, при котором вы не хотите, чтобы ваш сервер находился в режиме ожидания.

Затем мы создаем объект для хранения данных, которые мы хотим включить вместе с сокетом, используя класс + types. SimpleNamespace +. Поскольку мы хотим знать, когда клиентское соединение готово для чтения и записи, оба эти события устанавливаются с использованием следующего:

events = selectors.EVENT_READ | selectors.EVENT_WRITE

Затем маска + events +, сокет и объекты данных передаются в + sel.register () +.

Теперь давайте посмотрим на + service_connection () +, чтобы увидеть, как обрабатывается клиентское соединение, когда оно готово:

def service_connection(key, mask):
    sock = key.fileobj
    data = key.data
    if mask & selectors.EVENT_READ:
        recv_data = sock.recv(1024)  # Should be ready to read
        if recv_data:
            data.outb += recv_data
        else:
            print('closing connection to', data.addr)
            sel.unregister(sock)
            sock.close()
    if mask & selectors.EVENT_WRITE:
        if data.outb:
            print('echoing', repr(data. outb), 'to', data.addr)
            sent = sock.send(data.outb)  # Should be ready to write
            data.outb = data.outb[sent:]

Это сердце простого сервера мультисвязи. + key + — это + namedtuple +, возвращаемый из + select () +, который содержит объект сокета (+ fileobj +) и объект данных. + mask + содержит события, которые готовы.

Если сокет готов к чтению, то + mask & selectors.EVENT_READ + равно true и вызывается + sock.recv () +. Любые прочитанные данные добавляются в + data.outb +, поэтому их можно отправить позже.

Обратите внимание на блок + else: +, если данные не получены:

if recv_data:
    data.outb += recv_data
else:
    print('closing connection to', data.addr)
    sel.unregister(sock)
    sock.close()

Это означает, что клиент закрыл свой сокет, так что сервер тоже должен. Но не забудьте сначала вызвать + sel.unregister () +, чтобы он больше не отслеживался + select () +.

Когда сокет готов к записи, что всегда должно быть в случае работоспособного сокета, любые полученные данные, хранящиеся в + data.outb +, передаются клиенту с помощью + sock.send () +. Отправленные байты затем удаляются из буфера отправки:

data.outb = data.outb[sent:]
Multi-Connection Client

Теперь давайте посмотрим на клиент множественного подключения, + multiconn-client.py +. Он очень похож на сервер, но вместо прослушивания соединений он начинается с инициализации соединений с помощью + start_connections () +:

messages = [b'Message 1 from client.', b'Message 2 from client.']


def start_connections(host, port, num_conns):
    server_addr = (host, port)
    for i in range(0, num_conns):
        connid = i + 1
        print('starting connection', connid, 'to', server_addr)
        sock = socket. socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setblocking(False)
        sock.connect_ex(server_addr)
        events = selectors.EVENT_READ | selectors.EVENT_WRITE
        data = types.SimpleNamespace(connid=connid,
                                     msg_total=sum(len(m) for m in messages),
                                     recv_total=0,
                                     messages=list(messages),
                                     outb=b'')
        sel.register(sock, events, data=data)

+ num_conns + читается из командной строки, которая является числом соединений, которые нужно создать с сервером. Как и сервер, каждый сокет установлен в неблокирующий режим.

+ connect_ex () + используется вместо + connect () +, поскольку + connect () + немедленно вызовет исключение + BlockingIOError +. + connect_ex () + изначально возвращает индикатор ошибки + errno.EINPROGRESS + вместо того, чтобы вызывать исключение во время соединения. Как только соединение завершено, сокет готов к чтению и записи и возвращается как таковой + select () +.

После настройки сокета данные, которые мы хотим сохранить в сокете, создаются с использованием класса + types.SimpleNamespace +. Сообщения, которые клиент отправит на сервер, копируются с использованием + list (messages) +, поскольку каждое соединение вызывает + socket.send () + и изменяет список. Все необходимое для отслеживания того, что клиент должен отправить, отправил и получил, а общее количество байтов в сообщениях хранится в объекте + data +.

Давайте посмотрим на + service_connection () +. Он в основном такой же, как сервер:

def service_connection(key, mask):
    sock = key.fileobj
    data = key.data
    if mask & selectors.EVENT_READ:
        recv_data = sock.recv(1024)  # Should be ready to read
        if recv_data:
            print('received', repr(recv_data), 'from connection', data. connid)
            data.recv_total += len(recv_data)
        if not recv_data or data.recv_total == data.msg_total:
            print('closing connection', data.connid)
            sel.unregister(sock)
            sock.close()
    if mask & selectors.EVENT_WRITE:
        if not data.outb and data.messages:
            data.outb = data.messages.pop(0)
        if data.outb:
            print('sending', repr(data.outb), 'to connection', data.connid)
            sent = sock.send(data.outb)  # Should be ready to write
            data.outb = data.outb[sent:]

Есть одно важное отличие. Он отслеживает количество байтов, полученных от сервера, чтобы он мог закрыть свою сторону соединения. Когда сервер обнаруживает это, он также закрывает свою сторону соединения.

Обратите внимание, что при этом сервер зависит от хорошего поведения клиента: сервер ожидает, что клиент закроет свою сторону соединения после завершения отправки сообщений. Если клиент не закрывается, сервер оставит соединение открытым. В реальном приложении вы можете принять меры против этого на своем сервере и предотвратить накопление клиентских подключений, если они не отправляют запрос через определенное время.

Запуск клиента и сервера Multi-Connection

Теперь давайте запустим + multiconn-server.py + и + multiconn-client.py +. Они оба используют аргументы командной строки. Вы можете запустить их без аргументов, чтобы увидеть варианты.

Для сервера передайте номера + host + и + port +:

$ ./multiconn-server.py
usage: ./multiconn-server.py <host> <port>

Для клиента также передайте количество соединений, которые нужно создать, на сервер, + num_connections +:

$ ./multiconn-client.py
usage: ./multiconn-client.py <host> <port> <num_connections>

Ниже приведены выходные данные сервера при прослушивании интерфейса обратной связи через порт 65432:

$ . /multiconn-server.py 127.0.0.1 65432
listening on ('127.0.0.1', 65432)
accepted connection from ('127.0.0.1', 61354)
accepted connection from ('127.0.0.1', 61355)
echoing b'Message 1 from client.Message 2 from client.' to ('127.0.0.1', 61354)
echoing b'Message 1 from client.Message 2 from client.' to ('127.0.0.1', 61355)
closing connection to ('127.0.0.1', 61354)
closing connection to ('127.0.0.1', 61355)

Ниже приведен вывод клиента, когда он создает два соединения с сервером выше:

$ ./multiconn-client.py 127.0.0.1 65432 2
starting connection 1 to ('127.0.0.1', 65432)
starting connection 2 to ('127.0.0.1', 65432)
sending b'Message 1 from client.' to connection 1
sending b'Message 2 from client.' to connection 1
sending b'Message 1 from client.' to connection 2
sending b'Message 2 from client.' to connection 2
received b'Message 1 from client.Message 2 from client.' from connection 1
closing connection 1
received b'Message 1 from client. Message 2 from client.' from connection 2
closing connection 2

Клиент приложения и сервер

Пример клиента и сервера с несколькими подключениями, безусловно, является улучшением по сравнению с тем, с чего мы начали. Однако давайте сделаем еще один шаг и рассмотрим недостатки предыдущего примера «multiconn» в окончательной реализации: клиент приложения и сервер.

Мы хотим, чтобы клиент и сервер обрабатывали ошибки соответствующим образом, чтобы другие соединения не были затронуты. Очевидно, что наш клиент или сервер не должны рухнуть в ярости, если исключение не поймано. Это то, что мы не обсуждали до сих пор. Я намеренно пропустил обработку ошибок для краткости и ясности в примерах.

Теперь, когда вы знакомы с базовым API, неблокирующими сокетами и + select () +, мы можем добавить некоторую обработку ошибок и обсудить «слона в комнате», который я скрыл от вас за этим большой занавес там. Да, я говорю о пользовательском классе, который я упоминал еще во введении. Я знал, что ты не забудешь.

Во-первых, давайте исправим ошибки:

_ «Все ошибки вызывают исключения. Нормальные исключения для недопустимых типов аргументов и условий нехватки памяти могут быть вызваны; начиная с Python 3.3, ошибки, связанные с семантикой сокетов или адресов, приводят к + OSError + или одному из его подклассов. ” (Source) _

Нам нужно отловить + OSError +. Еще одна вещь, которую я не упомянул в связи с ошибками, это тайм-ауты. Вы увидите их во многих местах в документации. Тайм-ауты случаются и являются «нормальной» ошибкой. Хосты и маршрутизаторы перезагружаются, порты коммутатора выходят из строя, кабели выходят из строя, кабели отключаются, вы называете это. Вы должны быть готовы к этим и другим ошибкам и обрабатывать их в своем коде.

Что насчет «слона в комнате»? Как подсказывает тип сокета + socket.SOCK_STREAM +, при использовании TCP вы читаете из непрерывного потока байтов. Это похоже на чтение из файла на диске, но вместо этого вы читаете байты из сети.

Однако, в отличие от чтения файла, здесь нет https://docs.python.org/3/tutorial/inputoutput.html#methods-of-file-objects [+ f.seek () +]. Другими словами, вы не можете перемещать указатель сокета, если он был, и перемещаться случайным образом по данным, читая что угодно и когда угодно.

Когда байты поступают в ваш сокет, включаются сетевые буферы. Как только вы их прочитаете, их нужно где-то сохранить. Вызов + recv () + снова считывает следующий поток байтов, доступных из сокета.

Это означает, что вы будете читать из сокета порциями. Вам нужно вызвать + recv () + и сохранить данные в буфере, пока вы не прочитаете достаточно байтов, чтобы получить полное сообщение, которое имеет смысл для вашего приложения.

Вы сами должны определить и отслеживать границы сообщений. Что касается сокета TCP, то он просто отправляет и получает необработанные байты в и из сети. Он ничего не знает о том, что означают эти необработанные байты.

Это подводит нас к определению протокола прикладного уровня. Что такое протокол прикладного уровня? Проще говоря, ваше приложение будет отправлять и получать сообщения. Эти сообщения являются протоколом вашего приложения.

Другими словами, длина и формат, который вы выбираете для этих сообщений, определяют семантику и поведение вашего приложения. Это напрямую связано с тем, что я объяснил в предыдущем параграфе относительно чтения байтов из сокета. Когда вы читаете байты с помощью + recv () +, вам нужно следить за тем, сколько байтов было прочитано, и выяснить, где находятся границы сообщения.

Как это сделать? Одним из способов является отправка сообщений фиксированной длины. Если они всегда одинакового размера, то это легко. Когда вы прочитали это количество байтов в буфер, вы знаете, что у вас есть одно полное сообщение.

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

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

Мы реализуем это, создав собственный класс, который может отправлять и получать сообщения, содержащие текстовые или двоичные данные. Вы можете улучшить и расширить его для своих собственных приложений. Самое главное, что вы сможете увидеть пример того, как это делается.

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

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

Этот порядок байтов называется процессором endianness. Смотрите ссылку: # byte-endianness [Byte Endianness] в справочном разделе для деталей. Мы избежим этой проблемы, воспользовавшись Unicode для нашего заголовка сообщения и используя кодировку UTF-8. Поскольку UTF-8 использует 8-битное кодирование, проблем с порядком байтов не возникает.

Вы можете найти объяснение в документации Python Encodings and Unicode. Обратите внимание, что это относится только к текстовому заголовку. Мы будем использовать явный тип и кодировку, определенные в заголовке для содержимого, которое отправляется, полезной нагрузки сообщения. Это позволит нам передавать любые данные, которые нам нужны (текстовые или двоичные), в любом формате.

Вы можете легко определить порядок байтов вашей машины, используя + sys.byteorder +. Например, на моем ноутбуке Intel это происходит:

$ python3 -c 'import sys; print(repr(sys.byteorder))'
'little'

Если я запускаю это на виртуальной машине с emulations процессором с прямым порядком байтов (PowerPC), то это происходит:

$ python3 -c 'import sys; print(repr(sys.byteorder))'
'big'

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

Это будет зависеть от вашего приложения и от того, нужно ли ему обрабатывать многобайтовые двоичные данные с компьютера с другим порядком байтов. Вы можете помочь вашему клиенту или серверу реализовать двоичную поддержку, добавив дополнительные заголовки и используя их для передачи параметров, аналогично HTTP.

Не волнуйтесь, если это еще не имеет смысла. В следующем разделе вы увидите, как все это работает и соответствует друг другу.

Заголовок протокола приложения

Давайте полностью определим заголовок протокола. Заголовок протокола:

  • Текст переменной длины

  • Юникод с кодировкой UTF-8

  • Словарь Python, сериализованный с использованием JSON

Требуемые заголовки или подзаголовки в словаре заголовка протокола следующие:

NameDescription

byteorder

The byte order of the machine (uses sys.byteorder). This may not be required for your application.

content-length

The length of the content in bytes.

content-type

The type of content in the payload, for example, text/json or binary/my-binary-type.

content-encoding

The encoding used by the content, for example, utf-8 for Unicode text or binary for binary data.

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

Отправка сообщения приложения

Есть еще небольшая проблема. У нас есть заголовок переменной длины, который хорош и гибок, но как узнать длину заголовка при чтении его с помощью + recv () +?

Когда мы ранее говорили об использовании + recv () + и границ сообщения, я упоминал, что заголовки фиксированной длины могут быть неэффективными. Это правда, но мы собираемся использовать небольшой 2-байтовый заголовок фиксированной длины для префикса заголовка JSON, который содержит его длину.

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

Чтобы лучше понять формат сообщения, давайте рассмотрим сообщение целиком:

Сообщение начинается с заголовка фиксированной длины в 2 байта, который является целым числом в сетевом порядке байтов. Это длина следующего заголовка, JSON-заголовка переменной длины. После того, как мы прочитали 2 байта с помощью + recv () +, мы знаем, что можем обработать 2 байта как целое число и затем прочитать это число байтов перед декодированием заголовка JSON UTF-8.

Ссылка: # application-protocol-header [JSON header] содержит словарь дополнительных заголовков. Одним из них является + content-length +, которое представляет собой количество байтов содержимого сообщения (не включая заголовок JSON). Как только мы вызвали + recv () + и прочитали + content-length + байтов, мы достигли границы сообщения и прочитали все сообщение.

Класс сообщения приложения

Наконец, выигрыш! Давайте посмотрим на класс + Message + и посмотрим, как он используется с + select () +, когда в сокете происходят события чтения и записи.

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

Для простоты и демонстрации того, как все будет работать в реальном приложении, я создал протокол приложения, который реализует базовую функцию поиска. Клиент отправляет запрос поиска, а сервер выполняет поиск совпадения. Если запрос, отправленный клиентом, не распознается как поиск, сервер предполагает, что это двоичный запрос, и возвращает двоичный ответ.

Прочитав следующие разделы, выполнив примеры и поэкспериментировав с кодом, вы увидите, как все работает. Затем вы можете использовать класс + Message + в качестве отправной точки и изменить его для своего собственного использования.

Мы на самом деле не так уж далеки от «многоконного» примера клиента и сервера. Код цикла событий остается тем же в + app-client.py + и + app-server.py +. Что я сделал, так это переместил код сообщения в класс с именем + Message + и добавил методы для поддержки чтения, записи и обработки заголовков и содержимого. Это отличный пример использования класса.

Как мы уже говорили ранее, и вы увидите ниже, работа с сокетами подразумевает сохранение состояния. Используя класс, мы сохраняем все состояние, данные и код в единое целое. Экземпляр класса создается для каждого сокета на клиенте и сервере, когда соединение установлено или принято.

Класс в основном одинаков как для клиента, так и для сервера для методов-оболочек и утилит. Они начинаются с подчеркивания, например + Message._json_encode () +. Эти методы упрощают работу с классом. Они помогают другим методам, позволяя им оставаться короче и поддерживают принцип DRY.

Серверный класс + Message + работает по сути так же, как и клиентский, и наоборот. Разница в том, что клиент инициирует соединение и отправляет сообщение с запросом, после чего обрабатывает ответное сообщение сервера. И наоборот, сервер ожидает подключения, обрабатывает сообщение запроса клиента и затем отправляет ответное сообщение.

Это выглядит так:

StepEndpointAction/Message Content

1

Client

Sends a Message containing request content

2

Server

Receives and processes client request Message

3

Server

Sends a Message containing response content

4

Client

Receives and processes server response Message

Вот расположение файла и кода:

ApplicationFileCode

Server

app-server. py

The server’s main script

Server

libserver.py

The server’s Message class

Client

app-client.py

The client’s main script

Client

libclient.py

The client’s Message class

Точка ввода сообщения

Я хотел бы обсудить, как работает класс «+ Message +», упомянув сначала об одном аспекте его дизайна, который не был сразу очевиден для меня. Только после рефакторинга, по крайней мере, пять раз я пришел к тому, чем он является в настоящее время Why? Управляющий государством.

После того, как объект + Message + создан, он связывается с сокетом, который отслеживается для событий с помощью + selector.register () +:

message = libserver. Message(sel, conn, addr)
sel.register(conn, selectors.EVENT_READ, data=message)
*Примечание:* Некоторые примеры кода в этом разделе взяты из основного сценария сервера и класса `+ Message +`, но этот раздел и обсуждение в равной степени относятся и к клиенту. Я покажу и объясню версию клиента, когда она будет отличаться.

Когда события готовы к сокету, они возвращаются с помощью + selector.select () +. Затем мы можем получить ссылку обратно на объект сообщения, используя атрибут + data + объекта + key +, и вызвать метод в + Message +:

while True:
    events = sel.select(timeout=None)
    for key, mask in events:
        # ...
        message = key.data
        message.process_events(mask)

Посмотрев на цикл событий выше, вы увидите, что + sel.select () + находится на месте водителя. Это блокировка, ожидание наверху цикла событий. Он отвечает за пробуждение, когда события чтения и записи готовы для обработки в сокете. Это означает, что косвенно он также отвечает за вызов метода + process_events () +. Это то, что я имею в виду, когда говорю, что метод + process_events () + является точкой входа.

Давайте посмотрим, что делает метод + process_events () +:

def process_events(self, mask):
    if mask & selectors.EVENT_READ:
        self.read()
    if mask & selectors.EVENT_WRITE:
        self.write()

Это хорошо: + process_events () + просто. Он может делать только две вещи: вызывать + read () + и + write () +.

Это возвращает нас к управлению государством. После нескольких рефакторингов я решил, что если другой метод зависит от переменных состояния, имеющих определенное значение, то они будут вызываться только из + read () + и + write () +. Это сохраняет логику настолько простой, насколько это возможно, так как события поступают в сокет для обработки.

Это может показаться очевидным, но первые несколько итераций класса представляли собой смесь некоторых методов, которые проверяли текущее состояние и, в зависимости от их значения, вызывали другие методы для обработки данных вне + read () + или `+ write ( ) + `. В конце концов, это оказалось слишком сложным, чтобы справиться и не отставать.

Вам определенно следует изменить класс в соответствии с вашими потребностями, чтобы он работал лучше для вас, но я бы рекомендовал вам сохранять проверки состояния и вызовы методов, которые зависят от этого состояния, для + read () + и ` + write () + `методы, если это возможно.

Давайте посмотрим на + read () +. Это версия сервера, но версия клиента такая же. Он просто использует другое имя метода, + process_response () + вместо + process_request () +:

def read(self):
    self._read()

    if self._jsonheader_len is None:
        self. process_protoheader()

    if self._jsonheader_len is not None:
        if self.jsonheader is None:
            self.process_jsonheader()

    if self.jsonheader:
        if self.request is None:
            self.process_request()

Метод + _read () + вызывается первым. Он вызывает + socket.recv () + для чтения данных из сокета и сохранения их в приемном буфере.

Помните, что когда вызывается + socket.recv () +, все данные, составляющие полное сообщение, возможно, еще не поступили. + socket.recv () + может потребоваться вызвать снова. Вот почему существуют проверки состояния для каждой части сообщения перед вызовом соответствующего метода для его обработки.

Перед тем, как метод обрабатывает свою часть сообщения, он сначала проверяет, было ли прочитано достаточно байтов в приемный буфер. Если они есть, он обрабатывает свои соответствующие байты, удаляет их из буфера и записывает свои выходные данные в переменную, которая используется на следующем этапе обработки. Поскольку в сообщении есть три компонента, есть три проверки состояния и вызовы метода + process +:

Message ComponentMethodOutput

Fixed-length header

process_protoheader()

self._jsonheader_len

JSON header

process_jsonheader()

self.jsonheader

Content

process_request()

self.request

Далее давайте посмотрим на + write () +. Это версия сервера:

def write(self):
    if self.request:
        if not self.response_created:
            self.create_response()

    self._write()

+ write () + сначала проверяет наличие + request +. Если он существует и ответ не был создан, вызывается + create_response () +. + create_response () + устанавливает переменную состояния + response_created + и записывает ответ в буфер отправки.

Метод + _write () + вызывает + socket.send () +, если в буфере отправки есть данные.

Помните, что когда вызывается + socket.send () +, все данные в буфере отправки могут не помещаться в очередь для передачи. Сетевые буферы для сокета могут быть заполнены, и может потребоваться повторный вызов + socket.send () +. Вот почему существуют государственные проверки. + create_response () + следует вызывать только один раз, но ожидается, что + _write () + нужно будет вызывать несколько раз.

Клиентская версия + write () + похожа:

def write(self):
    if not self._request_queued:
        self.queue_request()

    self. _write()

    if self._request_queued:
        if not self._send_buffer:
            # Set selector to listen for read events, we're done writing.
            self._set_selector_events_mask('r')

Так как клиент инициирует соединение с сервером и сначала отправляет запрос, проверяется переменная состояния + _request_queued +. Если запрос не был поставлен в очередь, он вызывает + queue_request () +. + queue_request () + создает запрос и записывает его в буфер отправки. Он также устанавливает переменную состояния + _request_queued +, поэтому она вызывается только один раз.

Как и сервер, + _write () + вызывает + socket.send () +, если в буфере отправки есть данные.

Заметная разница в версии клиента + write () + — это последняя проверка, чтобы увидеть, был ли запрос поставлен в очередь. Это будет объяснено более подробно в ссылке раздела: # client-main-script [Client Main Script], но причина этого состоит в том, чтобы сказать + selector. select () +, чтобы прекратить мониторинг сокета на наличие событий записи. Если запрос был поставлен в очередь, а буфер отправки пуст, тогда мы закончили запись и нас интересуют только события чтения. Нет причин получать уведомление о том, что сокет доступен для записи.

Я завершу этот раздел, оставив вас с одной мыслью. Основной целью этого раздела было объяснить, что + selector.select () + вызывает класс + Message + через метод + process_events () +, и описать, как управляется состояние.

Это важно, потому что + process_events () + будет вызываться много раз в течение жизни соединения. Поэтому убедитесь, что любые методы, которые должны вызываться только один раз, либо сами проверяют переменную состояния, либо переменная состояния, установленная методом, проверяется вызывающей стороной.

Главный скрипт сервера

В основном скрипте сервера + app-server.py + аргументы считываются из командной строки, которые определяют интерфейс и порт для прослушивания:

$ . /app-server.py
usage: ./app-server.py <host> <port>

Например, чтобы прослушивать интерфейс обратной связи на порту + 65432 +, введите:

$ ./app-server.py 127.0.0.1 65432
listening on ('127.0.0.1', 65432)

Используйте пустую строку для + <host> + для прослушивания всех интерфейсов.

После создания сокета вызывается + socket.setsockopt () + с опцией + socket.SO_REUSEADDR +:

# Avoid bind() exception: OSError: [Errno 48] Address already in use
lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Установка этой опции сокета позволяет избежать ошибки + Адрес уже используется +. Вы увидите это при запуске сервера, и ранее использованный сокет TCP на том же порту имеет соединения в http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications -for-протоколы-и-масштабируемые-серверы. html [TIME_WAIT] состояние.

Например, если сервер активно закрыл соединение, оно будет оставаться в состоянии «+ TIME_WAIT » в течение двух или более минут, в зависимости от операционной системы. Если вы попытаетесь запустить сервер еще раз до истечения состояния ` TIME_WAIT `, вы получите исключение ` OSError ` для ` Адрес уже используется +`. Это гарантия того, что любые задержанные пакеты в сети не будут доставлены не тому приложению.

Цикл обработки событий фиксирует любые ошибки, чтобы сервер мог оставаться в рабочем состоянии и продолжать работу:

while True:
    events = sel.select(timeout=None)
    for key, mask in events:
        if key.data is None:
            accept_wrapper(key.fileobj)
        else:
            message = key.data
            try:
                message.process_events(mask)
            except Exception:
                print('main: error: exception for',
                      f'{message.addr}:\n{traceback. format_exc()}')
                message.close()

Когда клиентское соединение принято, создается объект + Message +:

def accept_wrapper(sock):
    conn, addr = sock.accept()  # Should be ready to read
    print('accepted connection from', addr)
    conn.setblocking(False)
    message = libserver.Message(sel, conn, addr)
    sel.register(conn, selectors.EVENT_READ, data=message)

Объект + Message + связан с сокетом в вызове + sel.register () + и изначально настроен на мониторинг только для событий чтения. Как только запрос будет прочитан, мы изменим его, чтобы прослушивать только события записи.

Преимущество такого подхода на сервере заключается в том, что в большинстве случаев, когда сокет исправен и нет проблем с сетью, он всегда будет доступен для записи.

Если бы мы сказали + sel.register () + также контролировать + EVENT_WRITE +, цикл обработки событий немедленно активировался бы и уведомил нас, что это так. Однако в этот момент нет причин просыпаться и вызывать + send () + для сокета. Ответа нет, поскольку запрос еще не обработан. Это будет потреблять и тратить ценные циклы процессора.

Класс сообщения сервера

В ссылке на раздел: # message-entry-point [Точка входа в сообщение] мы рассмотрели, как объект + Message + был вызван в действие, когда события сокета были готовы через + process_events () +. Теперь давайте посмотрим, что происходит, когда данные считываются в сокет и компонент или часть сообщения готова для обработки сервером.

Класс сообщений сервера находится в + libserver.py +. Вы можете найти source код на GitHub.

Методы появляются в классе в том порядке, в котором происходит обработка сообщения.

Когда сервер прочитал как минимум 2 байта, заголовок фиксированной длины может быть обработан:

def process_protoheader(self):
    hdrlen = 2
    if len(self._recv_buffer) >= hdrlen:
        self._jsonheader_len = struct.unpack('>H',
                                             self._recv_buffer[:hdrlen])[0]
        self._recv_buffer = self._recv_buffer[hdrlen:]

Заголовок фиксированной длины представляет собой 2-байтовое целое число в сетевом порядке (с прямым порядком байтов), которое содержит длину заголовка JSON. https://docs.python.org/3/library/struct.html [struct.unpack ()] используется для чтения значения, его декодирования и сохранения в + self._jsonheader_len +. После обработки фрагмента сообщения, за которое он отвечает, + process_protoheader () + удаляет его из буфера приема.

Как и заголовок фиксированной длины, когда в буфере приема достаточно данных для заголовка JSON, он также может быть обработан:

def process_jsonheader(self):
    hdrlen = self._jsonheader_len
    if len(self._recv_buffer) >= hdrlen:
        self.jsonheader = self._json_decode(self._recv_buffer[:hdrlen],
                                            'utf-8')
        self._recv_buffer = self._recv_buffer[hdrlen:]
        for reqhdr in ('byteorder', 'content-length', 'content-type',
                       'content-encoding'):
            if reqhdr not in self.jsonheader:
                raise ValueError(f'Missing required header "{reqhdr}".')

Метод + self._json_decode () + вызывается для декодирования и десериализации заголовка JSON в словарь. Поскольку заголовок JSON определен как Unicode с кодировкой UTF-8, в вызове жестко кодируется + utf-8 +. Результат сохраняется в + self.jsonheader +. После обработки фрагмента сообщения, за которое он отвечает, + process_jsonheader () + удаляет его из буфера приема.

Далее идет фактическое содержание или полезная нагрузка сообщения. Это описывается заголовком JSON в + self.jsonheader +. Когда байты + content-length + доступны в приемном буфере, запрос может быть обработан:

def process_request(self):
    content_len = self.jsonheader['content-length']
    if not len(self._recv_buffer) >= content_len:
        return
    data = self._recv_buffer[:content_len]
    self._recv_buffer = self._recv_buffer[content_len:]
    if self.jsonheader['content-type'] == 'text/json':
        encoding = self.jsonheader['content-encoding']
        self.request = self._json_decode(data, encoding)
        print('received request', repr(self.request), 'from', self.addr)
    else:
        # Binary or unknown content-type
        self.request = data
        print(f'received {self.jsonheader["content-type"]} request from',
              self.addr)
    # Set selector to listen for write events, we're done reading.
    self._set_selector_events_mask('w')

После сохранения содержимого сообщения в переменную + data +, + process_request () + удаляет его из буфера приема. Затем, если тип контента JSON, он декодирует и десериализует его. Если это не так, для этого примера приложения предполагается, что это двоичный запрос, и просто печатается тип содержимого.

Последнее, что делает + process_request () +, это модифицирует селектор для мониторинга только событий записи. В основном сценарии сервера + app-server.py + сокет изначально настроен на мониторинг только событий чтения. Теперь, когда запрос был полностью обработан, мы больше не заинтересованы в чтении.

Теперь можно создать ответ и записать его в сокет. Когда сокет доступен для записи, + create_response () + вызывается из + write () +:

def create_response(self):
    if self.jsonheader['content-type'] == 'text/json':
        response = self._create_response_json_content()
    else:
        # Binary or unknown content-type
        response = self._create_response_binary_content()
    message = self._create_message(**response)
    self.response_created = True
    self._send_buffer += message

Ответ создается путем вызова других методов, в зависимости от типа содержимого. В этом примере приложения простой поиск по словарю выполняется для запросов JSON, когда + action == 'search' +. Вы можете определить другие методы для своих собственных приложений, которые вызываются здесь.

После создания ответного сообщения устанавливается переменная состояния + self.response_created +, поэтому + write () + больше не вызывает + create_response () +. Наконец, ответ добавляется в буфер отправки. Это видно и отправлено через + _write () +.

Один хитрый момент, который нужно выяснить, — как закрыть соединение после написания ответа. Я поместил вызов + close () + в методе + _write () +:

def _write(self):
    if self._send_buffer:
        print('sending', repr(self._send_buffer), 'to', self.addr)
        try:
            # Should be ready to write
            sent = self.sock.send(self._send_buffer)
        except BlockingIOError:
            # Resource temporarily unavailable (errno EWOULDBLOCK)
            pass
        else:
            self._send_buffer = self._send_buffer[sent:]
            # Close when the buffer is drained. The response has been sent.
            if sent and not self._send_buffer:
                self.close()

Хотя это несколько «скрыто», я думаю, что это приемлемый компромисс, учитывая, что класс «+ Message +» обрабатывает только одно сообщение на соединение. После того как ответ написан, серверу ничего не остается сделать. Он завершил свою работу.

Основной скрипт клиента

В основном скрипте клиента + app-client.py + аргументы считываются из командной строки и используются для создания запросов и запуска соединений с сервером:

$ ./app-client.py
usage: ./app-client.py <host> <port> <action> <value>
$ ./app-client.py 127.0.0.1 65432 search needle

После создания словаря, представляющего запрос из аргументов командной строки, хост, порт и словарь запроса передаются в + start_connection () +:

def start_connection(host, port, request):
    addr = (host, port)
    print('starting connection to', addr)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(False)
    sock.connect_ex(addr)
    events = selectors.EVENT_READ | selectors.EVENT_WRITE
    message = libclient.Message(sel, sock, addr, request)
    sel.register(sock, events, data=message)

Для соединения с сервером создается сокет, а также объект + Message + с использованием словаря + request +.

Как и сервер, объект + Message + связан с сокетом при вызове + sel.register () +. Однако для клиента сокет изначально настроен на мониторинг событий чтения и записи. Как только запрос будет написан, мы изменим его, чтобы прослушивать только события чтения.

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

Класс сообщения клиента

В ссылке на раздел: # message-entry-point [Точка входа в сообщение] мы рассмотрели, как объект сообщения вызывался в действии, когда события сокета были готовы через + process_events () +. Теперь давайте посмотрим, что происходит после того, как данные прочитаны и записаны в сокет, и сообщение готово для обработки клиентом.

Класс сообщений клиента находится в + libclient.py +. Вы можете найти source код на GitHub.

Методы появляются в классе в том порядке, в котором происходит обработка сообщения.

Первая задача для клиента — поставить запрос в очередь:

def queue_request(self):
    content = self.request['content']
    content_type = self.request['type']
    content_encoding = self.request['encoding']
    if content_type == 'text/json':
        req = {
            'content_bytes': self._json_encode(content, content_encoding),
            'content_type': content_type,
            'content_encoding': content_encoding
        }
    else:
        req = {
            'content_bytes': content,
            'content_type': content_type,
            'content_encoding': content_encoding
        }
    message = self._create_message(**req)
    self._send_buffer += message
    self._request_queued = True

Словари, используемые для создания запроса, в зависимости от того, что было передано в командной строке, находятся в основном скрипте клиента, + app-client.py +. Словарь запроса передается классу в качестве аргумента при создании объекта + Message +.

Сообщение запроса создается и добавляется в буфер отправки, который затем просматривается и отправляется с помощью + _write () +. Переменная состояния + self._request_queued + установлена ​​так, что + queue_request () + больше не вызывается.

После того, как запрос был отправлен, клиент ожидает ответа от сервера.

Способы чтения и обработки сообщения на клиенте такие же, как на сервере. Когда данные ответа считываются из сокета, вызываются методы заголовка + process +: + process_protoheader () + и + process_jsonheader () +.

Разница заключается в именовании окончательных методов + process + и в том, что они обрабатывают ответ, а не создают его: + process_response () +, + _process_response_json_content () + и `+ _process_response_binary_content ( ) + `.

И последнее, но не менее важное, это последний вызов + process_response () +:

def process_response(self):
    # ...
    # Close when response has been processed
    self.close()
Заключение класса сообщений

Я завершу обсуждение класса «+ Message +», упомянув пару вещей, которые важно заметить с помощью нескольких поддерживающих методов.

Любые исключения, вызванные классом, перехватываются главным скриптом в его предложении + кроме +:

try:
    message.process_events(mask)
except Exception:
    print('main: error: exception for',
          f'{message.addr}:\n{traceback.format_exc()}')
    message.close()

Обратите внимание на последнюю строку: + message.close () +.

Это действительно важная линия по нескольким причинам! Он не только гарантирует, что сокет закрыт, но + message.close () + также удаляет сокет из списка + select () +. Это значительно упрощает код в классе и уменьшает сложность. Если есть исключение или мы явно вызываем его сами, мы знаем, что + close () + позаботится об очистке.

Методы + Message._read () + и + Message._write () + также содержат что-то интересное:

def _read(self):
    try:
        # Should be ready to read
        data = self.sock.recv(4096)
    except BlockingIOError:
        # Resource temporarily unavailable (errno EWOULDBLOCK)
        pass
    else:
        if data:
            self._recv_buffer += data
        else:
            raise RuntimeError('Peer closed.')

Обратите внимание на строку + кроме +: + кроме BlockingIOError: +.

У + _write () + тоже есть. Эти строки важны, потому что они ловят временную ошибку и пропускают ее, используя + pass +. Временная ошибка возникает, когда сокет связывает: # blocking-Call [block], например, если он ожидает в сети или на другом конце соединения (его одноранговый узел).

Перехватывая и пропуская исключение с помощью + pass +, + select () + в конечном итоге снова вызовет нас, и мы получим еще один шанс прочитать или записать данные.

Запуск приложения-клиента и сервера

После всей этой тяжелой работы давайте повеселимся и начнем поиски!

В этих примерах я запускаю сервер, чтобы он прослушивал все интерфейсы, передавая пустую строку для аргумента + host +. Это позволит мне запустить клиент и подключиться с виртуальной машины, которая находится в другой сети. Он эмулирует машину PowerPC с прямым порядком байтов.

Во-первых, давайте запустим сервер:

$ ./app-server.py '' 65432
listening on ('', 65432)

Теперь давайте запустим клиент и введем поиск. Давайте посмотрим, сможем ли мы найти его:

$ ./app-client.py 10.0.1.1 65432 search morpheus
starting connection to ('10.0.1.1', 65432)
sending b'\x00d{"byteorder": "big", "content-type": "text/json", "content-encoding": "utf-8", "content-length": 41}{"action": "search", "value": "morpheus"}' to ('10.0.1.1', 65432)
received response {'result': 'Follow the white rabbit. ????'} from ('10.0.1.1', 65432)
got result: Follow the white rabbit. ????
closing connection to ('10.0.1.1', 65432)

В моем терминале запущена оболочка с текстовой кодировкой Unicode (UTF-8), поэтому вышеприведенный вывод хорошо печатается с помощью emojis.

Давайте посмотрим, сможем ли мы найти щенков:

$ ./app-client.py 10.0.1.1 65432 search ????
starting connection to ('10.0.1.1', 65432)
sending b'\x00d{"byteorder": "big", "content-type": "text/json", "content-encoding": "utf-8", "content-length": 37}{"action": "search", "value": "\xf0\x9f\x90\xb6"}' to ('10.0.1.1', 65432)
received response {'result': '???? Playing ball! ????'} from ('10.0.1.1', 65432)
got result: ???? Playing ball! ????
closing connection to ('10.0.1.1', 65432)

Обратите внимание на строку байтов, отправленную по сети для запроса, в строке + send +. Проще увидеть, если вы ищите байты, напечатанные в шестнадцатеричном формате, которые представляют смайлики щенка: + \ xf0 \ x9f \ x90 \ xb6 +. Я смог enter emoji для поиска, так как мой терминал использует Unicode с кодировкой UTF-8.

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

Вот выходные данные сервера из обоих клиентских подключений выше:

accepted connection from ('10.0.2.2', 55340)
received request {'action': 'search', 'value': 'morpheus'} from ('10.0.2.2', 55340)
sending b'\x00g{"byteorder": "little", "content-type": "text/json", "content-encoding": "utf-8", "content-length": 43}{"result": "Follow the white rabbit. \xf0\x9f\x90\xb0"}' to ('10.0.2.2', 55340)
closing connection to ('10.0.2.2', 55340)

accepted connection from ('10.0.2.2', 55338)
received request {'action': 'search', 'value': '????'} from ('10.0.2.2', 55338)
sending b'\x00g{"byteorder": "little", "content-type": "text/json", "content-encoding": "utf-8", "content-length": 37}{"result": "\xf0\x9f\x90\xbe Playing ball! \xf0\x9f\x8f\x90"}' to ('10.0.2.2', 55338)
closing connection to ('10.0.2.2', 55338)

Посмотрите на строку + send +, чтобы увидеть байты, которые были записаны в сокет клиента. Это ответное сообщение сервера.

Вы также можете проверить отправку двоичных запросов на сервер, если аргумент + action + отличается от + search +:

$ ./app-client.py 10.0.1.1 65432 binary ????
starting connection to ('10.0.1.1', 65432)
sending b'\x00|{"byteorder": "big", "content-type": "binary/custom-client-binary-type", "content-encoding": "binary", "content-length": 10}binary\xf0\x9f\x98\x83' to ('10.0.1.1', 65432)
received binary/custom-server-binary-type response from ('10.0.1.1', 65432)
got response: b'First 10 bytes of request: binary\xf0\x9f\x98\x83'
closing connection to ('10.0.1.1', 65432)

Поскольку запрос + content-type + не является + text/json +, сервер обрабатывает его как пользовательский двоичный тип и не выполняет JSON-декодирование. Он просто печатает + content-type + и возвращает первые 10 байтов клиенту:

$ ./app-server.py '' 65432
listening on ('', 65432)
accepted connection from ('10.0.2.2', 55320)
received binary/custom-client-binary-type request from ('10.0.2.2', 55320)
sending b'\x00\x7f{"byteorder": "little", "content-type": "binary/custom-server-binary-type", "content-encoding": "binary", "content-length": 37}First 10 bytes of request: binary\xf0\x9f\x98\x83' to ('10.0.2.2', 55320)
closing connection to ('10.0.2.2', 55320)

Поиск проблемы

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

Если нет, то вашей первой остановкой должна стать документация Python socket module. Убедитесь, что вы прочитали всю документацию для каждой функции или метода, который вы вызываете. Кроме того, прочитайте ссылку: #reference [Ссылка] раздел для идей. В частности, проверьте ссылку: #errors [Errors] раздел.

Иногда дело не только в исходном коде. Исходный код может быть правильным, и это просто другой хост, клиент или сервер. Или это может быть сеть, например, маршрутизатор, брандмауэр или какое-то другое сетевое устройство, играющее «человек посередине».

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

ping

+ ping + проверит, если хост жив и подключен к сети, отправив эхо-запрос ICMP. Он напрямую связывается со стеком протоколов TCP/IP операционной системы, поэтому он работает независимо от любого приложения, работающего на хосте.

Ниже приведен пример запуска ping на macOS:

$ ping -c 3 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.058 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.165 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.164 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.058/0.129/0.165/0.050 ms

Обратите внимание на статистику в конце вывода. Это может быть полезно, когда вы пытаетесь обнаружить периодически возникающие проблемы с подключением. Например, есть ли потеря пакетов? Сколько существует задержка (см. Время туда-обратно)?

Если между вами и другим хостом установлен брандмауэр, эхо-запрос эхо-запроса может быть запрещен. Некоторые администраторы брандмауэра реализуют политики, обеспечивающие это. Идея в том, что они не хотят, чтобы их хосты были доступны для обнаружения. Если это так, и вы добавили правила брандмауэра, чтобы узлы могли обмениваться данными, убедитесь, что правила также позволяют ICMP проходить между ними.

ICMP — это протокол, используемый + ping +, но также протокол TCP и другие протоколы более низкого уровня, используемые для передачи сообщений об ошибках. Если вы испытываете странное поведение или медленные соединения, это может быть причиной.

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

ICMP TypeICMP CodeDescription

8

0

Echo request

0

0

Echo reply

3

0

Destination network unreachable

3

1

Destination host unreachable

3

2

Destination protocol unreachable

3

3

Destination port unreachable

3

4

Fragmentation required, and DF flag set

11

0

TTL expired in transit

См. Статью Path[Path MTU Discovery для получения информации о фрагментации и сообщениях ICMP. Это пример чего-то, что может вызвать странное поведение, о котором я упоминал ранее.

NetStat

В ссылке на раздел: # view-socket-state [Просмотр состояния сокета] мы рассмотрели, как + netstat + можно использовать для отображения информации о сокетах и ​​их текущем состоянии. Эта утилита доступна в macOS, Linux и Windows.

Я не упоминал столбцы + Recv-Q + и + Send-Q + в выходных данных примера. В этих столбцах будет показано количество байтов, которые хранятся в сетевых буферах, которые находятся в очереди для передачи или получения, но по какой-то причине не были прочитаны или записаны удаленным или локальным приложением.

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

Чтобы продемонстрировать это и увидеть, сколько данных я могу отправить до появления ошибки, я написал тестовый клиент, который подключается к тестовому серверу и многократно вызывает + socket.send () +. Тестовый сервер никогда не вызывает + socket.recv () +. Он просто принимает соединение. Это приводит к заполнению сетевых буферов на сервере, что в конечном итоге приводит к ошибке на клиенте.

Сначала я запустил сервер:

$ ./app-server-test.py 127.0.0.1 65432
listening on ('127.0.0.1', 65432)

Затем я запустил клиент. Давайте посмотрим, в чем ошибка:

$ ./app-client-test.py 127.0.0.1 65432 binary test
error: socket.send() blocking io exception for ('127.0.0.1', 65432):
BlockingIOError(35, 'Resource temporarily unavailable')

Вот вывод + netstat +, в то время как клиент и сервер все еще работали, с клиентом, выводящим сообщение об ошибке выше несколько раз:

$ netstat -an | grep 65432
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4  408300      0  127.0.0.1.65432        127.0.0.1.53225        ESTABLISHED
tcp4       0 269868  127.0.0.1.53225        127.0.0.1.65432        ESTABLISHED
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN

Первая запись — это сервер (+ Local Address + имеет порт 65432):

Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4  408300      0  127.0.0.1.65432        127.0.0.1.53225        ESTABLISHED

Обратите внимание на + Recv-Q +: + 408300 +.

Вторая запись — это клиент (+ Foreign Address + имеет порт 65432):

Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0 269868  127.0.0.1.53225        127.0.0.1.65432        ESTABLISHED

Обратите внимание на + Send-Q +: + 269868 +.

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

Windows

Если вы работаете с Windows, есть набор утилит, которые вы обязательно должны проверить, если вы еще этого не сделали: Windows Sysinternals.

Одним из них является + TCPView.exe +. TCPView является графическим + netstat + для Windows. В дополнение к адресам, номерам портов и состоянию сокета, он покажет вам промежуточные итоги для количества пакетов и байтов, отправленных и полученных. Как и утилита Unix + lsof +, вы также получаете имя и идентификатор процесса. Проверьте меню для других вариантов отображения.

Wireshark

Иногда вам нужно посмотреть, что происходит на проводе. Забудьте о том, что говорит журнал приложения или каково значение, которое возвращается из библиотечного вызова. Вы хотите увидеть, что на самом деле отправляется или принимается в сети. Так же, как отладчики, когда вам нужно это увидеть, нет замены.

Wireshark — это анализатор сетевых протоколов и приложение для захвата трафика, которое работает в MacOS, Linux и Windows и других. Существует версия графического интерфейса с именем + wireshark +, а также терминальная текстовая версия с именем + tshark +.

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

В Интернете есть много хороших учебных пособий и других ресурсов, которые познакомят вас с основами использования Wireshark и TShark.

Вот пример захвата трафика с использованием Wireshark на интерфейсе обратной связи:

Вот тот же пример, показанный выше с использованием + tshark +:

$ tshark -i lo0 'tcp port 65432'
Capturing on 'Loopback'
    1   0.000000    127.0.0.1 → 127.0.0.1    TCP 68 53942 → 65432 [SYN] Seq=0 Win=65535 Len=0 MSS=16344 WS=32 TSval=940533635 TSecr=0 SACK_PERM=1
    2   0.000057    127.0.0.1 → 127.0.0.1    TCP 68 65432 → 53942 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16344 WS=32 TSval=940533635 TSecr=940533635 SACK_PERM=1
    3   0.000068    127.0.0.1 → 127.0.0.1    TCP 56 53942 → 65432 [ACK] Seq=1 Ack=1 Win=408288 Len=0 TSval=940533635 TSecr=940533635
    4   0.000075    127.0.0.1 → 127.0.0.1    TCP 56 [TCP Window Update] 65432 → 53942 [ACK] Seq=1 Ack=1 Win=408288 Len=0 TSval=940533635 TSecr=940533635
    5   0.000216    127.0.0.1 → 127.0.0.1    TCP 202 53942 → 65432 [PSH, ACK] Seq=1 Ack=1 Win=408288 Len=146 TSval=940533635 TSecr=940533635
    6   0.000234    127.0.0.1 → 127.0.0.1    TCP 56 65432 → 53942 [ACK] Seq=1 Ack=147 Win=408128 Len=0 TSval=940533635 TSecr=940533635
    7   0.000627    127.0.0.1 → 127.0.0.1    TCP 204 65432 → 53942 [PSH, ACK] Seq=1 Ack=147 Win=408128 Len=148 TSval=940533635 TSecr=940533635
    8   0.C13 packets captured

СОКЕТЫ

Сокет — инструмент позволяющий организовать сетевое взаимодействие приложений типа «точка-точка». В ABL существует два типа сокетов: Server Socket и Client Socket. Server Socket выполняет роль коммутатора, а Client Socket — роль оконечного оборудования связи.


SERVER SOCKET

SERVER SOCKET — Слушает TCP/IP порт обрабатывая клиентские подключения.

CREATE SERVER-SOCKET handle [NO-ERROR].

где, handle — переменная HANDLE типа, в которою будет записан handle создаваемого сервер-сокета.


АТРИБУТЫ:

 

Атрибут Тип аргумента Чтение/Запись Описание
HANDLE HANDLE чтение handle сервер-сокета
INSTANTIATING-PROCEDURE  HANDLE чтение Возвращает handle процедуры в которой был объявлен сервер сокет
NAME CHARACTER чтение/запись имя сервер сокета
NEXT-SIBLING HANDLE чтение Возвращает последующий handle сервер сокета из числа сокетов текущей сессии. По достижению конца списка возвращает неизвестное значение  (?).
PREV-SIBLING HANDLE чтение Возвращает предыдущий handle сервер сокета из числа сокетов текущей сессии. По достижению начала списка возвращает неизвестное значение  (?).
PRIVATE-DATA CHARACTER чтение/запись Свободный атрибут
SENSITIVE LOGICAL чтение/запись Отражает возможность сервер сокета реагировать на события. По умолчание имеет значение TRUE.
TYPE CHARACTER чтение Возвращает значение SERVER-SOCKET


МЕТОДЫ:

ENABLE-CONNECTIONS(connection-parms) — осуществляет запуск сервера с указанными параметрами. Результатом является логическое значение, отражающее успешность или не успешность запуска сервера.

  • connection-parms  — Строка параметров подключения:
Параметр Описание
-S socket-port  Номер порта сервера
-pf filename  filename — файл с параметрами 
-qsize backlog  Максимальное число запросов находящихся в очереди ожидания на обработку сервером
-ssl SSL-соединение
-keyalias aliasname  
-keyaliaspasswd encpwd  
-nosessioncache Отключение кэширования SSL сессии
-sessiontimeout [seconds] Время ожидания сервера в секундах до сброса SSL клиента, по умолчанию 180 секунд.

DISABLE-CONNECTIONS() — прекращает прием запросов сервером. Возвращает логическое значение TRUE в случае успешной остановки сервера.

SET-CONNECT-PROCEDURE(event-internal-procedure [,procedure-context]) — определяет процедуру обработки клиентских запросов, т.е. процедуру вызываемую по событию подключения очередного клиента. Данная процедура должна обладать одним входным параметром типа handle. При вызове метода данной процедуре будет передан handle сокета.

  • event-internal-procedure — текстовое значение имени процедуры.
  • procedure-context — параметры передаваемые процедуре.


SOCKET

SOCKET — осуществляет непосредственный обмен сообщениями с подключившимся клиентом.


АТРИБУТЫ:

 

Атрибут Тип аргумента Чтение/Запись Описание
BYTES-READ  INTEGER чтение Возвращает количество байт полученных в ходе последнего выполнения метода READ(). Если последнее выполнение метода READ() завершилось с ошибкой, то возвращается 0.
BYTES-WRITTEN  INTEGER чтение Возвращает количество байт полученных в ходе последнего выполнения метода WRITE(). Если последнее выполнение метода WRITE() завершилось с ошибкой, то возвращается 0.
HANDLE  HANDLE чтение handle сокета
INSTANTIATING-PROCEDURE  HANDLE чтение Возвращает handle процедуры в которой был объявлен сокет
LOCAL-HOST    CHARACTER чтение  IP-адрес сокета. Если подключение не установлено, возвращает неизвестное значение (?).
LOCAL-PORT INTEGER чтение  Порт сокета. Если подключение не установлено, возвращает неизвестное значение (?).
NAME CHARACTER чтение/запись имя сервер сокета
NEXT-SIBLING HANDLE чтение Возвращает последующий handle  сокета из числа сокетов текущей сессии. По достижению конца списка возвращает неизвестное значение  (?).
PREV-SIBLING HANDLE чтение Возвращает предыдущий handle сервер сокета из числа  сокетов текущей сессии. По достижению начала списка возвращает неизвестное значение  (?).
PRIVATE-DATA CHARACTER чтение/запись Свободный атрибут
REMOTE-HOST CHARACTER чтение  IP-адрес подключенного клиента. Если подключение клиента не установлено, возвращает неизвестное значение (?).
REMOTE-PORT INTEGER чтение  Порт подключенного клиента. Если подключение клиента не установлено, возвращает неизвестное значение (?).
SENSITIVE LOGICAL чтение/запись Отражает возможность сокета реагировать на события. По умолчание имеет значение TRUE.
SSL-SERVER-NAME CHARACTER чтение  Имя SSL сервера. Если соединение не использует SSL возвращает неизвестное значение (?)
TYPE CHARACTER чтение Возвращает значение SERVER-SOCKET


МЕТОДЫ:

CONNECT([connect-parms]) — устанавливает соединение. Метод возвращает значение типа LOGICAL отражающее успешность (TRUE) или не успешность (FALSE) установки соединения.

connect-parms — параметры соединения:

Параметр Описание
-H socket-address Имя хоста или IP-адрес сокета
-S socket-port  Номер порта сокета
-pf filename  filename — файл с параметрами 
-ssl Указывается, если сервер-сокет использует SSL
-nosessionreuse  
-nohostverify  

CONNECTED( ) метод возвращает логическое значение состояния подключения.

DISCONNECT( ) — осуществляет отключение сокета. Метод возвращает логическое значение отражающее успешность выполнения отключения.

SET-SOCKET-OPTION(name, arguments) — устанавливает значение параметров (опций) сокета:

Параметр Описание
TCP-NODELAY Логическое значение («TRUE»/»FALSE») включающее/отключающее применение Алгоритма Нейглала
 SO-LINGER При вызове метода DISCONNECT() полное освобождение сокета происходит не сразу. Сокет перестает быть доступным, но продолжает удерживаться системой еще некоторое время, для дополучения из сети всех переданных ему данных «блуждающих» в сети.  Время между вызовом DISCONNECT() и полным освобождением сокета рассчитывается исходя из максимального времени существования пакета в сети. SO-LINGER позволяет управлять интервалом времени между  вызова метода DISCONNECT() и полным закрытием сокета. Значение данного параметра представляет собой логическое значение, отражающее применение данного параметра, и значение времени ожидания, указанные через разделитель запятая.
 SO-KEEPALIVE Эта опция указывает, должен ли TCP-протокол периодически передавать сообщение keepalive (подтверждение доступности) на соединенный сокет. Если адресат будет не в состоянии ответить на это сообщение, соединение считается разорванным. Возможное значение — «TRUE»/»FALSE».
 SO-REUSEADDR Возможное значение — «TRUE»/»FALSE».
 SO-RCVBUF
 SO-SNDBUF
Определяют размеры входных и выходных буферов сокета
 SO-RCVTIMEO Определяет значение тайм-аута сокета, заданного в секундах

GET-SOCKET-OPTION(name) — метод возвращает значение указанного параметра name cокета.  

SET-READ-RESPONSE-PROCEDURE(procedure, procedure-context) — определяет процедуру обрабатывающую событие запрос-ответ.

  • procedure — имя процедуры;
  • procedure-context — handle процедуры содержащей procedure . По умолчанию THIS-PROCEDURE.

GET-BYTES-AVAILABLE( ) Метод возвращает число байт доступных для чтения с сокета, INTEGER типа.

READ(buffer, position, bytes-to-read [, mode]) Чтение полученных сокетом данных. Метод возвращает логическое значение (тип LOGICAL), отражающее успешность выполнения чтения данных.

  • buffer — MEMPTR значение, определяющее, где должны быть размещены полученные данные;
  • position — порядковый номер байта (значение должно быть больше нуля), начиная с которого будет осуществляться запись в buffer, тип INTEGER;
  • bytes-to-read — количество байт которое будет прочитано из сокета, тип INTEGER;
  • mode — определяет режим чтения данных, тип INTEGER.

WRITE(buffer, position, bytes-to-write)

  • buffer — MEMPTR значение, определяющее буфер данных для записи в сокет;
  • position — целочисленное значение типа INTEGER большее 0, указывающее позицию стартового байта в буфере buffer, данные которого должны быть записаны в сокет;
  • bytes-to-write — количество байт данных, которые должны быть записаны из буфера в сокет.

 


ПРИМЕР РАБОТЫ С SOCKET

SERVER SOCKET

DEF VAR vOk AS LOGICAL NO-UNDO. /* успешность запуска сервера */

/* Создание server socket */
CREATE SERVER-SOCKET hServSocket NO-ERROR.

/* Запуск server socket */
DEF VAR aOk AS LOGICAL NO-UNDO.
vOk = hServSocket:ENABLE-CONNECTIONS("-S 4080").



IF NOT vOk THEN RETURN.

/* Определение процедуры метода CONNECT */

hServSocket:SET-CONNECT-PROCEDURE("connect-proc").

DO ON STOP UNDO, LEAVE:
WAIT-FOR CLOSE OF THIS-PROCEDURE.
END.

hServerSocket:DISABLE-CONNECTIONS().
DELETE OBJECT hServerSocket.

/* Процедура метода CONNECT */
PROCEDURE connect-proc:
  DEF INPUT PARAMETER hSocket AS HANDLE. /* socket handle */
IF VALID-HANDLE(hSocket) THEN
DO:
  hSocket:SET-READ-RESPONSE-PROCEDURE("read-handler", THIS-PROCEDURE).
END.
END PROCEDURE.

/* Процедура обработки запросов */
PROCEDURE read-handler:
DEF VAR mBuffer   AS MEMPTR   NO-UNDO.
DEF VAR vLenReq AS INT64   NO-UNDO.
  DEF VAR vLenResp  AS INT64   NO-UNDO.
  DEF VAR vHost    AS CHAR     NO-UNDO.  /* с какого хоста запрос */
  DEF VAR vRequest AS LONGCHAR NO-UNDO. /* Запрос */
  DEF VAR vResponce AS LONGCHAR NO-UNDO. /* Ответ */

  /********************** ЗАПРОС ************************/
vHost = hSocket:REMOTE-HOST. /* с какого хоста запрос */
  vLenReq = hSocket:GET-BYTES-AVAILABLE(). /* размер входящего запроса */

  IF vLenReq > 0 THEN
DO:
/* читаем запрос в буфер */
    SET-SIZE(mBuffer) = vLenReq.
    hSocket:READ(mBuffer, 1, vLenReq, 1).
COPY-LOB mBuffer FOR vLenReq TO vRequest.

    /* освобождаем буфер */
    SET-SIZE(mBuffer) = 0.

    /************************* ОТВЕТ **********************/
    vResponce = "Привет " + vRequest.
    vLenResp = LENGTH(vResponce).

IF vLenResp > 0 THEN
      DO:
    SET-SIZE(mBuffer) = vLenResp.
    COPY-LOB vResponce FOR vLenResp TO mBuffer.

    /* непосредственно ответ */
     hSocket:WRITE(mBuffer,1,vLenResp)).
 
/* освобождаем буфера */
      SET-SIZE(mBuffer) = 0.
    END.
END.

hSocket:DISCONNECT().
DELETE OBJECT hSocket.

END PROCEDURE.

 

Raspberry Pi Python Сетевое программирование (Начало работы с Socket)

 

Что такое сокет?

SocketТакже известный какРозетки«Приложения обычно проходят через» сокеты «Сделать запрос в сетьилиОтвечая на сетевые запросыЧтобы включить связь между хостами или процессами на компьютере.

 

Python ОбеспечиваетДва уровняпосещениеСетевой сервис。:

  • Сетевые сервисы низкого уровня поддерживают базовыеSocketОн предоставляет стандартный API сокетов BSD, который может получить доступ ко всем методам интерфейса Socket базовой операционной системы.
  • Сетевой сервисный модуль высокого уровня SocketServerОн предоставляет серверно-ориентированные классы, которые могут упростить разработку веб-серверов.

сервер

1, socket (): создать сокет Пример: s = socket.socket ()

2、bind():  Привязать IPАдрес и порт, Который он принимает, является примером данных кортежа s.bind ((«127.0.0.1», 8088))

3. listen (): наиболее определенныйКоличество ожидающих , Пример: s.listen (2) позволяет двум клиентам ждать отправки информации (зависание)

4, accept (): создать соединение между хостом и клиентом conn.addr = s.accept ()

Ниже приведен код на стороне сервера, server.py

импортировать сокет # импортировать модуль сокета

 s = socket.socket () # создать объект сокета
 host = socket.gethostname () # Получить имя локального хоста
 port = 12345 # установить порт
 s.bind ((хост, порт)) # порт привязки

 s.listen (5) # Ожидание соединения с клиентом, пока True:
         c, addr = s.accept () # Установить клиентское соединение.
         выведите 'адрес подключения:', адрес
         c.send («Добро пожаловать на курс Raspberry Pi!»)
         c.close () # Закрыть соединение

 

клиент

1.socket (): создать сокет s = socket.socket ()

2.connect (): использовать собственный объект для подключения к серверу s.connect ((«127.0.0.1», 8088))

Ниже приведен код клиента client.py

импортировать сокет # импортировать модуль сокета

 s = socket.socket () # создать объект сокета
 host = socket.gethostname () # Получить имя локального хоста
 port = 12345 # установить номер порта

s.connect((host, port))print s.recv(1024)
s.close() 

Конкретные этапы работы:

Теперь мы открываем два терминала (или два Python IDLE), первый терминал выполняет файл server.py:

 

первыйФайл client.py выполняется на двух терминалах:

 

В это время мы открываем первый терминал (на стороне сервера), и мы увидим следующую информацию:Адрес подключения: (‘192.168.0.118’, 62461)

В это время мы открываем клиент и видим следующую информацию: b’hello’

Хорошо, на этот раз я поделился здесь, пожалуйста, поправьте меня, спасибо за просмотр.

 

 

 

 

 

Что такое сокет?

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

Функциональность

Как правило, сокет придерживается определенного потока событий, чтобы он работал. Для модели клиент-сервер с ориентацией на подключение, сокет на сервере ожидает запроса от клиента.04]

Типы розеток

Ниже перечислены различные типы розеток:

Розетки датаграммы

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

Сырьевые розетки

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

Последовательные розетки пакетов Гнезда

Это похоже на сокет потока, за исключением того, что границы записи сохраняются. Этот тип сокета позволяет пользователям управлять протоколом последовательностей пакетов (SPP) или заголовками протокола интернет-протокола датаграмм (IDP) в пакете или даже группе пакетов. Этот сокет также позволяет пользователю получать заголовки входящих пакетов.

Розетки для ручьев

Этот тип сокета полагается на TCP для передачи данных. Если доставка данных невозможна, отправитель получит сообщение о том, что соединение привело к ошибке. Записи данных не имеют границ. Этот разъем обеспечивает ориентированный на подключение, последовательный и уникальный поток данных без границ записи, с четко определенными механизмами для создания и/или разрушения соединений и обнаружения ошибок. Он передает надежные данные в порядке и без использования внеполосных возможностей. Предполагается, что процессы взаимодействуют только между розетками одного типа, но нет никаких ограничений, препятствующих взаимодействию между этими розетками разных типов.

Активное гнездо

Это разъемное соединение с активными удаленными разъемами через открытое соединение для передачи данных. Если это соединение будет закрыто, активные розетки в каждой точке также будут разрушены. Используется клиентами, которые хотят инициировать запросы на подключение для подключения. Однако, это активное гнездо также может быть преобразовано в пассивное гнездо путем привязки имени к гнезду с помощью bind-macro и путем указания готовности принимать соединения с микрофоном listen-macro.

Пассивная розетка

Эта розетка не подключена, а ждет входящего соединения, которое вызовет новую активную розетку. Используется серверами для того, чтобы принимать запросы на соединение с микрофоном Connect-macro. Эта пассивная розетка не может использоваться для инициирования запросов на подключение. Понятия активных и пассивных сокетов для потоковых сокетов не применимы к другим типам сокетов, таким как сокеты датаграммы.

Порты и розетки

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

История

Термин «розетка» начал употребляться с 1971 года, когда он использовался при разработке ARPANET. Большинство розеток, реализуемых сегодня, основаны на розетках Беркерли, которые были разработаны в 1983 году. Однако розетки, используемые для подключения к Интернету, созданы по образцу моделей Winsock, которые были сделаны в 1991 году. Гнезда Беркерли также известны как гнезда BSD. В 1989 году Berkerley выпустила версии своей операционной системы и сетевой библиотеки, свободные от лицензионных ограничений. Другие ранние версии были написаны для TOPS-20, MVS, VM и IBM-DOS.

Что такое розетка? (Учебники по Java ™> Пользовательские сети> Все о сокетах)

Обычно сервер работает на определенном компьютере и имеет сокет, привязанный к определенному номеру порта. Сервер просто ждет, прослушивая сокет, чтобы клиент сделал запрос на соединение.

На стороне клиента: клиент знает имя хоста машины, на которой работает сервер, и номер порта, на котором сервер прослушивает. Чтобы сделать запрос на соединение, клиент пытается встретиться с сервером на машине и порту сервера.Клиенту также необходимо идентифицировать себя для сервера, чтобы он привязался к номеру локального порта, который он будет использовать во время этого соединения. Обычно это назначается системой.

Если все в порядке, сервер принимает соединение. После принятия сервер получает новый сокет, привязанный к тому же локальному порту, а также устанавливает для своей удаленной конечной точки адрес и порт клиента. Ему нужен новый сокет, чтобы он мог продолжать прослушивать исходный сокет для запросов на соединение, одновременно удовлетворяя потребности подключенного клиента.

На стороне клиента, если соединение принято, сокет успешно создан, и клиент может использовать сокет для связи с сервером.

Теперь клиент и сервер могут обмениваться данными посредством записи или чтения из своих сокетов.


Определение:

Сокет — это одна конечная точка двустороннего канала связи между двумя программами, работающими в сети. Сокет привязан к номеру порта, чтобы уровень TCP мог идентифицировать приложение, в которое должны быть отправлены данные.


Конечная точка — это комбинация IP-адреса и номера порта. Каждое TCP-соединение можно однозначно идентифицировать по двум его конечным точкам. Таким образом, вы можете иметь несколько соединений между вашим хостом и сервером.

Пакет java.net на платформе Java предоставляет класс Socket , который реализует одну сторону двустороннего соединения между вашей программой Java и другой программой в сети. Класс Socket находится на вершине платформенно-зависимой реализации, скрывая детали любой конкретной системы от вашей программы Java.Используя класс java.net.Socket вместо того, чтобы полагаться на собственный код, ваши Java-программы могут обмениваться данными по сети независимо от платформы.

Кроме того, java.net включает класс ServerSocket , который реализует сокет, который серверы могут использовать для прослушивания и приема соединений с клиентами. В этом уроке показано, как использовать классы Socket и ServerSocket .

Если вы пытаетесь подключиться к Интернету, класс URL и связанные классы ( URLConnection , URLEncoder ), вероятно, более подходят, чем классы сокетов.Фактически, URL-адреса являются относительно высокоуровневым соединением с Интернетом и используют сокеты как часть базовой реализации. Видеть Работа с URL-адресами для получения информации о подключении к Интернету через URL-адреса.

Развлечения с розетками. Когда я не понимал, что такое… | автор: Нирав Бхарадия

Когда я был сбит с толку, что такое розетка, я обратился к одному из моих самых увлеченных друзей Сумедху Нимкарде и спросил его о том же. Он заявил: «Все в Linux — это файлы» и выгнал меня из своей комнаты.Что ж, он не ошибался, но этого было недостаточно для понимания концепции. Итак, после небольшого исследования я обнаружил, что сокеты проще для понимания, и если мы сделаем некоторые базовые реализации, мы не забудем это на всю жизнь. Итак, вот полная пошаговая история.

Как компьютер идентифицирует другой компьютер в сети? В основном это публичный IP-адрес компьютера, верно? Теперь, чтобы быть более конкретным, как мы можем идентифицировать конкретный процесс на конкретном компьютере. Теперь на сцену выходит «порт».Порт — это идентификатор процесса (16-битное целое число без знака) на главном компьютере. Итак, вместе по IP-адресу и номеру порта мы получаем полный адрес для связи с процессом. Это то, что называется сокетом, конечной точкой соединения для отправки / получения данных. Поскольку он позволяет отправлять и получать данные, его можно рассматривать как аналог файлового ввода-вывода в сети.

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

В python нам нужно будет импортировать модуль под названием socket, чтобы использовать его встроенные классы, что упрощает наши задачи. Ниже приведены не требующие пояснений фрагменты кода серверных, а затем клиентских сокетов, которые показывают создание сокетов и различные методы для сокетов на стороне клиента и сервера.

Простая программа сокетов на стороне сервера в Python 3 Простая программа сокетов на стороне клиента в Python 3

Итак, в программе, показанной выше, есть некоторые вещи, на которые следует обратить внимание. В python адрес сокета хранится как кортеж из IP-адреса и номера порта. Addr — это кортеж (хост, порт) , где хост и порт выглядят следующим образом.

Хост → IP-адрес

Порт → номер порта

Хост = socket.gethostbyname (socket.gethostname ())

socket.gethostname () возвращает имя хоста, то есть имя ПК, а socket.gethostbyname () возвращает нам IP адрес. Это 127.0.0.1, если ваше устройство не в сети, или 192.X.X.X ваш публичный IP-адрес, если ваше устройство подключено к сети.

Теперь второе, на что следует обратить внимание: независимо от того, является ли он клиентским или серверным, сокет создается в python одинаково (в java есть два разных класса: Socket и ServerSocket). s = socket.socket () . Есть также некоторые необязательные аргументы, чтобы сделать сокет более конкретным. Например, мы можем указать семейство сокета, тип сокета и т. Д. По умолчанию мы получаем сокет TCP.

Итак, до сих пор мы разобрались, что такое адрес, а что такое сокет. Теперь о различии между серверными и клиентскими сокетами.

На следующем рисунке показано, какие методы должны быть вызваны на сервере и на клиенте на временной шкале.

Методы, участвующие в обмене данными между клиентом и сервером

Теперь пусть s будет серверным сокетом, а r будет клиентским сокетом, s должен вызывать методы связывания, прослушивания, принятия, чтения / записи и закрытия.Тогда как r должен выполнять только методы подключения, чтения / записи и закрытия. Итак, сокет сервера должен быть привязан к адресу, чтобы клиент мог подключиться к этому адресу. Метод Listen (n) сообщает серверу, сколько соединений он будет прослушивать. Теперь метод accept () — это метод остановки, который ожидает соединения, и если соединение установлено, он возвращает сокет соединения и его адрес. Подключение на стороне клиента дополняет accept (). Метод Connect принимает аргумент адреса и отправляет запрос на соединение, если сервер по этому адресу получает запрос, соединение устанавливается.

Теперь соединение установлено, серверные и клиентские сокеты могут обмениваться данными, как файловый ввод-вывод. В python есть методы s.send () и s.recv () .

s.send (msg) принимает аргумент сообщения и отправляет сообщение в соединение. Сообщение перед отправкой должно быть закодировано в каком-либо стандарте, который, если за ним следует это соединение, обычно кодировка по умолчанию — UTF-8.

s.recv (2048) принимает размер буфера в качестве аргумента и возвращает полученное сообщение.Сообщение имеет форму байт-кода, поэтому мы можем его декодировать, чтобы получить строку.

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

Терминал сервера Терминал клиента

В программе сервера сокет сервера прослушивает порт с номером 2000, а потом, черт возьми, произошло такое, что соединение установлено на порт с номером 44276! Удивлен?

Итак, давайте снова запустим программу в режиме ожидания и напечатаем свойства s и con.Получаем следующий результат.

сервер печати и объекты сокета подключения

Заявление об ограничении ответственности: я снова запустил серверные и клиентские программы в режиме ожидания, поэтому номер порта снова был изменен.

Итак, что происходит, упрощается в следующих шагах.

  1. Сервер прослушивает порт №2000.
  2. Запрос на соединение попадает на порт №2000.
  3. Сервер открывает другой порт, скажем, по адресу 44276, и устанавливает соединение на нем и продолжает прослушивать запросы на порт № 2000 г.
номера портов!

На картинке это хорошо видно.

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

Итак, мы видели, как устанавливается соединение. Но как клиентские, так и серверные программы были написаны на python3. Итак, зависят ли сокеты от языка?

Ответ — большое НЕТ.

Просто для собственного удовольствия вы можете запустить BasicServer.py и Client.java, чтобы проверить, работает оно или нет. Я сделал это за вас, вот скриншоты.

Запуск сервера сокетов, написанного на python3 Запуск клиента сокета, написанного на Java

Для справки ниже приведен снимок экрана клиентской программы, написанной на Java.

Программа клиентского сокета, написанная на Java

Итак, здесь мы можем сделать вывод, что сокеты, написанные на любом языке, могут обмениваться данными без каких-либо особых усилий. Самое важное, о чем вы должны помнить, — это кодирование.В настоящее время это UTF-8 по умолчанию для большинства языков, но это может быть или не всегда иметь место, поэтому лучше упомянуть стандарты кодирования-декодирования в самой программе, кроме этого, все будет работать плавно, как файл IO.

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

Ниже приведен снимок экрана с простыми и понятными программами.

Базовая программа сервера чата Программа базового чата

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

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

Чат со стороны сервера Чат со стороны клиента

Теперь синхронизировать программы чата довольно просто, но помогает ли это сделать приложение чата в реальном времени ? Было бы здорово, если бы был дуплекс, не так ли?

Итак, вот решение для этого.

После небольшого исследования я обнаружил, что есть два способа реализовать полнодуплексный чат в Python.

  1. Многопоточность
  2. С помощью модуля Select.

В первом подходе мы можем создать две функции, одна из которых говорит sendmsg (), а другая — recvmsg (), обе будут иметь бесконечный цикл while для отправки и получения сообщения.Затем мы создадим два потока, используя threading модуль .

t1 = threading (target = sendmsg, args = (con,))

t2 = threading (target = recvmsg, args = (con,))

Теперь у нас есть два разных потока для отправки и получения сообщения, теперь мы можем запустить оба потока с помощью T [n] .start (). Эти программы будут выполнять свою задачу недетерминированно, потому что input () является блокирующей функцией. Это означает, что когда мы находимся в потоке T1 и функция sendmsg () работает, она ожидает некоторого input (), , поскольку input () является функцией блокировки, T2 не может получить сообщение, пока input () не будет удовлетворен. .Теперь мы можем решить эту проблему, установив тайм-аут для функции input (), но это не совсем просто. Следовательно, я не сторонник такого подхода.

Во втором подходе мы используем select module для создания буфера ввода-вывода, что означает, получает ли программа ввод от сокета или stdin, он сохраняется в буфере и отзывается только тогда, когда мы явно отзываем его. Ниже приведены скриншоты программы.

Полнодуплексный чат-сервер Полнодуплексный чат-клиент

Итак, в приведенных выше программах мы используем модуль выбора для создания буфера ввода-вывода.Под буфером ввода-вывода я имею в виду, будет ли вход от стандартного ввода (стандартный ввод с терминала) или от сокета (ввод от подключенного сокета), он будет сохранен в буфере. Это означает, что никакие входы не будут потеряны, и никакие входы не будут блокировать другой входной поток. Следовательно, мы получаем желаемые данные один за другим. Такая же логика реализована на стороне клиента и сервера. Ниже приведены снимки экрана полнодуплексного чата, в котором клиент и сервер могут отправлять и получать сообщения в любое время, не дожидаясь ответа друг друга.

Полнодуплексный чат с серверного терминала Полнодуплексный чат с клиентского терминала

На этом я завершаю статью. Я надеюсь, что после прочтения этой статьи вы сможете понять, что такое программирование сокетов и сокетов, как устанавливается соединение и почему соединение не зависит от языка. Кроме того, теперь вы можете реализовать от простого приложения для подключения клиент-сервер к полнодуплексному чат-приложению.

Вот ссылка на репозиторий GitHub кодов, использованных в статье: Основы работы с сокетами.

Спасибо за чтение, и вы можете оценить или предложить в разделе комментариев ниже.

Программирование TCP / UDP с сокетами

Это краткое руководство по программированию TCP / IP и UDP / IP клиент / сервер в Common Лисп с использованием usockets.

TCP / IP

Как обычно, мы будем использовать quicklisp для загрузки usocket.

  (ql: quickload "usocket")
  

Теперь нам нужно создать сервер. Нам нужны 2 основные функции звонить. usocket: socket-listen и usocket: socket-accept .

usocket: socket-listen привязывается к порту и прослушивает его. Возвращает сокет объект. Нам нужно подождать с этим объектом, пока мы не получим соединение, которое мы принимать. Здесь на помощь приходит usocket: socket-accept . Это блокирующий вызов. который возвращается только при установлении соединения. Это возвращает новый объект сокета это относится к этому соединению. Затем мы можем использовать это соединение для общаться с нашим клиентом.

Итак, с какими проблемами я столкнулся из-за своих ошибок? Ошибка 1. Я изначально понимал, что socket-accept вернет объект потока.НЕТ…. Он возвращает объект сокета. Оглядываясь назад, это правильно и моя собственная ошибка стоила мне времени. Итак, если вы хотите писать в сокет, вы нужно фактически получить соответствующий поток из этого нового сокета. Розетка объект имеет слот потока, и нам нужно явно его использовать. И как один знаете это? (опишите подключение) — ваш друг!

Ошибка 2 — Вам необходимо закрыть и новый сокет, и серверный сокет. Опять же, это довольно очевидно, но поскольку мой исходный код только закрывался соединение, я продолжал сталкиваться с проблемой использования сокета.Конечно еще один вариант — повторно использовать сокет, когда мы слушаем.

Как только вы избавитесь от этих ошибок, довольно легко сделать все остальное. Закрывать соединения, серверный сокет и бум готово!

  (defun create-server (порт)
  (let * ((socket (usocket: порт для прослушивания сокетов "127.0.0.1")))
(соединение (usocket: socket-accept socket: символ типа элемента)))
    (размотать-защитить
        (прогноз
(формат (usocket: соединение с сокетом) "Hello World ~%")
(принудительный вывод (usocket: соединение с потоком сокета)))
      (прогноз
(формат t "Закрытие сокетов ~%")
(usocket: соединение с закрытым сокетом)
        (usocket: закрытый сокет)))))
  

Теперь о клиенте.Эта часть проста. Просто подключитесь к порту сервера и вы должны иметь возможность читать с сервера. Единственная глупая ошибка я сделано здесь, чтобы использовать чтение, а не чтение строки. В итоге я увидел только «Привет» с сервера. Я пошел на прогулку и вернулся, чтобы найти проблему и исправить.

  (defun create-client (порт)
  (usocket: with-client-socket (поток сокета "127.0.0.1" порт: символ типа элемента)
    (размотать-защитить
         (прогноз
           (usocket: сокет ожидания ввода)
           (формат t "Ввод: ~ a ~%" (поток строки чтения)))
      (usocket: сокет с закрытым сокетом))))
  

Итак, как это запустить? Вам нужно два REPL, один для сервера и один для клиента.Загрузите этот файл в оба REPL. Создать сервер в первом REPL.

  (создать-сервер 12321)
  

Теперь вы готовы запустить клиент на втором REPL

.
  (создать-клиент 12321)
  

Вуаля! Вы должны увидеть «Hello World» во втором REPL.

UDP / IP

В качестве протокола UDP не требует установления соединения, поэтому нет концепция привязки и принятия соединения. Вместо этого мы делаем только socket-connect , но передайте определенный набор параметров, чтобы убедиться, что мы создаем сокет UDP, который ожидает данных на определенном порту.

Итак, с какими проблемами я столкнулся из-за своих ошибок? Ошибка 1. В отличие от TCP, вы не передаете хост и порт в socket-connect . Если вы это сделаете, вы укажете, что хотите отправить пакет. Вместо этого вы передаете nil , но устанавливаете : local-host и : local-port на адрес и порт, по которому вы хотите получать данные. Эта часть заняла некоторое время, чтобы выяснить, потому что документация не охватывала это. Вместо чтения немного кода из https: // код.google.com/p/blackthorn-engine-3d/source/browse/src/examples/usocket/usocket.lisp очень помог.

Кроме того, поскольку UDP не требует установления соединения, любой может отправлять на него данные в любое время. время. Итак, нам нужно знать, с какого хоста / порта мы получили данные, поэтому что мы можем ответить на это. Итак, мы привязываем несколько значений к socket-receive и использовать эти значения для отправки данных нашему одноранговому «клиенту».

  (defun create-server (буфер порта)
  (let * ((socket (usocket: socket-connect nil nil
: протокол: дейтаграмма
: тип-элемента '(беззнаковый байт 8)
: local-host "127.0,0.1 "
: локальный порт порт)))
    (размотать-защитить
(привязка нескольких значений (порт приема клиента с размером буфера)
(usocket: буфер сокета-приемника 8)
(формат буфера t "~ A ~%")
(usocket: размер сокета-сокета (обратный буфер)
: порт приемный порт
: хост-клиент))
      (usocket: сокет с закрытым сокетом))))
  

Теперь об отправителе / ​​получателе. Эта часть довольно проста. Создайте сокет, отправлять данные на него и получать данные обратно.

  (defun create-client (буфер порта)
  (let ((socket (usocket: socket-connect "127.0,0.1 "порт
: протокол: дейтаграмма
: тип-элемента '(беззнаковый байт 8))))
    (размотать-защитить
(прогноз
(формат t «Отправка данных ~%»)
(замените буфер # (1 2 3 4 5 6 7 8))
(формат t "Получение данных ~%")
(usocket: буфер сокета для отправки сокета 8)
(usocket: буфер сокета-приемника 8)
(формат буфера t "~ A ~%"))
      (usocket: сокет с закрытым сокетом))))
  

Итак, как это запустить? Вам снова нужны два REPL, один для сервера и один для клиента. Загрузите этот файл в оба REPL.Создать сервер в первом REPL.

  (создать-сервер 12321 (make-array 8: element-type '(unsigned-byte 8)))
  

Теперь вы готовы запустить клиент на втором REPL

.
  (создать-клиент 12321 (make-array 8: element-type '(unsigned-byte 8)))
  

Вуаля! Вы должны увидеть вектор # (1 2 3 4 5 6 7 8) на первом REPL и # (8 7 6 5 4 3 2 1) на втором.

Кредит

Это руководство изначально взято с https: // gist.github.com/shortsightedsid/71cf34282dfae0dd2528

Источник страницы: sockets.md

Wera Tools 056490 Набор бит-трещоток Tool-Check Plus с головками

Набор бит-трещоток Wera Tool-Check PLUS с метрическими головками
8001 A Бит-трещотка, штампованная стальная конструкция для стандартных вставных бит (шестигранник 1/4 дюйма)
813 Рукоятка отвертки, удерживающая бит
870/1 Адаптер для головки (1/4 От шестигранника до квадрата 1/4 дюйма)
889/4/1 K Держатель бит Rapidaptor
851/1 TZ PH Phillips бит: # 1 (x2)
851/1 TZ PH Phillips бит: # 2 (x3)
851 / 1 TZ PH Phillips бит: # 3
851/1 TH PZ Pozidriv бит: # 1
851/1 TH PZ Pozidriv бит: # 2 (x3)
851/1 TH PZ Pozidriv бит: # 3
867/1 TZ Torx бит: T10
867/1 TZ Torx бит: T15
867/1 TZ Torx бит: T20
867/1 TZ Torx бит: T25
867/1 TZ Torx бит: T30
867/1 TZ Torx бит: T40
867 / 1 Z BO Security Torx бит: T10s
867/1 Z BO Security Torx бит: T15s
867/1 Z BO Security Torx бит: T20s
867/1 Z BO Security Torx бит: T25s
867/1 Z BO Security Torx бит: T30s
800/1 TZ Прорезной бит: 5.5 мм
840/1 Z Hex-Plus бит: 3,0 мм
840/1 Z Hex-Plus бит: 4,0 мм
840/1 Z Hex-Plus бит: 5,0 мм
840/1 Z Hex-Plus бит: 6,0 мм
840/1 Z Hex-Plus бит: 8,0 мм
8790 HMA Головка для привода 1/4 «: 5,5 мм
8790 HMA Головка для привода 1/4″: 6,0 мм
8790 HMA Головка для привода 1/4 «: 7,0 мм
8790 HMA Гнездо для привода 1/4 «: 8,0 мм
8790 HMA Гнездо для привода 1/4″: 10,0 мм
8790 HMA Гнездо для привода 1/4 «: 12,0 мм
8790 Гнездо для привода HMA 1/4″: 13,0 мм
«Все может лучше не станет ». Хотя нам нравится слышать подобные комментарии, именно такие отзывы являются настоящим мотиватором для разработчиков продуктов в Wera.

Те, кто думал, что удивительно компактный Tool-Check с его набором из 28 бит, 7 гнезд, 1 переходник для гнезда, 1 бит-трещотка и 1 Rapidaptor не может быть лучше, теперь с изумлением протирают глаза.

Благодаря рукоятке Kraftform, которая теперь интегрирована в качестве 39-й детали, с помощью новой Tool-Check PLUS можно сразу изготавливать 28 различных отверток, используя рукоятку и биты. И каждая из этих отверток обладает всеми преимуществами ручки Kraftform, такими как высокая скорость работы и эргономичное управление с передачей высокого крутящего момента.

Вес: 514 г (18,1 унции)
Номер по каталогу производителя: 050564

На все профессиональные ручные инструменты Wera распространяется пожизненная гарантия от поломки из-за дефектов материалов или изготовления в течение нормального срока службы продукта. Эта политика не распространяется на продукты, в которые были внесены какие-либо изменения, а также на продукты, которые подвергались злоупотреблениям, неправильному использованию, небрежности или ненадлежащему хранению. Биты, битодержатели и L-образные ключи также не подпадают под действие данной гарантии, поскольку они считаются расходными материалами.Wera Tools оставляет за собой право проверять претензии по гарантии до того, как будет произведена замена. Затраты на обратную транспортировку не включены, однако Wera бесплатно отправит инструменты для замены. Любые претензии по причинам, отличным от указанных здесь, должны быть одобрены Wera Tools Inc. или ее уполномоченными представителями.

Верните инструменты для гарантийной замены непосредственно в KC Tool. Все возвраты подлежат проверке перед заменой.

Тип стиля:
шлицевой
Тип стиля:
Филипс
Тип стиля:
PoziDriv
Тип стиля:
Торкс
Тип стиля:
Безопасность Torx
Тип стиля:
шестигранник
Типоразмер:
Филипс № 1
Типоразмер:
Филипс № 2
Типоразмер:
Филипс № 3
Типоразмер:
Позидрив №1
Типоразмер:
Позидрив №2
Типоразмер:
Позидрив №3
Типоразмер:
Торкс Т10
Типоразмер:
Торкс Т15
Типоразмер:
Торкс Т20
Типоразмер:
Торкс Т25
Типоразмер:
Торкс Т30
Типоразмер:
Торкс Т40
Типоразмер:
Безопасность Torx T10s
Типоразмер:
Безопасность Torx T15s
Типоразмер:
Безопасность Torx T20s
Типоразмер:
Безопасность Torx T25s
Типоразмер:
Безопасность Torx T30s
Типоразмер:
Шестигранник, метрический 3.0 мм
Типоразмер:
Шестигранник Метрический 4,0 мм
Типоразмер:
Шестигранник Метрический 5,0 мм
Типоразмер:
Шестигранник Метрический 6,0 мм
Типоразмер:
Шестигранник Метрический 8,0 мм
Размер метрики:
5.5 мм
Размер метрики:
6,0 мм
Размер метрики:
7,0 мм
Размер метрики:
8,0 мм
Размер метрики:
10 мм
Размер метрики:
12 мм
Размер метрики:
13 мм
Инди-сет:
Наборы инструментов
Типоразмер:
Прорезь 5.5 мм
с храповым механизмом, приводной размер:
Квадратный привод 1/4 дюйма
Характеристики:
Трещотка
Страна происхождения:
Сделано в Чехии
Состояние запаса:
Нет в наличии
UPC / EAN:
4013288173003

новых созданных подходящих легендарных предметов получают бесплатное гнездо у Резчика рун в обновлении 9.1 — Модернизированные легендарные предметы не

Мы подтвердили, что только новые созданные легендарные предметы получат гнездо (если оно есть) у Резчика рун на PTR 9.1. Если вы улучшите легендарный предмет без гнезда, он не получит бесплатного гнезда.

Хотите создать свою легендарку с помощью системы «Осколки господства», узнайте, в какой слот ее нужно создать, с помощью наших классных писателей!

Рекомендации классного писателя по созданию легендарных предметов

Сводка результатов

Вот краткое изложение наших открытий, касающихся изготовления легендарных предметов и гнезд в патче 9.1 PTR. Однако это может быть изменено посредством ежедневных исправлений, которые Blizzard недавно выпускала.

  • Белые базовые предметы могут иметь гнездо в патче 9.1, но эти гнезда не влияют ни на что . Незакрепленный Базовый предмет функционально равен размещенному Базовому предмету у Резчика рун.
    Базовые предметы, созданные до патча, не будут иметь гнезда.
  • Новые базовые предметы будут автоматически созданы с помощью гнезда.

Совершенно новые легендарных предметов будут созданы с помощью гнезда (если есть возможность), независимо от того, было ли у вашего базового предмета гнездо или нет .
Улучшение легендарного предмета без гнезда не даст ему гнезда, независимо от того, был ли у вашего базового предмета гнездо или нет .

Мы тестировали только ранги с 1 по 4, поэтому при переходе к 5 рангу может быть разница. Однако мы считаем это маловероятным, поскольку это ожидаемый результат при чтении примечаний к патчу.

Blizzard


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

Базовые предметы имеют гнезда в патче 9.1

Белые базовые предметы будут автоматически созданы с гнездами на них в патче 9.1. Это , совершенно не относящийся к делу , и на самом деле он только сбивает игроков с толку, заставляя думать, что им понадобится базовый предмет с гнездами. Вы никогда не собираетесь вставлять гнездо в базовый предмет, а гнездо на базовом предмете вообще не влияет на создание Runecarver.

Установка гнезда для недавно созданного легендарного предмета

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

Ниже показано видео, показывающее, что если вы возьмете незакрепленный базовый предмет и создадите новый легендарный предмет, он все равно будет крафтиться с бесплатным гнездом.

Ваш браузер не поддерживает видео тег.

Установка ранее созданного легендарного предмета

При улучшении легендарного предмета без гнезда вы не получите бесплатного гнезда, независимо от того, используете ли вы базовый предмет с гнездом или нет.

Если вы уже создали легендарный предмет, на который можно установить сокет, у вас есть два варианта:

  • Переработайте легендарный предмет для бесплатного сокета в патче 9.1, если у вас есть избыток пепла души, а не Стигия.
  • Используйте гнездо 1-го или 2-го сезона на легендарном предмете для получения гнезда.

Способность ходить и качество жизни у пациентов с трансфеморальной ампутацией: сравнение остеоинтеграции с ортопедическими протезами

Задача: Изучить способность ходить и качество жизни остеоинтегрированных протезов ног по сравнению с суставными протезами.

Дизайн: Проспективное исследование случай-контроль.

Параметр: Университетский медицинский центр.

Участники: Пациенты (N = 22) с трансфеморальной ампутацией (1 двусторонняя) обратились в наш центр из-за проблем с кожей и остаточными проблемами конечностей, связанных с суставами, что привело к ограниченному использованию протезов.Их средний возраст составлял 46,5 лет (диапазон от 23 до 67 лет), а среднее время после ампутации — 16,4 года (диапазон от 2 до 45 лет). Причины ампутации: травма (n = 20) и опухоль (n = 2).

Вмешательство: Имплантация остеоинтеграционного протеза (ОИП).

Основные показатели результатов: Общий балл анкеты для людей с трансфеморальной ампутацией (Q-TFA), использованием протезов, 6-минутной ходьбой (6MWT), тестом Timed Up & Go (TUG) и потреблением кислорода во время ходьбы по беговой дорожке.

Полученные результаты: При использовании протезов с гнездом средний балл ± стандартное отклонение Q-TFA, использование протеза, 6MWT, TUG и потребление кислорода составили 39 ± 4,7 балла, 56 ± 7,9 ч / нед, 321 ± 28 мес, 15,1 ± 2,1 секунды и 1330 ± 310 мл / мин, соответственно, и значительно улучшились с OIP до 63 ± 5,3 балла, 101 ± 2,4 ч / нед, 423 ± 21 мес, 8,1 ± 0,7 секунды и 1093 ± 361 мл / мин, соответственно.

Выводы: Остеоинтеграция — подходящее вмешательство для людей, у которых использование протезов ограничено из-за проблем, связанных с суставами.Субъекты с ОИП значительно улучшили способность ходить и качество жизни, связанное с протезированием.

Ключевые слова: 6-минутная ходьба; 6 МВт; Ампутация; Функция; Исследование медицинских результатов Краткое обследование состояния здоровья, состоящее из 36 пунктов; ОИС; OPRA; Остеоинтегрированный протез для реабилитации ампутантов; Остеоинтеграция; PWS; Протез; Q-TFA; Качество жизни; Анкета для лиц с трансфеморальной ампутацией; Реабилитация; SF-36; БУКСИР; Время Up & Go; остеоинтеграционный протез; предпочтительная скорость ходьбы.

WebSockets против REST: понимание различий

В этом сообщении блога рассматривается WebSockets и REST, различия в производительности, варианты использования и способы вывода WebSockets на новый уровень.


Сокеты — это парадигма работы с сетями, и эта концепция существует уже несколько десятилетий. Когда-то сокеты были способом стандартизации сетевого ввода и вывода, как это делает API, так что, независимо от особенностей оборудования, приложения могли программировать «сокеты», и это могло работать с множеством различных аппаратных реализаций.

Идея сокета заключается в том, что это «порт», через который данные входят и выходят. Как и настоящий торговый порт для данных, сам сокет похож на док, именно здесь происходит обмен с точки зрения приложения. Само гнездо является абстрактным и низкоуровневым, и, оставаясь с метафорой, его могут использовать многие разные корабли, поезда и оборудование. Мы можем назвать все эти протоколы.

В Интернете существуют сотни протоколов, но некоторые из них выделяются как наиболее распространенные, например HTTP, FTP, SMTP, POP3 и т. Д.и транспортные протоколы нижнего уровня, такие как TCP и UDP . По сути, протоколы определяют, как интерпретировать данные, поступающие в сокет и из него, и машины, которые обмениваются данными друг с другом.

Что такое WebSockets?

WebSockets — это просто расширение идеи сокетов. Хотя протокол HTTP был изобретен для Всемирной паутины и с тех пор используется браузерами, у него были ограничения. Это был особый протокол, который работал определенным образом и не подходил для всех нужд.В частности, как HTTP обрабатывает соединения. Каждый раз, когда вы делали запрос, скажем, загрузить html или изображение, порт / сокет открывался, данные передавались, а затем закрывались.

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

Другим ограничением HTTP было то, что это была парадигма «тяни». Браузер запрашивал или извлекал информацию с серверов, но сервер не мог отправить данные браузеру, когда он этого хотел.Это означает, что браузеры должны будут опрашивать сервер для получения новой информации, повторяя запросы каждые несколько секунд или минут, чтобы увидеть, есть ли что-нибудь новое. В конце 2000-х нарастало движение за добавление сокетов в браузеры.

В 2011 году WebSocket был стандартизирован, и это позволило людям использовать протокол WebSocket, который был очень гибким, для передачи данных на серверы и с серверов из браузера, а также для одноранговой (P2P) или прямой связи. между браузерами.В отличие от HTTP, сокет, подключенный к серверу, остается «открытым» для связи. Это означает, что данные могут быть «отправлены» в браузер в режиме реального времени по запросу.

Что такое ОТДЫХ?

В REST или REpresentational State Transfer — еще одна абстракция для создания API для приложений стандартизированным способом. В типичных, а теперь и традиционных веб-приложениях создание конечных точек REST с использованием HTTP — это то, как построено подавляющее большинство приложений. Будь то Ruby, Java, Go, NodesJS или любая из множества доступных технологий, они принципиально похожи в том, что они получают запросы на информацию, а затем отвечают на запрос.

REST организует эти запросы предсказуемым образом, используя типы операций HTTP или глаголы для создания соответствующих ответов. Запросы исходят от клиента, и общие HTTP-команды включают GET, POST, PUT, DELETE, но есть несколько других. Они соответствуют ожидаемым операциям, получению данных, отправке данных, обновлению данных и удалению данных.

REST на сегодняшний день является наиболее стандартизированным способом структурирования API для запросов. Но поскольку он включает использование HTTP, это также связано с накладными расходами, связанными с этим протоколом.Для большинства приложений информация должна передаваться только тогда, когда пользователь выполняет действие. Например, при просмотре новостного сайта, когда браузер запросил статью, пользователь занят ее чтением и не предпринимает никаких действий. Закрытие порта-сокета в это время фактически экономит ресурсы. При менее частом взаимодействии HTTP работает очень хорошо, и поэтому он используется.

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

WebSockets против REST: сравнение производительности

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

REST Производительность

Образно говоря, мы могли бы думать об этом как об армии с цепочкой командования.Давайте сделаем сервер генералом, а все браузеры солдатами на земле, ожидающими приказов. Используя HTTP / REST, если каждый солдат должен спросить генерала, есть ли какие-либо новые приказы, это сильно обременяет генерала, особенно когда нет ничего нового. Это означает, что генерал большую часть времени говорит «нет, ничего нового».

Если ваши серверы — генералы, их ресурсы тратятся в основном на то, что нет ничего нового. Кроме того, поскольку генерал может ответить только такому количеству солдат одновременно, требуется много времени, чтобы сказать «ничего нового» каждому солдату хотя бы один раз, есть временная задержка, когда последние в очереди слышат «ничего нового» далеко. позже, чем первый солдат, который спросил.

Производительность WebSockets

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

Эффективность здесь с обеих сторон.

General больше не нужно обременять ненужной работой, поэтому вам нужно меньше серверов для обслуживания клиентов. Клиентам также не нужно тратить впустую сеть и ресурсы для опросов и отправки запросов. Намного эффективнее с обеих сторон.

В этой замечательной статье приводятся некоторые информативные тесты, касающиеся различий в производительности между REST / HTTP и WebSockets: REST и WebSocket Сравнение и тесты.

Использовать WebSockets поверх REST?

В PubNub мы довели WebSockets до предельного масштаба и надежности, имея возможность обслуживать миллионы устройств по всему миру и ежедневно отправлять миллиарды сообщений по сети.Существует ряд фреймворков WebSocket, и Socket.IO, вероятно, является наиболее популярным и широко известным.

Это отличный способ познакомиться с программированием в стиле Socket и узнать, как работает эта парадигма. Однако на самом деле его использование в производстве — это еще одна история. Вместо этого использование PubNub намного более рентабельно на многих различных уровнях.

При использовании Socket.IO, как и любого другого сервера, вы должны выделить время и ресурсы на его настройку, настройку, мониторинг, управление и масштабирование.Все это требует времени, людей и машин, а все это стоит денег. Если что-то пойдет не так, как следует, добавьте уроки на ошибках и аномалиях, что будет всегда. Каждый раз, когда вы усваиваете урок, ценность — это урок, но цена — простои для приложения, которое полагается, что все работает, и, вероятно, потеря клиентов. Когда вы все это сложите, это будет дорого, намного дороже, чем использование уже масштабируемого отказоустойчивого PubNub с 99,999% SLA!

.

Leave a comment