2013-03-06 3 views
1

Я работаю над приложением, которое отправляет и получает данные в другой экземпляр себя через сокеты, и мне любопытно, как наиболее эффективный способ инкапсулировать данные тегом «END». Например, вот две функции, которые используются для чтения и записи через сокет-соединение:Правильный способ инкапсуляции данных сокетов в python?

def sockWrite(conn, data): 
    data = data + ":::END" 
    conn.write(data) 

def sockRead(conn): 
    data = "" 
    recvdata = conn.read() 
    while recvdata: 
     data = data + recvdata 
     if data.endswith(':::END'): 
      data = data[:len(data)-6] 
      break 
     recvdata = conn.read() 
    if data == "": 
     print 'SOCKR: No data') 
    else: 
     print 'SOCKR: %s', data) 
    return data 

Я в основном лавируя в «::: END» на записи, потому что несколько операций чтения может произойти для этого одной записи. Таким образом, чтение читается до тех пор, пока оно не достигнет «::: END».

Это, конечно, вызывает проблему, если переменная данных содержит строку «::: END», которая появляется в конце одного из чтений.

Существует ли надлежащий способ инкапсуляции данных с минимальным добавлением полосы пропускания? Я думал о pickle или json, но беспокоюсь, что добавит значительную полосу пропускания, так как я считаю, что они преобразуют двоичные данные в ASCII. Правильно ли я это понимаю?

Спасибо, Бен

ответ

0

Нулевой: Вы действительно нужно оптимизировать это?

Обычно вы отправляете относительно небольшие сообщения. Бритье 60 байтов с 512-байтовым сообщением обычно бывает глупым, когда вы смотрите на то, сколько вы тратите на сетевые, IP и TCP-ресурсы, а RTT, который увеличивает пропускную способность.

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

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

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


Между тем, есть два проблемы с кодом.

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

def sockWrite(conn, data): 
    data = data.replace(':', r'\:') + ":::END" 
    conn.write(data) 

Теперь на стороне чтения, вы просто снять ограничитель, а затем replace('r\:', ':') на сообщении. (Конечно, расточительно избегать каждой толстой кишки только для использования 6-байтового разделителя ':::END' - вы можете использовать просто необработанный двоеточие в качестве разделителя или написать более сложный механизм экранирования).

Во-вторых, вы что «многократные чтения могут возникать для этой одиночной записи», но также верно, что для этого одного чтения может возникать несколько записей. Вы можете прочитать половину этого сообщения, плюс половину следующего.Это означает, что вы не можете просто использовать endswith; вы должны использовать что-то вроде partition или split и написать код, который может обрабатывать несколько сообщений, а также писать код, который может хранить частичные сообщения до следующего раза через цикл read.


Между тем, на конкретные вопросы:

Есть правильный способ инкапсуляции данных с, как минимум пропускного того, как это возможно?

Конечно, существует как минимум три правильных пути: разделители, префиксы или саморазграничивающие форматы.

Вы уже нашли первый. И проблема с ним: если в ваших данных нет какой-либо строки, которая никогда не может появиться (например, '\0' в тексте UTF-8, читаемом человеком), вы не можете выбрать разделитель, который не требует экранирования.

Формат саморазграничения, такой как JSON, является самым простым решением. Когда последняя открытая скобка/скобка закрывается, сообщение заканчивается, и пришло время для следующего.

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


Я думал о рассоле или JSON, но обеспокоен тем, что добавит значительное количество трафика, так как я считаю, что они будут преобразовывать бинарные данные в ASCII

pickle имеет как двоичные и текст форматы. Как объясняет the documentation, если вы используете протокол 2, 3, или HIGHEST_PROTOCOL, вы получите достаточно эффективный двоичный формат.

JSON, с другой стороны, обрабатывает только строки, цифры, массивы и словари. Вы должны вручную визуализировать любые двоичные данные в строку (или массив строк или чисел или что-то еще), прежде чем вы сможете JSON-кодировать ее, а затем перевернуть все с другой стороны. Два общих способа сделать это - base-64 и hex, которые добавляют 25% и 100% соответственно к размеру ваших данных, но есть более эффективные способы сделать это, если вам действительно нужно.

И, конечно, сам протокол JSON использует несколько символов, чем это абсолютно необходимо, что со всеми этими цитатами и запятыми и т. Д., И любые имена, которые вы даете любым полям, отправляются как несжатый UTF-8. Вы всегда можете заменить JSON на BSON, Protocol Buffers, XDR или другие форматы сериализации, которые менее «расточительны», если это действительно проблема.

Между тем, pickle не является самораспределяющимся. Вы должны сначала разделить сообщения друг от друга, прежде чем сможете их разблокировать. JSON - это саморазграничение, но вы не можете просто использовать json.loads, если только вы не разделите раздельные сообщения; вам придется писать что-то более сложное. Самое простое, что работает, - это повторно вызвать raw_decode в буфер до получения объекта.

Смежные вопросы