Networking API similar to Berkeley sockets, but for games.
- It's a connection-oriented API (like TCP, not UDP). When sending and receiving messages, the peer is identified using a connection handle.
- But unlike TCP, it's message-oriented, not stream-oriented. (The boundaries between the messages are maintained by the API.)
- Both reliable and unreliable messages are supported.
- Large messages are split into multiple packets, small messages are combined into fewer packets.
- A robust ACK / reassembly / retransmission strategy.
- Strong encryption and authentication. When a player connects, you can be sure that if a certain SteamID is authenticated, that someone who has access to that person's account has authorized the connection. Eavesdropping / tampering requires hacking into the VAC-secured process. Impersonation requires access to the target's computer.
- Supports relayed connections through the Valve network. This prevents IP addresses from being revealed, protecting your players and gameservers from attack.
- Also supports standard connectivity over plain UDP using IPv4 or IPv6.
Related documents:
- Steam Networking overview of the different networking APIs.
- ISteamNetworkingUtils Utilities for measuring ping and estimating ping time between peers.
- steamnetworkingtypes Miscellaneous types and utilities.
- Steam Datagram Relay A service for relaying your game traffic over the Valve backbone. This prevents IP addresses from being revealed and in many cases improves ping times and connection quality.
- ISteamNetworkingMessages is a UDP-style interface. Send and receive messages by specifying the destination peer in each send call, instead of establishing a connection first. The same connections are still used under the hood, so you still get the same performance, but the connections are created and idle timed out for you automatically, and you do not receive quite as much control over the connection, or feedback about its state.
An opensource version of this API is available on
github. You can use it for whatever purpose you want. To use the Valve network you need to be a Steam partner and use the version in the Steamworks SDK.
Member functions for ISteamNetworkingSockets are called through the global accessor function SteamNetworkingSockets().
Managing connections
CreateListenSocketIP
HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Creates a "server" socket that listens for clients to connect to by calling
ConnectByIPAddress, over ordinary UDP (IPv4 or IPv6)
You must select a specific local port to listen on and set it as the port field of the local address.
Usually you will set the IP portion of the address to zero, (
SteamNetworkingIPAddr::Clear()). This means that you will not bind to any particular local interface (i.e. the same as INADDR_ANY in plain socket code). Furthermore, if possible the socket will be bound in "dual stack" mode, which means that it can accept both IPv4 and IPv6 client connections. If you really do wish to bind a particular interface, then set the local address to the appropriate IPv4 or IPv6 IP.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
When a client attempts to connect, a
SteamNetConnectionStatusChangedCallback_t will be posted. The connection will be in the k_ESteamNetworkingConnectionState_Connecting state.
ConnectByIPAddress
HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &address, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Creates a connection and begins talking to a "server" over UDP at the given IPv4 or IPv6 address. The remote host must be listening with a matching call to
ISteamnetworkingSockets::CreateListenSocketIP on the specified port.
A
SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start connecting, and then another one on either timeout or successful connection.
If the server does not have any identity configured, then their network address will be the only identity in use. Or, the network host may provide a platform-specific identity with or without a valid certificate to authenticate that identity. (These details will be contained in the
SteamNetConnectionStatusChangedCallback_t.) It's up to your application to decide whether to allow the connection.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
CreateListenSocketP2P
HSteamListenSocket CreateListenSocketP2P( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Like
CreateListenSocketIP, but clients will connect using
ConnectP2P. The connection will be relayed through the Valve network.
nLocalVirtualPort specifies how clients can connect to this socket using
ConnectP2P. It's very common for applications to only have one listening socket; in that case, use zero. If you need to open multiple listen sockets and have clients be able to connect to one or the other, then nLocalVirtualPort should be a small integer (<1000) unique to each listen socket you create.
If you are listening on a dedicated servers in known data center, then you can listen using this function instead of
CreateHostedDedicatedServerListenSocket, to allow clients to connect without a ticket. Any user that owns the app and is signed into Steam will be able to attempt to connect to your server. Also, a connection attempt may require the client to be connected to Steam, which is one more moving part that may fail. When tickets are used, then once a ticket is obtained, a client can connect to your server even if they got disconnected from Steam or Steam is offline.
If you use this, you probably want to call
ISteamNetworkingUtils::InitRelayNetworkAccess when your app initializes.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
ConnectP2P
HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Begin connecting to a peer that is identified using a platform-specific identifier. This uses the default rendezvous service, which depends on the platform and library configuration. (E.g. on Steam, it goes through the steam backend.) The traffic is relayed over the Steam Datagram Relay network.
If you use this, you probably want to call
ISteamNetworkingUtils::InitRelayNetworkAccess when your app initializes.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
AcceptConnection
EResult AcceptConnection( HSteamNetConnection hConn );
Accept an incoming connection that has been received on a listen socket.
When a connection attempt is received (perhaps after a few basic handshake packets have been exchanged to prevent trivial spoofing), a connection interface object is created in the k_ESteamNetworkingConnectionState_Connecting state and a
SteamNetConnectionStatusChangedCallback_t is posted. At this point, your application MUST either accept or close the connection. (It may not ignore it.) Accepting the connection will transition it either into the connected state, or the finding route state, depending on the connection type.
You should take action within a second or two, because accepting the connection is what actually sends the reply notifying the client that they are connected. If you delay taking action, from the client's perspective it is the same as the network being unresponsive, and the client may timeout the connection attempt. In other words, the client cannot distinguish between a delay caused by network problems and a delay caused by the application.
This means that if your application goes for more than a few seconds without processing callbacks (for example, while loading a map), then there is a chance that a client may attempt to connect in that interval and fail due to timeout.
If the application does not respond to the connection attempt in a timely manner, and we stop receiving communication from the client, the connection attempt will
be timed out locally, transitioning the connection to the k_ESteamNetworkingConnectionState_ProblemDetectedLocally state. The client may also close the connection before it is accepted, and a transition to the k_ESteamNetworkingConnectionState_ClosedByPeer is also possible depending the exact sequence of events.
Returns:
- k_EResultInvalidParam if the handle is invalid.
- k_EResultInvalidState if the connection is not in the appropriate state. (Remember that the connection state could change in between the time that the notification being posted to the queue and when it is received by the application.)
A note about connection configuration options. If you need to set any configuration options that are common to all connections accepted through a particular listen socket, consider setting the options on the listen socket, since such options are inherited automatically. If you really do need to set options that are connection specific, it is safe to set them on the connection before accepting the connection.
CloseConnection
bool CloseConnection( HSteamNetConnection hPeer, int nReason, const char *pszDebug, bool bEnableLinger );
Disconnects from the remote host and invalidates the connection handle. Any unread data on the connection is discarded.
nReason is an application defined code that will be received on the other end and recorded (when possible) in backend analytics. The value should come from a restricted range. (See
ESteamNetConnectionEnd.) If you don't need to communicate any information to the remote host, and do not want analytics to be able to distinguish "normal" connection terminations from "exceptional" ones, you may pass zero, in which case the generic value of k_ESteamNetConnectionEnd_App_Generic will be used.
pszDebug is an optional human-readable diagnostic string that will be received by the remote host and recorded (when possible) in backend analytics.
Optionally, you may put the socket into a "linger" state, which causes the connection to attempt is deliver any remaining outbound reliable messages before actually closing the connection. Otherwise any unsent reliable data is discarded. In any case, once you close the connection you will have no further visibility into the status of the connection or any messages. (In some uses cases, this is acceptable. If not, then you need to wait to close the connection.) Remember that this is an application protocol, not a kernel protocol like TCP. So you need to make sure the application stays running long enough for the data to be flushed out.
If the connection has already ended (k_ESteamNetworkingConnectionState_ClosedByPeer or k_ESteamNetworkingConnectionState_ProblemDetectedLocally) and you are just freeing up the connection object, then nReason, pszDebug , and bEnableLinger are ignored.
CloseListenSocket
bool CloseListenSocket( HSteamListenSocket hSocket );
Destroy a listen socket. All the connections that were accepted on the listen socket are closed ungracefully.
CreateSocketPair
bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 );
Create a pair of connections that are talking to each other, e.g. a loopback connection. This is very useful for testing, or so that your client/server code can work the same even when you are running a local "server".
The two connections will immediately be placed into the connected state, and no callbacks will be posted immediately. After this, if you close either connection, the other connection will receive a callback, exactly as if they were communicating over the network. You must close *both* sides in order to fully clean up the resources!
By default, internal buffers are used, completely bypassing the network, the chopping up of messages into packets, encryption, copying the payload, etc. This means that loopback packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will cause the socket pair to send packets through the local network loopback device (127.0.0.1) on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended to encrypt and decrypt.
If you wish to assign a specific identity to either connection, you may pass a particular identity. Otherwise, if you pass nullptr, the respective connection will assume a generic "localhost" identity. If you use real network loopback, this might be translated to the actual bound loopback port. Otherwise, the port will be zero.
Sending and receiving messages
SendMessageToConnection
EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber );
Send a message to the remote host on the specified connection.
nSendFlags determines the delivery guarantees that will be provided, when data should be buffered, etc. E.g.:
- k_nSteamNetworkingSend_Unreliable
- k_nSteamNetworkingSend_Reliable
- k_nSteamNetworkingSend_NoNagle
- k_nSteamNetworkingSend_NoDelay
Note that the semantics we use for messages are not precisely the same as the semantics of a standard "stream" socket. (SOCK_STREAM) For an ordinary stream socket, the boundaries between chunks are not considered relevant, and the sizes of the chunks of data written will not necessarily match up to the sizes of the chunks that are returned by the reads on the other end. The remote host might read a partial chunk, or chunks might be coalesced. For the message semantics used here, however, the sizes WILL match. Each send call will match a successful read call on the remote host one-for-one. If you are porting existing stream-oriented code to the semantics of reliable messages, your code should work the same, since reliable message semantics are more strict than stream semantics. The only caveat is related to performance: there is per-message overhead to retain the message sizes, and so if your code sends many small chunks of data, performance will suffer. Any code based on stream sockets that does not write excessively small chunks will work without any changes.
The pOutMessageNumber is an optional pointer to receive the message number assigned to the message, if sending was successful.
Returns:
- k_EResultInvalidParam: invalid connection handle, or the individual message is too big. (See k_cbMaxSteamNetworkingSocketsMessageSizeSend)
- k_EResultInvalidState: connection is in an invalid state
- k_EResultNoConnection: connection has ended
- k_EResultIgnored: You used k_nSteamNetworkingSend_NoDelay, and the message was dropped because we were not ready to send it.
- k_EResultLimitExceeded: there was already too much data queued to be sent. (See k_ESteamNetworkingConfig_SendBufferSize)
SendMessages
void SendMessages( int nMessages, SteamNetworkingMessage_t *const *pMessages, int64 *pOutMessageNumberOrResult );
Send one or more messages without copying the message payload. This is the most efficient way to send messages. To use this function, you must first allocate a message object using
ISteamNetworkingUtils::AllocateMessage. (Do not declare one on the stack or allocate your own.)
You should fill in the message payload. You can either let it allocate the buffer for you and then fill in the payload, or if you already have a buffer allocated, you can just point m_pData at your buffer and set the callback to the appropriate function to free it. Note that if you use your own buffer, it MUST remain valid until the callback is executed. And also note that your callback can be invoked at any time from any thread (perhaps even before SendMessages returns!), so it MUST be fast and threadsafe.
You MUST also fill in:
- m_conn - the handle of the connection to send the message to
- m_nFlags - bitmask of k_nSteamNetworkingSend_xxx flags.
All other fields are currently reserved and should not be modified.
The library will take ownership of the message structures. They may be modified or become invalid at any time, so you must not read them after passing them to this function.
pOutMessageNumberOrResult is an optional array that will receive, for each message, the message number that was assigned to the message if sending was successful. If sending failed, then a negative EResult value is placed into the array. For example, the array will hold -k_EResultInvalidState if the connection was in an invalid state. See
SendMessageToConnection for possible failure codes.
FlushMessagesOnConnection
EResult FlushMessagesOnConnection( HSteamNetConnection hConn );
Flush any messages waiting on the Nagle timer and send them at the next transmission opportunity (often that means right now).
If Nagle is enabled (it's on by default) then when calling SendMessageToConnection the message will be buffered, up to the Nagle time before being sent, to merge small messages into the same packet. (See k_ESteamNetworkingConfig_NagleTime)
Returns:
- k_EResultInvalidParam: invalid connection handle
- k_EResultInvalidState: connection is in an invalid state
- k_EResultNoConnection: connection has ended
- k_EResultIgnored: We weren't (yet) connected, so this operation has no effect.
ReceiveMessagesOnConnection
int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages );
Fetch the next available message(s) from the connection, if any. Returns the number of messages returned into your array, up to nMaxMessages.
If the connection handle is invalid, -1 is returned. If no data is available, 0, is returned.
The order of the messages returned in the array is relevant. Reliable messages will be received in the order they were sent (and with the same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket).
Unreliable messages may be dropped, or delivered out of order with respect to each other or with respect to reliable messages.
If any messages are returned, you MUST call
SteamNetworkingMessage_t::Release() to each of them free up resources after you are done. It is safe to keep the object alive for a little while (put it into some queue, etc), and you may call Release() from any thread.
Polling many connections efficiently
A
poll group is a set of connections that can be polled efficiently. (In this API, to "poll" a connection means to retrieve all pending messages. We actually don't have an API to "poll" the connection
state, like BSD sockets.)
CreatePollGroup
HSteamNetPollGroup CreatePollGroup()
Create a new poll group.
You should destroy the poll group when you are done using
DestroyPollGroup.
DestroyPollGroup
bool DestroyPollGroup( HSteamNetPollGroup hPollGroup )
Destroy a poll group created with
CreatePollGroup.
If there are any connections in the poll group, they are removed from the group, and left in a state where they are not part of any poll group. Returns false if passed an invalid poll group handle.
SetConnectionPollGroup
bool SetConnectionPollGroup( HSteamNetConnection hConn, HSteamNetPollGroup hPollGroup );
Assign a connection to a poll group. Note that a connection may only belong to a single poll group. Adding a connection to a poll group implicitly removes it from any other poll group it is in.
You can pass k_HSteamNetPollGroup_Invalid to remove a connection from its current poll group without adding it to a new poll group.
If there are received messages currently pending on the connection, an attempt is made to add them to the queue of messages for the poll group in approximately the order that would have applied if the connection was already part of the poll group at the time that the messages were received.
Returns false if the connection handle is invalid, or if the poll group handle is invalid (and not k_HSteamNetPollGroup_Invalid).
ReceiveMessagesOnPollGroup
int ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages );
Same as
ReceiveMessagesOnConnection, but will return the next messages available on any connection in the poll group. Examine
SteamNetworkingMessage_t::m_conn to know which connection. (
SteamNetworkingMessage_t::m_nConnUserData might also be useful.)
Delivery order of messages among different connections will usually match the order that the last packet was received which completed the message. But this is not a strong guarantee, especially for packets received right as a connection is being assigned to poll group.
Delivery order of messages on the same connection is well defined and the same guarantees are present as mentioned in
ReceiveMessagesOnConnection. (But the messages are not grouped by connection, so they will not necessarily appear consecutively in the list; they may be interleaved with messages for other connections.)
Connection info
GetConnectionInfo
bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo );
Returns basic information about the high-level state of the connection.
Returns false if the connection handle is invalid.
GetConnectionRealTimeStatus
EResult GetConnectionRealTimeStatus( HSteamNetConnection hConn, SteamNetConnectionRealTimeStatus_t *pStatus, int nLanes, SteamNetConnectionRealTimeLaneStatus_t *pLanes );
Returns a small set of information about the real-time state of the connection and the queue status of each lane.
pStatus may be NULL if the information is not desired. (E.g. you are only interested in the lane information.)
On entry, nLanes specifies the length of the pLanes array. This may be 0 if you do not wish to receive any lane data. It's OK for this to be smaller than the total number of configured lanes.
pLanes points to an array that will receive lane-specific info. It can be NULL if this is not needed.
Return value:
- k_EResultNoConnection - connection handle is invalid or connection has been closed.
- k_EResultInvalidParam - nLanes is bad
GetDetailedConnectionStatus
int GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf );
Returns very detailed connection stats in diagnostic text format. Useful for dumping to a log, etc. The format of this information is subject to change.
Returns:
- -1: failure (bad connection handle)
- 0: OK, your buffer was filled in and '\0'-terminated
- >0: Your buffer was either nullptr, or it was too small and the text got truncated. Try again with a buffer of at least N bytes.
SetConnectionUserData
bool SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData );
Set connection user data. The data is used in the following places:
Do you need to set this atomically when the connection is created? See k_ESteamNetworkingConfig_ConnectionUserData.
WARNING: Be *very careful* when using the value provided in callbacks structs. Callbacks are queued, and the value that you will receive in your callback is the userdata that was effective at the time the callback was queued. There are subtle race conditions that can happen if you
don't understand this!
If any incoming messages for this connection are queued, the userdata field is updated, so that when when you receive messages (e.g. with ReceiveMessagesOnConnection), they will always have the very latest userdata. So the tricky race conditions that can happen with callbacks do not apply to retrieving messages.
Returns false if the handle is invalid.
GetConnectionUserData
int64 GetConnectionUserData( HSteamNetConnection hPeer );
Fetch connection user data. Returns -1 if handle is invalid or if you haven't set any userdata on the connection.
SetConnectionName
void SetConnectionName( HSteamNetConnection hPeer, const char *pszName );
Set a name for the connection, used mostly for debugging
GetConnectionName
bool GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen );
Fetch connection name into your buffer, which is at least nMaxLen bytes. Returns false if handle is invalid.
ConfigureConnectionLanes
EResult ConfigureConnectionLanes( HSteamNetConnection hConn, int nNumLanes, const int *pLanePriorities, const uint16 *pLaneWeights );
Configure multiple outbound messages streams ("lanes") on a connection, and control head-of-line blocking between them. Messages within a given lane are always sent in the order they are queued, but messages from different lanes may be sent out of order. Each lane has its own message number sequence. The first message sent on each lane will be assigned the number 1.
Each lane has a "priority". Lower priority lanes will only be processed when all higher-priority lanes are empty. The magnitudes of the priority values are not relevant, only their sort order. Higher numeric values take priority over lower numeric values.
Each lane also is assigned a weight, which controls the approximate proportion of the bandwidth that will be consumed by the lane, relative to other lanes of the same priority. (This is assuming the lane stays busy. An idle lane does not build up "credits" to be be spent once a message is queued.) This value is only meaningful as a proportion, relative to other lanes with the same priority. For lanes with different priorities, the strict priority order will prevail, and their weights relative to each other are not relevant. Thus, if a lane has a unique priority value, the weight value for that lane is not relevant.
Example: 3 lanes, with priorities { 0, 10, 10 } and weights { (NA), 20, 5 }. Messages sent on the first will always be sent first, before messages in the other two lanes. Its weight value is irrelevant, since there are no other lanes with priority=0. The other two lanes will share bandwidth, with the second and third lanes sharing bandwidth using a ratio of approximately 4:1. (The weights { NA, 4, 1 } would be equivalent.)
Notes:
- At the time of this writing, some code has performance cost that is linear in the number of lanes, so keep the number of lanes to an absolute minimum. 3 or so is fine; >8 is a lot. The max number of lanes on Steam is 255, which is a very large number and not recommended!
- Lane priority values may be any int. Their absolute value is not relevant, only the order matters.
- Weights must be positive, and due to implementation details, they are restricted to 16-bit values. The absolute magnitudes don't matter, just the proportions.
- Messages sent on a lane index other than 0 have a small overhead on the wire, so for maximum wire efficiency, lane 0 should be the "most common" lane, regardless of priorities or weights.
- A connection has a single lane by default. Calling this function with nNumLanes=1 is legal, but pointless, since the priority and weight values are irrelevant in that case.
- You may reconfigure connection lanes at any time, however reducing the number of lanes is not allowed.
- Reconfiguring lanes might restart any bandwidth sharing balancing. Usually you will call this function once, near the start of the connection, perhaps after exchanging a few messages.
- To assign all lanes the same priority, you may use pLanePriorities=NULL.
- If you wish all lanes with the same priority to share bandwidth equally (or if no two lanes have the same priority value, and thus priority values are irrelevant), you may use pLaneWeights=NULL
- Priorities and weights determine the order that messages are SENT on the wire. There are NO GUARANTEES on the order that messages are RECEIVED! Due to packet loss, out-of-order delivery, and subtle details of packet serialization, messages might still be received slightly out-of-order! The *only* strong guarantee is that *reliable* messages on the *same lane* will be delivered in the order they are sent.
- Each host configures the lanes for the packets they send; the lanes for the flow in one direction are completely unrelated to the lanes in the opposite direction.
Return value:
- k_EResultNoConnection - bad hConn
- k_EResultInvalidParam - Invalid number of lanes, bad weights, or you tried to reduce the number of lanes
- k_EResultInvalidState - Connection is already dead, etc
See also:
- SteamNetworkingMessage_t::m_idxLane
Misc
GetListenSocketAddress
bool GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *address );
Returns local IP and port that a listen socket created using CreateListenSocketIP is bound to.
Specific IPv6/IPv4 addresses may be returned if you bound the socket to a particular interface.
An IPv6 address of ::0 means "any IPv4 or IPv6"
An IPv6 address of ::ffff:0000:0000 means "any IPv4"
NOTE: This is not how you find out your public IP that clients can connect to.
Try
ISteamGameServer::GetPublicIP.
Returns false if the handle is invalid, or the information is not available for that type of listen socket.
GetIdentity
bool GetIdentity( SteamNetworkingIdentity *pIdentity );
Get the identity assigned to this interface.
E.g. on Steam, this is the user's SteamID, or for the gameserver interface, the SteamID assigned to the gameserver. Returns false and sets the result to an invalid identity if we don't know our identity yet. (E.g. GameServer has not logged in. On Steam, the user will know their SteamID even if they are not signed into Steam.)
InitAuthentication
ESteamNetworkingAvailability InitAuthentication();
Indicate our desire to be ready participate in authenticated communications. If we are currently not ready, then steps will be taken to obtain the necessary certificates. (This includes a certificate for us, as well as any CA certificates needed to authenticate peers.)
You can call this at program init time if you know that you are going to be making authenticated connections, so that we will be ready immediately when those connections are attempted. (Note that essentially all connections require authentication, with the exception of ordinary UDP connections with authentication disabled using k_ESteamNetworkingConfig_IP_AllowWithoutAuth.) If you don't call this function, we will wait until a feature is utilized that that necessitates these resources.
You can also call this function to force a retry, if failure has occurred. Once we make an attempt and fail, we will not automatically retry. In this respect, the behavior of the system after trying and failing is the same as before the first attempt: attempting authenticated communication or calling this function will call the system to attempt to acquire the necessary resources.
You can use
GetAuthenticationStatus or listen for
SteamNetAuthenticationStatus_t to monitor the status.
Returns the current value that would be returned from
GetAuthenticationStatus.
GetAuthenticationStatus
ESteamNetworkingAvailability GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails );
Query our readiness to participate in authenticated communications. A
SteamNetAuthenticationStatus_t callback is posted any time this status changes, but you can use this function to query it at any time.
The value of SteamNetAuthenticationStatus_t::m_eAvail is returned. If you only want this high level status, you can pass NULL for pDetails. If you want further details, pass non-NULL to receive them.
Gameservers utilizing Steam Datagram Relay
The following functions are used when your gameserver is in a data center known to Valve and game traffic is taking advantage of the Steam Datagram Relay network. For more information, see
Steam Datagram Relay.
ReceivedRelayAuthTicket
bool ReceivedRelayAuthTicket( const void *pvTicket, int cbTicket, SteamDatagramRelayAuthTicket *pOutParsedTicket );
Call this when you receive a ticket from your backend / matchmaking system. Puts the ticket into a persistent cache, and optionally returns the parsed ticket.
See stamdatagram_gamecoordinator.h for more details.
FindRelayAuthTicketForServer
int FindRelayAuthTicketForServer( const SteamNetworkingIdentity &identityGameServer, int nRemoteVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket );
Search cache for a ticket to talk to the server on the specified virtual port. If found, returns the number of seconds until the ticket expires, and optionally the complete cracked ticket. Returns 0 if we don't have a ticket.
Typically this is useful just to confirm that you have a ticket, before you call ConnectToHostedDedicatedServer to connect to the server.
ConnectToHostedDedicatedServer
HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Client call to connect to a server hosted in a Valve data center, on the specified virtual port. You must have placed a ticket for this server into the cache, or else this connect attempt will fail! If you are not issuing your own tickets, then to connect to a dedicated server via SDR in auto-ticket mode, use
ConnectP2P. (The server must be configured to allow this type of connection by listening using
CreateListenSocketP2P.)
You may wonder why tickets are stored in a cache, instead of simply being passed as an argument here. The reason is to make reconnection to a gameserver robust, even if the client computer loses connection to Steam or the central backend, or the app is restarted or crashes, etc.
If you use this, you probably want to call
ISteamNetworkingUtils::InitRelayNetworkAccess when your app initializes.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
GetHostedDedicatedServerPort
uint16 GetHostedDedicatedServerPort();
Returns the value of the SDR_LISTEN_PORT environment variable. This is the UDP server your server will be listening on. This will configured automatically for you in production environments.
In development, you'll need to set it yourself. See
Steam Datagram Relay for more information on how to configure dev environments.
GetHostedDedicatedServerPOPID
SteamNetworkingPOPID GetHostedDedicatedServerPOPID();
Returns 0 if SDR_LISTEN_PORT is not set. Otherwise, returns the data center the server is running in. This will be k_SteamDatagramPOPID_dev in non-production envirionment.
GetHostedDedicatedServerAddress
EResult GetHostedDedicatedServerAddress( SteamDatagramHostedAddress *pRouting );
Return info about the hosted server. This contains the PoPID of the server, and opaque routing information that can be used by the relays to send traffic to your server.
You will need to send this information to your backend, and put it in tickets, so that the relays will know how to forward traffic from clients to your server. See
SteamDatagramRelayAuthTicket for more info.
Also, note that the routing information is contained in
SteamDatagramGameCoordinatorServerLogin, so if possible, it's preferred to use
SteamNetworkingSockets::GetGameCoordinatorServerLogin to send this info to your game coordinator service, and also login securely at the same time.
On a successful exit, k_EResultOK is returned
Unsuccessful exit:
- Something other than k_EResultOK is returned.
- k_EResultInvalidState: We are not configured to listen for SDR (SDR_LISTEN_SOCKET is not set.)
- k_EResultPending: we do not (yet) have the authentication information needed. (See GetAuthenticationStatus.) If you use environment variables to pre-fetch the network config, this data should always be available immediately.
- A non-localized diagnostic debug message will be placed in m_data that describes the cause of the failure.
NOTE: The returned blob is not encrypted. Send it to your backend, but don't directly share it with clients.
CreateHostedDedicatedServerListenSocket
HSteamListenSocket CreateHostedDedicatedServerListenSocket( int nLocalVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Create a listen socket on the specified virtual port. The physical UDP port to use will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not configured, this call will fail.
Note that this call MUST be made through the SteamGameServerNetworkingSockets() interface.
This function should be used when you are using the ticket generator library to issue your own tickets. Clients connecting to the server on this virtual port will need a ticket, and they must connect using
ConnectToHostedDedicatedServer.
If you need to set any initial config options, pass them here. See
SteamNetworkingConfigValue_t for more about why this is preferable to setting the options "immediately" after creation.
GetGameCoordinatorServerLogin
EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob );
Generate an authentication blob that can be used to securely login with your backend, using SteamDatagram_ParseHostedServerLogin. (See steamdatagram_gamecoordinator.h)
Before calling the function:
- Populate the app data in pLoginInfo (m_cbAppData and m_appData). You can leave all other fields uninitialized.
- *pcbSignedBlob contains the size of the buffer at pBlob. (It should be at least k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized.)
On a successful exit:
- k_EResultOK is returned
- All of the remaining fields of pLoginInfo will be filled out.
- *pcbSignedBlob contains the size of the serialized blob that has been placed into pBlob.
Unsuccessful exit:
- Something other than k_EResultOK is returned.
- k_EResultNotLoggedOn: you are not logged in (yet)
- See ISteamNetwrokingSockets::GetHostedDedicatedServerAddress for more potential failure return values.
- A non-localized diagnostic debug message will be placed in pBlob that describes the cause of the failure.
This works by signing the contents of the
SteamDatagramGameCoordinatorServerLogin with the cert that is issued to this server. In dev environments, it's OK if you do not have a cert. (You will need to enable insecure dev login in SteamDatagram_ParseHostedServerLogin.)
Otherwise, you will need a signed cert.
NOTE: The routing blob returned here is not encrypted. Send it to your backend and don't share it directly with clients.
FakeIP system
A "FakeIP" is essentially a temporary, arbitrary identifier that happens to be a valid IPv4 address. The purpose of this system is to make it easy to integrate with existing code that identifies hosts using IPv4 addresses. The FakeIP address will never actually be used to send or receive any packets on the Internet, it is strictly an identifier.
FakeIP addresses are designed to (hopefully) pass through existing code as transparently as possible, while conflicting with "real" addresses that might be in use on networks (both the Internet and LANs) in the same code as little as possible. At the time this comment is being written, they come from the 169.254.0.0/16 range, and the port number will always be >1024. HOWEVER, this is subject to change! Do not make assumptions about these addresses, or your code might break in the future. In particular, you should use functions such as
ISteamNetworkingUtils::IsFakeIP to determine if an IP address is a "fake" one used by this system.
BeginAsyncRequestFakeIP
bool BeginAsyncRequestFakeIP( int nNumPorts );
Begin asynchronous process of allocating a fake IPv4 address that other peers can use to contact us via P2P. IP addresses returned by this function are globally unique for a given appid.
nNumPorts is the numbers of ports you wish to reserve. This is useful for the same reason that listening on multiple UDP ports is useful for different types of traffic. Because these allocations come from a global namespace, there is a relatively strict limit on the maximum number of ports you may request. (At the time of this writing, the limit is 4.) The port assignments are *not* guaranteed to have any particular order or relationship! Do *not* assume they are contiguous, even though that may often occur in practice.
Returns false if a request was already in progress, true if a new request was started. A SteamNetworkingFakeIPResult_t will be posted when the request completes.
For gameservers, you *must* call this after initializing the SDK but before beginning login. Steam needs to know in advance that FakeIP will be used. Everywhere your public IP would normally appear (such as the server browser) will be replaced by the FakeIP, and the fake port at index 0. The request is actually queued until the logon completes, so you must not wait until the allocation completes before logging in. Except for trivial failures that can be detected locally (e.g. invalid parameter), a SteamNetworkingFakeIPResult_t callback (whether success or failure) will not be posted until after we have logged in. Furthermore, it is assumed that FakeIP allocation is essential for your application to function, and so failure will not be reported until *several* retries have been attempted. This process may last several minutes. It is *highly* recommended to treat failure as fatal.
To communicate using a connection-oriented (TCP-style) API:
- Server creates a listen socket using CreateListenSocketP2PFakeIP
- Client connects using ConnectByIPAddress, passing in the FakeIP address.
- The connection will behave mostly like a P2P connection. The identities that appear in SteamNetConnectionInfo_t will be the FakeIP identity until we know the real identity. Then it will be the real identity. If the SteamNetConnectionInfo_t::m_addrRemote is valid, it will be a real IPv4 address of a NAT-punched connection. Otherwise, it will not be valid.
To communicate using an ad-hoc sendto/recv from (UDP-style) API, use
CreateFakeUDPPort.
GetFakeIP
void GetFakeIP( int idxFirstPort, SteamNetworkingFakeIPResult_t *pInfo );
Return info about the FakeIP and port(s) that we have been assigned, if any. idxFirstPort is currently reserved and must be zero. Make sure and check SteamNetworkingFakeIPResult_t::m_eResult
CreateListenSocketP2PFakeIP
HSteamListenSocket CreateListenSocketP2PFakeIP( int idxFakePort, int nOptions, const SteamNetworkingConfigValue_t *pOptions );
Create a listen socket that will listen for P2P connections sent to our FakeIP. A peer can initiate connections to this listen socket by calling
ConnectByIPAddress.
idxFakePort refers to the *index* of the fake port requested, not the actual port number. For example, pass 0 to refer to the first port in the reservation. You must call this only after calling
BeginAsyncRequestFakeIP. However, you do not need to wait for the request to complete before creating the listen socket.
GetRemoteFakeIPForConnection
EResult GetRemoteFakeIPForConnection( HSteamNetConnection hConn, SteamNetworkingIPAddr *pOutAddr );
If the connection was initiated using the "FakeIP" system, then we we can get an IP address for the remote host. If the remote host had a global FakeIP at the time the connection was established, this function will return that global IP. Otherwise, a FakeIP that is unique locally will be allocated from the local FakeIP address space, and that will be returned.
The allocation of local FakeIPs attempts to assign addresses in a consistent manner. If multiple connections are made to the same remote host, they *probably* will return the same FakeIP. However, since the namespace is limited, this cannot be guaranteed.
On failure, returns:
- k_EResultInvalidParam: invalid connection handle
- k_EResultIPNotFound: This connection wasn't made using FakeIP system
CreateFakeUDPPort
ISteamNetworkingFakeUDPPort *CreateFakeUDPPort( int idxFakeServerPort );
Get an interface that can be used like a UDP port to send/receive datagrams to a FakeIP address. This is intended to make it easy to port existing UDP-based code to take advantage of SDR.
idxFakeServerPort refers to the *index* of the port allocated using
BeginAsyncRequestFakeIP and is used to create "server" ports. You may call this before the allocation has completed. However, any attempts to send packets will fail until the allocation has succeeded. When the peer receives packets sent from this interface, the from address of the packet will be the globally-unique FakeIP. If you call this function multiple times and pass the same (nonnegative) fake port index, the same object will be returned, and this object is not reference counted.
To create a "client" port (e.g. the equivalent of an ephemeral UDP port) pass -1. In this case, a distinct object will be returned for each call. When the peer receives packets sent from this interface, the peer will assign a FakeIP from its own locally-controlled namespace.
Callback structures
SteamNetConnectionStatusChangedCallback_t
This callback is posted whenever a connection is created, destroyed, or changes state. The m_info field will contain a complete description of the connection at the time the change occurred and the callback was posted. In particular, m_info.m_eState will have the new connection state.
You will usually need to listen for this callback to know when:
- A new connection arrives on a listen socket. m_info.m_hListenSocket will be set, m_eOldState = k_ESteamNetworkingConnectionState_None, and m_info.m_eState = k_ESteamNetworkingConnectionState_Connecting. See AcceptConnection.
- A connection you initiated has been accepted by the remote host. m_eOldState = k_ESteamNetworkingConnectionState_Connecting, and m_info.m_eState = k_ESteamNetworkingConnectionState_Connected. Some connections might transition to k_ESteamNetworkingConnectionState_FindingRoute first.
- A connection has been actively rejected or closed by the remote host. m_eOldState = k_ESteamNetworkingConnectionState_Connecting or k_ESteamNetworkingConnectionState_Connected, and m_info.m_eState = k_ESteamNetworkingConnectionState_ClosedByPeer. m_info.m_eEndReason and m_info.m_szEndDebug will have more details. NOTE: upon receiving this callback, you must still destroy the connection using CloseConnection to free up local resources. (The details passed to the function are not used in this case, since the connection is already closed.)
- A problem was detected with the connection, and it has been closed by the local host. The most common failure is timeout, but other configuration or authentication failures can cause this. m_eOldState = k_ESteamNetworkingConnectionState_Connecting or k_ESteamNetworkingConnectionState_Connected, and m_info.m_eState = k_ESteamNetworkingConnectionState_ProblemDetectedLocally. m_info.m_eEndReason and m_info.m_szEndDebug will have more details. NOTE: upon receiving this callback, you must still destroy the connection using CloseConnection to free up local resources. (The details passed to the function are not used in this case, since the connection is already closed.)
Remember that callbacks are posted to a queue, and networking connections can change at any time. It is possible that the connection has already changed state by the time you process this callback.
Also note that callbacks will be posted when connections are created and destroyed by your own API calls.
struct SteamNetConnectionStatusChangedCallback_t
{
/// Connection handle
HSteamNetConnection m_hConn;
/// Full connection info
SteamNetConnectionInfo_t m_info;
/// Previous state. (Current state is in m_info.m_eState)
ESteamNetworkingConnectionState m_eOldState;
};
SteamNetAuthenticationStatus_t
A struct used to describe our readiness to participate in authenticated, encrypted communication. In order to do this we need:
- The list of trusted CA certificates that might be relevant for this app.
- A valid certificate issued by a CA.
This callback is posted whenever the state of our readiness changes.
struct SteamNetAuthenticationStatus_t
{
/// Status
ESteamNetworkingAvailability m_eAvail;
/// Non-localized English language status. For diagnostic/debugging
/// purposes only.
char m_debugMsg[];
};
Custom P2P signaling
Signaling refers to rendezvous messages sent through a trusted channel that is low bandwidth and high latency, used by peers to negotiate a P2P connection. Steam provides a signaling service for you. However, in some situations you may wish to do your own signaling. (For example, if one or both of your peers are not on Steam.) You can use SteamNetworkingSockets to make P2P connections, with your own signaling. You can use ICE to pierce NAT and use standard STUN/TURN servers, or depending on your situation, SDR relay network and Valve backbone may not be available. Please let us know if you need more information about these APIs.