【C#】通过中间服务器实现客户端向指定客户端传输数据
问题需求
我找了很多资料,都是向所有客户端发消息的方案,但我要实现的是:多个客户端向服务器发起请求,获取另一客户端的数据;目标客户端把数据发给服务器,同时指明该数据要发给发起请求的客户端。我是新手,不知道怎么实现。
目标流程
客户端A(请求方)→ 服务器 → 客户端B(数据提供方),随后客户端B→服务器→客户端A,实现定向的数据传输,规避公网环境下的端口转发问题。
现有尝试
我试过让数据发送端监听,请求端直接连接发送端传输数据,局域网里能用,但公网环境需要端口转发。用户群体庞大,没法给每个用户配置端口转发,所以打算用一台服务器做中转节点。
我写了个控制台测试服务器,监听IP:端口X接收客户端连接和数据,再转发到IP:端口Y,但这样会把数据发给端口Y上的所有客户端。我没法直接向客户端公网IP发送数据,也不想给每个交互的客户端分配新端口,希望找到不用分配新端口的方式实现请求与数据接收。
现有测试代码
服务器代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Test___server { class server { public static string serverIP = "192.168.0.102"; static void Main(string[] args) { Thread listenSendingThread = new Thread(listenSending); listenSendingThread.IsBackground = true; listenSendingThread.Start(); Thread listenReceivingThread = new Thread(listenReceiving); listenReceivingThread.IsBackground = true; listenReceivingThread.Start(); Console.ReadKey(); } public static List<TcpClient> listSending = new List<TcpClient>(); public static List<TcpClient> listReceiving = new List<TcpClient>(); public static TcpClient clientSending = null; private static void listenSending() { TcpListener listenerSending = new TcpListener(IPAddress.Parse(serverIP), 5319); listenerSending.Start(); Console.WriteLine("Server listening to " + serverIP + ":5319"); while(true) { clientSending = listenerSending.AcceptTcpClient(); listSending.Add(clientSending); Console.WriteLine("Sender connection received from " + clientSending.Client.RemoteEndPoint); } } private static void send() { StreamWriter sw = new StreamWriter(clientSending.GetStream()); sw.WriteLine(message); sw.Flush(); Console.WriteLine("Message sent!"); } public static string message = string.Empty; private static void listenReceiving() { TcpListener listener = new TcpListener(IPAddress.Parse(serverIP), 0045); listener.Start(); Console.WriteLine("Server listening to " + serverIP + ":0045"); while (true) { TcpClient client = listener.AcceptTcpClient(); listReceiving.Add(client); Console.WriteLine("Receiver connection received from " + client.Client.RemoteEndPoint); StreamReader sr = new StreamReader(client.GetStream()); message = sr.ReadLine(); send(); } } } }
请求客户端代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Test____admin { class admin { static void Main(string[] args) { Console.WriteLine("Begin"); string serverIP = "192.168.0.102"; System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient(); clientSocket.Connect(serverIP, ); Console.WriteLine("Connected"); while (true) { Console.WriteLine("Reading"); StreamReader sr = new StreamReader(clientSocket.GetStream()); Console.WriteLine("Message: " + sr.ReadLine()); } } } }
响应请求的客户端代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace Test___client { class client { public static string serverIP = "192.168.0.102"; static void Main(string[] args) { clientConnect(); } private static void clientConnect() { try { TcpClient client = new TcpClient(serverIP, 0045); StreamWriter sw = new StreamWriter(client.GetStream()); sw.WriteLine("Karan!"); sw.Flush(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } } }
解决方案
核心思路是给每个客户端分配唯一标识,服务器通过标识管理连接并定向转发数据,无需新增端口,只用一个固定端口即可。
关键改进点
- 客户端标识绑定:每个客户端连接服务器时发送唯一ID(如用户名、UUID),服务器用
Dictionary<string, TcpClient>存储ID与连接的映射,替代原有的List<TcpClient>。 - 统一消息协议:定义包含
目标ID、内容的消息格式,让服务器能识别数据的接收方。 - 异步连接处理:每个客户端连接后开启独立异步线程处理消息读写,避免单线程阻塞。
改进后的示例代码
服务器代码
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace TestServer { class Server { // 存储客户端ID与连接的映射 private static Dictionary<string, TcpClient> _clientMap = new Dictionary<string, TcpClient>(); private static TcpListener _listener; private const int Port = 5319; // 只用一个固定端口 private const string ServerIp = "192.168.0.102"; static void Main(string[] args) { _listener = new TcpListener(IPAddress.Parse(ServerIp), Port); _listener.Start(); Console.WriteLine($"服务器启动,监听 {ServerIp}:{Port}"); AcceptClientsAsync(); Console.ReadKey(); } // 异步接收客户端连接 private static async void AcceptClientsAsync() { while (true) { var client = await _listener.AcceptTcpClientAsync(); Console.WriteLine($"新客户端连接:{client.Client.RemoteEndPoint}"); // 异步处理该客户端的消息 _ = HandleClientAsync(client); } } // 处理单个客户端的消息读写 private static async Task HandleClientAsync(TcpClient client) { string clientId = null; var stream = client.GetStream(); var reader = new StreamReader(stream, Encoding.UTF8); var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true }; try { // 第一步:接收客户端的唯一ID clientId = await reader.ReadLineAsync(); if (!string.IsNullOrEmpty(clientId)) { lock (_clientMap) { _clientMap[clientId] = client; Console.WriteLine($"客户端 {clientId} 注册成功"); } } // 持续接收消息 string message; while ((message = await reader.ReadLineAsync()) != null) { Console.WriteLine($"收到 {clientId} 的消息:{message}"); // 解析消息:格式为 "目标ID|消息内容" var parts = message.Split('|'); if (parts.Length == 2) { var targetId = parts[0]; var content = parts[1]; // 定向转发给目标客户端 if (_clientMap.TryGetValue(targetId, out var targetClient)) { var targetWriter = new StreamWriter(targetClient.GetStream(), Encoding.UTF8) { AutoFlush = true }; await targetWriter.WriteLineAsync($"来自 {clientId} 的数据:{content}"); Console.WriteLine($"已转发消息到 {targetId}"); } else { await writer.WriteLineAsync($"错误:目标客户端 {targetId} 未在线"); } } } } catch (Exception ex) { Console.WriteLine($"客户端 {clientId} 连接异常:{ex.Message}"); } finally { // 客户端断开后移除映射 if (!string.IsNullOrEmpty(clientId)) { lock (_clientMap) { _clientMap.Remove(clientId); Console.WriteLine($"客户端 {clientId} 已断开连接"); } } client.Close(); } } } }
请求客户端代码(客户端A)
using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace RequestClient { class RequestClient { private const string ServerIp = "192.168.0.102"; private const int Port = 5319; private const string ClientId = "ClientA"; // 自己的唯一标识 static async Task Main(string[] args) { using var client = new TcpClient(); await client.ConnectAsync(ServerIp, Port); Console.WriteLine("已连接到服务器"); var stream = client.GetStream(); var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true }; var reader = new StreamReader(stream, Encoding.UTF8); // 发送自己的ID到服务器 await writer.WriteLineAsync(ClientId); // 发送请求给目标客户端(比如ClientB) Console.WriteLine("输入要发送给ClientB的请求内容:"); var requestContent = Console.ReadLine(); await writer.WriteLineAsync($"ClientB|{requestContent}"); // 等待接收响应 Console.WriteLine("等待接收响应..."); var response = await reader.ReadLineAsync(); Console.WriteLine($"收到响应:{response}"); Console.ReadKey(); } } }
响应客户端代码(客户端B)
using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace ResponseClient { class ResponseClient { private const string ServerIp = "192.168.0.102"; private const int Port = 5319; private const string ClientId = "ClientB"; // 自己的唯一标识 static async Task Main(string[] args) { using var client = new TcpClient(); await client.ConnectAsync(ServerIp, Port); Console.WriteLine("已连接到服务器"); var stream = client.GetStream(); var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true }; var reader = new StreamReader(stream, Encoding.UTF8); // 发送自己的ID到服务器 await writer.WriteLineAsync(ClientId); Console.WriteLine("等待接收请求..."); // 持续监听请求 string message; while ((message = await reader.ReadLineAsync()) != null) { Console.WriteLine($"收到请求:{message}"); // 解析请求来源(格式:"来自 ClientA 的数据:XXX") var parts = message.Split(':'); if (parts.Length >= 2) { var sourceId = parts[0].Replace("来自 ", ""); // 准备响应内容 Console.WriteLine("输入要回复的内容:"); var responseContent = Console.ReadLine(); // 发送响应给请求来源 await writer.WriteLineAsync($"{sourceId}|{responseContent}"); Console.WriteLine("响应已发送"); } } } } }
说明
- 所有客户端都连接同一个服务器端口,无需额外端口配置。
- 服务器通过客户端ID精准管理连接,实现定向数据转发。
- 消息用
|分隔目标ID和内容,新手易理解修改;也可换成JSON格式(如用Newtonsoft.Json序列化),提升扩展性。 - 异步处理机制支持多客户端同时连接,避免单线程阻塞问题。
内容的提问来源于stack exchange,提问作者karan ugale
相关产品推荐
相关产品推荐

