Как читать данные переменной длины из асинхронного TCP-сокета?
Я использую CocoaAsyncSocket для проекта iOS. Я пытаюсь читать VarInts через асинхронный интерфейс. Проблема в отличие от чего-то еще, например String, где я могу поставить префикс длины, я не знаю длины varint заранее. Он должен обрабатываться по одному байту за раз, но поскольку каждая операция чтения является асинхронной, другие вызовы чтения могли быть поставлены в очередь между ними.
Я подумал о том, чтобы прочитать в буфер, затем обработать его, скажем, прочитать 5 байтов (максимальная длина для varint-32) и отодвинуть дополнительные байты назад, но это может излишне зависать, если varint составляет всего 4 байта, и я жду 5-й байт будет доступен.
Как я могу это сделать? Кроме того, я не могу изменить протокол на другом конце, чтобы использовать фиксированные размеры.
Вот фрагмент кода, который попросил Джош
- (void)readByte:(void (^)(int8_t))onComplete {
NSUInteger size = 1;
int32_t tag = OSAtomicAdd32(1, &_nextTag);
dispatch_async(self.dispatchQueue, ^{
[self.onCompleteHandlers setObject:(^void (NSData* data) {
int8_t x = 0;
[data getBytes:&x length:size];
onComplete(x);
}) forKey:[NSNumber numberWithInteger:((NSInteger) tag)]];
[self.socket readDataToLength:size withTimeout:-1 tag:tag];
});
}
Обратный вызов сохраняется в словаре, который используется в методе делегата socket: didReadData: withTag
,
Предположим, я читаю байт VarInt:
- выполнить чтение первого байта для varint
- не знаю, нужно ли нам читать другой байт для varint или нет; это зависит от результата первого чтения
- (возможно) прочитать другой байт для чего-то другого
- прочитайте второй байт для varint, но теперь это фактически третий читаемый байт
Я могу представить себе использование флага для указания того, нахожусь ли я в режиме многочастичного чтения, и очереди для чтения, которая должна быть выполнена после многократного чтения, и я начал писать ее, но это довольно грязно. Просто интересно, есть ли стандартный / рекомендуемый / лучший способ решения этой проблемы.
1 ответ
Короче есть 4 способа узнать сколько читать из сокета...
- прочитайте некоторый формат, который вы можете определить длину, как
Content-Length
header... работает только в том случае, если весь запрос может быть составлен перед отправкой тела. - читать до некоторой картины: как
\r\n\r\n
в конце заголовков - читать до некоторого времени ожидания... после того, как вы не получите байтов через n секунд, вы очищаете буферы и закрываете соединение.
- читать до тех пор, пока сервер не закроет соединение... на самом деле довольно часто.
у каждого из них есть проблемы, и я бы, вероятно, отказался от использования какого-либо существующего протокола.
Конечно, есть такие издержки, если вы делаете это таким образом, и вы можете обнаружить, что не хотите использовать какие-либо вещи такого уровня приложений, и ваши запросы могут выглядеть так:
client>"doMath(2+5)\0"
server>"(7)\0"
но трудно ответить на ваш общий вопрос конкретно.
редактировать:
Так что я немного подробнее рассмотрел проблему с varint base-128 и думаю, что на самом деле будет работать только тайм-аут или сервер, закрывающий соединение, если вы пишете их прямо на уровне TCP, что ужасно...