Recv: примеры (PYTHON)

Использование функции recv для работы с сетевыми соединениями в Python
Раздел: Сокеты, Сетевые операции
recv(bufsize, flags): bytes

Основные сведения о методе recv

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

Сигнатура метода: socket.recv(bufsize[, flags]).

Аргументы:

  • bufsize: целое число, определяющее максимальное количество байтов для приема за один вызов. Это обязательный аргумент.
  • flags (необязательный): целое число, управляющее поведением операции приема. Значение по умолчанию - 0. Флаги могут комбинироваться с помощью побитового ИЛИ (|). Распространенные флаги:
    • socket.MSG_PEEK: просмотр данных в буфере без их удаления.
    • socket.MSG_WAITALL: ожидание полного заполнения буфера до достижения заданного размера (может не поддерживаться на всех системах).
    • socket.MSG_DONTWAIT: неблокирующий режим операции (эквивалентно установке socket.setblocking(0)).

Возвращаемое значение: Метод возвращает объект bytes, содержащий полученные данные. Если соединение закрыто корректно, возвращается пустой bytes объект (b''). В случае ошибки возникает исключение (например, socket.timeout, ConnectionResetError).

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

Пример 1: Базовый прием данных

import socket

# Создание сокета и подключение
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
sock.send(b'GET / HTTP/1.0\r\n\r\n')

# Прием данных с буфером 4096 байт
data = sock.recv(4096)
print(f'Получено {len(data)} байт')
sock.close()
Получено 1460 байт

Пример 2: Использование флага MSG_PEEK

import socket

# Создание пары соединенных сокетов
sock1, sock2 = socket.socketpair()
sock1.send(b'Hello World')

# Просмотр данных без их извлечения из буфера
peek_data = sock2.recv(1024, socket.MSG_PEEK)
print('Просмотренные данные:', peek_data)

# Фактический прием данных (те же данные)
actual_data = sock2.recv(1024)
print('Фактические данные:', actual_data)

sock1.close()
sock2.close()
Просмотренные данные: b'Hello World'
Фактические данные: b'Hello World'

Пример 3: Использование флага MSG_WAITALL

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
sock.send(b'GET / HTTP/1.0\r\n\r\n')

# Ожидание получения ровно 100 байт (может зависнуть)
try:
    sock.settimeout(5.0)
    data = sock.recv(100, socket.MSG_WAITALL)
    print(f'Получено ровно {len(data)} байт')
except socket.timeout:
    print('Таймаут: не удалось получить достаточно данных')
finally:
    sock.close()
Получено ровно 100 байт

Похожие функции в Python

В модуле socket существуют другие методы для приема данных, которые отличаются поведением:

  • recv_into(buffer): Принимает данные непосредственно в предоставленный буфер (объект, поддерживающий buffer protocol). Эффективен для минимизации выделения памяти при многократных вызовах.
  • recvfrom(bufsize[, flags]): Используется с датаграммными сокетами (SOCK_DGRAM). Возвращает кортеж (данные, адрес_отправителя).
  • recvmsg() и recvmsg_into(): Позволяют получать вспомогательные данные (ансиллярные сообщения) вместе с основными, что полезно для расширенных протоколов.

Выбор метода зависит от типа сокета (потоковый или датаграммный) и требований к производительности. recv_into предпочтителен для высоконагруженных приложений.

Типичные ошибки и их решение

1. Бесконечная блокировка: Вызов recv без таймаута на сокете, где данные никогда не поступят.

import socket
sock = socket.socket()
sock.bind(('127.0.0.1', 0))
sock.listen(1)
conn, addr = sock.accept()  # Ждет подключения
# Если клиент не отправит данные, recv заблокируется навсегда
# data = conn.recv(1024)  # Риск бесконечной блокировки

Решение: Установить таймаут.

conn.settimeout(10.0)
try:
    data = conn.recv(1024)
except socket.timeout:
    print('Таймаут приема данных')

2. Неверная интерпретация пустого результата: Пустой bytes объект (b'') означает закрытие соединения, а не просто отсутствие данных.

data = sock.recv(1024)
if not data:  # Верно
    print('Соединение закрыто')
# if data == b'':  # Аналогичная проверка

3. Предположение о полном приеме сообщения: recv может вернуть меньше данных, чем указано в bufsize.

# ОШИБОЧНЫЙ ПОДХОД
sock.send(b'HelloWorld')  # Отправлено 10 байт
# На принимающей стороне:
data = sock.recv(10)  # Может вернуть только часть, например, 5 байт
print(data)  # Возможно, b'Hello'

Решение: Прием в цикле до получения нужного количества байт.

def recv_all(sock, n):
    data = b''
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            raise ConnectionError("Соединение прервано")
        data += packet
    return data

Изменения в последних версиях Python

Основное поведение метода recv остается стабильным на протяжении многих версий Python 3. Однако, есть связанные изменения:

  • В Python 3.5 улучшена поддержка неблокирующих сокетов и введены более четкие исключения (например, BlockingIOError).
  • В Python 3.7 добавлена возможность использования socket.MSG_DONTWAIT как альтернатива вызову setblocking(False).
  • В Python 3.10 и выше продолжаются оптимизации внутренней реализации сетевых операций, но API метода recv не меняется.

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

Расширенные примеры использования

Пример 1: Прием данных переменной длины с заголовком

Пример python
import socket
import struct

def receive_message(sock):
    # Сначала принимаем 4 байта, указывающие длину сообщения
    raw_len = recv_all(sock, 4)
    if not raw_len:
        return None
    msg_len = struct.unpack('>I', raw_len)[0]
    # Принимаем само сообщение
    return recv_all(sock, msg_len)

def recv_all(sock, n):
    data = b''
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data += packet
    return data

# Пример использования на сервере
server_sock = socket.socket()
server_sock.bind(('localhost', 12345))
server_sock.listen()
conn, addr = server_sock.accept()
message = receive_message(conn)
print(f'Получено сообщение длиной {len(message)}')
conn.close()
Получено сообщение длиной 128

Пример 2: Неблокирующий прием с селектором

Пример python
import socket
import selectors
import types

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    try:
        data = conn.recv(1024, socket.MSG_DONTWAIT)
        if data:
            print(f'Получены данные: {data}')
        else:
            sel.unregister(conn)
            conn.close()
    except BlockingIOError:
        pass  # Данных пока нет

server = socket.socket()
server.bind(('localhost', 12346))
server.listen()
server.setblocking(False)
sel.register(server, selectors.EVENT_READ, accept)

# Основной цикл событий
while True:
    events = sel.select(timeout=1)
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

Пример 3: Использование recv_into для снижения нагрузки на сборщик мусора

Пример python
import socket
import time

# Создаем буфер один раз и переиспользуем его
buffer = bytearray(4096)
mv = memoryview(buffer)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
sock.send(b'GET / HTTP/1.0\r\n\r\n')

total_received = 0
while True:
    # Принимаем данные непосредственно в memoryview
    nbytes = sock.recv_into(mv[total_received:])
    if nbytes == 0:
        break
    total_received += nbytes
    if total_received >= 4096:
        print('Буфер заполнен')
        break

print(f'Всего получено {total_received} байт')
# Данные доступны в buffer[:total_received]
sock.close()
Всего получено 1460 байт

Аналоги в других языках программирования

JavaScript (Node.js): Метод socket.read() или событие 'data' на потоке.

// Node.js пример
const net = require('net');
const client = net.createConnection({ port: 8080 }, () => {
  console.log('Подключено');
});
client.on('data', (data) => {
  console.log(`Получено: ${data}`);
});

Java: Методы read() класса InputStream, связанного с сокетом.

// Java пример
Socket socket = new Socket("example.com", 80);
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer);
System.out.println("Получено " + bytesRead + " байт");
socket.close();

Go: Метод Read() для соединения net.Conn.

// Go пример
conn, err := net.Dial("tcp", "example.com:80")
if err != nil { log.Fatal(err) }
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
fmt.Printf("Получено %d байт\n", n)
conn.Close()

C#: Метод Socket.Receive().

// C# пример
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("example.com", 80);
byte[] buffer = new byte[1024];
int received = socket.Receive(buffer);
Console.WriteLine($"Получено {received} байт");
socket.Close();

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

питон recv function comments

En
Recv Receive data from socket