之前写的SignalR通信,是基于.net6api,BS和CS进行通信的。
.net6API使用SignalR+vue3聊天+WPF聊天_signalr wpf_故里2130的博客-CSDN博客
今天写一篇关于CS客户端的SignalR通信,后台服务使用.net6api 。其实和之前写的差不多,主要在于服务端以后台进程的方式存在,而客户端以exe方式存在,其实代码都一样,只是生成的方式不一样。
一、服务端
1.首先建立一个.net6的webapi服务端
2.Program.cs
cs
using SignalRServerApi.Controllers;
namespace SignalRServerApi
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSignalR(); //增加AddSignalR
string[] urls = new[] { "http://localhost:3000" }; //此处一定要写指定的ip地址,地址是前端的ip地址,坑了我1天的时间
builder.Services.AddCors(options =>
options.AddDefaultPolicy(builder => builder.WithOrigins(urls)
.AllowAnyMethod().AllowAnyHeader().AllowCredentials())
);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(); //增加跨域问题
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.MapHub<ChatHub>("/api/chat"); //前端访问的地址,2边要统一就行了
app.Run();
}
}
}
3.ChatHub.cs
cs
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
namespace SignalRServerApi.Controllers
{
public class ChatHub : Hub
{
private static Dictionary<string, string> dicUsers = new Dictionary<string, string>();
public override Task OnConnectedAsync() //登录
{
Console.WriteLine($"ID:{Context.ConnectionId} 已连接"); //控制台记录
var cid = Context.ConnectionId;
//根据id获取指定客户端
var client = Clients.Client(cid);
//向指定用户发送消息
//client.SendAsync("Self", cid);
//像所有用户发送消息
Clients.All.SendAsync("ReceivePublicMessageLogin", $"{cid}加入了聊天室"); //界面显示登录
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception? exception) //退出的时候
{
Console.WriteLine($"ID:{Context.ConnectionId} 已断开");
var cid = Context.ConnectionId;
//根据id获取指定客户端
var client = Clients.Client(cid);
//向指定用户发送消息
//client.SendAsync("Self", cid);
//像所有用户发送消息
Clients.All.SendAsync("ReceivePublicMessageLogin", $"{cid}离开了聊天室"); //界面显示登录
return base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 向所有客户端发送消息
/// </summary>
/// <param name="user"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendPublicMessage(string user, string message)
{ //string user,
await Clients.All.SendAsync("ReceivePublicMessage", user, message); //ReceiveMessage 提供给客户端使用
}
/// <summary>
/// 用户登录,密码就不判断了
/// </summary>
/// <param name="userId"></param>
public void Login(string userId) //对应前端的invoke
{
if (!dicUsers.ContainsKey(userId))
{
dicUsers[userId] = Context.ConnectionId;
}
Console.WriteLine($"{userId}登录成功,ConnectionId={Context.ConnectionId}");
//向所有用户发送当前在线的用户列表
Clients.All.SendAsync("dicUsers", dicUsers.Keys.ToList()); //对应前端的on
}
public void ChatOne(string userId, string toUserId, string msg) //用户 发送到的用户 发送的消息
{
string newMsg = $"{userId}对你说{msg}";//组装后的消息体
//如果当前用户在线
if (dicUsers.ContainsKey(toUserId))
{
Clients.Client(dicUsers[toUserId]).SendAsync("ChatInfo", newMsg);
}
else
{
//如果当前用户不在线,正常是保存数据库,等上线时加载,暂时不做处理
}
}
}
}
4.生成方式
选择Windows应用程序
5.运行
运行后,服务是以进程的方式存在
6.效果
此时需要注意代码的这个地址
当然IP和端口都可以修改的,也可以增加网页显示,根据业务而定。
二、客户端
1.首先建立一个.net6的wpf客户端
2.安装Microsoft.AspNetCore.SignalR.Client
3.建立界面
界面代码
cs
<Window x:Class="SignalRClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SignalRClient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock>账号:</TextBlock>
<TextBox Name="user" Width="300" Height="20" Margin="0,5"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>密码:</TextBlock>
<TextBox Name="password" Width="300" Height="20" Margin="0,5"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Button Name="btnLogin" Width="50" Height="20" Margin="0,5" Click="btnLogin_Click">登录</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>发送给某人:</TextBlock>
<TextBox Name="toUser" Width="300" Height="20" Margin="0,5" ></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>发送内容:</TextBlock>
<TextBox Name="content" Width="300" Height="20" Margin="0,5"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Name="btnSendAll" Width="100" Height="20" Margin="0,5" Click="btnSendAll_Click">发送所有人</Button>
<Button Name="btnSendOne" Width="100" Height="20" Margin="0,5" Click="btnSendOne_Click">发送到个人</Button>
</StackPanel>
<RichTextBox Height="100" Name="rtbtxt">
<FlowDocument>
<Paragraph>
<Run Text=""/>
</Paragraph>
</FlowDocument>
</RichTextBox>
</StackPanel>
</Grid>
</Window>
4.后台代码
cs
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SignalRClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private HubConnection hubConnection;
public MainWindow()
{
InitializeComponent();
//rtbtxt.AppendText("4444");
}
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
//此处和VUE3界面是一样的,参照写就行了。
//1.初始化
InitInfo();
//2.连接
Link();
//3.监听
Listen();
//4.登录
Login();
}
/// <summary>
/// 初始化
/// </summary>
private void InitInfo()
{
hubConnection = new HubConnectionBuilder().WithUrl("http://127.0.0.1:5000/api/chat", (opt) =>
{
opt.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
return message;
};
}).WithAutomaticReconnect().Build();
hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5);
}
List<string> LoginUser;
string msgContent;
/// <summary>
/// 监听数据的变化
/// </summary>
private void Listen()
{
hubConnection.On<List<string>>("dicUsers", msg =>
{
LoginUser = msg;
string s = string.Empty;
foreach (string item in msg)
{
s += item + "用户登录" + Environment.NewLine;
}
rtbtxt.AppendText(s);
}); //匿名方法 真实环境中,此处使用的是属性变化,不要使用赋值的方式
hubConnection.On<string>("ReceivePublicMessageLogin", msg => { msgContent = msg; rtbtxt.AppendText(msg + Environment.NewLine); });
hubConnection.On<string, string>("ReceivePublicMessage", (user, msg) => { msgContent = msg; rtbtxt.AppendText(user + "说:" + msg + Environment.NewLine); }); //匿名方法
hubConnection.On<string>("ChatInfo", msg => { msgContent = msg; rtbtxt.AppendText(msg + Environment.NewLine); });
}
/// <summary>
/// 连接
/// </summary>
private async void Link()
{
try
{
await hubConnection.StartAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// 在这里添加你的证书验证逻辑
// 返回true表示验证通过,返回false表示验证失败
// 例如,你可以添加自定义的证书验证逻辑来允许不受信任的证书
return true;
}
private void Login()
{
hubConnection.InvokeAsync("Login", user.Text);
}
private void btnSendAll_Click(object sender, RoutedEventArgs e)
{
hubConnection.InvokeAsync("SendPublicMessage", user.Text, content.Text);
}
private void btnSendOne_Click(object sender, RoutedEventArgs e)
{
hubConnection.InvokeAsync("ChatOne", user.Text, toUser.Text, content.Text);
}
}
}
这里需要注意, 一起运行不会报错,但是单独运行会报错
The SSL connection could not be established, see inner exception
需要在初始化InitInfo()方法中增加HttpMessageHandlerFactory,即可解决。
5.效果
此时,后台的服务以进行的方式存在,然后可以和客户端进行通信,其实和之前写的是一样的,只是生成方式不同而已。
源码