P2P(Peer-to-Peer)通信是一种去中心化的网络通信模式,参与者(节点)既作为客户端也作为服务器,直接进行数据交换,无需依赖中心服务器中转。
P2P通信核心原理
1. NAT穿透技术
-
STUN协议:通过公网服务器发现NAT后的公网IP和端口
-
TURN协议:当直接连接失败时,通过中继服务器转发数据
-
ICE框架:综合STUN/TURN,自动选择最佳连接路径
-
UDP打洞:利用NAT映射规则建立直接UDP连接
2. 节点发现机制
-
中央服务器索引:通过中心服务器记录在线节点信息
-
分布式哈希表(DHT):如Kademlia算法实现的去中心化节点发现
-
组播/广播发现:在局域网内自动发现对等节点
3. 连接建立过程
-
节点发现与身份验证
-
NAT类型检测与穿透方案选择
-
连接尝试(直接连接或中继)
-
安全通道建立与加密协商
TeamNetworkingSockets P2P通信
核心功能特点
-
NAT穿透能力
-
自动使用STUN/TURN技术穿透大多数NAT设备
-
内置ICE协议实现连接最佳路径选择
-
支持中继备用连接方式
-
-
连接可靠性
-
提供可靠和不可靠两种传输模式
-
自动重传和拥塞控制
-
消息分段和重组支持
-
-
安全机制
-
默认启用加密通信
-
身份验证系统防止中间人攻击
-
可配置的安全选项
-
P2P连接建立流程
1. 初始化设置
// 初始化SteamNetworkingSockets
SteamNetworkingErrMsg errMsg;
if(!GameNetworkingSockets_Init(nullptr, errMsg)) {
printf("初始化失败: %s", errMsg);
return;
}
// 设置身份标识
SteamNetworkingIdentity identityLocal;
identityLocal.SetSteamID( steamIDLocal ); // 或其他标识方式
// 配置STUN服务器
SteamNetworkingUtils()->SetGlobalConfigValueString(
k_ESteamNetworkingConfig_P2P_STUN_ServerList,
"stun.l.google.com:19302"
);
2. 创建监听端(服务器)
// 创建P2P监听套接字
HSteamListenSocket hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P(
0, // 虚拟端口
0, // 选项数量
nullptr // 选项数组
);
// 设置连接状态变更回调
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(
OnSteamNetConnectionStatusChanged
);
3. 发起连接(客户端)
// 准备连接选项
SteamNetworkingConfigValue_t options[] = {
{ k_ESteamNetworkingConfig_SymmetricConnect, k_ESteamNetworkingConfig_Int32, 1 }
};
// 发起P2P连接
HSteamNetConnection hConn = SteamNetworkingSockets()->ConnectP2P(
identityRemote, // 远程身份
0, // 虚拟端口
1, // 选项数量
options // 选项数组
);
4. 信令服务集成(可选)
// 创建自定义信令客户端
ITrivialSignalingClient* pSignaling = CreateTrivialSignalingClient(
"signaling.example.com:10000",
SteamNetworkingSockets(),
errMsg
);
// 使用自定义信令建立连接
ISteamNetworkingConnectionSignaling* pConnSignaling = pSignaling->CreateSignalingForConnection(
identityRemote,
errMsg
);
HSteamNetConnection hConn = SteamNetworkingSockets()->ConnectP2PCustomSignaling(
pConnSignaling,
&identityRemote,
0, // 虚拟端口
0, // 选项数量
nullptr // 选项数组
);
消息收发处理
发送消息
void SendP2PMessage(HSteamNetConnection hConn, const void* pData, uint32 cbData)
{
EResult result = SteamNetworkingSockets()->SendMessageToConnection(
hConn,
pData,
cbData,
k_nSteamNetworkingSend_Reliable, // 或k_nSteamNetworkingSend_Unreliable
nullptr
);
if(result != k_EResultOK) {
// 处理发送失败
}
}
接收消息
void PollIncomingMessages()
{
SteamNetworkingMessage_t* pMsg;
int numMsgs = SteamNetworkingSockets()->ReceiveMessagesOnConnection(
hConnection,
&pMsg,
1
);
if(numMsgs > 0) {
// 处理消息
ProcessMessage(pMsg->GetData(), pMsg->GetSize());
// 释放消息
pMsg->Release();
}
}
连接状态管理
void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* pInfo)
{
switch(pInfo->m_info.m_eState) {
case k_ESteamNetworkingConnectionState_Connected:
// 连接成功建立
break;
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
// 连接断开
SteamNetworkingSockets()->CloseConnection(pInfo->m_hConn, 0, nullptr, false);
break;
// 其他状态处理...
}
}
高级配置选项
-
传输优化
// 启用SDR传输后备
SteamNetworkingConfigValue_t opt;
opt.SetInt32(k_ESteamNetworkingConfig_Transport_ICE_Enable,
k_nSteamNetworkingConfig_Transport_ICE_Enable_All);
-
性能调优
// 调整发送缓冲区大小
SteamNetworkingUtils()->SetGlobalConfigValueInt32(
k_ESteamNetworkingConfig_SendBufferSize,
256 * 1024 // 256KB
);
-
调试支持
// 启用详细日志
SteamNetworkingUtils()->SetGlobalConfigValueInt32(
k_ESteamNetworkingConfig_LogLevel_P2PRendezvous,
k_ESteamNetworkingSocketsDebugOutputType_Debug
);
完整示例工程
ITrivialSignalingClient类
// 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;
/// Interface to our client.
class ITrivialSignalingClient
{
public:
/// Create signaling object for a connection to peer
virtual ISteamNetworkingConnectionSignaling *CreateSignalingForConnection(
const SteamNetworkingIdentity &identityPeer,
SteamNetworkingErrMsg &errMsg ) = 0;
/// 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
);
// 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 "../tests/test_common.h"
#include <string>
#include <mutex>
#include <deque>
#include <assert.h>
#include "trivial_signaling_client.h"
#include <steam/isteamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include <steam/steamnetworkingcustomsignaling.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/ioctl.h>
typedef int SOCKET;
constexpr SOCKET INVALID_SOCKET = -1;
inline void closesocket( SOCKET s ) { close(s); }
inline int GetSocketError() { return errno; }
inline bool IgnoreSocketError( int e )
{
return e == EAGAIN || e == ENOTCONN || e == EWOULDBLOCK;
}
#ifndef ioctlsocket
#define ioctlsocket ioctl
#endif
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
typedef int socklen_t;
inline int GetSocketError() { return WSAGetLastError(); }
inline bool IgnoreSocketError( int e )
{
return e == WSAEWOULDB

最低0.47元/天 解锁文章
3048

被折叠的 条评论
为什么被折叠?



