I'm trying to connect two peers using SteamNetworkingSockets->ConnectP2P
which should establish a datagram relay between two peers.
My server uses CreateListenSocketP2P
which the client uses ConnectP2P and SteamNetworkingIdentity.
The issue is the client is unable to connect and I'm not sure why. I have the server running and the API loads its able to display my steam id but connection refuses..
#include <iostream>
#include <steam/steam_api.h>
#include <steam/isteamnetworking.h>
#include <steam/isteamfriends.h>
#include <steam/isteamnetworkingmessages.h>
#include <steam/isteamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include <thread>
#include <assert.h>
#define VALID_64_STEAMID 0xFFFFFFFFFFFFFFFF
bool g_bQuit = false;
class Client {
public:
SteamNetworkingIdentity sni;
ISteamNetworkingSockets* m_pInterface;
HSteamNetConnection m_hConnection;
Client() {
memset(&sni, 0, sizeof(SteamNetworkingIdentity));
sni.m_eType = k_ESteamNetworkingIdentityType_SteamID;
sni.SetSteamID64(VALID_64_STEAMID);
if (sni.IsInvalid()) {
printf("invalid id\n");
exit(0);
}
m_pInterface = SteamNetworkingSockets();
SteamNetworkingConfigValue_t opt;
opt.SetPtr(k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged, (void*)SteamNetConnectionStatusChangedCallback);
m_hConnection = m_pInterface->ConnectP2P(sni, 0, 1, &opt);
if (m_hConnection == k_HSteamNetConnection_Invalid) {
printf(":failed to connect\n");
exit(0);
}
}
void Run() {
while (!g_bQuit) {
PollIncomingMessages();
PollConnectionStateChanges();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void PollIncomingMessages() {
while (!g_bQuit)
{
ISteamNetworkingMessage* pIncomingMsg = nullptr;
int numMsgs = m_pInterface->ReceiveMessagesOnConnection(m_hConnection, &pIncomingMsg, 1);
if (numMsgs == 0)
break;
if (numMsgs < 0) {
printf("Fatal error on incoming messages\n");
exit(0);
}
printf("message received\n");
// We don't need this anymore.
pIncomingMsg->Release();
}
}
void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* pInfo)
{
assert(pInfo->m_hConn == m_hConnection || m_hConnection == k_HSteamNetConnection_Invalid);
// What's the state of the connection?
switch (pInfo->m_info.m_eState)
{
case k_ESteamNetworkingConnectionState_None:
// NOTE: We will get callbacks here when we destroy connections. You can ignore these.
break;
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
{
g_bQuit = true;
// Print an appropriate message
if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting)
{
// Note: we could distinguish between a timeout, a rejected connection,
// or some other transport problem.
printf("Unable to connect to host", pInfo->m_info.m_szEndDebug);
}
else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
printf("Lost connection to host", pInfo->m_info.m_szEndDebug);
}
else
{
// NOTE: We could check the reason code for a normal disconnection
printf("The host hath bidden us farewell. (%s)", pInfo->m_info.m_szEndDebug);
}
// Clean up the connection. This is important!
// The connection is "closed" in the network sense, but
// it has not been destroyed. We must close it on our end, too
// to finish up. The reason information do not matter in this case,
// and we cannot linger because it's already closed on the other end,
// so we just pass 0's.
m_pInterface->CloseConnection(pInfo->m_hConn, 0, nullptr, false);
m_hConnection = k_HSteamNetConnection_Invalid;
break;
}
case k_ESteamNetworkingConnectionState_Connecting:
// We will get this callback when we start connecting.
// We can ignore this.
break;
case k_ESteamNetworkingConnectionState_Connected:
printf("Connected to server OK");
break;
default:
// Silences -Wswitch
break;
}
}
static Client* s_pCallbackInstance;
static void SteamNetConnectionStatusChangedCallback(SteamNetConnectionStatusChangedCallback_t* pInfo)
{
s_pCallbackInstance->OnSteamNetConnectionStatusChanged(pInfo);
}
void PollConnectionStateChanges()
{
s_pCallbackInstance = this;
m_pInterface->RunCallbacks();
}
};
Client* Client::s_pCallbackInstance = nullptr;
int main(int argc, const char* argv[]) {
// insert code here...
if (!SteamAPI_Init()) {
printf("Failed to initialize Steamworks SDK\n");
exit(1);
}
// Get local user Steam ID
CSteamID localID = SteamUser()->GetSteamID();
printf("Local user Steam ID: %llu\n", localID.ConvertToUint64());
printf("%s\n", SteamFriends()->GetPersonaName());
ISteamNetworkingUtils* utils;
utils = SteamNetworkingUtils();
utils->InitRelayNetworkAccess();
printf("Running client\n");
//run client
Client c;
c.Run();
SteamAPI_Shutdown();
return 0;
}
It attempts to connect but then prints printf("Unable to connect to host", pInfo->m_info.m_szEndDebug);
inside
// Print an appropriate message
if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting)
{
// Note: we could distinguish between a timeout, a rejected connection,
// or some other transport problem.
printf("Unable to connect to host", pInfo->m_info.m_szEndDebug);
}
I'm confused as to why this fails, also the steam ID is valid 0xFFFFFFFFFFFFFFFF is just a placeholder..
Does anyone have experience with SteamNetworkingSockets and SteamNetworkingIdentity who could shed some light on peer to peer connectivity?
I see two issues which could cause your code to fail. How you assign SteamID and networking availability.
I tested your code although instead of providing a manual CSteamID I used SteamFriends to gather the steam id of available friends between two separate accounts and the connection went through. Even though sni.IsInvalid()
returns false I'm assuming Steam's back end has some safe guards in place to prevent two obscure accounts from communicating. One thing to also note is I'm not sure which APP_ID you're using for testing but I'll guess it's APP_ID 480 which has some limitations in place regarding matchmaking a peer to peer connectivity.
But this is how I connected the client to the host. First I made sure the two peers were friends. Then I did:
#include <iostream>
#include <steam/steam_api.h>
#include <steam/isteamnetworking.h>
#include <steam/isteamfriends.h>
#include <steam/isteamnetworkingmessages.h>
#include <steam/isteamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include <thread>
#include <assert.h>
bool g_bQuit = false;
#define TARGET_FRIEND_ID 0xFFFFFFFFFFFFFFFF
class Client {
public:
SteamNetworkingIdentity sni;
ISteamNetworkingSockets* m_pInterface;
HSteamNetConnection m_hConnection;
ISteamFriends* m_pFriends;
Client() {
bool bFoundFriend = false;
m_pFriends = SteamFriends();
m_pInterface = SteamNetworkingSockets();
memset(&sni, 0, sizeof(SteamNetworkingIdentity));
sni.m_eType = k_ESteamNetworkingIdentityType_SteamID;
//IMPORTANT BIT HERE
int nFriendCount = m_pFriends->GetFriendCount(k_EFriendFlagFriendshipRequested | k_EFriendFlagRequestingFriendship );
for(int nIndex = 0; nIndex < nFriendCount ; ++nIndex)
{
CSteamID csFriendId = m_pFriends->GetFriendByIndex(nIndex, k_EFriendFlagFriendshipRequested | k_EFriendFlagRequestingFriendship);
if(csFriendId.ConvertToUint64() == TARGET_FRIEND_ID)
{
sni.SetSteamID(csFriendId);
bFoundFriend = true;
break;
}
}
if(bFoundFriend)
{
SteamNetworkingConfigValue_t opt;
opt.SetPtr(k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged, (void*)SteamNetConnectionStatusChangedCallback);
m_hConnection = m_pInterface->ConnectP2P(sni, 0, 1, &opt);
if (m_hConnection == k_HSteamNetConnection_Invalid) {
fprintf(stderr,"Bad socket\n");
exit(0);
}
}
else
{
fprintf(stderr,"Unable locate friend");
}
}
void Run() {
while (!g_bQuit) {
PollIncomingMessages();
PollConnectionStateChanges();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void PollIncomingMessages() {
while (!g_bQuit)
{
ISteamNetworkingMessage* pIncomingMsg = nullptr;
int numMsgs = m_pInterface->ReceiveMessagesOnConnection(m_hConnection, &pIncomingMsg, 1);
if (numMsgs == 0)
break;
fprintf(stderr,"We got a message with size:%d\n",pIncomingMsg->m_cbSize);
pIncomingMsg->Release();
}
}
void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* pInfo)
{
assert(pInfo->m_hConn == m_hConnection || m_hConnection == k_HSteamNetConnection_Invalid);
switch (pInfo->m_info.m_eState)
{
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
{
fprintf(stderr,"Unable to connect to host\n");
g_bQuit = true;
m_pInterface->CloseConnection(pInfo->m_hConn, 0, nullptr, false);
m_hConnection = k_HSteamNetConnection_Invalid;
break;
}
case k_ESteamNetworkingConnectionState_Connected:
fprintf(stderr,"Connection OK\n");
break;
default:
// Silences -Wswitch
break;
}
}
static Client* s_pCallbackInstance;
static void SteamNetConnectionStatusChangedCallback(SteamNetConnectionStatusChangedCallback_t* pInfo)
{
s_pCallbackInstance->OnSteamNetConnectionStatusChanged(pInfo);
}
void PollConnectionStateChanges()
{
s_pCallbackInstance = this;
m_pInterface->RunCallbacks();
}
};
Client* Client::s_pCallbackInstance = nullptr;
int main(int argc, const char* argv[]) {
// insert code here...
if (!SteamAPI_Init()) {
fprintf(stderr,"Failed to init steam\n");
exit(1);
}
// Get local user Steam ID
CSteamID localID = SteamUser()->GetSteamID();
fprintf(stderr,"Local user Steam ID: %llu\n", localID.ConvertToUint64());
fprintf(stderr,"%s\n", SteamFriends()->GetPersonaName());
ISteamNetworkingUtils* utils;
utils = SteamNetworkingUtils();
utils->InitRelayNetworkAccess();
//ALSO VERY IMPORTANT
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
ESteamNetworkingAvailability eAvailable = utils->GetRelayNetworkStatus(nullptr);
if(k_ESteamNetworkingAvailability_Current == eAvailable)
{
//Our network is ready
//run client
Client c;
c.Run();
}
SteamAPI_Shutdown();
return 0;
}
Something you have to be wary of is you have to call m_pFriends->GetFriendCount(...)
before a call to m_pFriends->GetFriendByIndex
is made. And they both need to use the bit flags.
There are several flags you can use, but those who requested to be friends and those who you've requested as friends are the most common.
You can also compare the name's of the friend's rather than having to look up the 64bit ID of each individual peer. This is done through m_pFriends->GetFriendPersonaName()
Here's a quick example:
//IMPORTANT BIT HERE
int nFriendCount = m_pFriends->GetFriendCount(k_EFriendFlagFriendshipRequested | k_EFriendFlagRequestingFriendship );
for(int nIndex = 0; nIndex < nFriendCount ; ++nIndex)
{
CSteamID csFriendId = m_pFriends->GetFriendByIndex(nIndex, k_EFriendFlagFriendshipRequested | k_EFriendFlagRequestingFriendship);
std::string sFriendName = m_pFriends->GetFriendPersonaName(csFriendId);
if(sFriendName.compare("friendname"))
{
sni.SetSteamID(csFriendId);
bFoundFriend = true;
break;
}
}
On a final note from the steam documentation:
If you know that you are going to be using the relay network (for example, because you anticipate making P2P connections), call this to initialize the relay network. If you do not call this, the initialization will be delayed until the first time you use a feature that requires access to the relay network, which will delay that first access.
You can also call this to force a retry if the previous attempt has failed. Performing any action that requires access to the relay network will also trigger a retry, and so calling this function is never strictly necessary, but it can be useful to call it a program launch time, if access to the relay network is anticipated. Use GetRelayNetworkStatus or listen for SteamRelayNetworkStatus_t callbacks to know when initialization has completed. Typically initialization completes in a few seconds.
This is extremely important! In your code you did not check the result of GetRelayNetworkStatus and InitRelayNetworkAccess is a non blocking function which can take a few seconds to complete. You might have to loop until your status is available. In my example I just slept on the thread for 5 seconds to see if it was available.