using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class HybridP2PClient
{
// 配置参数
private readonly string _serverIp;
private readonly int _serverPort;
private readonly string _clientId;
private const int PunchAttempts = 5;
private const int PunchInterval = 1000; // ms
// 网络组件
private TcpClient _tcpServerConnection;
private UdpClient _udpClient;
private TcpListener _tcpListener;
private NetworkStream _tcpStream;
// 状态变量
private IPEndPoint _peerUdpEP;
private IPEndPoint _peerTcpEP;
private bool _isConnected = false;
private bool _useTCP = true;
private bool _isRunning = true;
private int _localUdpPort;
public HybridP2PClient(string serverIp, int serverPort, string clientId)
{
_serverIp = serverIp;
_serverPort = serverPort;
_clientId = clientId;
}
public void Start()
{
// 连接到协调服务器
ConnectToServer();
// 启动本地TCP监听器
StartTcpListener();
// 启动本地UDP监听器
StartUdpListener();
// 启动UDP心跳线程
new Thread(UdpHeartbeat).Start();
Console.WriteLine("输入要连接的客户端ID (或按回车退出):");
while (_isRunning)
{
string targetId = Console.ReadLine();
if (string.IsNullOrEmpty(targetId)) break;
RequestConnection(targetId);
}
// 清理资源
_isRunning = false;
_tcpListener?.Stop();
_udpClient?.Close();
_tcpServerConnection?.Close();
}
#region 服务器通信
private void ConnectToServer()
{
try
{
// 使用TCP连接服务器
_tcpServerConnection = new TcpClient(_serverIp, _serverPort);
_tcpStream = _tcpServerConnection.GetStream();
Console.WriteLine("已连接到协调服务器");
// 注册到服务器
string registerMsg = $"REGISTER:{_clientId}";
byte[] data = Encoding.ASCII.GetBytes(registerMsg);
_tcpStream.Write(data, 0, data.Length);
// 启动接收服务器消息的线程
new Thread(ReceiveFromServer).Start();
}
catch (Exception ex)
{
Console.WriteLine($"连接服务器失败: {ex.Message}");
}
}
private void RequestConnection(string targetId)
{
if (_isConnected)
{
Console.WriteLine("已连接到对等方,请先断开当前连接");
return;
}
string message = $"CONNECT:{_clientId}:{targetId}";
byte[] data = Encoding.ASCII.GetBytes(message);
_tcpStream.Write(data, 0, data.Length);
}
private void ReceiveFromServer()
{
try
{
byte[] buffer = new byte[1024];
while (_isRunning)
{
int bytesRead = _tcpStream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine($"收到服务器消息: {message}");
string[] parts = message.Split(':');
if (parts[0] == "PEER_INFO")
{
HandlePeerInfo(parts);
}
else if (parts[0] == "REQUEST_UDP")
{
// 服务器请求UDP端口信息
SendUdpPortInfo();
}
else if (parts[0] == "ERROR")
{
Console.WriteLine($"服务器错误: {message.Substring(6)}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"接收服务器消息错误: {ex.Message}");
}
}
private void SendUdpPortInfo()
{
try
{
string message = $"UDP_PORT:{_clientId}:{_localUdpPort}";
byte[] data = Encoding.ASCII.GetBytes(message);
_tcpStream.Write(data, 0, data.Length);
Console.WriteLine($"已发送UDP端口信息: {_localUdpPort}");
}
catch (Exception ex)
{
Console.WriteLine($"发送UDP端口信息失败: {ex.Message}");
}
}
private void HandlePeerInfo(string[] parts)
{
// 格式: PEER_INFO:<peer_id>:<tcp_ep>:<udp_ep>
if (parts.Length < 4) return;
string peerId = parts[1];
// 解析TCP端点
string[] tcpParts = parts[2].Split(':');
if (tcpParts.Length < 2) return;
_peerTcpEP = new IPEndPoint(
IPAddress.Parse(tcpParts[0]),
int.Parse(tcpParts[1]));
// 解析UDP端点
string[] udpParts = parts[3].Split(':');
if (udpParts.Length < 2) return;
_peerUdpEP = new IPEndPoint(
IPAddress.Parse(udpParts[0]),
int.Parse(udpParts[1]));
Console.WriteLine($"目标客户端信息: TCP={_peerTcpEP}, UDP={_peerUdpEP}");
// 启动打洞线程
new Thread(AttemptPunch).Start();
}
#endregion
#region 打洞与连接
private void AttemptPunch()
{
Console.WriteLine("开始P2P连接尝试...");
// 优先尝试TCP连接
if (AttemptTcpConnection())
{
_useTCP = true;
_isConnected = true;
Console.WriteLine("TCP连接成功!使用TCP进行通信");
StartChatting();
return;
}
Console.WriteLine("TCP连接失败,尝试UDP打洞...");
// TCP失败后尝试UDP打洞
if (AttemptUdpPunch())
{
_useTCP = false;
_isConnected = true;
Console.WriteLine("UDP打洞成功!使用UDP进行通信");
StartChatting();
return;
}
Console.WriteLine("所有连接尝试失败,无法建立P2P连接");
}
private bool AttemptTcpConnection()
{
Console.WriteLine("尝试TCP打洞连接...");
for (int i = 0; i < PunchAttempts; i++)
{
try
{
Console.WriteLine($"TCP尝试 {i+1}/{PunchAttempts} 连接到 {_peerTcpEP}");
var tcpClient = new TcpClient();
tcpClient.Connect(_peerTcpEP);
// 保存连接
_tcpPeerConnection = tcpClient;
return true;
}
catch (SocketException sex)
{
Console.WriteLine($"TCP连接失败: {sex.SocketErrorCode}");
}
catch (Exception ex)
{
Console.WriteLine($"TCP连接异常: {ex.Message}");
}
Thread.Sleep(PunchInterval);
}
return false;
}
private bool _udpConnected = false;
private bool AttemptUdpPunch()
{
Console.WriteLine("尝试UDP打洞...");
_udpConnected = false;
// 发送多个打洞包(确保穿过NAT)
for (int i = 0; i < PunchAttempts; i++)
{
try
{
string message = $"PUNCH:{_clientId}:{i}";
byte[] data = Encoding.ASCII.GetBytes(message);
_udpClient.Send(data, data.Length, _peerUdpEP);
Console.WriteLine($"发送UDP打洞包到 {_peerUdpEP}");
}
catch (Exception ex)
{
Console.WriteLine($"发送UDP打洞包失败: {ex.Message}");
}
Thread.Sleep(PunchInterval);
}
// 检查是否收到对方消息
Console.WriteLine("等待UDP连接确认... (10秒)");
DateTime startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalSeconds < 10)
{
if (_udpConnected)
{
return true;
}
Thread.Sleep(100);
}
return false;
}
#endregion
#region 网络监听
private void StartTcpListener()
{
try
{
// 绑定随机本地端口
_tcpListener = new TcpListener(IPAddress.Any, 0);
_tcpListener.Start();
var localEp = (IPEndPoint)_tcpListener.LocalEndpoint;
Console.WriteLine($"TCP监听端口: {localEp.Port}");
// 启动接受TCP连接的线程
new Thread(() =>
{
while (_isRunning)
{
try
{
TcpClient peer = _tcpListener.AcceptTcpClient();
var remoteEp = (IPEndPoint)peer.Client.RemoteEndPoint;
if (_isConnected)
{
Console.WriteLine($"已连接,拒绝来自 {remoteEp} 的TCP连接");
peer.Close();
continue;
}
_tcpPeerConnection = peer;
_isConnected = true;
_useTCP = true;
Console.WriteLine($"接受来自 {remoteEp} 的TCP连接");
StartChatting();
}
catch (Exception ex)
{
if (_isRunning) Console.WriteLine($"接受TCP连接错误: {ex.Message}");
}
}
}).Start();
}
catch (Exception ex)
{
Console.WriteLine($"启动TCP监听器失败: {ex.Message}");
}
}
private void StartUdpListener()
{
try
{
// 绑定随机本地端口
_udpClient = new UdpClient(0);
var localEp = (IPEndPoint)_udpClient.Client.LocalEndPoint;
_localUdpPort = localEp.Port;
Console.WriteLine($"UDP监听端口: {localEp.Port}");
// 启动UDP接收线程
new Thread(() =>
{
while (_isRunning)
{
try
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = _udpClient.Receive(ref remoteEP);
string message = Encoding.ASCII.GetString(data);
// 检查是否是来自目标对等方的消息
if (_peerUdpEP != null &&
(remoteEP.Address.Equals(_peerUdpEP.Address) ||
remoteEP.Port == _peerUdpEP.Port))
{
if (message.StartsWith("PUNCH:"))
{
Console.WriteLine($"收到UDP打洞包: {message}");
if (!_isConnected) _udpConnected = true;
}
else if (message.StartsWith("MSG:"))
{
Console.WriteLine($"收到UDP消息: {message.Substring(4)}");
}
}
}
catch (Exception ex)
{
if (_isRunning) Console.WriteLine($"接收UDP消息错误: {ex.Message}");
}
}
}).Start();
}
catch (Exception ex)
{
Console.WriteLine($"启动UDP监听器失败: {ex.Message}");
}
}
private void UdpHeartbeat()
{
var serverEP = new IPEndPoint(IPAddress.Parse(_serverIp), _serverPort);
while (_isRunning)
{
try
{
// 每30秒发送一次心跳
Thread.Sleep(30000);
string message = $"HEARTBEAT:{_clientId}";
byte[] data = Encoding.ASCII.GetBytes(message);
_udpClient.Send(data, data.Length, serverEP);
}
catch (Exception ex)
{
Console.WriteLine($"发送UDP心跳失败: {ex.Message}");
}
}
}
#endregion
#region 通信处理
private TcpClient _tcpPeerConnection;
private void StartChatting()
{
if (_useTCP)
{
// TCP通信模式
new Thread(() => ReceiveTcpMessages(_tcpPeerConnection)).Start();
new Thread(() => SendTcpMessages(_tcpPeerConnection)).Start();
}
else
{
// UDP通信模式
new Thread(SendUdpMessages).Start();
}
}
private void ReceiveTcpMessages(TcpClient peer)
{
try
{
NetworkStream stream = peer.GetStream();
byte[] buffer = new byte[1024];
while (_isConnected)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine($"对方(TCP): {message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"接收TCP消息错误: {ex.Message}");
}
finally
{
_isConnected = false;
peer.Close();
Console.WriteLine("TCP连接已关闭");
}
}
private void SendTcpMessages(TcpClient peer)
{
try
{
NetworkStream stream = peer.GetStream();
Console.WriteLine("输入消息开始聊天 (输入'exit'退出):");
while (_isConnected)
{
string message = Console.ReadLine();
if (message == "exit") break;
byte[] data = Encoding.ASCII.GetBytes(message);
stream.Write(data, 0, data.Length);
}
}
catch (Exception ex)
{
Console.WriteLine($"发送TCP消息错误: {ex.Message}");
}
finally
{
_isConnected = false;
peer.Close();
}
}
private void SendUdpMessages()
{
Console.WriteLine("输入消息开始聊天 (输入'exit'退出):");
while (_isConnected)
{
try
{
string message = Console.ReadLine();
if (message == "exit") break;
byte[] data = Encoding.ASCII.GetBytes($"MSG:{message}");
_udpClient.Send(data, data.Length, _peerUdpEP);
}
catch (Exception ex)
{
Console.WriteLine($"发送UDP消息错误: {ex.Message}");
}
}
_isConnected = false;
}
#endregion
static void Main(string[] args)
{
Console.Write("输入协调服务器IP: ");
string serverIp = Console.ReadLine();
Console.Write("输入客户端ID: ");
string clientId = Console.ReadLine();
var client = new HybridP2PClient(serverIp, 11000, clientId);
client.Start();
}
}