Documentation
¶
Overview ¶
Package netcode implements the netcode 1.02 protocol: a simple protocol for creating secure client/server connections over UDP.
This is a faithful port of the C reference implementation at https://github.com/mas-bandwidth/netcode and is wire compatible with it. See STANDARD.md for the protocol specification.
Copyright © 2017 - 2026, Más Bandwidth LLC ¶
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Index ¶
- Constants
- Variables
- func ClientStateName(clientState int) string
- func EnablePacketTagging()
- func GenerateConnectToken(publicServerAddresses []string, internalServerAddresses []string, ...) ([]byte, error)
- func GenerateKey() []byte
- func RandomBytes(data []byte)
- func SetLogLevel(level int)
- func SetPrintfFunction(function func(format string, args ...any))
- type Address
- type Client
- func (client *Client) Close()
- func (client *Client) Connect(connectTokenData []byte)
- func (client *Client) ConnectLoopback(clientIndex int, maxClients int)
- func (client *Client) Disconnect()
- func (client *Client) DisconnectLoopback()
- func (client *Client) Index() int
- func (client *Client) Loopback() bool
- func (client *Client) MaxClients() int
- func (client *Client) NextPacketSequence() uint64
- func (client *Client) Port() uint16
- func (client *Client) ProcessLoopbackPacket(packetData []byte, packetSequence uint64)
- func (client *Client) ProcessPacket(from *Address, packetData []byte)
- func (client *Client) ReceivePacket() ([]byte, uint64)
- func (client *Client) SendPacket(packetData []byte)
- func (client *Client) ServerAddress() Address
- func (client *Client) State() int
- func (client *Client) Update(time float64)
- type ClientConfig
- type NetworkSimulator
- type Server
- func (server *Server) ClientAddress(clientIndex int) Address
- func (server *Server) ClientConnected(clientIndex int) bool
- func (server *Server) ClientDisconnectReason(clientIndex int) int
- func (server *Server) ClientID(clientIndex int) uint64
- func (server *Server) ClientLoopback(clientIndex int) bool
- func (server *Server) ClientUserData(clientIndex int) []byte
- func (server *Server) Close()
- func (server *Server) ConnectLoopbackClient(clientIndex int, clientID uint64, userData []byte)
- func (server *Server) DisconnectAllClients()
- func (server *Server) DisconnectClient(clientIndex int)
- func (server *Server) DisconnectLoopbackClient(clientIndex int)
- func (server *Server) MaxClients() int
- func (server *Server) NextPacketSequence(clientIndex int) uint64
- func (server *Server) NumConnectedClients() int
- func (server *Server) Port() uint16
- func (server *Server) ProcessLoopbackPacket(clientIndex int, packetData []byte, packetSequence uint64)
- func (server *Server) ProcessPacket(from *Address, packetData []byte)
- func (server *Server) ReceivePacket(clientIndex int) ([]byte, uint64)
- func (server *Server) Running() bool
- func (server *Server) SendPacket(clientIndex int, packetData []byte)
- func (server *Server) Start(maxClients int)
- func (server *Server) Stop()
- func (server *Server) Update(time float64)
- type ServerConfig
Examples ¶
Constants ¶
const ( AddressNone = 0 AddressIPv4 = 1 AddressIPv6 = 2 )
Address types.
const ( VersionFull = "1.3.5" VersionMajor = 1 VersionMinor = 3 VersionPatch = 5 )
const ( ConnectTokenBytes = 2048 KeyBytes = 32 MacBytes = 16 UserDataBytes = 256 MaxServersPerConnect = 32 MaxClients = 256 MaxPacketSize = 1200 )
const ( ClientStateConnectTokenExpired = -6 ClientStateInvalidConnectToken = -5 ClientStateConnectionTimedOut = -4 ClientStateConnectionResponseTimedOut = -3 ClientStateConnectionRequestTimedOut = -2 ClientStateConnectionDenied = -1 ClientStateDisconnected = 0 ClientStateSendingConnectionRequest = 1 ClientStateSendingConnectionResponse = 2 ClientStateConnected = 3 )
Client states. The initial state is ClientStateDisconnected. Negative states are error states. The goal state is ClientStateConnected.
const ( DisconnectReasonNone = 0 DisconnectReasonTimedOut = 1 DisconnectReasonClientDisconnect = 2 DisconnectReasonServerDisconnect = 3 )
The reason the client in a server slot was last disconnected. Tracked per-client slot: reset to None when the server starts and when a new client connects to the slot, and recorded before the connect/disconnect callback fires, so it can be queried from inside that callback via Server.ClientDisconnectReason.
const ( LogLevelNone = 0 LogLevelError = 1 LogLevelInfo = 2 LogLevelDebug = 3 )
Log levels.
Variables ¶
var ( ErrClientParseAddressFailed = errors.New("netcode: failed to parse client address") ErrClientParseAddress2Failed = errors.New("netcode: failed to parse client address2") ErrClientSimulatorRequiresPort = errors.New("netcode: must bind to a specific port when using network simulator") ErrClientCreateSocketIPv4Failed = errors.New("netcode: failed to create client ipv4 socket") ErrClientCreateSocketIPv6Failed = errors.New("netcode: failed to create client ipv6 socket") )
Client create errors, returned from NewClient and NewClientDual.
var ( ErrServerParseAddressFailed = errors.New("netcode: failed to parse server public address") ErrServerParseAddress2Failed = errors.New("netcode: failed to parse server public address2") ErrServerCreateSocketIPv4Failed = errors.New("netcode: failed to create server ipv4 socket") ErrServerCreateSocketIPv6Failed = errors.New("netcode: failed to create server ipv6 socket") ErrServerBindSocketIPv4Failed = errors.New("netcode: failed to bind server ipv4 socket") ErrServerBindSocketIPv6Failed = errors.New("netcode: failed to bind server ipv6 socket") )
Server create errors, returned from NewServer and NewServerDual. Bind failures are reported separately from other socket errors because a port already in use is the common operational failure for dedicated servers.
Functions ¶
func ClientStateName ¶
ClientStateName returns a human readable name for a client state.
func EnablePacketTagging ¶
func EnablePacketTagging()
EnablePacketTagging tags packets sent from sockets created after this call as low latency (DSCP EF) which can significantly reduce jitter on Wi-Fi routers. It is off by default because it doesn't play well with some older home routers.
func GenerateConnectToken ¶
func GenerateConnectToken(publicServerAddresses []string, internalServerAddresses []string, expireSeconds int, timeoutSeconds int, clientID uint64, protocolID uint64, privateKey []byte, userData []byte) ([]byte, error)
GenerateConnectToken generates a connect token for a client, exactly as the web backend would. Each entry of publicServerAddresses is the address the client connects to, with the corresponding entry of internalServerAddresses being the address the server sees itself as (often the same). Pass a negative expireSeconds or timeoutSeconds to disable expiry or timeout respectively (dev only). userData is up to 256 bytes of user defined data, and may be nil.
The returned connect token is ConnectTokenBytes (2048 bytes) long.
Example ¶
The web backend generates a connect token for an authenticated client and returns it over HTTPS. Only the backend and the dedicated servers know the private key.
package main
import (
"crypto/rand"
"encoding/binary"
"fmt"
"log"
netcode "github.com/mas-bandwidth/netcode.go"
)
func main() {
var privateKey [netcode.KeyBytes]byte
if _, err := rand.Read(privateKey[:]); err != nil {
log.Fatal(err)
}
var clientIDBytes [8]byte
if _, err := rand.Read(clientIDBytes[:]); err != nil {
log.Fatal(err)
}
clientID := binary.LittleEndian.Uint64(clientIDBytes[:])
serverAddresses := []string{"127.0.0.1:40000"}
const (
expireSeconds = 30 // how long the token stays valid
timeoutSeconds = 5 // how long before an idle connection times out
protocolID = 0x1122334455667788
)
connectToken, err := netcode.GenerateConnectToken(
serverAddresses, // public addresses the client connects to
serverAddresses, // internal addresses the servers see themselves as
expireSeconds, timeoutSeconds, clientID, protocolID, privateKey[:],
nil, // optional user data, up to netcode.UserDataBytes
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("connect token is %d bytes\n", len(connectToken))
}
Output: connect token is 2048 bytes
func RandomBytes ¶
func RandomBytes(data []byte)
RandomBytes fills data with cryptographically secure random bytes.
func SetLogLevel ¶
func SetLogLevel(level int)
SetLogLevel sets the log level. The default is LogLevelNone.
func SetPrintfFunction ¶
SetPrintfFunction overrides where log output goes. The default prints to stdout.
Types ¶
type Address ¶
Address is a network address: an IPv4 or IPv6 address plus a port.
IPv4 addresses are stored as four bytes a.b.c.d in IPv4[0..3]. IPv6 addresses are stored as eight 16 bit groups [a:b:c:d:e:f:g:h] in IPv6[0..7], in the order they appear in the address string.
func ParseAddress ¶
ParseAddress parses an address string in one of the following forms:
"a.b.c.d" IPv4 address "a.b.c.d:port" IPv4 address and port "a:b:...:h" IPv6 address "[a:b:...:h]" IPv6 address in brackets "[a:b:...:h]:port" IPv6 address and port
If a port is omitted, it is assumed to be zero.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the client side of a netcode connection.
A client is not safe for concurrent use: call all methods from the same goroutine, and drive it by calling Update regularly (for example, 60 times per second).
func NewClient ¶
func NewClient(address string, config *ClientConfig, time float64) (*Client, error)
NewClient creates a client bound to a single address. Bind to port zero to get an ephemeral port, e.g. "0.0.0.0:0" or "[::]:0".
Example ¶
A client: obtain a connect token from your web backend over HTTPS, then connect and update every frame until connected (or an error state).
package main
import (
"fmt"
"log"
"time"
netcode "github.com/mas-bandwidth/netcode.go"
)
func main() {
client, err := netcode.NewClient("0.0.0.0:0", nil, 0.0)
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
defer client.Close()
connectToken := requestConnectTokenFromWebBackend()
client.Connect(connectToken)
start := time.Now()
deltaTime := time.Second / 60
for i := 0; i < 10; i++ { // your game loop
client.Update(time.Since(start).Seconds())
if client.State() == netcode.ClientStateConnected {
client.SendPacket([]byte("hello server"))
}
if client.State() <= netcode.ClientStateDisconnected {
fmt.Printf("client failed to connect: %s\n", netcode.ClientStateName(client.State()))
break
}
time.Sleep(deltaTime)
}
}
// requestConnectTokenFromWebBackend stands in for hitting a REST API on your
// web backend that authenticates the client and returns a connect token.
func requestConnectTokenFromWebBackend() []byte {
var privateKey [netcode.KeyBytes]byte
token, err := netcode.GenerateConnectToken(
[]string{"127.0.0.1:40000"}, []string{"127.0.0.1:40000"},
30, 5, 1, 0x1122334455667788, privateKey[:], nil)
if err != nil {
log.Fatal(err)
}
return token
}
Output:
func NewClientDual ¶
func NewClientDual(address1String string, address2String string, config *ClientConfig, time float64) (*Client, error)
NewClientDual creates a client bound to two addresses, one IPv4 and one IPv6, so it can connect to servers over either protocol. Bind to port zero to get an ephemeral port. time is the current time in seconds, from any base you like; pass the same time base to Update.
func (*Client) Close ¶
func (client *Client) Close()
Close disconnects the client (sending disconnect packets to the server if connected) and releases its sockets.
func (*Client) Connect ¶
Connect takes a connect token generated by GenerateConnectToken (typically obtained from the web backend over HTTPS) and starts connecting to one of the server addresses in it. Progress is made in Update; poll State to see the result.
func (*Client) ConnectLoopback ¶
ConnectLoopback puts the client in loopback mode, connected to a local server in the same process without any packets going over the network. See Server.ConnectLoopbackClient for the server side.
func (*Client) Disconnect ¶
func (client *Client) Disconnect()
Disconnect disconnects the client from the server, sending a number of redundant disconnect packets so the server can free the client slot quickly instead of timing out.
func (*Client) DisconnectLoopback ¶
func (client *Client) DisconnectLoopback()
DisconnectLoopback disconnects a client in loopback mode.
func (*Client) Index ¶
Index returns the client slot index assigned by the server, valid once connected.
func (*Client) MaxClients ¶
MaxClients returns the maximum number of client slots on the server the client is connected to, valid once connected.
func (*Client) NextPacketSequence ¶
NextPacketSequence returns the sequence number of the next packet the client will send.
func (*Client) ProcessLoopbackPacket ¶
ProcessLoopbackPacket delivers a payload packet to a client in loopback mode, as if it had been received from the server.
func (*Client) ProcessPacket ¶
ProcessPacket processes a raw packet received from the given address, as if it had arrived on the client's socket. This is useful together with ClientConfig.OverrideSendAndReceive to drive the client with your own transport.
func (*Client) ReceivePacket ¶
ReceivePacket pops the next payload packet received from the server off the receive queue, returning the payload and the sequence number of the packet it arrived in. Returns nil when no packets remain. Call it in a loop after Update until it returns nil.
func (*Client) SendPacket ¶
SendPacket sends a payload packet to the server. The packet must be between 1 and MaxPacketSize bytes. Packets are unreliable and unordered. Does nothing unless the client is connected.
func (*Client) ServerAddress ¶
ServerAddress returns the address of the server the client is connecting or connected to.
type ClientConfig ¶
type ClientConfig struct {
// NetworkSimulator, if set, routes all packets through a network simulator
// instead of real sockets. The client must then bind to a specific port.
NetworkSimulator *NetworkSimulator
// StateChangeCallback, if set, is called whenever the client state changes.
StateChangeCallback func(previousState int, currentState int)
// SendLoopbackPacketCallback is called to deliver packets sent by a client
// in loopback mode. See Client.ConnectLoopback.
SendLoopbackPacketCallback func(clientIndex int, packetData []byte, packetSequence uint64)
// OverrideSendAndReceive replaces socket send and receive with the
// SendPacketOverride and ReceivePacketOverride callbacks. No sockets are
// created.
OverrideSendAndReceive bool
SendPacketOverride func(to *Address, packetData []byte)
ReceivePacketOverride func() (packetData []byte, from Address, ok bool)
}
ClientConfig configures a Client. The zero value gives working defaults: pass nil to NewClient to use it.
type NetworkSimulator ¶
type NetworkSimulator struct {
LatencyMilliseconds float32
JitterMilliseconds float32
PacketLossPercent float32
DuplicatePacketPercent float32
// contains filtered or unexported fields
}
NetworkSimulator simulates latency, jitter, packet loss and duplicate packets between clients and servers created with it, entirely in memory. It is used by the test suite and is handy for testing your own protocol under adverse network conditions.
Set the public fields before sending packets. Zero values simulate a perfect network.
func NewNetworkSimulator ¶
func NewNetworkSimulator() *NetworkSimulator
NewNetworkSimulator creates a network simulator.
func (*NetworkSimulator) ReceivePackets ¶
func (sim *NetworkSimulator) ReceivePackets(to *Address, maxPackets int) (packetData [][]byte, from []Address)
ReceivePackets pops up to maxPackets packets addressed to the given address out of the pending receive buffer. It returns the packet payloads and the addresses they were sent from.
func (*NetworkSimulator) Reset ¶
func (sim *NetworkSimulator) Reset()
Reset discards all packets in flight and reseeds the random number generator.
func (*NetworkSimulator) SendPacket ¶
func (sim *NetworkSimulator) SendPacket(from, to *Address, packetData []byte)
SendPacket queues a packet for delivery from one address to another, subject to the simulated latency, jitter, packet loss and duplication.
func (*NetworkSimulator) Update ¶
func (sim *NetworkSimulator) Update(time float64)
Update advances the simulator to the given time, moving any packets whose delivery time has passed into the pending receive buffer. Packets that were not picked up with ReceivePackets since the last update are discarded.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the dedicated server side of the netcode protocol. It manages a set of client slots, where each slot from [0,maxClients-1] represents room for one connected client.
A server is not safe for concurrent use: call all methods from the same goroutine, and drive it by calling Update regularly (for example, 60 times per second).
func NewServer ¶
func NewServer(serverAddressString string, config *ServerConfig, time float64) (*Server, error)
NewServer creates a server with a single public address, e.g. "127.0.0.1:40000". The public address must match an address in the connect tokens clients connect with.
Example ¶
A dedicated server: create it with the private key shared with the web backend, start it with the number of client slots, then update it every frame, exchanging payload packets with connected clients.
package main
import (
"crypto/rand"
"log"
"time"
netcode "github.com/mas-bandwidth/netcode.go"
)
func main() {
var privateKey [netcode.KeyBytes]byte
if _, err := rand.Read(privateKey[:]); err != nil {
log.Fatal(err)
}
config := &netcode.ServerConfig{
ProtocolID: 0x1122334455667788,
PrivateKey: privateKey,
}
server, err := netcode.NewServer("127.0.0.1:40000", config, 0.0)
if err != nil {
log.Fatalf("failed to create server: %v", err)
}
defer server.Close()
server.Start(16)
start := time.Now()
deltaTime := time.Second / 60
for i := 0; i < 10; i++ { // your game loop
server.Update(time.Since(start).Seconds())
for clientIndex := 0; clientIndex < server.MaxClients(); clientIndex++ {
for {
packet, sequence := server.ReceivePacket(clientIndex)
if packet == nil {
break
}
_ = sequence // process the payload packet from this client...
}
}
time.Sleep(deltaTime)
}
}
Output:
func NewServerDual ¶
func NewServerDual(serverAddress1String string, serverAddress2String string, config *ServerConfig, time float64) (*Server, error)
NewServerDual creates a server with two public addresses, one IPv4 and one IPv6, so clients can connect over either protocol. time is the current time in seconds, from any base you like; pass the same time base to Update.
func (*Server) ClientAddress ¶
ClientAddress returns the address of the client in the given slot.
func (*Server) ClientConnected ¶
ClientConnected reports whether a client is connected in the given slot.
func (*Server) ClientDisconnectReason ¶
ClientDisconnectReason returns why the client in the given slot was last disconnected: one of the DisconnectReason constants. It is reset to DisconnectReasonNone when the server starts and when a new client connects to the slot, and is recorded before the ConnectDisconnectCallback fires, so it can be queried from inside that callback.
func (*Server) ClientID ¶
ClientID returns the client id of the client in the given slot, or zero if no client is connected there.
func (*Server) ClientLoopback ¶
ClientLoopback reports whether the client in the given slot is in loopback mode.
func (*Server) ClientUserData ¶
ClientUserData returns the user data from the connect token of the client in the given slot. It is UserDataBytes long.
func (*Server) Close ¶
func (server *Server) Close()
Close stops the server, disconnecting any connected clients, and releases its sockets.
func (*Server) ConnectLoopbackClient ¶
ConnectLoopbackClient connects a client to the given slot in loopback mode. Packets for this client flow through the SendLoopbackPacketCallback and ProcessLoopbackPacket instead of the network. This is intended for a local player being hosted in the same process as the server, e.g. listen servers.
func (*Server) DisconnectAllClients ¶
func (server *Server) DisconnectAllClients()
DisconnectAllClients disconnects all connected clients. Loopback clients are left alone; use DisconnectLoopbackClient for those.
func (*Server) DisconnectClient ¶
DisconnectClient disconnects the client in the given slot, sending a number of redundant disconnect packets so the client finds out quickly instead of timing out.
func (*Server) DisconnectLoopbackClient ¶
DisconnectLoopbackClient disconnects the loopback client in the given slot.
func (*Server) MaxClients ¶
MaxClients returns the number of client slots the server was started with.
func (*Server) NextPacketSequence ¶
NextPacketSequence returns the sequence number of the next packet the server will send to the given client.
func (*Server) NumConnectedClients ¶
NumConnectedClients returns the number of clients currently connected.
func (*Server) ProcessLoopbackPacket ¶
func (server *Server) ProcessLoopbackPacket(clientIndex int, packetData []byte, packetSequence uint64)
ProcessLoopbackPacket delivers a payload packet from a loopback client to the server, as if it had been received over the network.
func (*Server) ProcessPacket ¶
ProcessPacket processes a raw packet received from the given address, as if it had arrived on one of the server's sockets. This is useful together with ServerConfig.OverrideSendAndReceive to drive the server with your own transport.
func (*Server) ReceivePacket ¶
ReceivePacket pops the next payload packet received from the client in the given slot, returning the payload and the sequence number of the packet it arrived in. Returns nil when no packets remain. Call it in a loop after Update until it returns nil.
func (*Server) SendPacket ¶
SendPacket sends a payload packet to the client in the given slot. The packet must be between 1 and MaxPacketSize bytes. Packets are unreliable and unordered.
func (*Server) Start ¶
Start starts the server with the given number of client slots, in [1,MaxClients]. If the server is already running it is stopped and restarted.
type ServerConfig ¶
type ServerConfig struct {
// ProtocolID is a 64 bit value unique to this particular game/application.
// Only clients with a connect token generated for the same protocol id can
// connect.
ProtocolID uint64
// PrivateKey is shared between the web backend and the dedicated servers.
// Do not share your private key with anybody, and especially, do not
// include it in your client executable!
PrivateKey [KeyBytes]byte
// NetworkSimulator, if set, routes all packets through a network simulator
// instead of real sockets.
NetworkSimulator *NetworkSimulator
// ConnectDisconnectCallback, if set, is called when a client connects to
// or disconnects from the server. During a disconnect it fires before the
// client slot is reset, so the slot can still be queried from inside the
// callback.
ConnectDisconnectCallback func(clientIndex int, connected bool)
// SendLoopbackPacketCallback is called to deliver packets sent to a client
// in loopback mode. See Server.ConnectLoopbackClient.
SendLoopbackPacketCallback func(clientIndex int, packetData []byte, packetSequence uint64)
// OverrideSendAndReceive replaces socket send and receive with the
// SendPacketOverride and ReceivePacketOverride callbacks. No sockets are
// created.
OverrideSendAndReceive bool
SendPacketOverride func(to *Address, packetData []byte)
ReceivePacketOverride func() (packetData []byte, from Address, ok bool)
}
ServerConfig configures a Server. At minimum set ProtocolID and PrivateKey.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
client
command
netcode example client
|
netcode example client |
|
client_server
command
netcode example client/server
|
netcode example client/server |
|
server
command
netcode example server
|
netcode example server |
|
soak
command
netcode soak test
|
netcode soak test |