Skip to content

Binary messages

Gerasimos (Makis) Maropoulos edited this page Oct 6, 2026 · 1 revision

When to use this page

  • You're sending raw bytes: a protobuf payload, an image chunk, anything that is not meant to be read as UTF-8 text.
  • A client library (or a native websocket tool) cares whether a frame arrived as text or binary.
  • You're relaying a message you received and the receiver needs to see the same frame type it was sent as.

Text and binary frames

A websocket frame is either text or binary at the protocol level; neffos's Message.SetBinary field says which one a given Message is. SetBinary is not part of the serialized message body (Namespace, Room, Event, Body, and so on travel as one text blob either way); it only decides, at write time, whether the frame itself goes out as text or binary, and, on read, it is filled in from the frame the socket actually received:

type Message struct {
	// ...
	SetBinary bool
}

neffos.MessageType names the two kinds, for code that logs or branches on which one arrived:

const (
	TextMessage MessageType = iota + 1
	BinaryMessage
)

func (t MessageType) String() string // "text", "binary", or "unknown"

Sending binary

The short way, from inside a handler with an NSConn:

c.EmitBinary("wave", []byte{0x00, 0x7f, 0xff, 0x7f})

NSConn.EmitBinary(event, body) is Emit, except it sets Message.SetBinary = true first. There is no Room.EmitBinary in Go: *neffos.Room only has Emit and Send, and neither takes a binary flag (neffos.js's Room.emitBinary does exist; see "From JavaScript" below). To put a room member's binary frame back out to the rest of that room, relay the Message you already received instead of calling Room.Emit; see "Relaying" below.

The general way, for any of Conn, NSConn or Room, is to build the Message yourself and set SetBinary on it:

c.Conn.Write(neffos.Message{
	Namespace: "chat",
	Event:     "wave",
	Body:      []byte{0x00, 0x7f, 0xff, 0x7f},
	SetBinary: true,
})

Conn.Write (bool) and Conn.Send (error) take the same Message and differ only in how they report failure; see Broadcast for the difference. Server.Broadcast and NSConn.Broadcast/BroadcastOthers take Message values too, so a server pushing a binary frame to many connections sets SetBinary on the one Message it broadcasts.

Receiving binary, and relaying it

On the receiving side, msg.SetBinary tells you how the frame arrived; it is set from the actual frame type, not guessed from the content:

neffos.Events{
	"wave": func(c *neffos.NSConn, msg neffos.Message) error {
		frame := neffos.TextMessage
		if msg.SetBinary {
			frame = neffos.BinaryMessage
		}
		fmt.Printf("wave in a %s frame: % x\n", frame, msg.Body)
		return nil
	},
}

Relays must set SetBinary again. Because it is not part of the wire bytes, it does not "come along for free" the way Namespace or Event would if you copied the body into a brand new Message. The safe pattern is to relay the Message value you already deserialized, SetBinary and all, rather than building a fresh one:

neffos.Events{
	"wave": func(c *neffos.NSConn, msg neffos.Message) error {
		c.BroadcastOthers(msg) // msg.SetBinary is kept, so it goes out binary too
		return nil
	},
}

If you instead construct a new neffos.Message{Event: "wave", Body: msg.Body} to forward it, the copy defaults to SetBinary: false and goes out as text even though it arrived as binary.

Size limits

An oversized incoming message, text or binary, closes the connection once WithTimeout.MaxMessageSize (or Struct.SetMaxMessageSize) is set above zero. The two sides see different things. The receiver, the side enforcing the limit, gets neffos.ErrMessageTooBig from its own Conn.Err() (errors.Is(err, neffos.ErrMessageTooBig) is true; this is not a CloseError, so CloseStatus on it is -1). The sender of the oversized message receives a real close frame with code 1009 (neffos.CloseMessageTooBig) before the connection drops, so its Conn.Err() is a CloseError and CloseStatus on it is 1009. See Timeouts for the setting and Close codes and shutdown for the close code.

From JavaScript

nsConn.emitBinary("wave", new Uint8Array([0x00, 0x7f, 0xff, 0x7f]));

Unlike the Go Room, neffos.js's Room.emitBinary(event, body) exists alongside NSConn.emitBinary, both taking a WSData (string | Uint8Array). On receipt, a binary frame's msg.Body is a Uint8Array, not the string a text frame gives you; msg.bytes() returns it as Uint8Array either way (encoding a string body as UTF-8 if needed), and msg.text() does the opposite, decoding a Uint8Array body as UTF-8. Use whichever matches what you are expecting, instead of branching on typeof msg.Body yourself.

Protobuf payloads

Protocol Buffers messages are just another binary body: marshal with proto.Marshal, send with EmitBinary, unmarshal with proto.Unmarshal on the other side. See Protobufs for the full example.

Example

_examples/01-getting-started/05-encoding: JSON bodies for Chat/Private, and a /wave command that sends four raw bytes with EmitBinary, relayed to the rest of the namespace with BroadcastOthers so the frame stays binary.

See also

  • Encoding, JSON bodies with SendObject, msg.As[T]() and Marshal
  • Protobufs, Protocol Buffers over a binary frame
  • Native messages, raw frames with no neffos envelope at all
  • Timeouts, MaxMessageSize and the heartbeat

Clone this wiki locally