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();
}
}