проблема с сокетом в питоне - PullRequest
4 голосов
/ 15 марта 2009

У меня есть сервер, который написан на C, и я хочу написать клиент на Python. Клиент Python отправит строку «send some_file», когда он хочет отправить файл, за которым следует его содержимое и строка «end some_file» Вот мой код клиента:


file = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
send_str = "send %s" % file
end_str = "end %s" % file
sock.send(send_str)
sock.send("\n")
sock.send(open(file).read())
sock.send("\n")
sock.send(end_str)
sock.send("\n")

Проблема заключается в следующем:

  • сервер получает строку "send some_file" из recv

  • при втором recv содержимое файла и строки «end file» отправляются вместе

В коде сервера размер буфера равен 4096. Я впервые заметил эту ошибку при попытке отправить файл размером менее 4096 КБ. Как я могу убедиться, что сервер получает строки независимо?

Ответы [ 4 ]

9 голосов
/ 15 марта 2009

При программировании сокетов, даже если вы делаете 2 независимых отправки, это не означает, что другая сторона получит их как 2 независимых recvs.

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

Вот что вы должны делать для каждого сообщения, будь то файл или строка:

Сторона отправителя:

  • Отправка 4 байтов, содержащих количество байтов при следующей отправке
  • Отправка актуальных данных

Сторона получателя:

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

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

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

1 голос
/ 15 марта 2009

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

Первый - использовать отступы. Допустим, вы отправляете файл. Что вам нужно сделать, это прочитать файл, закодировать его в более простой формат, такой как Base64, а затем отправить достаточно пробелов, чтобы заполнить оставшуюся часть 4096-байтового «чанка». Что бы вы сделали, это что-то вроде этого:

from cStringIO import StringIO
import base64
import socket
import sys

CHUNK_SIZE = 4096 # bytes

# Extract the socket data from the file arguments
filename = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
# Make the socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
# Prepare the message to send
send_str = "send %s" % (filename,)
end_str = "end %s" % (filename,)
data = open(filename).read()
encoded_data = base64.b64encode(data)
encoded_fp = StringIO(encoded_data)
sock.send(send_str + '\n')
chunk = encoded_fp.read(CHUNK_SIZE)
while chunk:
    sock.send(chunk)
    if len(chunk) < CHUNK_SIZE:
        sock.send(' ' * (CHUNK_SIZE - len(chunk)))
    chunk = encoded_fp.read(CHUNK_SIZE)
sock.send('\n' + end_str + '\n')

Этот пример кажется немного более сложным, но он гарантирует, что сервер может продолжать чтение данных в 4096-байтовых блоках, и все, что ему нужно, это Base64-декодировать данные на другом конце (библиотека C, для которой доступно здесь . Декодер Base64 игнорирует лишние пробелы, и формат может обрабатывать как двоичные, так и текстовые файлы (что произойдет, например, если файл содержит строку «end filename»? сервер).

Другой подход заключается в добавлении префикса отправки файла к его длине. Так, например, вместо отправки send filename вы можете сказать send 4192 filename, чтобы указать, что длина файла составляет 4192 байта. Клиент должен был бы построить send_str на основе длины файла (как считывается в переменной data в приведенном выше коде), и ему не нужно было бы использовать кодировку Base64, поскольку сервер не будет пытаться интерпретировать какие-либо end filename Синтаксис появляется в теле отправленного файла. Это то, что происходит в HTTP; HTTP-заголовок Content-length используется для указания длины отправляемых данных. Пример клиента может выглядеть так:

import socket
import sys

# Extract the socket data from the file arguments
filename = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
# Make the socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
# Prepare the message to send
data = open(filename).read()
send_str = "send %d %s" % (len(data), filename)
end_str = "end %s" % (filename,)
sock.send(send_str + '\n')
sock.send(data)
sock.send('\n' + end_str + '\n')

В любом случае вам придется вносить изменения как на сервере, так и на клиенте. В конце концов, возможно, будет проще реализовать элементарный HTTP-сервер (или получить уже внедренный) в C, поскольку, похоже, именно это вы здесь и делаете. Решение для кодирования / заполнения является быстрым, но создает много избыточно отправленных данных (поскольку Base64 обычно вызывает увеличение количества отправляемых данных на 33%), решение с префиксом длины также легко со стороны клиента, но может быть более сложным сервер.

0 голосов
/ 15 марта 2009

Возможно использование

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

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

0 голосов
/ 15 марта 2009

Данные TCP / IP буферизуются, более или менее случайным образом.

Это просто «поток» байтов. Если хотите, вы можете прочитать его так, как если бы он был разделен символами '\ n'. Тем не менее, он не разбит на значимые куски; и не может быть. Это должен быть непрерывный поток байтов.

Как вы читаете это в C? Вы читаете до '\ n'? Или вы просто все читаете в буфере?

Если вы читаете все в буфере, вы должны видеть строки, буферизованные более или менее случайным образом.

Однако, если вы прочитаете до '\ n', вы увидите каждую строку по одной за раз.

Если вы хотите, чтобы это действительно работало, вы должны прочитать http://www.w3.org/Protocols/rfc959/. Это показывает, как просто и надежно передавать файлы: используйте два сокета. Одна для команд, другая для данных.

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