mirror of
https://github.com/ValveSoftware/GameNetworkingSockets.git
synced 2026-05-29 16:20:34 +00:00
Merge branch 'master' into older-cmake
This commit is contained in:
@@ -99,21 +99,23 @@ if [[ $BUILD_SANITIZERS -ne 0 ]]; then
|
||||
fi
|
||||
|
||||
# Run basic tests
|
||||
build-cmake-ref/tests/test_crypto
|
||||
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/tests/test_crypto
|
||||
build-cmake-sodium25519/tests/test_crypto
|
||||
build-cmake/tests/test_crypto
|
||||
build-cmake/tests/test_connection
|
||||
build-cmake-ref/bin/test_crypto
|
||||
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/bin/test_crypto
|
||||
build-cmake-sodium25519/bin/test_crypto
|
||||
build-cmake/bin/test_crypto
|
||||
build-cmake/bin/test_connection
|
||||
|
||||
# Run sanitized builds
|
||||
if [[ $BUILD_SANITIZERS -ne 0 ]]; then
|
||||
for SANITIZER in asan ubsan tsan; do
|
||||
[[ -d build-${SANITIZER} ]] || continue
|
||||
build-${SANITIZER}/tests/test_crypto
|
||||
build-${SANITIZER}/tests/test_connection
|
||||
build-${SANITIZER}/bin/test_crypto
|
||||
build-${SANITIZER}/bin/test_connection
|
||||
done
|
||||
fi
|
||||
|
||||
# FIXME Run P2P tests?
|
||||
|
||||
set +x
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Meson build test
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
cleanup() {
|
||||
echo "Cleaning up Meson build directories" >&2
|
||||
rm -rf build-meson{,-ref,-sodium{,25519}}
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
cleanup
|
||||
|
||||
MESON_ARGS=(
|
||||
-Dlight_tests=true
|
||||
-DWerror=true
|
||||
)
|
||||
|
||||
BUILD_LIBSODIUM=1
|
||||
|
||||
# libsodium's AES implementation only works on x86_64
|
||||
[[ $(uname -m) != x86_64 ]] && BUILD_LIBSODIUM=0
|
||||
|
||||
set -x
|
||||
|
||||
# Build lightweight test builds
|
||||
meson . build-meson ${MESON_ARGS[@]} --buildtype debugoptimized
|
||||
meson . build-meson-ref ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=Reference
|
||||
[[ $BUILD_LIBSODIUM -ne 0 ]] && meson . build-meson-sodium ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto=libsodium -Duse_crypto25519=libsodium
|
||||
meson . build-meson-sodium25519 ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=libsodium
|
||||
|
||||
# Build all targets
|
||||
ninja -v -C build-meson
|
||||
[[ $BUILD_LIBSODIUM -ne 0 ]] && ninja -v -C build-meson-sodium
|
||||
ninja -v -C build-meson-sodium25519
|
||||
ninja -v -C build-meson-ref
|
||||
|
||||
# Run basic tests
|
||||
build-meson/tests/test_crypto
|
||||
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-meson-sodium/tests/test_crypto
|
||||
build-meson-sodium25519/tests/test_crypto
|
||||
build-meson-ref/tests/test_crypto
|
||||
|
||||
set +x
|
||||
|
||||
exit 0
|
||||
+6
-6
@@ -52,8 +52,8 @@ fi
|
||||
|
||||
msg "Image is $IMAGE:$IMAGE_TAG, sanitizers enabled: $BUILD_SANITIZERS"
|
||||
|
||||
# We need at least one build system available
|
||||
has cmake || has meson || die "No build system available"
|
||||
# Make sure cmake is installed
|
||||
has cmake || die "cmake required"
|
||||
|
||||
# We also need at least one compiler
|
||||
has_clang || has_gcc || die "No compiler available"
|
||||
@@ -63,16 +63,16 @@ ccache -M4G
|
||||
|
||||
if has_clang; then
|
||||
msg "Beginning build tests with Clang"
|
||||
export CC="ccache clang" CXX="ccache clang++"
|
||||
has meson && bash .travis/build-meson.sh
|
||||
#export CC="ccache clang" CXX="ccache clang++"
|
||||
#has meson && bash .travis/build-meson.sh
|
||||
export CC=clang CXX=clang++
|
||||
has cmake && bash .travis/build-cmake.sh
|
||||
fi
|
||||
|
||||
if has_gcc; then
|
||||
msg "Beginning build tests with GCC"
|
||||
export CC="ccache gcc" CXX="ccache g++"
|
||||
has meson && bash .travis/build-meson.sh
|
||||
#export CC="ccache gcc" CXX="ccache g++"
|
||||
#has meson && bash .travis/build-meson.sh
|
||||
export CC=gcc CXX=g++
|
||||
has cmake && bash .travis/build-cmake.sh
|
||||
fi
|
||||
|
||||
@@ -18,6 +18,7 @@ PACKAGES=(
|
||||
pkgconf
|
||||
git
|
||||
linux-headers
|
||||
go
|
||||
)
|
||||
|
||||
PACKAGES+=(protobuf-dev)
|
||||
|
||||
@@ -12,6 +12,7 @@ PACKAGES=(
|
||||
cmake
|
||||
meson
|
||||
git
|
||||
golang
|
||||
)
|
||||
|
||||
PACKAGES+=(protobuf)
|
||||
|
||||
@@ -16,6 +16,7 @@ PACKAGES=(
|
||||
pkgconf-pkg-config
|
||||
git
|
||||
make
|
||||
golang
|
||||
)
|
||||
|
||||
PACKAGES+=(protobuf-compiler protobuf-devel)
|
||||
|
||||
@@ -10,7 +10,7 @@ apt-get update
|
||||
apt-get install -y locales
|
||||
locale-gen en_US.UTF-8
|
||||
|
||||
PACKAGES=(build-essential pkg-config ccache cmake meson clang git)
|
||||
PACKAGES=(build-essential pkg-config ccache cmake meson clang git golang)
|
||||
|
||||
PACKAGES+=(libprotobuf-dev protobuf-compiler)
|
||||
PACKAGES+=(libssl-dev)
|
||||
|
||||
+15
-21
@@ -3,7 +3,8 @@ Building
|
||||
|
||||
## Dependencies
|
||||
|
||||
* CMake or Meson, and build tool like Ninja, GNU Make or Visual Studio
|
||||
* CMake 3.10 or later
|
||||
* A build tool like Ninja, GNU Make or Visual Studio
|
||||
* A C++11-compliant compiler, such as:
|
||||
* GCC 7.3 or later
|
||||
* Clang 3.3 or later
|
||||
@@ -13,8 +14,12 @@ Building
|
||||
* OpenSSL 1.1.x, plus ed25519-donna and curve25519-donna. (We've made some
|
||||
minor changes, so the source is included in this project.)
|
||||
* libsodium
|
||||
* [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/) (windows only)
|
||||
* [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/)
|
||||
(windows only)
|
||||
* Google protobuf 2.6.1+
|
||||
* Google [webrtc](https://opensource.google/projects/webrtc) is used for
|
||||
NAT piercing (ICE) for P2P connections. The relevant code is linked in as a
|
||||
git submodule. You'll need to initialize that submodule to compile.
|
||||
|
||||
## Linux
|
||||
|
||||
@@ -38,14 +43,7 @@ Arch Linux:
|
||||
|
||||
### Building
|
||||
|
||||
Using Meson:
|
||||
|
||||
```
|
||||
$ meson . build
|
||||
$ ninja -C build
|
||||
```
|
||||
|
||||
Or CMake:
|
||||
Using CMake (preferred):
|
||||
|
||||
```
|
||||
$ mkdir build
|
||||
@@ -56,8 +54,7 @@ $ ninja
|
||||
|
||||
## Windows / Visual Studio
|
||||
|
||||
On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager. Or setup the
|
||||
dependencies by hand, which is a bit of an arduous gauntlet.
|
||||
On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager.
|
||||
|
||||
### vcpkg
|
||||
|
||||
@@ -77,6 +74,8 @@ To use libsodium as the crypto backend rather than OpenSSL, install `gamenetwork
|
||||
|
||||
### Manual setup
|
||||
|
||||
Setting up the dependencies by hand is a bit of an arduous gauntlet.
|
||||
|
||||
#### OpenSSL
|
||||
|
||||
You can install the [OpenSSL binaries](https://slproweb.com/products/Win32OpenSSL.html)
|
||||
@@ -224,7 +223,6 @@ a 32-bit build, install the i686 versions of these packages):
|
||||
$ pacman -S \
|
||||
git \
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-meson \
|
||||
mingw-w64-x86_64-openssl \
|
||||
mingw-w64-x86_64-pkg-config \
|
||||
mingw-w64-x86_64-protobuf
|
||||
@@ -235,8 +233,10 @@ And finally, clone the repository and build it:
|
||||
```
|
||||
$ git clone https://github.com/ValveSoftware/GameNetworkingSockets.git
|
||||
$ cd GameNetworkingSockets
|
||||
$ meson . build
|
||||
$ ninja -C build
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -G Ninja ..
|
||||
$ ninja
|
||||
```
|
||||
|
||||
**NOTE:** When building with MSYS2, be sure you launch the correct version of
|
||||
@@ -262,9 +262,3 @@ This extension allows for configuring the CMake project and building it from
|
||||
within the Visual Studio Code IDE.
|
||||
|
||||
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=vector-of-bool.cmake-tools
|
||||
|
||||
### Meson by Ali Sabil
|
||||
This extension comes in handy if you're editing the Meson build files.
|
||||
|
||||
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=asabil.meson
|
||||
|
||||
|
||||
+7
-4
@@ -4,6 +4,11 @@ include(CMakeDependentOption)
|
||||
include(CMakePushCheckState)
|
||||
include(CheckSymbolExists)
|
||||
|
||||
# Put all the output from all projects into the same folder
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
project(GameNetworkingSockets C CXX)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
@@ -140,9 +145,7 @@ endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
message(STATUS "---------------------------------------------------------")
|
||||
#message(STATUS "---------------------------------------------------------")
|
||||
message(STATUS "Crypto library for AES/SHA256: ${USE_CRYPTO}")
|
||||
message(STATUS "Crypto library for ed25519/curve25519: ${USE_CRYPTO25519}")
|
||||
message(STATUS "---------------------------------------------------------")
|
||||
|
||||
# vim: set ts=4 sts=4 sw=4 noet:
|
||||
#message(STATUS "---------------------------------------------------------")
|
||||
|
||||
@@ -69,8 +69,11 @@ to facilitate binding to other languages.
|
||||
|
||||
Third party language bindings:
|
||||
|
||||
* C#: <https://github.com/nxrighthere/ValveSockets-CSharp>
|
||||
* Go: <https://github.com/nielsAD/gns/>
|
||||
* C#:
|
||||
* <https://github.com/nxrighthere/ValveSockets-CSharp>
|
||||
* <https://github.com/Facepunch/Facepunch.Steamworks>
|
||||
* Go:
|
||||
* <https://github.com/nielsAD/gns/>
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
||||
+23
-2
@@ -1,6 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(gns_example C CXX)
|
||||
|
||||
find_library(golang REQUIRED)
|
||||
|
||||
#
|
||||
# Trivial signaling server, written in go
|
||||
#
|
||||
set(SIGNAL_SERVER_TARGET trivial_signaling_server)
|
||||
set(SIGNAL_SERVER_OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/trivial_signaling_server)
|
||||
set(SIGNAL_SERVER_SRCS
|
||||
trivial_signaling_server.go
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${SIGNAL_SERVER_OUTPUT}
|
||||
DEPENDS ${SIGNAL_SERVER_SRCS}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Building GO Trivial signaling server"
|
||||
COMMAND env go build -o "${SIGNAL_SERVER_OUTPUT}" ${CMAKE_GO_FLAGS} ${SIGNAL_SERVER_SRCS}
|
||||
)
|
||||
add_custom_target(${SIGNAL_SERVER_TARGET} ALL DEPENDS ${SIGNAL_SERVER_OUTPUT})
|
||||
|
||||
#
|
||||
# Really simple client/server chat
|
||||
#
|
||||
|
||||
add_executable(
|
||||
example_chat
|
||||
example_chat.cpp)
|
||||
@@ -15,5 +38,3 @@ target_link_libraries(example_chat GameNetworkingSockets::GameNetworkingSockets)
|
||||
if(COMMAND add_sanitizers)
|
||||
add_sanitizers(example_chat)
|
||||
endif()
|
||||
|
||||
# vim: set ts=4 sts=4 sw=4 noet:
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
cxx_compiler = meson.get_compiler('cpp')
|
||||
|
||||
dependencies = [
|
||||
dep_threads,
|
||||
dep_GameNetworkingSockets_so # Declared in the other project
|
||||
]
|
||||
|
||||
executable('example_chat',
|
||||
'example_chat.cpp',
|
||||
dependencies: dependencies,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
// Client of our dummy trivial signaling server service.
|
||||
// Serves as an example of you how to hook up signaling server
|
||||
// to SteamNetworkingSockets P2P connections
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <assert.h>
|
||||
|
||||
#include "trivial_signaling_client.h"
|
||||
#include <steam/isteamnetworkingsockets.h>
|
||||
#include <steam/isteamnetworkingutils.h>
|
||||
|
||||
#include "../tests/test_common.h"
|
||||
|
||||
#ifdef POSIX
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
typedef int SOCKET;
|
||||
constexpr SOCKET INVALID_SOCKET = -1;
|
||||
inline void closesocket( SOCKET s ) { close(s); }
|
||||
inline int GetSocketError() { return errno; }
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#endif
|
||||
|
||||
inline int HexDigitVal( char c )
|
||||
{
|
||||
if ( '0' <= c && c <= '9' )
|
||||
return c - '0';
|
||||
if ( 'a' <= c && c <= 'f' )
|
||||
return c - 'a' + 0xa;
|
||||
if ( 'A' <= c && c <= 'F' )
|
||||
return c - 'A' + 0xa;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Implementation of ITrivialSignalingClient
|
||||
class CTrivialSignalingClient : public ITrivialSignalingClient
|
||||
{
|
||||
|
||||
// This is the thing we'll actually create to send signals for a particular
|
||||
// connection.
|
||||
struct ConnectionSignaling : ISteamNetworkingConnectionCustomSignaling
|
||||
{
|
||||
CTrivialSignalingClient *const m_pOwner;
|
||||
std::string const m_sPeerIdentity; // Save off the string encoding of the identity we're talking to
|
||||
|
||||
ConnectionSignaling( CTrivialSignalingClient *owner, const char *pszPeerIdentity )
|
||||
: m_pOwner( owner )
|
||||
, m_sPeerIdentity( pszPeerIdentity )
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Implements ISteamNetworkingConnectionCustomSignaling
|
||||
//
|
||||
|
||||
// This is called from SteamNetworkingSockets to send a signal. This could be called from any thread,
|
||||
// so we need to be threadsafe, and avoid duoing slow stuff or calling back into SteamNetworkingSockets
|
||||
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) override
|
||||
{
|
||||
|
||||
// We'll use a dumb hex encoding.
|
||||
std::string signal;
|
||||
signal.reserve( m_sPeerIdentity.length() + cbMsg*2 + 4 );
|
||||
signal.append( m_sPeerIdentity );
|
||||
signal.push_back( ' ' );
|
||||
for ( const uint8_t *p = (const uint8_t *)pMsg ; cbMsg > 0 ; --cbMsg, ++p )
|
||||
{
|
||||
static const char hexdigit[] = "0123456789abcdef";
|
||||
signal.push_back( hexdigit[ *p >> 4U ] );
|
||||
signal.push_back( hexdigit[ *p & 0xf ] );
|
||||
}
|
||||
signal.push_back('\n');
|
||||
|
||||
m_pOwner->Send( signal );
|
||||
}
|
||||
|
||||
// Self destruct. This will be called by SteamNetworkingSockets when it's done with us.
|
||||
virtual void Release() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
sockaddr_storage m_adrServer;
|
||||
size_t const m_adrServerSize;
|
||||
ISteamNetworkingSockets *const m_pSteamNetworkingSockets;
|
||||
std::string m_sGreeting;
|
||||
|
||||
std::mutex sockMutex;
|
||||
SOCKET m_sock;
|
||||
std::string m_sBufferedData;
|
||||
|
||||
void CloseSocket()
|
||||
{
|
||||
if ( m_sock != INVALID_SOCKET )
|
||||
{
|
||||
closesocket( m_sock );
|
||||
m_sock = INVALID_SOCKET;
|
||||
}
|
||||
m_sBufferedData.clear();
|
||||
}
|
||||
|
||||
void Connect()
|
||||
{
|
||||
CloseSocket();
|
||||
|
||||
int sockType = SOCK_STREAM;
|
||||
#ifdef LINUX
|
||||
sockType |= SOCK_CLOEXEC;
|
||||
#endif
|
||||
#if !defined( _WIN32 )
|
||||
sockType |= SOCK_NONBLOCK;
|
||||
#endif
|
||||
m_sock = socket( m_adrServer.ss_family, sockType, IPPROTO_TCP );
|
||||
if ( m_sock == INVALID_SOCKET )
|
||||
{
|
||||
TEST_Printf( "socket() failed, error=%d\n", GetSocketError() );
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
opt = 1;
|
||||
if ( ioctlsocket( sock, FIONBIO, (unsigned long*)&opt ) == -1 )
|
||||
{
|
||||
CloseSocket();
|
||||
TEST_Printf( "ioctlsocket() failed, error=%d\n", GetSocketError() );
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
connect( m_sock, (const sockaddr *)&m_adrServer, (socklen_t )m_adrServerSize );
|
||||
}
|
||||
|
||||
public:
|
||||
CTrivialSignalingClient( const sockaddr *adrServer, size_t adrServerSize, ISteamNetworkingSockets *pSteamNetworkingSockets )
|
||||
: m_adrServerSize( adrServerSize ), m_pSteamNetworkingSockets( pSteamNetworkingSockets )
|
||||
{
|
||||
memcpy( &m_adrServer, adrServer, adrServerSize );
|
||||
m_sock = INVALID_SOCKET;
|
||||
|
||||
// Save off our identity
|
||||
SteamNetworkingIdentity identitySelf; identitySelf.Clear();
|
||||
pSteamNetworkingSockets->GetIdentity( &identitySelf );
|
||||
assert( !identitySelf.IsInvalid() );
|
||||
assert( !identitySelf.IsLocalHost() ); // We need something more specific than that
|
||||
m_sGreeting = SteamNetworkingIdentityRender( identitySelf ).c_str();
|
||||
assert( strchr( m_sGreeting.c_str(), ' ' ) == nullptr ); // Our protocol is dumb and doesn't support this
|
||||
m_sGreeting.push_back( '\n' );
|
||||
|
||||
// Begin connecting immediately
|
||||
Connect();
|
||||
}
|
||||
|
||||
// Send the signal.
|
||||
void Send( const std::string &s )
|
||||
{
|
||||
assert( s.length() > 0 && s[ s.length()-1 ] == '\n' ); // All of our signals are '\n'-terminated
|
||||
|
||||
sockMutex.lock();
|
||||
if ( m_sock != INVALID_SOCKET )
|
||||
{
|
||||
int l = s.length();
|
||||
int r = ::send( m_sock, s.c_str(), s.length(), 0 );
|
||||
if ( r != l && r != 0 )
|
||||
{
|
||||
// Socket hosed, or we sent a partial signal.
|
||||
// We need to restart connection
|
||||
TEST_Printf( "Failed to send %d bytes to trivial signaling server. send() returned %d, errno=%d. Closing and restarting connection.\n",
|
||||
l, r, GetSocketError() );
|
||||
CloseSocket();
|
||||
}
|
||||
}
|
||||
sockMutex.unlock();
|
||||
}
|
||||
|
||||
//
|
||||
// Implements ISteamNetworkingCustomSignalingService
|
||||
//
|
||||
virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection(
|
||||
const SteamNetworkingIdentity &identityPeer,
|
||||
const char *pszRoutingInfo,
|
||||
SteamNetworkingErrMsg &errMsg
|
||||
) override {
|
||||
SteamNetworkingIdentityRender sIdentityPeer( identityPeer );
|
||||
|
||||
// FIXME - here we really ouight to confirm that the string version of the
|
||||
// identity does not have spaces, since our protocol doesn't permit it.
|
||||
|
||||
return new ConnectionSignaling( this, sIdentityPeer.c_str() );
|
||||
}
|
||||
|
||||
virtual void Poll() override
|
||||
{
|
||||
// Drain the socket into the buffer, and check for reconnecting
|
||||
sockMutex.lock();
|
||||
if ( m_sock == INVALID_SOCKET )
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
char buf[256];
|
||||
int r = recv( m_sock, buf, sizeof(buf), 0 );
|
||||
if ( r == 0 )
|
||||
break;
|
||||
if ( r < 0 )
|
||||
{
|
||||
int e = GetSocketError();
|
||||
if ( e != EAGAIN && e != EWOULDBLOCK )
|
||||
{
|
||||
TEST_Printf( "Failed to recv from trivial signaling server. recv() returned %d, errno=%d. Closing and restarting connection\n", r, e );
|
||||
CloseSocket();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_sBufferedData.append( buf, r );
|
||||
}
|
||||
}
|
||||
|
||||
// Release the lock now. See the notes below about why it's very important
|
||||
// to release the lock early and not hold it while we try to dispatch the
|
||||
// received callbacks.
|
||||
sockMutex.unlock();
|
||||
|
||||
// Now dispatch any buffered signals
|
||||
for (;;)
|
||||
{
|
||||
|
||||
// Find end of line. Do we have a complete signal?
|
||||
size_t l = m_sBufferedData.find( '\n' );
|
||||
if ( l == std::string::npos )
|
||||
break;
|
||||
|
||||
// Locate the space that seperates [from] [payload]
|
||||
size_t spc = m_sBufferedData.find( ' ' );
|
||||
if ( spc != std::string::npos && spc < l )
|
||||
{
|
||||
|
||||
// Hex decode the payload. As it turns out, we actually don't
|
||||
// need the sender's identity. The payload has everything needed
|
||||
// to process the message. Maybe we should remove it from our
|
||||
// dummy signaling protocol? It might be useful for debugging, tho.
|
||||
std::string data; data.reserve( ( l - spc ) / 2 );
|
||||
for ( size_t i = spc+1 ; i+2 <= l ; i += 2 )
|
||||
{
|
||||
int h = HexDigitVal( m_sBufferedData[i] );
|
||||
int l = HexDigitVal( m_sBufferedData[i+1] );
|
||||
if ( ( h | l ) & ~0xf )
|
||||
{
|
||||
// Failed hex decode. Not a bug in our code here, but this is just example code, so we'll handle it this way
|
||||
assert( !"Failed hex decode from signaling server?!" );
|
||||
goto next_message;
|
||||
}
|
||||
data.push_back( (char)(h<<4 | l ) );
|
||||
}
|
||||
|
||||
// Setup a context object that can respond if this signal is a connection request.
|
||||
struct Context : ISteamNetworkingCustomSignalingRecvContext
|
||||
{
|
||||
CTrivialSignalingClient *m_pOwner;
|
||||
|
||||
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest(
|
||||
HSteamNetConnection hConn,
|
||||
const SteamNetworkingIdentity &identityPeer,
|
||||
int nLocalVirtualPort
|
||||
) override {
|
||||
|
||||
// We will just always handle requests thorugh the usual listen socket state
|
||||
// machine. See the docuemntation for this function for other behaviour we
|
||||
// might take.
|
||||
|
||||
// Also, note that if there was routing/session info, it should have been in
|
||||
// our envelope that we know how to parse, and we should save it off in this
|
||||
// context object.
|
||||
SteamNetworkingErrMsg ignoreErrMsg;
|
||||
return m_pOwner->CreateSignalingForConnection( identityPeer, nullptr, ignoreErrMsg );
|
||||
}
|
||||
|
||||
virtual void SendRejectionSignal(
|
||||
const SteamNetworkingIdentity &identityPeer,
|
||||
const void *pMsg, int cbMsg
|
||||
) override {
|
||||
|
||||
// We'll just silently ignore all failures. This is actually the more secure
|
||||
// Way to handle it in many cases. Actively returning failure might allow
|
||||
// an attacker to just scrape random peers to see who is online. If you know
|
||||
// the peer has a good reason for trying to connect, sending an active failure
|
||||
// can improve error handling and the UX, instead of relying on timeout. But
|
||||
// just consider the security implications.
|
||||
}
|
||||
};
|
||||
Context context;
|
||||
context.m_pOwner = this;
|
||||
|
||||
// Dispatch.
|
||||
// Remember: From inside this function, our context object might get callbacks.
|
||||
// And we might get asked to send signals, either now, or really at any time
|
||||
// from any thread! If possible, avoid calling this function while holding locks.
|
||||
// To process this call, SteamnetworkingSockets will need take its own internal lock.
|
||||
// That lock may be held by another thread that is asking you to send a signal! So
|
||||
// be warned that deadlocks are a possibility here.
|
||||
m_pSteamNetworkingSockets->ReceivedP2PCustomSignal( data.c_str(), data.length(), &context );
|
||||
}
|
||||
|
||||
next_message:
|
||||
m_sBufferedData.erase( 0, l );
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Release()
|
||||
{
|
||||
// NOTE: Here we are assuming that the calling code has already cleaned
|
||||
// up all the connections, to keep the example simple.
|
||||
CloseSocket();
|
||||
}
|
||||
};
|
||||
|
||||
// Start connecting to the signaling server.
|
||||
ITrivialSignalingClient *CreateTrivialSignalingClient(
|
||||
const char *pszServerAddress, // Address of the server.
|
||||
ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them?
|
||||
SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail
|
||||
) {
|
||||
|
||||
std::string sAddress( pszServerAddress );
|
||||
std::string sService;
|
||||
size_t colon = sAddress.find( ':' );
|
||||
if ( colon == std::string::npos )
|
||||
{
|
||||
sService = "10000"; // Default port
|
||||
}
|
||||
else
|
||||
{
|
||||
sService = sAddress.substr( colon+1 );
|
||||
sAddress.erase( colon );
|
||||
}
|
||||
|
||||
// Resolve name synchronously
|
||||
addrinfo *pAddrInfo = nullptr;
|
||||
int r = getaddrinfo( sAddress.c_str(), sService.c_str(), nullptr, &pAddrInfo );
|
||||
if ( r != 0 || pAddrInfo == nullptr )
|
||||
{
|
||||
sprintf( errMsg, "Invalid/unknown server address. getaddrinfo returned %d", r );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *pClient = new CTrivialSignalingClient( pAddrInfo->ai_addr, pAddrInfo->ai_addrlen, pSteamNetworkingSockets );
|
||||
|
||||
freeaddrinfo( pAddrInfo );
|
||||
|
||||
return pClient;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Client of our dummy trivial signaling server service.
|
||||
// Serves as an example of you how to hook up signaling server
|
||||
// to SteamNetworkingSockets P2P connections
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <steam/steamnetworkingcustomsignaling.h>
|
||||
class ISteamNetworkingSockets;
|
||||
|
||||
// FIXME - Eventually I intend to add a mechanism to set the default
|
||||
// signaling service, so that SteamnetworkingSockets can
|
||||
// initiate creation of signaling sessions. This will be the
|
||||
// interface used for that.
|
||||
/// Interface used to create signaling sessions to particular peers.
|
||||
/// Typically this represents a connection to some service.
|
||||
class ISteamNetworkingCustomSignalingService
|
||||
{
|
||||
public:
|
||||
|
||||
/// Setup a session for sending signals for a particular connection.
|
||||
/// The signals will always be sent to the same peer.
|
||||
///
|
||||
/// pszSessionRoutingInfo is reserved for future use, it will always
|
||||
/// be NULL right now.
|
||||
///
|
||||
/// On failure, return NULL
|
||||
virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection(
|
||||
const SteamNetworkingIdentity &identityPeer,
|
||||
const char *pszRoutingInfo,
|
||||
SteamNetworkingErrMsg &errMsg
|
||||
) = 0;
|
||||
};
|
||||
|
||||
/// Interface to our client.
|
||||
class ITrivialSignalingClient : public ISteamNetworkingCustomSignalingService
|
||||
{
|
||||
public:
|
||||
|
||||
/// Poll the server for incoming signals and dispatch them.
|
||||
/// We use polling in this example just to keep it simple.
|
||||
/// You could use a service thread.
|
||||
virtual void Poll() = 0;
|
||||
|
||||
/// Disconnect from the server and close down our polling thread.
|
||||
virtual void Release() = 0;
|
||||
};
|
||||
|
||||
// Start connecting to the signaling server.
|
||||
ITrivialSignalingClient *CreateTrivialSignalingClient(
|
||||
const char *address, // Address:port
|
||||
ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them?
|
||||
SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Really simple P2P signaling server.
|
||||
//
|
||||
// When establishing peer-to-peer connections, the peers
|
||||
// need some sort of pre-arranged side channels that they
|
||||
// can use to exchange messages. This channel is assumed
|
||||
// to be relatively low bandwidth and high latency. This
|
||||
// service is often called "signaling".
|
||||
//
|
||||
// This server has the following really simple protocol:
|
||||
// It listens on a particular TCP port. Clients connect
|
||||
// raw TCP. The protocol is text-based and line oriented,
|
||||
// so it is easy to test using telnet. When a client
|
||||
// connects, it should send its identity on the first line.
|
||||
// Afterwards, clients can send a message to a peer by
|
||||
// sending a line formatted as follows:
|
||||
//
|
||||
// DESTINATION_IDENTITY PAYLOAD
|
||||
//
|
||||
// Identites may not contain spaces, and the payload
|
||||
// should be plain ASCII text. (Hex or base64 encode it).
|
||||
//
|
||||
// If there is a client with that destination identity,
|
||||
// then the server will forward the message on. Otherwise
|
||||
// it is discarded.
|
||||
//
|
||||
// Forwarded messages have basically the same format and
|
||||
// are the only type of message the server ever sends to the
|
||||
// client. The only difference is that the identity is the
|
||||
// identity of the sender.
|
||||
//
|
||||
// This is just an example code to illustrate what a
|
||||
// signaling service is. A real production server would
|
||||
// probably need to be able to scale across multiple
|
||||
// processes, and provide authentication and rate
|
||||
// limiting.
|
||||
//
|
||||
// Note that SteamNetworkingSockets use of signaling
|
||||
// service does NOT assume guaranteed delivery.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
"net"
|
||||
"flag"
|
||||
"bufio"
|
||||
"log"
|
||||
)
|
||||
|
||||
const DEFAULT_LISTEN_PORT = 10000
|
||||
|
||||
// Current list of client connections
|
||||
var g_mapClientConnections = make(map[string]net.Conn)
|
||||
|
||||
// Goroutine to service a client connection
|
||||
func ServiceConnection(conn net.Conn) {
|
||||
|
||||
// Save off address
|
||||
addr := conn.RemoteAddr().String()
|
||||
|
||||
// Attach a Reader object to the connection, so we can read from it easily
|
||||
in := bufio.NewReader(conn)
|
||||
|
||||
// In our trivial protocol, the first line contains the client identity
|
||||
// on a line by itself
|
||||
intro,err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Printf( "[%s] Aborting connection before we ever received client identity", addr )
|
||||
conn.Close()
|
||||
}
|
||||
identity := strings.TrimSpace(intro)
|
||||
|
||||
// Amnnnnnd that's it. No authentication.
|
||||
|
||||
// Locate existing connection, if any.
|
||||
existingConn := g_mapClientConnections[identity]
|
||||
|
||||
// Add us to map or replace existing entry
|
||||
g_mapClientConnections[identity] = conn
|
||||
|
||||
// Now handle existing entry
|
||||
if existingConn != nil {
|
||||
log.Printf( "[%s@%s] Closing connection to make room for new connection from '%s'", identity, existingConn.RemoteAddr().String(), addr )
|
||||
existingConn.Close()
|
||||
}
|
||||
log.Printf( "[%s@%s] Added connection", identity, addr )
|
||||
|
||||
// Keep reading until connection is closed
|
||||
for {
|
||||
line,err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
|
||||
// Are we stil in the map?
|
||||
if g_mapClientConnections[identity] == conn {
|
||||
log.Printf( "[%s@%s] Connecton closed. %s", identity, addr, err )
|
||||
delete(g_mapClientConnections, identity)
|
||||
} else {
|
||||
// Assume it's because we got replaced by another connection.
|
||||
// The other connection already logged, so don't do anything here
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Our protocol is just [destination peer identity] [payload]
|
||||
// And everything is in text.
|
||||
dest_and_msg := strings.SplitN(line, " ", 2)
|
||||
if len(dest_and_msg) != 2 {
|
||||
log.Printf("[%s@%s] Ignoring weird input '%s' (maybe truncated?)", identity, addr, line)
|
||||
continue
|
||||
}
|
||||
dest_identity := strings.TrimSpace(dest_and_msg[0]);
|
||||
payload := dest_and_msg[1]
|
||||
|
||||
// Locate the destination peer's connection.
|
||||
dest_conn := g_mapClientConnections[dest_identity]
|
||||
if dest_conn == nil {
|
||||
log.Printf("[%s@%s] Ignoring, destination peer '%s' not found", identity, addr, dest_identity)
|
||||
continue
|
||||
}
|
||||
|
||||
// Format new message, putting the sender's identity in front.
|
||||
msg := identity + " " + payload
|
||||
|
||||
// Send to the peer
|
||||
dest_conn.Write([]byte(msg))
|
||||
|
||||
// Lawg
|
||||
log.Printf("[%s@%s] -> %s (%d chars)", identity, addr, dest_identity, len(payload))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main entry point
|
||||
func main() {
|
||||
|
||||
// Parse command line flags
|
||||
port := flag.Int("port", DEFAULT_LISTEN_PORT, "Port to listen on")
|
||||
flag.Parse()
|
||||
listen_addr := fmt.Sprintf("0.0.0.0:%d", *port)
|
||||
|
||||
// Start listening
|
||||
listener, err := net.Listen("tcp", listen_addr)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
log.Printf("Listening at %s", listen_addr)
|
||||
|
||||
// Main loop
|
||||
for {
|
||||
|
||||
// Wait for the next incoming connection
|
||||
conn,err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
// Start goroutine to service it
|
||||
go ServiceConnection(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -557,9 +557,9 @@ public:
|
||||
/// NOTE: The routing blob returned here is not encrypted. Send it to your backend
|
||||
/// and don't share it directly with clients.
|
||||
virtual EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ) = 0;
|
||||
|
||||
#endif // #ifndef STEAMNETWORKINGSOCKETS_ENABLE_SDR
|
||||
|
||||
|
||||
//
|
||||
// Relayed connections using custom signaling protocol
|
||||
//
|
||||
@@ -661,87 +661,15 @@ protected:
|
||||
};
|
||||
#define STEAMNETWORKINGSOCKETS_INTERFACE_VERSION "SteamNetworkingSockets009"
|
||||
|
||||
/// Interface used to send signaling messages for a particular connection.
|
||||
/// You will need to construct one of these per connection.
|
||||
///
|
||||
/// - For connections initiated locally, you will construct it and pass
|
||||
/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling.
|
||||
/// - For connections initiated remotely and "accepted" locally, you
|
||||
/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest
|
||||
class ISteamNetworkingConnectionCustomSignaling
|
||||
{
|
||||
public:
|
||||
/// Called to send a rendezvous message to the remote peer. This may be called
|
||||
/// from any thread, at any time, so you need to be thread-safe! Don't take
|
||||
/// any locks that might hold while calling into SteamNetworkingSockets functions,
|
||||
/// because this could lead to deadlocks.
|
||||
///
|
||||
/// Note that when initiating a connection, we may not know the identity
|
||||
/// of the peer, if you did not specify it in ConnectP2PCustomSignaling.
|
||||
///
|
||||
/// Return true if a best-effort attempt was made to deliver the message.
|
||||
/// If you return false, it is assumed that the situation is fatal;
|
||||
/// the connection will be closed, and Release() will be called
|
||||
/// eventually.
|
||||
///
|
||||
/// Signaling objects will not be shared between connections.
|
||||
/// You can assume that the same value of hConn will be used
|
||||
/// every time.
|
||||
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0;
|
||||
|
||||
/// Called when the connection no longer needs to send signals.
|
||||
/// Note that this happens eventually (but not immediately) after
|
||||
/// the connection is closed. Signals may need to be sent for a brief
|
||||
/// time after the connection is closed, to clean up the connection.
|
||||
virtual void Release() = 0;
|
||||
};
|
||||
|
||||
/// Interface used when a custom signal is received.
|
||||
/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal
|
||||
class ISteamNetworkingCustomSignalingRecvContext
|
||||
{
|
||||
public:
|
||||
|
||||
/// Called when the signal represents a request for a new connection.
|
||||
///
|
||||
/// If you want to ignore the request, just return NULL. In this case,
|
||||
/// the peer will NOT receive any reply. You should consider ignoring
|
||||
/// requests rather than actively rejecting them, as a security measure.
|
||||
/// If you actively reject requests, then this makes it possible to detect
|
||||
/// if a user is online or not, just by sending them a request.
|
||||
///
|
||||
/// If you wish to send back a rejection, then use
|
||||
/// ISteamNetworkingSockets::CloseConnection() and then return NULL.
|
||||
/// We will marshal a properly formatted rejection signal and
|
||||
/// call SendRejectionSignal() so you can send it to them.
|
||||
///
|
||||
/// If you return a signaling object, the connection is NOT immediately
|
||||
/// accepted by default. Instead, it stays in the "connecting" state,
|
||||
/// and the usual callback is posted, and your app can accept the
|
||||
/// connection using ISteamNetworkingSockets::AcceptConnection. This
|
||||
/// may be useful so that these sorts of connections can be more similar
|
||||
/// to your application code as other types of connections accepted on
|
||||
/// a listen socket. If this is not useful and you want to skip this
|
||||
/// callback process and immediately accept the connection, call
|
||||
/// ISteamNetworkingSockets::AcceptConnection before returning the
|
||||
/// signaling object.
|
||||
///
|
||||
/// After accepting a connection (through either means), the connection
|
||||
/// will transition into the "finding route" state.
|
||||
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0;
|
||||
|
||||
/// This is called actively communication rejection or failure
|
||||
/// to the incoming message. If you intend to ignore all incoming requests
|
||||
/// that you do not wish to accept, then it's not strictly necessary to
|
||||
/// implement this.
|
||||
virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0;
|
||||
};
|
||||
// Global accessor.
|
||||
#if defined( STEAMNETWORKINGSOCKETS_PARTNER )
|
||||
|
||||
// Standalone lib
|
||||
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets();
|
||||
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets();
|
||||
// Standalone lib. Use different symbol name, so that we can dynamically switch between steamclient.dll
|
||||
// and the standalone lib
|
||||
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_Lib();
|
||||
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets_Lib();
|
||||
inline ISteamNetworkingSockets *SteamNetworkingSockets() { return SteamNetworkingSockets_Lib(); }
|
||||
inline ISteamNetworkingSockets *SteamGameServerNetworkingSockets() { return SteamGameServerNetworkingSockets_Lib(); }
|
||||
|
||||
#elif defined( STEAMNETWORKINGSOCKETS_OPENSOURCE ) || defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//====== Copyright Valve Corporation, All rights reserved. ====================
|
||||
//
|
||||
// Interfaces needed to implement your own P2P signaling service. If you
|
||||
// aren't using P2P connections, or you can use the default service provided
|
||||
// by the platform (e.g. a typical Steam game), then you don't need anything
|
||||
// in this file.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef STEAMNETWORKINGCUSTOMSIGNALING
|
||||
#define STEAMNETWORKINGCUSTOMSIGNALING
|
||||
#pragma once
|
||||
|
||||
#include "steamnetworkingtypes.h"
|
||||
|
||||
/// Interface used to send signaling messages for a particular connection.
|
||||
///
|
||||
/// - For connections initiated locally, you will construct it and pass
|
||||
/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling.
|
||||
/// - For connections initiated remotely and "accepted" locally, you
|
||||
/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest
|
||||
class ISteamNetworkingConnectionCustomSignaling
|
||||
{
|
||||
public:
|
||||
/// Called to send a rendezvous message to the remote peer. This may be called
|
||||
/// from any thread, at any time, so you need to be thread-safe! Don't take
|
||||
/// any locks that might hold while calling into SteamNetworkingSockets functions,
|
||||
/// because this could lead to deadlocks.
|
||||
///
|
||||
/// Note that when initiating a connection, we may not know the identity
|
||||
/// of the peer, if you did not specify it in ConnectP2PCustomSignaling.
|
||||
///
|
||||
/// Return true if a best-effort attempt was made to deliver the message.
|
||||
/// If you return false, it is assumed that the situation is fatal;
|
||||
/// the connection will be closed, and Release() will be called
|
||||
/// eventually.
|
||||
///
|
||||
/// Signaling objects will not be shared between connections.
|
||||
/// You can assume that the same value of hConn will be used
|
||||
/// every time.
|
||||
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0;
|
||||
|
||||
/// Called when the connection no longer needs to send signals.
|
||||
/// Note that this happens eventually (but not immediately) after
|
||||
/// the connection is closed. Signals may need to be sent for a brief
|
||||
/// time after the connection is closed, to clean up the connection.
|
||||
virtual void Release() = 0;
|
||||
};
|
||||
|
||||
/// Interface used when a custom signal is received.
|
||||
/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal
|
||||
class ISteamNetworkingCustomSignalingRecvContext
|
||||
{
|
||||
public:
|
||||
|
||||
/// Called when the signal represents a request for a new connection.
|
||||
///
|
||||
/// If you want to ignore the request, just return NULL. In this case,
|
||||
/// the peer will NOT receive any reply. You should consider ignoring
|
||||
/// requests rather than actively rejecting them, as a security measure.
|
||||
/// If you actively reject requests, then this makes it possible to detect
|
||||
/// if a user is online or not, just by sending them a request.
|
||||
///
|
||||
/// If you wish to send back a rejection, then use
|
||||
/// ISteamNetworkingSockets::CloseConnection() and then return NULL.
|
||||
/// We will marshal a properly formatted rejection signal and
|
||||
/// call SendRejectionSignal() so you can send it to them.
|
||||
///
|
||||
/// If you return a signaling object, the connection is NOT immediately
|
||||
/// accepted by default. Instead, it stays in the "connecting" state,
|
||||
/// and the usual callback is posted, and your app can accept the
|
||||
/// connection using ISteamNetworkingSockets::AcceptConnection. This
|
||||
/// may be useful so that these sorts of connections can be more similar
|
||||
/// to your application code as other types of connections accepted on
|
||||
/// a listen socket. If this is not useful and you want to skip this
|
||||
/// callback process and immediately accept the connection, call
|
||||
/// ISteamNetworkingSockets::AcceptConnection before returning the
|
||||
/// signaling object.
|
||||
///
|
||||
/// After accepting a connection (through either means), the connection
|
||||
/// will transition into the "finding route" state.
|
||||
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0;
|
||||
|
||||
/// This is called to actively communicate rejection or failure
|
||||
/// to the incoming message. If you intend to ignore all incoming requests
|
||||
/// that you do not wish to accept, then it's not strictly necessary to
|
||||
/// implement this.
|
||||
virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0;
|
||||
};
|
||||
|
||||
#endif // STEAMNETWORKINGCUSTOMSIGNALING
|
||||
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
project('GameNetworkingSockets', 'cpp', 'c',
|
||||
default_options: [
|
||||
'buildtype=debugoptimized',
|
||||
],
|
||||
license: 'BSD'
|
||||
)
|
||||
|
||||
flags_common = [
|
||||
'-fno-strict-aliasing',
|
||||
'-fvisibility=hidden',
|
||||
]
|
||||
|
||||
target_os = target_machine.system()
|
||||
if target_os == 'windows'
|
||||
flags_common += [
|
||||
'-fno-stack-protector',
|
||||
]
|
||||
else
|
||||
flags_common += [
|
||||
'-fstack-protector-strong',
|
||||
'-fstack-clash-protection',
|
||||
]
|
||||
endif
|
||||
|
||||
flags_cxx = [
|
||||
'-fno-rtti',
|
||||
'-fno-exceptions',
|
||||
'-fvisibility-inlines-hidden',
|
||||
]
|
||||
|
||||
warn_flags_common = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
|
||||
# Explicitly disable noisy warnings
|
||||
'-Wno-documentation',
|
||||
'-Wno-implicit-fallthrough',
|
||||
'-Wno-padded',
|
||||
'-Wno-sign-compare',
|
||||
'-Wno-sign-conversion',
|
||||
'-Wno-signed-enum-bitfield',
|
||||
'-Wno-unknown-pragmas',
|
||||
'-Wno-unused-const-variable',
|
||||
'-Wno-unused-local-typedef',
|
||||
'-Wno-unused-parameter',
|
||||
'-Wno-nested-anon-types',
|
||||
'-Wno-format-truncation'
|
||||
]
|
||||
|
||||
werror = get_option('Werror')
|
||||
if werror
|
||||
warn_flags_common += [ '-Werror' ]
|
||||
endif
|
||||
|
||||
use_crypto_25519 = get_option('use_crypto25519')
|
||||
if use_crypto_25519 == 'Reference'
|
||||
warn_flags_common += [ '-Wno-unused-function' ]
|
||||
endif
|
||||
|
||||
warn_flags_c = [
|
||||
'-Wimplicit',
|
||||
'-Wstrict-prototypes',
|
||||
'-Wno-missing-prototypes',
|
||||
]
|
||||
|
||||
warn_flags_cxx = [
|
||||
'-Wno-c++98-compat',
|
||||
'-Wno-c++98-compat-pedantic',
|
||||
'-Wno-exit-time-destructors',
|
||||
'-Wno-global-constructors',
|
||||
'-Wno-non-virtual-dtor',
|
||||
'-Wno-old-style-cast',
|
||||
'-Wno-reorder',
|
||||
|
||||
# These ones are prolific but don't really matter. Most are in generated
|
||||
# protobuf code.
|
||||
'-Wno-zero-as-null-pointer-constant',
|
||||
'-Wno-missing-variable-declarations',
|
||||
]
|
||||
|
||||
c_compiler = meson.get_compiler('c')
|
||||
cxx_compiler = meson.get_compiler('cpp')
|
||||
|
||||
c_flags = []
|
||||
c_flags += c_compiler.first_supported_argument(['-std=c11', '-std=gnu99'])
|
||||
foreach arg : flags_common + warn_flags_common + warn_flags_c
|
||||
if c_compiler.has_argument(arg)
|
||||
c_flags += [ arg ]
|
||||
endif
|
||||
endforeach
|
||||
add_project_arguments(c_flags, language: 'c')
|
||||
|
||||
cxx_flags = []
|
||||
cxx_flags += cxx_compiler.first_supported_argument(['-std=c++11', '-std=c++0x'])
|
||||
foreach arg : flags_common + warn_flags_common + flags_cxx + warn_flags_cxx
|
||||
if cxx_compiler.has_argument(arg)
|
||||
cxx_flags += [ arg ]
|
||||
endif
|
||||
endforeach
|
||||
add_project_arguments(cxx_flags, language: 'cpp')
|
||||
|
||||
# Preprocessor defines for platform and compiler
|
||||
cpp_flags = []
|
||||
if target_os == 'linux'
|
||||
cpp_flags += ['-DPOSIX', '-DLINUX']
|
||||
elif target_os == 'darwin'
|
||||
cpp_flags += ['-DPOSIX', '-DOSX']
|
||||
elif target_os == 'windows'
|
||||
cpp_flags += ['-D_WIN32', '-DWIN32', '-D__STDC_FORMAT_MACROS=1', '-D__USE_MINGW_ANSI_STDIO=0']
|
||||
else
|
||||
error('Could not identify your target operating system')
|
||||
endif
|
||||
|
||||
add_project_arguments(cpp_flags, language: 'c')
|
||||
add_project_arguments(cpp_flags, language: 'cpp')
|
||||
|
||||
dep_threads = dependency('threads')
|
||||
|
||||
subdir('src')
|
||||
subdir('tests')
|
||||
subdir('examples')
|
||||
@@ -1,21 +0,0 @@
|
||||
option('light_tests',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
description: 'Use smaller/shorter tests for simple integration testing (e.g. Travis)')
|
||||
|
||||
option('Werror',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Enable -Werror flag when compiling')
|
||||
|
||||
option('use_crypto',
|
||||
type: 'combo',
|
||||
value: 'OpenSSL',
|
||||
description: 'Crypto library to use for AES/SHA256',
|
||||
choices: ['OpenSSL', 'libsodium', 'BCrypt'])
|
||||
|
||||
option('use_crypto25519',
|
||||
type: 'combo',
|
||||
value: 'OpenSSL',
|
||||
description: 'Crypto library to use for ed25519/curve25519',
|
||||
choices: ['OpenSSL', 'libsodium', 'Reference'])
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
dependencies = []
|
||||
|
||||
c_compiler = meson.get_compiler('c')
|
||||
|
||||
protoc_bin = find_program('protoc')
|
||||
protoc = generator(protoc_bin,
|
||||
output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'],
|
||||
arguments : ['-I@CURRENT_SOURCE_DIR@/common', '--proto_path=@CURRENT_SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@'])
|
||||
|
||||
use_crypto = get_option('use_crypto')
|
||||
use_crypto25519 = get_option('use_crypto25519')
|
||||
|
||||
if use_crypto == 'OpenSSL' or use_crypto25519 == 'OpenSSL'
|
||||
min_libcrypto = dependency('libcrypto', version: '>=1.1.0')
|
||||
good_libcrypto = dependency('libcrypto', version: '>=1.1.1', required: false)
|
||||
|
||||
if good_libcrypto.found()
|
||||
dependencies += [ good_libcrypto ]
|
||||
else
|
||||
dependencies += [ min_libcrypto ]
|
||||
endif
|
||||
|
||||
if use_crypto25519 == 'OpenSSL' and not good_libcrypto.found()
|
||||
error('This version of OpenSSL does not support ed25519/curve25519. Please use -Duse_crypto25519=Reference or upgrade OpenSSL to 1.1.1 or later.')
|
||||
endif
|
||||
|
||||
code = '''#include <openssl/evp.h>
|
||||
int main(int argc, char **argv) {
|
||||
EVP_MD_CTX_free(NULL);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
result = c_compiler.links(code, dependencies: dependencies, name: 'EVP API')
|
||||
if not result
|
||||
error('Your OpenSSL version appears to be too old. Check that you\'re using OpenSSL 1.1.0 or later.')
|
||||
endif
|
||||
endif
|
||||
|
||||
if use_crypto == 'libsodium' or use_crypto25519 == 'libsodium'
|
||||
dependencies += [ dependency('libsodium') ]
|
||||
endif
|
||||
|
||||
dependencies += [
|
||||
dep_threads,
|
||||
dependency('protobuf', version: '>=3.0.0'),
|
||||
]
|
||||
|
||||
incdirs = include_directories('.', '../include', 'common', 'public')
|
||||
|
||||
cpp_flags = [
|
||||
'-DSTEAMNETWORKINGSOCKETS_FOREXPORT',
|
||||
'-DVALVE_CRYPTO_ENABLE_25519',
|
||||
'-DCRYPTO_DISABLE_ENCRYPT_WITH_PASSWORD',
|
||||
'-DGOOGLE_PROTOBUF_NO_RTTI',
|
||||
]
|
||||
|
||||
target_os = target_machine.system()
|
||||
|
||||
if target_os == 'windows'
|
||||
dependencies += [
|
||||
c_compiler.find_library('ws2_32'),
|
||||
]
|
||||
endif
|
||||
|
||||
protobuf_sources = [
|
||||
'common/steamnetworkingsockets_messages_certs.proto',
|
||||
'common/steamnetworkingsockets_messages.proto',
|
||||
'common/steamnetworkingsockets_messages_udp.proto',
|
||||
]
|
||||
|
||||
sources = [
|
||||
'common/crypto_textencode.cpp',
|
||||
'common/keypair.cpp',
|
||||
'common/steamid.cpp',
|
||||
'steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_flat.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp',
|
||||
'steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp',
|
||||
'steamnetworkingsockets/steamnetworkingsockets_certs.cpp',
|
||||
'steamnetworkingsockets/steamnetworkingsockets_certstore.cpp',
|
||||
'steamnetworkingsockets/steamnetworkingsockets_shared.cpp',
|
||||
'steamnetworkingsockets/steamnetworkingsockets_stats.cpp',
|
||||
'steamnetworkingsockets/steamnetworkingsockets_thinker.cpp',
|
||||
'tier0/dbg.cpp',
|
||||
'tier0/platformtime.cpp',
|
||||
'tier1/bitstring.cpp',
|
||||
'tier1/ipv6text.c',
|
||||
'tier1/netadr.cpp',
|
||||
'tier1/utlbuffer.cpp',
|
||||
'tier1/utlmemory.cpp',
|
||||
'vstdlib/strtools.cpp',
|
||||
]
|
||||
|
||||
if use_crypto == 'OpenSSL'
|
||||
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_VALVEOPENSSL' ]
|
||||
sources += [
|
||||
'common/opensslwrapper.cpp',
|
||||
'common/crypto_openssl.cpp',
|
||||
]
|
||||
endif
|
||||
|
||||
if use_crypto == 'BCrypt'
|
||||
dependencies += [ c_compiler.find_library('bcrypt') ]
|
||||
cpp_flags += [ '-DED25519_HASH_BCRYPT', '-DSTEAMNETWORKINGSOCKETS_CRYPTO_BCRYPT' ]
|
||||
sources += [ 'common/crypto_bcrypt.cpp' ]
|
||||
endif
|
||||
|
||||
if use_crypto == 'libsodium'
|
||||
if target_machine.cpu_family() != 'x86' and target_machine.cpu_family() != 'x86_64'
|
||||
error('-Duse_crypto=libsodium invalid, libsodium AES implementation only works on x86/x86_64 CPUs')
|
||||
endif
|
||||
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_LIBSODIUM' ]
|
||||
sources += [ 'common/crypto_libsodium.cpp' ]
|
||||
endif
|
||||
|
||||
if use_crypto25519 == 'OpenSSL'
|
||||
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_OPENSSL' ]
|
||||
sources += [ 'common/crypto_25519_openssl.cpp' ]
|
||||
endif
|
||||
|
||||
if use_crypto25519 == 'libsodium'
|
||||
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_LIBSODIUM' ]
|
||||
sources += [ 'common/crypto_25519_libsodium.cpp' ]
|
||||
endif
|
||||
|
||||
if use_crypto25519 == 'Reference'
|
||||
cpp_flags += [ '-DVALVE_CRYPTO_25519_DONNA' ]
|
||||
sources += [
|
||||
'common/crypto_25519_donna.cpp',
|
||||
'external/curve25519-donna/curve25519.c',
|
||||
'external/curve25519-donna/curve25519_VALVE_sse2.c',
|
||||
'external/ed25519-donna/ed25519_VALVE.c',
|
||||
'external/ed25519-donna/ed25519_VALVE_sse2.c',
|
||||
]
|
||||
endif
|
||||
|
||||
protobufs = protoc.process(protobuf_sources)
|
||||
|
||||
GameNetworkingSockets_static = static_library('GameNetworkingSockets',
|
||||
sources, protobufs,
|
||||
c_args: cpp_flags,
|
||||
cpp_args: cpp_flags,
|
||||
include_directories: incdirs,
|
||||
dependencies: dependencies)
|
||||
|
||||
dep_GameNetworkingSockets_static = declare_dependency(
|
||||
include_directories: include_directories( '../include', '.' ),
|
||||
compile_args: ['-DSTEAMNETWORKINGSOCKETS_STATIC_LINK'],
|
||||
link_with: GameNetworkingSockets_static )
|
||||
|
||||
GameNetworkingSockets_so = library('GameNetworkingSockets',
|
||||
sources, protobufs,
|
||||
c_args: cpp_flags,
|
||||
cpp_args: cpp_flags,
|
||||
include_directories: incdirs,
|
||||
dependencies: dependencies)
|
||||
|
||||
dep_GameNetworkingSockets_so = declare_dependency(
|
||||
include_directories: include_directories( '../include', '.' ),
|
||||
link_with: GameNetworkingSockets_so )
|
||||
|
||||
# vim: set ts=2 sts=2 sw=2 et:
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "csteamnetworkingsockets.h"
|
||||
#include "crypto.h"
|
||||
|
||||
#ifdef POSIX
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
|
||||
#include "steamnetworkingsockets_sdr_p2p.h"
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define STEAMNETWORKINGSOCKETS_P2P_H
|
||||
#pragma once
|
||||
|
||||
#include <steam/steamnetworkingcustomsignaling.h>
|
||||
#include "steamnetworkingsockets_connections.h"
|
||||
#include "csteamnetworkingsockets.h"
|
||||
|
||||
|
||||
+12
-2
@@ -7,11 +7,19 @@ endif()
|
||||
|
||||
add_executable(
|
||||
test_connection
|
||||
test_common.cpp
|
||||
test_connection.cpp)
|
||||
add_executable(
|
||||
test_p2p
|
||||
test_common.cpp
|
||||
test_p2p.cpp
|
||||
../examples/trivial_signaling_client.cpp)
|
||||
if(SANITIZE)
|
||||
target_link_libraries(test_connection GameNetworkingSockets_s)
|
||||
target_link_libraries(test_p2p GameNetworkingSockets_s)
|
||||
else()
|
||||
target_link_libraries(test_connection GameNetworkingSockets)
|
||||
target_link_libraries(test_p2p GameNetworkingSockets)
|
||||
endif()
|
||||
add_sanitizers(test_connection)
|
||||
|
||||
@@ -23,6 +31,8 @@ target_include_directories(test_crypto PRIVATE ../src ../src/public ../src/commo
|
||||
target_link_libraries(test_crypto GameNetworkingSockets_s)
|
||||
add_sanitizers(test_crypto)
|
||||
|
||||
file(COPY aesgcmtestvectors DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY aesgcmtestvectors DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# vim: set ts=4 sts=4 sw=4 noet:
|
||||
# NOTE: This happens when we generate the projects, and really we'd like to actually
|
||||
# make a build rule to copy it. I don't want to do that work right now.
|
||||
file(COPY test_p2p.py DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
common_deps = [ dep_threads ]
|
||||
cppflags = [
|
||||
'-DVALVE_CRYPTO_ENABLE_25519'
|
||||
]
|
||||
|
||||
light_tests= get_option('light_tests')
|
||||
if light_tests
|
||||
cppflags += ['-DLIGHT_TESTS']
|
||||
endif
|
||||
|
||||
executable('test_connection',
|
||||
'test_connection.cpp',
|
||||
dependencies: common_deps + [ dep_GameNetworkingSockets_so ],
|
||||
cpp_args: cppflags,
|
||||
)
|
||||
|
||||
executable('test_crypto',
|
||||
'test_crypto.cpp',
|
||||
dependencies: common_deps + [ dep_GameNetworkingSockets_static ],
|
||||
cpp_args: cppflags + ['-DTEST_VECTOR_DIR="' + join_paths(meson.source_root(), 'tests/aesgcmtestvectors') + '/"'],
|
||||
include_directories: include_directories('../src', '../src/public', '../src/common')
|
||||
)
|
||||
|
||||
# !FIXME! Ug cannot link with the static lib, because we need to #define the hardcoded key.
|
||||
# So we'll need the crypto and protobuf dependencies, and those are pretty complicated.
|
||||
# We need to refactor these files to get that organized.
|
||||
#executable('test_pki',
|
||||
# 'test_pki.cpp',
|
||||
# dependencies: common_deps,
|
||||
# cpp_args: cppflags + ['-DSTEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMPsJA3FUaQSsHkyYhWvcYt9v3XbwRBcWuVuayWGdK1"'],
|
||||
# include_directories: include_directories('../src', '../include', '../src/public', '../src/common')
|
||||
#)
|
||||
|
||||
#incdirs = include_directories('../include')
|
||||
#executable('test_flat',
|
||||
# 'test_flat.c',
|
||||
# dependence: dependencies,
|
||||
# include_directories: incdirs
|
||||
#)
|
||||
@@ -0,0 +1,120 @@
|
||||
// Misc stuff used in the tests
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <steam/steamnetworkingsockets.h>
|
||||
#include <steam/isteamnetworkingutils.h>
|
||||
|
||||
#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
#include <steam/steam_api.h>
|
||||
#endif
|
||||
|
||||
static FILE *g_fpLog = nullptr;
|
||||
static SteamNetworkingMicroseconds g_logTimeZero;
|
||||
|
||||
static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg )
|
||||
{
|
||||
SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero;
|
||||
if ( g_fpLog )
|
||||
fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg );
|
||||
if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg )
|
||||
{
|
||||
printf( "%10.6f %s\n", time*1e-6, pszMsg );
|
||||
fflush(stdout);
|
||||
}
|
||||
if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug )
|
||||
{
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
if ( g_fpLog )
|
||||
fflush( g_fpLog );
|
||||
|
||||
// !KLUDGE! Our logging (which is done while we hold the lock)
|
||||
// is occasionally triggering this assert. Just ignroe that one
|
||||
// error for now.
|
||||
// Yes, this is a kludge.
|
||||
if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) )
|
||||
return;
|
||||
|
||||
assert( !"TEST FAILED" );
|
||||
}
|
||||
}
|
||||
|
||||
void TEST_Printf( const char *fmt, ... )
|
||||
{
|
||||
char text[ 2048 ];
|
||||
va_list ap;
|
||||
va_start( ap, fmt );
|
||||
vsprintf( text, fmt, ap );
|
||||
va_end(ap);
|
||||
char *nl = strchr( text, '\0' ) - 1;
|
||||
if ( nl >= text && *nl == '\n' )
|
||||
*nl = '\0';
|
||||
DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text );
|
||||
}
|
||||
|
||||
void TEST_Fatal( const char *fmt, ... )
|
||||
{
|
||||
fflush(stdout);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
fflush(stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void TEST_Init( const SteamNetworkingIdentity *pIdentity )
|
||||
{
|
||||
g_fpLog = fopen( "log.txt", "wt" );
|
||||
g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp();
|
||||
|
||||
SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput );
|
||||
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput );
|
||||
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
|
||||
|
||||
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
SteamDatagramErrMsg errMsg;
|
||||
if ( !GameNetworkingSockets_Init( pIdentity, errMsg ) )
|
||||
{
|
||||
fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg );
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
//SteamAPI_Init();
|
||||
|
||||
SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what
|
||||
//SteamDatagramClient_SetUniverse( k_EUniverseDev );
|
||||
|
||||
SteamDatagramErrMsg errMsg;
|
||||
if ( !SteamDatagramClient_Init( true, errMsg ) )
|
||||
{
|
||||
fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg );
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TEST_Kill()
|
||||
{
|
||||
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
GameNetworkingSockets_Kill();
|
||||
#else
|
||||
SteamDatagramClient_Kill();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TEST_PumpCallbacks()
|
||||
{
|
||||
SteamNetworkingSockets()->RunCallbacks();
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Misc stuff used in the tests
|
||||
#pragma once
|
||||
|
||||
struct SteamNetworkingIdentity;
|
||||
|
||||
extern void TEST_Init();
|
||||
extern void TEST_Printf( const char *fmt, ... );
|
||||
extern void TEST_Fatal( const char *fmt, ... );
|
||||
extern void TEST_Init( const SteamNetworkingIdentity *pIdentity );
|
||||
extern void TEST_Kill();
|
||||
extern void TEST_PumpCallbacks();
|
||||
+39
-129
@@ -8,6 +8,8 @@
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include <steam/steamnetworkingsockets.h>
|
||||
#include <steam/isteamnetworkingutils.h>
|
||||
#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
@@ -19,93 +21,6 @@
|
||||
static std::default_random_engine g_rand;
|
||||
static SteamNetworkingMicroseconds g_usecTestElapsed;
|
||||
|
||||
FILE *g_fpLog = nullptr;
|
||||
SteamNetworkingMicroseconds g_logTimeZero;
|
||||
|
||||
void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo );
|
||||
|
||||
static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg )
|
||||
{
|
||||
SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero;
|
||||
if ( g_fpLog )
|
||||
fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg );
|
||||
if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg )
|
||||
{
|
||||
printf( "%10.6f %s\n", time*1e-6, pszMsg );
|
||||
fflush(stdout);
|
||||
}
|
||||
if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug )
|
||||
{
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
if ( g_fpLog )
|
||||
fflush( g_fpLog );
|
||||
|
||||
// !KLUDGE! Our logging (which is done while we hold the lock)
|
||||
// is occasionally triggering this assert. Just ignroe that one
|
||||
// error for now.
|
||||
// Yes, this is a kludge.
|
||||
if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) )
|
||||
return;
|
||||
|
||||
assert( !"TEST FAILED" );
|
||||
}
|
||||
}
|
||||
|
||||
static void Printf( const char *fmt, ... )
|
||||
{
|
||||
char text[ 2048 ];
|
||||
va_list ap;
|
||||
va_start( ap, fmt );
|
||||
vsprintf( text, fmt, ap );
|
||||
va_end(ap);
|
||||
char *nl = strchr( text, '\0' ) - 1;
|
||||
if ( nl >= text && *nl == '\n' )
|
||||
*nl = '\0';
|
||||
DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text );
|
||||
}
|
||||
|
||||
static void InitSteamDatagramConnectionSockets()
|
||||
{
|
||||
g_fpLog = fopen( "log.txt", "wt" );
|
||||
g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp();
|
||||
|
||||
SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput );
|
||||
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput );
|
||||
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
|
||||
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
|
||||
|
||||
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
SteamDatagramErrMsg errMsg;
|
||||
if ( !GameNetworkingSockets_Init( nullptr, errMsg ) )
|
||||
{
|
||||
fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg );
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
//SteamAPI_Init();
|
||||
|
||||
SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what
|
||||
//SteamDatagramClient_SetUniverse( k_EUniverseDev );
|
||||
|
||||
SteamDatagramErrMsg errMsg;
|
||||
if ( !SteamDatagramClient_Init( true, errMsg ) )
|
||||
{
|
||||
fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg );
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ShutdownSteamDatagramConnectionSockets()
|
||||
{
|
||||
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
|
||||
GameNetworkingSockets_Kill();
|
||||
#else
|
||||
SteamDatagramClient_Kill();
|
||||
#endif
|
||||
}
|
||||
|
||||
static HSteamListenSocket g_hSteamListenSocket = k_HSteamListenSocket_Invalid;
|
||||
|
||||
struct TestMsg
|
||||
@@ -186,7 +101,7 @@ struct SFakePeer
|
||||
|
||||
if ( result != k_EResultOK )
|
||||
{
|
||||
Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
|
||||
TEST_Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
|
||||
m_sName.c_str(),
|
||||
g_usecTestElapsed*1e-6,
|
||||
msg.m_bReliable ? "reliable" : "unreliable",
|
||||
@@ -197,7 +112,7 @@ struct SFakePeer
|
||||
}
|
||||
#if 0
|
||||
else
|
||||
Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
|
||||
TEST_Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
|
||||
connection.m_sName.c_str(),
|
||||
g_usecTestElapsed*1e-6,
|
||||
msg.m_bReliable ? "reliable" : "unreliable",
|
||||
@@ -244,7 +159,7 @@ static void Recv( ISteamNetworkingSockets *pSteamSocketNetworking )
|
||||
{
|
||||
|
||||
// Print that it happened.
|
||||
Printf(
|
||||
TEST_Printf(
|
||||
"Recv: %s, %s MISMATCH NUM wanted %lld got %lld\n",
|
||||
pConnection->m_sName.c_str(),
|
||||
pTestMsg->m_bReliable ? "RELIABLE" : "UNRELIABLE",
|
||||
@@ -278,7 +193,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
|
||||
{
|
||||
case k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
Printf( "Steam Net connection %x %s, reason %d: %s\n",
|
||||
TEST_Printf( "Steam Net connection %x %s, reason %d: %s\n",
|
||||
pInfo->m_hConn,
|
||||
( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ),
|
||||
pInfo->m_info.m_eEndReason,
|
||||
@@ -301,7 +216,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
|
||||
|
||||
/*
|
||||
case k_ESteamNetworkingConnectionState_None:
|
||||
Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() );
|
||||
TEST_Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() );
|
||||
|
||||
if ( g_hSteamNetConnection == pInfo->m_hConn )
|
||||
{
|
||||
@@ -317,7 +232,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
|
||||
if ( g_hSteamListenSocket != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hSteamListenSocket )
|
||||
{
|
||||
// Somebody's knocking
|
||||
Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
|
||||
TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
|
||||
g_peerServer.m_hSteamNetConnection = pInfo->m_hConn;
|
||||
g_peerServer.m_bIsConnected = true;
|
||||
SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn );
|
||||
@@ -331,7 +246,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
|
||||
{
|
||||
g_peerClient.m_bIsConnected = true;
|
||||
}
|
||||
Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
|
||||
TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
|
||||
|
||||
break;
|
||||
|
||||
@@ -341,15 +256,9 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
|
||||
}
|
||||
}
|
||||
|
||||
static void PumpCallbacks()
|
||||
{
|
||||
SteamNetworkingSockets()->RunCallbacks();
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) );
|
||||
}
|
||||
|
||||
static void PumpCallbacksAndMakeSureStillConnected()
|
||||
{
|
||||
PumpCallbacks();
|
||||
TEST_PumpCallbacks();
|
||||
assert( g_peerClient.m_bIsConnected );
|
||||
assert( g_peerServer.m_bIsConnected );
|
||||
assert( g_peerServer.m_hSteamNetConnection != k_HSteamNetConnection_Invalid );
|
||||
@@ -368,35 +277,35 @@ static void PrintStatus( const SFakePeer &p1, const SFakePeer &p2 )
|
||||
{
|
||||
const SteamNetworkingQuickConnectionStatus &info1 = p1.m_info;
|
||||
const SteamNetworkingQuickConnectionStatus &info2 = p2.m_info;
|
||||
Printf( "\n" );
|
||||
Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() );
|
||||
Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing );
|
||||
Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() );
|
||||
Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f );
|
||||
Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f );
|
||||
Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f );
|
||||
Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec );
|
||||
Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f );
|
||||
Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f );
|
||||
Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f );
|
||||
Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec );
|
||||
Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond );
|
||||
Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 );
|
||||
Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 );
|
||||
TEST_Printf( "\n" );
|
||||
TEST_Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() );
|
||||
TEST_Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing );
|
||||
TEST_Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() );
|
||||
TEST_Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f );
|
||||
TEST_Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f );
|
||||
TEST_Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f );
|
||||
TEST_Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec );
|
||||
TEST_Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f );
|
||||
TEST_Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f );
|
||||
TEST_Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f );
|
||||
TEST_Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec );
|
||||
TEST_Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond );
|
||||
TEST_Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 );
|
||||
TEST_Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 );
|
||||
}
|
||||
|
||||
static void TestNetworkConditions( int rate, float loss, int lag, float reorderPct, int reorderLag, bool bActLikeGame )
|
||||
{
|
||||
ISteamNetworkingSockets *pSteamSocketNetworking = SteamNetworkingSockets();
|
||||
|
||||
Printf( "---------------------------------------------------\n" );
|
||||
Printf( "NETWORK CONDITIONS\n" );
|
||||
Printf( "Rate . . . . . . : %d Bps\n", rate );
|
||||
Printf( "Loss . . . . . . : %g%%\n", loss );
|
||||
Printf( "Ping . . . . . . : %d\n", lag*2 );
|
||||
Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag );
|
||||
Printf( "Act like game. . : %d\n", (int)bActLikeGame );
|
||||
Printf( "---------------------------------------------------\n" );
|
||||
TEST_Printf( "---------------------------------------------------\n" );
|
||||
TEST_Printf( "NETWORK CONDITIONS\n" );
|
||||
TEST_Printf( "Rate . . . . . . : %d Bps\n", rate );
|
||||
TEST_Printf( "Loss . . . . . . : %g%%\n", loss );
|
||||
TEST_Printf( "Ping . . . . . . : %d\n", lag*2 );
|
||||
TEST_Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag );
|
||||
TEST_Printf( "Act like game. . : %d\n", (int)bActLikeGame );
|
||||
TEST_Printf( "---------------------------------------------------\n" );
|
||||
|
||||
SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMin, rate );
|
||||
SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMax, rate );
|
||||
@@ -470,14 +379,14 @@ static void TestNetworkConditions( int rate, float loss, int lag, float reorderP
|
||||
usecWhenStateEnd = g_usecTestElapsed + ( bQuiet ? usecQuietDuration : usecActiveDuration );
|
||||
if ( nIterations-- <= 0 )
|
||||
break;
|
||||
Printf( "Entering active time (sending enabled)\n" );
|
||||
TEST_Printf( "Entering active time (sending enabled)\n" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bQuiet = true;
|
||||
usecWhenStateEnd = g_usecTestElapsed + usecQuietDuration;
|
||||
Printf( "Entering quiet time (no sending) to see how fast queues drain\n" );
|
||||
TEST_Printf( "Entering quiet time (no sending) to see how fast queues drain\n" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +462,7 @@ static void RunSteamDatagramConnectionTest()
|
||||
|
||||
// Wait for connection to complete
|
||||
while ( !g_peerClient.m_bIsConnected || !g_peerServer.m_bIsConnected )
|
||||
PumpCallbacks();
|
||||
TEST_PumpCallbacks();
|
||||
|
||||
auto Test = []( int rate, float loss, int lag, float reorderPct, int reorderLag )
|
||||
{
|
||||
@@ -626,12 +535,13 @@ int main( )
|
||||
TestSteamNetworkingIdentity();
|
||||
|
||||
// Create client and server sockets
|
||||
InitSteamDatagramConnectionSockets();
|
||||
TEST_Init( nullptr );
|
||||
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
|
||||
|
||||
// Run the test
|
||||
RunSteamDatagramConnectionTest();
|
||||
|
||||
ShutdownSteamDatagramConnectionSockets();
|
||||
TEST_Kill();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include <steam/steamnetworkingsockets.h>
|
||||
#include <steam/isteamnetworkingutils.h>
|
||||
#include "../examples/trivial_signaling_client.h"
|
||||
|
||||
HSteamListenSocket g_hListenSock;
|
||||
HSteamNetConnection g_hConnection;
|
||||
enum ETestRole
|
||||
{
|
||||
k_ETestRole_Undefined,
|
||||
k_ETestRole_Server,
|
||||
k_ETestRole_Client,
|
||||
k_ETestRole_Symmetric,
|
||||
};
|
||||
ETestRole g_eTestRole = k_ETestRole_Undefined;
|
||||
|
||||
int g_nVirtualPortLocal = 0; // Used when listening, and when connecting
|
||||
int g_nVirtualPortRemote = 0; // Only used when connecting
|
||||
|
||||
void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo )
|
||||
{
|
||||
// What's the state of the connection?
|
||||
switch ( pInfo->m_info.m_eState )
|
||||
{
|
||||
case k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
|
||||
TEST_Printf( "[%s] %s, reason %d: %s\n",
|
||||
pInfo->m_info.m_szConnectionDescription,
|
||||
( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ),
|
||||
pInfo->m_info.m_eEndReason,
|
||||
pInfo->m_info.m_szEndDebug
|
||||
);
|
||||
|
||||
// Close our end
|
||||
SteamNetworkingSockets()->CloseConnection( pInfo->m_hConn, 0, nullptr, false );
|
||||
|
||||
if ( g_hConnection == pInfo->m_hConn )
|
||||
{
|
||||
g_hConnection = k_HSteamNetConnection_Invalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Why are we hearing about any another connection?
|
||||
assert( false );
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_None:
|
||||
// Notification that a connection was destroyed. (By us, presumably.)
|
||||
// We don't need this, so ignore it.
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_Connecting:
|
||||
|
||||
// Is this a connection we initiated, or one that we are receiving?
|
||||
if ( g_hListenSock != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hListenSock )
|
||||
{
|
||||
// Somebody's knocking
|
||||
TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
|
||||
g_hConnection = pInfo->m_hConn;
|
||||
SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that we will get notification when our own connection that
|
||||
// we initiate enters this state.
|
||||
}
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_FindingRoute:
|
||||
// P2P connections will spend a bried time here where they swap addresses
|
||||
// and try to find a route.
|
||||
TEST_Printf( "[%s] finding route\n", pInfo->m_info.m_szConnectionDescription );
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_Connected:
|
||||
// We got fully connected
|
||||
assert( pInfo->m_hConn == g_hConnection ); // We don't initiate or accept any other connections, so this should be out own connection
|
||||
TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, const char **argv )
|
||||
{
|
||||
SteamNetworkingIdentity identityLocal; identityLocal.Clear();
|
||||
SteamNetworkingIdentity identityRemote; identityRemote.Clear();
|
||||
const char *pszTrivialSignalingService = "localhost:10000";
|
||||
|
||||
// Parse the command line
|
||||
for ( int idxArg = 1 ; idxArg < argc ; ++idxArg )
|
||||
{
|
||||
const char *pszSwitch = argv[idxArg];
|
||||
|
||||
auto GetArg = [&]() -> const char * {
|
||||
if ( idxArg + 1 >= argc )
|
||||
TEST_Fatal( "Expected argument after %s", pszSwitch );
|
||||
return argv[++idxArg];
|
||||
};
|
||||
auto ParseIdentity = [&]( SteamNetworkingIdentity &x ) {
|
||||
const char *pszArg = GetArg();
|
||||
if ( !x.ParseString( pszArg ) )
|
||||
TEST_Fatal( "'%s' is not a valid identity string", pszArg );
|
||||
};
|
||||
|
||||
if ( !strcmp( pszSwitch, "--identity-local" ) )
|
||||
ParseIdentity( identityLocal );
|
||||
else if ( !strcmp( pszSwitch, "--identity-remote" ) )
|
||||
ParseIdentity( identityRemote );
|
||||
else if ( !strcmp( pszSwitch, "--signaling-server" ) )
|
||||
pszTrivialSignalingService = GetArg();
|
||||
else if ( !strcmp( pszSwitch, "--client" ) )
|
||||
g_eTestRole = k_ETestRole_Client;
|
||||
else if ( !strcmp( pszSwitch, "--server" ) )
|
||||
g_eTestRole = k_ETestRole_Server;
|
||||
else if ( !strcmp( pszSwitch, "--symmetric" ) )
|
||||
g_eTestRole = k_ETestRole_Symmetric;
|
||||
else
|
||||
TEST_Fatal( "Unexpected command line argument '%s'", pszSwitch );
|
||||
}
|
||||
|
||||
if ( g_eTestRole == k_ETestRole_Undefined )
|
||||
TEST_Fatal( "Must specify test role (--server, --client, or --symmetric" );
|
||||
if ( identityLocal.IsInvalid() )
|
||||
TEST_Fatal( "Must specify local identity using --identity-local" );
|
||||
if ( identityRemote.IsInvalid() && g_eTestRole != k_ETestRole_Server )
|
||||
TEST_Fatal( "Must specify remote identity using --identity-remote" );
|
||||
|
||||
// Initialize library, with the desired local identity
|
||||
TEST_Init( &identityLocal );
|
||||
|
||||
// Create the signaling service
|
||||
SteamNetworkingErrMsg errMsg;
|
||||
ITrivialSignalingClient *pSignaling = CreateTrivialSignalingClient( pszTrivialSignalingService, SteamNetworkingSockets(), errMsg );
|
||||
if ( pSignaling == nullptr )
|
||||
TEST_Fatal( "Failed to initializing signaling client. %s", errMsg );
|
||||
|
||||
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
|
||||
|
||||
// Create listen socket to receive connections on, unless we are the client
|
||||
if ( g_eTestRole == k_ETestRole_Server )
|
||||
{
|
||||
TEST_Printf( "Creating listen socket, local virtual port %d\n", g_nVirtualPortLocal );
|
||||
g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 0, nullptr );
|
||||
assert( g_hListenSock != k_HSteamListemSocket_Invalid );
|
||||
}
|
||||
else if ( g_eTestRole == k_ETestRole_Symmetric )
|
||||
{
|
||||
|
||||
// Currently you must create a listen socket to use symmetric mode,
|
||||
// even if you know that you will always create connections "both ways".
|
||||
// In the future we might try to remove this requirement. It is a bit
|
||||
// less efficient, since it always triggered the race condition case
|
||||
// where both sides create their own connections, and then one side
|
||||
// decides to their theirs away. If we have a listen socket, then
|
||||
// it can be the case that one peer will receive the incoming connection
|
||||
// from the other peer, and since he has a listen socket, can save
|
||||
// the connection, and then implicitly accept it when he initiates his
|
||||
// own connection. Without the listen socket, if an incoming connection
|
||||
// request arrives before we have started connecting out, then we are forced
|
||||
// to ignore it, as the app has given no indication that it desires to
|
||||
// receive inbound connections at all.
|
||||
TEST_Printf( "Creating listen socket in symmetric mode, local virtual port %d\n", g_nVirtualPortLocal );
|
||||
SteamNetworkingConfigValue_t opt;
|
||||
opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 ); // << Note we set symmetric mode on the listen socket
|
||||
g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 1, &opt );
|
||||
assert( g_hListenSock != k_HSteamListemSocket_Invalid );
|
||||
}
|
||||
|
||||
// Begin connecting to peer, unless we are the server
|
||||
if ( g_eTestRole != k_ETestRole_Server )
|
||||
{
|
||||
std::vector< SteamNetworkingConfigValue_t > vecOpts;
|
||||
|
||||
// If we want the local and virtual port to differ, we must set
|
||||
// an option. This is a pretty rare use case, and usually not needed.
|
||||
// The local virtual port is only usually relevant for symmetric
|
||||
// connections, and then, it almost always matches. Here we are
|
||||
// just showing in this example code how you could handle this if you
|
||||
// needed them to differ.
|
||||
if ( g_nVirtualPortRemote != g_nVirtualPortLocal )
|
||||
{
|
||||
SteamNetworkingConfigValue_t opt;
|
||||
opt.SetInt32( k_ESteamNetworkingConfig_LocalVirtualPort, g_nVirtualPortLocal );
|
||||
vecOpts.push_back( opt );
|
||||
}
|
||||
|
||||
// Symmetric mode? Noce that since we created a listen socket on this local
|
||||
// virtual port and tagged it for symmetric connect mode, any connections
|
||||
// we create that use the same local virtual port will automatically inherit
|
||||
// this setting. However, this is really not recommended. It is best to be
|
||||
// explicit.
|
||||
if ( g_eTestRole == k_ETestRole_Symmetric )
|
||||
{
|
||||
SteamNetworkingConfigValue_t opt;
|
||||
opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 );
|
||||
vecOpts.push_back( opt );
|
||||
TEST_Printf( "Connecting to '%s' in symmetric mode, virtual port %d, from local virtual port %d.\n",
|
||||
SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote,
|
||||
g_nVirtualPortLocal );
|
||||
}
|
||||
else
|
||||
{
|
||||
TEST_Printf( "Connecting to '%s', virtual port %d, from local virtual port %d.\n",
|
||||
SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote,
|
||||
g_nVirtualPortLocal );
|
||||
}
|
||||
|
||||
g_hConnection = SteamNetworkingSockets()->ConnectP2P( identityRemote, g_nVirtualPortRemote, (int)vecOpts.size(), vecOpts.data() );
|
||||
}
|
||||
|
||||
// Main test loop
|
||||
for (;;)
|
||||
{
|
||||
// Check for incoming signals, and dispatch them
|
||||
pSignaling->Poll();
|
||||
|
||||
// Check callbacks
|
||||
TEST_PumpCallbacks();
|
||||
}
|
||||
|
||||
|
||||
TEST_Kill();
|
||||
return 0;
|
||||
}
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test script to run the p2p test. This runs three processes: the two clients,
|
||||
# and the dummy signaling service.
|
||||
#
|
||||
# NOTE: You usually won't run this script from its original location. The
|
||||
# makefiles will copy it into the same location as the tests and examples
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
def RunProcess( tag, cmdline, **popen_kwargs ):
|
||||
|
||||
with open( tag + ".log", "wt" ) as log:
|
||||
|
||||
def WriteLn( ln ):
|
||||
print( "%s> %s" % (tag, ln ) )
|
||||
log.write( "%s\n" % ln )
|
||||
|
||||
WriteLn( "Executing: " + ' '.join( cmdline ) )
|
||||
process = subprocess.Popen( cmdline, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, **popen_kwargs )
|
||||
process.stdin.close()
|
||||
while True:
|
||||
sOutput = process.stdout.readline()
|
||||
if sOutput:
|
||||
sOutput = str(sOutput, 'utf-8', 'ignore')
|
||||
WriteLn( sOutput.rstrip() )
|
||||
elif process.poll() is not None:
|
||||
break
|
||||
process.wait()
|
||||
WriteLn( "Exitted with %d" % process.returncode )
|
||||
|
||||
def StartProcessInThread( tag, cmdline, **popen_kwargs ):
|
||||
def ThreadProc():
|
||||
RunProcess( tag, cmdline, **popen_kwargs )
|
||||
thread = threading.Thread( target=ThreadProc, name=tag )
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
def StartClientInThread( role, local, remote ):
|
||||
cmdline = [
|
||||
"./test_p2p",
|
||||
"--" + role,
|
||||
"--identity-local", "str:"+local,
|
||||
"--identity-remote", "str:"+remote,
|
||||
"--signaling-server", "localhost:10000"
|
||||
]
|
||||
return StartProcessInThread( local, cmdline );
|
||||
|
||||
signaling = StartProcessInThread( "signaling", [ './trivial_signaling_server' ], cwd="../examples" )
|
||||
|
||||
client1 = StartClientInThread( "server", "peer_server", "peer_client" )
|
||||
client2 = StartClientInThread( "client", "peer_client", "peer_server" )
|
||||
|
||||
client1.join()
|
||||
client2.join()
|
||||
signaling.join()
|
||||
|
||||
Reference in New Issue
Block a user