The following short code samples illustrate the use of the Xbox Secure Sockets API.
You must specify your app’s valid socket and secure device association templates in the network manifest section of your application manifest. For details on the network manifest, including the example manifest used in this example, see Specifying the Secure Device Association Templates for your App.
The first step in using the Secure Sockets API to establish multiplayer communication is to instantiate the SecureDeviceAssociationTemplate object that describes the network configuration to be used in your app. The template is described in the network manifest. This example assumes the network manifest in use matches the example shown in Specifying the Secure Device Association Templates for your App, and uses the template named “PeerTraffic”.
void Sample::InitializeSecureSockets()
{
// Instantiate the SecureDeviceAssociationTemplate using the "PeerTraffic"
// template from the networkmanifest.xml. This template will be used
// to create SecureDeviceAssociations in the helper method
// CreateAndUsePeerAssociation. This code assumes that the remote
// device has similarly performed the same Xbox Secure Device Association
// Template creation and is ready to accept a new SecureDeviceAssociation.
try
{
m_peerDeviceAssociationTemplate =SecureDeviceAssociationTemplate::GetTemplateByName("PeerTraffic");
}
catch(Platform::Exception^ e)
{
LogComment("Failed to get the SecureDeviceAssociationTemplate.\n");
throw;
}
...
}
The next step is to obtain a SecureDeviceAddress value for each peer connection to be established. This code demonstrates how to find the SecureDeviceAddress values for all of the other consoles in a MultiplayerSession returned from the Xbox Live Multiplayer service.
void Sample::GetPeersAddressesFromGameSessionAndConnect( Platform::String^ gameSessionName )
{
// Not shown: m_xboxLiveContext is an XboxLiveContext created for the current User.
// This user's auth token will be used for the service requests.
XboxLiveContext^ xboxLiveContext = m_xboxLiveContext;
Platform::String^ serviceConfigurationId = m_serviceConfigurationId; // must match the SCID value in the app manifest
Platform::String^ sessionTemplateName = m_sessionTemplateName; // the multiplayer session name you are using
MultiplayerSessionReference^ sessionRef = ref new MultiplayerSessionReference(
serviceConfigurationId,
sessionTemplateName,
gameSessionName
);
IAsyncOperation<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^>^ asyncOp =
xboxLiveContext->MultiplayerService->GetCurrentSessionAsync(sessionRef);
create_task(asyncOp)
.then([this] (MultiplayerSession^ session)
{
Windows::Data::Json::JsonObject^ jsonSessionCustomProperties = Windows::Data::Json::JsonObject::Parse(session->Properties->Custom);
for(MultiplayerSessionMember^ member : session->Members)
{
Windows::Data::Json::JsonObject^ jsonMemberCustomProperties = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson);
// Parse the jsonMemberCustomProperties as desired.
Platform::String^ xboxUserId = member->XboxUserId;
Platform::String^ secureDeviceAddressBase64 = member->SecureDeviceAddressBase64;
Windows::Xbox::Networking::SecureDeviceAddress^ peerSecureDeviceAddress =
Windows::Xbox::Networking::SecureDeviceAddress::FromBase64String(secureDeviceAddressBase64);
LogComment("Got peerSecureDeviceAddress for " + xboxUserId);
// Create and use SecureDeviceAssociations using the acquired SecureDeviceAddress.
// This method is detailed in the next section.
CreateAndUsePeerAssociation(peerSecureDeviceAddress, xboxUserId);
}
}).wait();
}
For each peer’s SecureDeviceAddress, your code must create an outbound SecureDeviceAssociation object. This is the method called in the previous code after the peer SecureDeviceAddress is obtained, and it uses the previously-initialized SecureDeviceAssociationTemplate, which is based on the “PeerTraffic” template.
void Sample::CreateAndUsePeerAssociation(
SecureDeviceAddress^ peerAddress,
Platform::String^ xboxUserId
)
{
// Begin establishing a SecureDeviceAssociation to the remote device
// using the template created in InitializeSecureSockets().
// This code assumes that the remote device has performed the same
// SecureDeviceAssociationTemplate creation and is ready to accept
// a new SecureDeviceAssociation based on the same template.
IAsyncOperation<SecureDeviceAssociation^>^ asyncOp =
m_peerDeviceAssociationTemplate->CreateAssociationAsync(
peerAddress,
CreateSecureDeviceAssociationBehavior::Default
);
create_task(asyncOp)
.then([this, xboxUserId] (SecureDeviceAssociation^ association)
{
...
if(FAILED(hr))
{
LogComment("Error sending on peer socket ");
}
else
{
LogComment("Sent game data to" + xboxUserId);
}
// When a SecureDeviceAssociation is no longer desired, it can be
// explicitly disassociated gracefully by either side.
IAsyncAction^ asyncAction = association->DestroyAsync();
create_task(asyncAction).wait();
}).wait();
}
Binding to a socket follows usual Winsock practice, but requires use of IPv6 addresses. Note that some port numbers are reserved and may not be used by apps.
// Create a UDP IPv6 (cannot be IPv4) socket we will use for communicating
// with remote devices once we have SecureDeviceAssociations established to
// them. Note that most games reuse their UDP sockets for multiple
// connnections (unlike SecureDeviceAssociations, which are per device
// pair).
SOCKET mySocket = WSASocket(
AF_INET6,
SOCK_DGRAM,
IPPROTO_UDP,
NULL,
0,
WSA_FLAG_OVERLAPPED
);
if( mySocket == INVALID_SOCKET )
{
// handle error
}
// Set the v6only socket option to false. This is mandatory, as
// SecureDeviceAssociationTemplate::CreateAssociationAsync will use every
// means possible to identify the best network path to the remote device,
// including paths that leverage "dual-IP-stack" support on the socket.
// Despite how the option name might sound, apps will still only ever work
// with IPv6 addresses.
int v6only = 0;
setsockopt(
mySocket,
IPPROTO_IPV6,
IPV6_V6ONLY,
(char*) &v6only,
sizeof( v6only )
);
// Bind the socket to myUdpPortNumber (in network byte order) on any local
// address. The value of myUdpPortNumber must match what was defined as the
// bound port for the template (both the initiator and acceptor socket
// descriptions are assumed to be the same for this example).
// Note: most apps find it convenient to create the UDP socket *before* the
// SecureDeviceAssociation is created, since the same one will be reused for
// *all* peers (not be association-specific). Hence, even though we know
// we've acquired a SecureDeviceAssociation by this point, we're not
// leveraging secureDeviceAssociation->GetLocalSocketAddressBytes() to
// completely fill in a SOCKADDR_STORAGE with the IPv6 address for us. We've
// chosen to call bind here simply so that all the example socket calls are
// grouped together for easier reading.
UINT16 myUdpPortNumber = 43210; // Matches a SocketDescription in networkmanifest.xml.
SOCKADDR_IN6 sockaddrin6;
ZeroMemory( &sockaddrin6, sizeof( sockaddrin6 ) );
sockaddrin6.sin6_family = AF_INET6;
sockaddrin6.sin6_port = htons( myUdpPortNumber );
int result = bind( mySocket, (SOCKADDR*) &sockaddrin6, sizeof( sockaddrin6 ) );
if( result != 0 )
{
// handle error (such as the port already being in use)
}
Sending data on the socket follows normal Winsock practice.
// Get the remote SOCKADDR_STORAGE structure to use.
SOCKADDR_STORAGE remoteSocketAddress;
Platform::ArrayReference<BYTE> remoteSocketAddressBytes(
(BYTE*) &remoteSocketAddress,
sizeof( remoteSocketAddress )
);
secureDeviceAssociation->GetRemoteSocketAddressBytes(
remoteSocketAddressBytes
);
// Send a datagram message to the previously acquired remote address using the
// newly bound socket. The message is sent synchronously for simplicity, but
// could use overlapped I/O as well.
WSABUF wsabuf;
wsabuf.len = sizeof( msg );
wsabuf.buf = (char*) &msg;
result = WSASendTo(
mySocket,
&wsabuf,
1,
&numBytesSent,
0,
(SOCKADDR*) &remoteSocketAddress,
sizeof( remoteSocketAddress ),
NULL,
NULL
);
// Insert logic to receive desired UDP data, and continue exchanging messages
// as desired.
...
SecureDeviceAssociations can be broken by either side of the association, by destroying the SecureDeviceAssociation object.
// When a Secure Device Association is no longer desired, it can be explicitly
// disassociated gracefully by either side.
auto destroyAction = secureDeviceAssociation->DestroyAsync();
// Insert logic for waiting for destroyAction to complete.
...