PyCraft
Июнь 15, 2008 21:20:55
В pickler.py есть неприятная особенность. Если ему скормить что-то, что он не понимает(испорченный объект), то он валит сервер с ошибкой “insecure string pickle” в модуле pickler.py стандартной библиотеки. Поскольку с 2000-2001 года эту особенность не пофиксили, значит это считается нормальным и нужно заточить пользовательский скрипт под неё.
Суть проблемы
Чат-Клиент посылает на сервер сообщение
writesock.send(pickle.dumps(message))
Чат-Сервер выполняет select,
затем для каждого готового сокета выполняет data = sockobj.recv(1024)
затем, если данные есть
try:
data = pickle.loads(data)
except EOFError:
…
continue
Если размер сообщения(точнее сериализованного объекта) больше 1024
то сервер сваливается внутри функции loads не выходя в except
Клиенты при этом не сваливаются.
Большие сообщения я просто набираю в строке ввода.
Если сообщения маленькие, то чат работает нормально
Что тут происходит и как решить проблему?
Я попытался в цикле догрузить остальные пакеты, но ничего не вышло
data = sockobj.recv(1024)
while 1:
d = sockobj.recv(1024)
if not d:
break
data.append(d)
j2a
Июнь 16, 2008 05:05:46
PyCraft
В pickler.py есть неприятная особенность. Если ему скормить что-то, что он не понимает(испорченный объект), то он валит сервер с ошибкой “insecure string pickle” в модуле pickler.py стандартной библиотеки. Поскольку с 2000-2001 года эту особенность не пофиксили, значит это считается нормальным и нужно заточить пользовательский скрипт под неё.
http://docs.python.org/lib/node314.html:Warning: The pickle module is not intended to be secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.
Данные по сети - как раз “untrusted source”. Вариант с pickle более-менее приемлим, если ты можешь проверить правильность запикленных данных
до операции pickle.loads.
Так что используй для общения между сервером и клиентом стандартные протоколы. XML RPC, JSON RPC в первом приближении вполне подходят как “умные” протоколы, понимающие различные типы данных.
bw
Июнь 16, 2008 14:15:59
> data.append(d)
data += d ?
message = pickle.dumps(message)
message_length = '%08d'%len(message)
writesock.send(message_length)
writesock.send(message)
На сервере, соответственно, ты должен сначала получить длину сообщения, а за тем добрать пакетов до этой длины (не больше, а может прийти и больше), ни меньше. Я длину запихал в строку с фиксированной длиной в 8 символов, ты можешь передать int (4 байта) или long (8 байт), на выбор. Еще для верности можешь передать магическое слово и контрольную сумму.
p.s. Или используй существующие протоколы, например подойдет HTTP.
..bw
PyCraft
Июнь 16, 2008 14:24:38
Спасибо за разъяснение.
Но мне кажется, в таких случаях, когда untrusted or unauthenticated данные, правильнее генерировать исключение и выходить в exception, а не валить приложение системной ошибкой… Эта ошибка будет в любом случае и для локального вызова и для локальной сети и для интернета. В моем случае это локальный вызов(localhost). Достаточно послать любое неправильное сообщение и сервер упадет. И получается, никак кроме собственной наколенной логики от этого не защититься, даже try … exception не поможет.
XML/JSON это я тоже планирую использовать только по HTTP для Web-интерфейса. Но у меня еще и TCP-клиент специально затачиваемый под скорость и хотелось бы вариант для TCP, без парсинга XML и без JavaScript.
И еще я не совсем понимаю как передавать/принимать через сокеты большие массивы. Придется наверное свой протокол начала-накопления-завершения изобретать? Как раз “накопление” у меня и не получилось.
PyCraft
Июнь 16, 2008 14:30:36
bw
> data.append(d)
data += d ?
message = pickle.dumps(message)
message_length = '%08d'%len(message)
writesock.send(message_length)
writesock.send(message)
На сервере, соответственно, ты должен сначала получить длину сообщения, а за тем добрать пакетов до этой длины (не больше, а может прийти и больше), ни меньше. Я длину запихал в строку с фиксированной длиной в 8 символов, ты можешь передать int (4 байта) или long (8 байт), на выбор. Еще для верности можешь передать магическое слово и контрольную сумму.
p.s. Или используй существующие протоколы, например подойдет HTTP.
..bw
Определяем длину и подкачиваем.
А если в очередном пакете на тот же сокет придет больше данных? Чьи это данные? Это начало другого сообщения того же клиента?
bw
Июнь 16, 2008 16:49:20
> А если в очередном пакете на тот же сокет придет больше данных?
Они точно не будут относиться к данному сообщению, возможно это фрагмент следующего сообщения. Отложи эти данные в буфер и начни их обрабатывать в следующем цикле.
> Чьи это данные? Это начало другого сообщения того же клиента?
Ну, на сокет приходят данные только по одному соединению (клиенту).
Если ты знаешь длину сообщения, а узнать её достоверно ты можешь только в том случае, когда она явно указанна в запросе/данных. Дальше последовательно из пакетов читаешь ровно столько байт, сколько тебе нужно. В одном пакете хоть 10 сообщений может быть, это не нарушает работу алгоритма. Или даже данные указывающие на размер сообщения могут быть разбиты на несколько пакетов, не вижу тут проблемы. Заведи буфер и поплняй его до тех пор, пока сообщение не будет полностью получено, все “лишние данные” в этом буфере перенеси в начало и урежь длину буфера по их размеру.
..bw
PyCraft
Июнь 16, 2008 18:50:56
Аминь. Так и сделаю.
Вот только теперь я перестал понимать как мой клиент-сервер до этого мог работать???
Ведь в текущем варианте данные из всех сокетов читаются в одну переменную поочередно data = sockobj.recv(1024),
сокеты не закрываются, клиенты передают сообщения непрерывно, размер сообщений маленький не кратный 1024.
Таким образом на каждом сокете в каждом пакете должно быть много сообщений, со сдвинутой рамкой считывания.
За один вызов pikler.load берет из массива data только первый объект(сообщение), находящийся вначале data, остальные, по идее, должны теряться. Только если функция sockobj.recv(1024) берет не 1024 байта а меньше и сама выполняет умный сдвиг или умное чтение (контролирует сколько байт было отдано), тогда при каждом новом запросе sockobj.recv(1024) пакет должен начинаться с начала нового сообщения.
Именно так внешне и выглядит работа программы в настоящее время.
Cледствие: Тогда за 2 вызова recv(1024) будет принято меньшее количество байт, чем за 1 вызов recv(2048) !?
В чем тут секрет или заблуждение?
bw
Июнь 17, 2008 09:22:31
То что получалось - случайность.
Если отправляешь 1024 байт, то ты и получишь 1024, но получить ты их можешь разбитыми на множество пакетов, каждый из которых может приходить с некоторой и возможно значительной задержкой. Я не очень в этом разбираюсь. Какого размера будут созданы TCP паеты я понятия не имению, но совершенно не обязательно 1024 (сколько ты шлешь за раз). На медлненых интерфейсах и сетых плохого качества, пакеты будут мельче.
recv(1024) возвращает 1024 или меньше байт. На сколько я помню устанавливается некоторый timeout на прием и если в этот промежуток времени не было получено 1024 байт, то возвращается лишь столько сколько все удалось получить.
p.s. У меня тут свои дела, завтра уезжаю из города на неделю, да и руку сегодня загибсовал, так что продолжительное время не смогу отвечать в этой теме и форуме вообще. Приеду, постараюсь еще помочь.
..bw