Что может пойти не так при обработке первых пяти байт сообщения для определения типа в Go
Что проверяют
Умеете ли вы работать с потоком байтов, а не с уже собранным сообщением. Вопрос почти всегда про сеть, и главный ответ в том, что Read не обязан вернуть столько байт, сколько вы попросили.
Как отвечать
Начните с чтения. Контракт io.Reader разрешает вернуть меньше данных без ошибки, поэтому одиночный conn.Read(buf) даёт любое число от одного до длины буфера. Правильно так:
var hdr [5]byte
if _, err := io.ReadFull(r, hdr[:]); err != nil {
return err // io.ErrUnexpectedEOF, если пришло меньше пяти байт
}
kind := hdr[0]
size := binary.BigEndian.Uint32(hdr[1:])Дальше перечислите остальные грабли:
- срез фиксированной длины без проверки
lenпаникует, а паника в горутине валит весь процесс; - порядок байт: сетевые протоколы обычно старшим байтом вперёд, чтение теми же байтами наоборот даёт бессмысленную длину и не ловится тестами на одной машине;
- длина из заголовка это внешние данные. Вызов
makeпо ней позволяет попросить память гигабайтами, нужен предел иio.LimitReader; - неизвестный тип сообщения мало вернуть ошибкой, надо решить, что делать с потоком: если тело не вычитано, следующее чтение начнётся с середины;
- буфер переиспользуется. Слайс от
bufio.Reader.Peekили общий буфер живут до следующего чтения, наружу отдавайте копию; - без
SetReadDeadlineсоединение зависает навсегда.
Где спотыкаются
Считают, что один Read вернёт ровно пять байт. В TCP границ сообщений нет, при фрагментации придёт два байта и nil в ошибке, тип определится по мусору, а воспроизведётся это только в бою на плохой сети.
Проверяют ошибку раньше числа прочитанных байт. Read может вернуть данные и io.EOF одновременно, и ранний выход по ошибке теряет последнее сообщение. Вызов io.ReadFull эту развилку убирает совсем.
Отдают наружу слайс на общий буфер. Заголовок разобран верно, но через несколько миллисекунд те же байты перезаписаны следующим сообщением, и в логах появляется перепутанный тип. Под нагрузкой это выглядит как случайная порча данных, а по коду читается как совершенно нормальная функция.