You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#/.NET WPF 双应用TCP双向双监听通信实现方案问询

需求可行性确认

该需求完全可以实现,不存在技术障碍。TCP协议本身没有限制单个应用只能作为服务端或客户端存在,你可以在同一个应用中同时运行两套完全独立的套接字逻辑:一套作为服务端监听指定端口接收连接,另一套作为客户端向目标端口发起连接,二者端口互不冲突即可。

具体实现方案

1. 前置约定

因为两个应用完全相同,你需要提前约定两个不冲突的本地端口,两个实例分别监听其中一个端口,主动连接时指向对方的监听端口即可:

  • 实例1:监听端口18001,主动连接目标端口18002
  • 实例2:监听端口18002,主动连接目标端口18001
  • 本地通信统一使用环回地址127.0.0.1

你可以把端口配置做成启动参数传入,这样无需编译两个不同的安装包,同一个程序启动时传入不同参数即可区分身份。

2. 核心逻辑拆分

每个应用内部拆分为两个独立运行的模块:

  • 服务端监听模块:应用启动后自动启动,绑定约定的监听端口,异步等待对方连接,连接建立后处理收到的消息
  • 客户端连接模块:用户触发连接操作时,向对方的监听端口发起连接,连接建立后可主动发送消息

注意:所有套接字IO操作必须使用异步实现,避免阻塞WPF UI线程导致界面卡死

3. 代码示例(基于.NET内置TcpListener/TcpClient实现)

3.1 全局变量定义

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows;

// 窗口类内部的全局变量
private const int DefaultPortA = 18001;
private const int DefaultPortB = 18002;
private int _localListenPort;
private int _remoteTargetPort;
private TcpListener _tcpListener;
private TcpClient _establishedConnection;
private CancellationTokenSource _operationCts = new();

3.2 初始化与监听启动逻辑

// 窗口Loaded事件中调用,启动参数传入端口标识,比如第一个实例传"1",第二个传"2"
private void Init(string instanceFlag)
{
    if (instanceFlag == "1")
    {
        _localListenPort = DefaultPortA;
        _remoteTargetPort = DefaultPortB;
    }
    else
    {
        _localListenPort = DefaultPortB;
        _remoteTargetPort = DefaultPortA;
    }
    // 启动服务端监听
    _ = StartListenerAsync();
}

private async Task StartListenerAsync()
{
    _tcpListener = new TcpListener(IPAddress.Loopback, _localListenPort);
    _tcpListener.Start();
    try
    {
        while (!_operationCts.Token.IsCancellationRequested)
        {
            var incomingClient = await _tcpListener.AcceptTcpClientAsync(_operationCts.Token);
            _establishedConnection = incomingClient;
            // 启动后台任务处理消息接收
            _ = ProcessMessageLoopAsync(incomingClient, _operationCts.Token);
        }
    }
    catch (OperationCanceledException)
    {
        // 正常停止监听,无需处理
    }
    finally
    {
        _tcpListener.Stop();
    }
}

3.3 主动连接逻辑

// 绑定到主动连接按钮的点击事件
private async void OnConnectButtonClick(object sender, RoutedEventArgs e)
{
    if (_establishedConnection is { Connected: true })
    {
        MessageBox.Show("已建立连接,无需重复连接");
        return;
    }
    try
    {
        var client = new TcpClient();
        await client.ConnectAsync(IPAddress.Loopback, _remoteTargetPort, _operationCts.Token);
        _establishedConnection = client;
        _ = ProcessMessageLoopAsync(client, _operationCts.Token);
        MessageBox.Show("连接成功");
    }
    catch (SocketException)
    {
        MessageBox.Show("连接失败,请确认对方应用已启动");
    }
}

3.4 消息收发逻辑

private async Task ProcessMessageLoopAsync(TcpClient client, CancellationToken token)
{
    var stream = client.GetStream();
    var buffer = new byte[4096];
    try
    {
        while (!token.IsCancellationRequested && client.Connected)
        {
            var readBytes = await stream.ReadAsync(buffer, token);
            if (readBytes == 0)
            {
                // 对方主动断开连接
                break;
            }
            var receivedText = Encoding.UTF8.GetString(buffer, 0, readBytes);
            // 切回UI线程更新界面
            Dispatcher.Invoke(() =>
            {
                // 此处将收到的消息追加到你的UI消息列表控件即可
            });
        }
    }
    catch (OperationCanceledException) { }
    finally
    {
        client.Dispose();
        if (_establishedConnection == client)
        {
            _establishedConnection = null;
        }
    }
}

// 绑定到发送消息按钮的点击事件
private async void OnSendButtonClick(object sender, RoutedEventArgs e)
{
    if (_establishedConnection is not { Connected: true })
    {
        MessageBox.Show("未建立连接,请先连接对方");
        return;
    }
    // 此处取你的输入框内容作为发送消息
    string sendText = "你的待发送消息";
    var data = Encoding.UTF8.GetBytes(sendText);
    await _establishedConnection.GetStream().WriteAsync(data, _operationCts.Token);
}

3.5 资源释放逻辑

// 窗口Closing事件中调用,释放所有连接资源
private void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
    _operationCts.Cancel();
    _establishedConnection?.Dispose();
    _tcpListener?.Stop();
}

4. 优化建议

  • 实际生产使用时建议给消息加上长度前缀,解决TCP粘包、半包问题
  • 可以添加心跳包逻辑,定期检测连接存活状态,避免半开连接导致的假在线问题
  • 可以加入连接断开自动重试逻辑,提升可用性

内容的提问来源于stack exchange,提问作者grif_noob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 01:00:04