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

基于C# .NET的即时通讯项目消息交互相关问题求助

基于C# .NET即时通讯项目的问题解决方案

1. TCP客户端携带Bearer Token的实现方式

TCP是基于字节流的传输协议,没有HTTP原生的请求头概念,需要自定义应用层协议传递Token。核心逻辑是:发送业务数据前先传输Token验证信息,服务端按约定格式读取并验证Token,通过后再处理后续消息。

客户端示例代码

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

var serverIp = "127.0.0.1";
var port = 8888;
var bearerToken = "Bearer your-jwt-token-here";

using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(serverIp, port);
using var stream = tcpClient.GetStream();

// 第一步:发送Token(用换行符做分隔符,方便服务端识别)
var tokenBytes = Encoding.UTF8.GetBytes(bearerToken + "\n");
await stream.WriteAsync(tokenBytes, 0, tokenBytes.Length);

// 第二步:发送实际消息
var message = "Hello, this is a chat message!";
var messageBytes = Encoding.UTF8.GetBytes(message);
await stream.WriteAsync(messageBytes, 0, messageBytes.Length);

服务端验证逻辑示例

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IdentityModel.Tokens.Jwt;

var port = 8888;
using var listener = new TcpListener(IPAddress.Any, port);
listener.Start();

while (true)
{
    using var client = await listener.AcceptTcpClientAsync();
    using var stream = client.GetStream();
    using var reader = new StreamReader(stream);

    // 先读取Token
    var token = await reader.ReadLineAsync();
    if (string.IsNullOrWhiteSpace(token) || !ValidateBearerToken(token))
    {
        // 验证失败,直接关闭连接
        client.Close();
        continue;
    }

    // 读取并处理业务消息
    var buffer = new byte[1024];
    var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
    var receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    Console.WriteLine($"Received message: {receivedMessage}");
}

// Token验证方法(示例用JWT验证)
bool ValidateBearerToken(string token)
{
    if (!token.StartsWith("Bearer ")) return false;
    var jwtToken = token.Substring(7);
    var handler = new JwtSecurityTokenHandler();
    try
    {
        handler.ValidateToken(jwtToken, new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidIssuer = "your-issuer",
            ValidAudience = "your-audience",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-signing-key"))
        }, out var validatedToken);
        return true;
    }
    catch
    {
        return false;
    }
}

2. 服务端主动推送消息的最优方案

HttpListener属于请求-响应模式,无法主动向客户端发起推送,完全不适合即时通讯的主动通知场景。推荐使用ASP.NET原生的SignalR框架——它专为实时通信设计,支持WebSocket、Server-Sent Events等多种传输方式,自带身份验证集成,能轻松实现服务端向特定用户推送消息。

服务端SignalR配置

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// 添加SignalR服务
builder.Services.AddSignalR();
// 配置Bearer Token验证
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidIssuer = "your-issuer",
            ValidAudience = "your-audience",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-signing-key"))
        };
    });

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// 映射SignalR Hub
app.MapHub<ChatHub>("/chathub");

app.Run();

// ChatHub.cs(自定义Hub类)
using Microsoft.AspNetCore.SignalR;
using System.Security.Claims;

public class ChatHub : Hub
{
    // 客户端连接时,将连接ID与用户ID绑定(从Token的Claims中获取用户ID)
    public override async Task OnConnectedAsync()
    {
        var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (!string.IsNullOrEmpty(userId))
        {
            // 将用户加入以自己ID命名的分组,方便后续定向推送
            await Groups.AddToGroupAsync(Context.ConnectionId, userId);
        }
        await base.OnConnectedAsync();
    }

    // 供服务端其他逻辑调用,向特定用户推送消息
    public async Task SendMessageToUser(string userId, string message)
    {
        await Clients.Group(userId).SendAsync("ReceiveMessage", message);
    }
}

WPF客户端连接与接收消息

using Microsoft.AspNetCore.SignalR.Client;
using System.Windows;

public class ChatClient
{
    private HubConnection _connection;

    public async Task StartConnection(string token)
    {
        _connection = new HubConnectionBuilder()
            .WithUrl("https://your-server-url/chathub", options =>
            {
                // 配置Bearer Token
                options.AccessTokenProvider = () => Task.FromResult(token);
            })
            .Build();

        // 注册接收消息的回调
        _connection.On<string>("ReceiveMessage", message =>
        {
            // 在WPF UI线程更新界面
            Application.Current.Dispatcher.Invoke(() =>
            {
                MessageBox.Show($"收到新消息:{message}");
                // 替换为你的消息展示逻辑
            });
        });

        try
        {
            await _connection.StartAsync();
        }
        catch (Exception ex)
        {
            MessageBox.Show($"连接失败:{ex.Message}");
        }
    }

    public async Task StopConnection()
    {
        if (_connection != null)
        {
            await _connection.StopAsync();
            await _connection.DisposeAsync();
        }
    }
}

3. "每个套接字和端口只能使用一次"错误解决

这个错误的核心是指定端口已被占用,可能是其他进程,也可能是当前进程中未正确释放的TcpListener实例。解决方法:

  • 检查端口占用:打开命令提示符,执行netstat -ano | findstr :<你的端口号>,找到占用端口的PID,在任务管理器中结束对应进程。
  • 正确释放TcpListener:确保所有TcpListener实例使用完毕后调用Stop(),或用using语句自动释放资源:
    using var listener = new TcpListener(IPAddress.Any, port);
    listener.Start();
    // 业务逻辑...
    // 依赖using自动释放,或手动调用listener.Stop()
    
  • 避免端口冲突:如果ASP.NET服务已占用某个端口(如默认5000/7000),TcpListener要使用不同端口,不要重复绑定。
  • 允许端口共享(不推荐):若必须共享端口,可设置ExclusiveAddressUse为false,但可能引发不可预期问题:
    listener.ExclusiveAddressUse = false;
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 03:01:01