之前写的SignalR通信,是基于.net6api,BS和CS进行通信的。
.net6API使用SignalR+vue3聊天+WPF聊天_signalr wpf_故里2130的博客-CSDN博客
今天写一篇关于CS客户端的SignalR通信,后台服务使用.net6api 。其实和之前写的差不多,主要在于服务端以后台进程的方式存在,而客户端以exe方式存在,其实代码都一样,只是生成的方式不一样。
一、服务端
1.首先建立一个.net6的webapi服务端
2.Program.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/swashbucklebuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();builder.Services.AddSignalR(); //增加AddSignalRstring[] 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
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.建立界面
界面代码
<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.后台代码
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 certificateclientHandler.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.效果
此时,后台的服务以进行的方式存在,然后可以和客户端进行通信,其实和之前写的是一样的,只是生成方式不同而已。
源码
https://download.csdn.net/download/u012563853/88061397