前言
前幾篇介紹了整個中間件的構成,路由,基本配置等等.基本上沒有涉及到通訊部分。不過已經實現了融雲的通訊功能,由於是第三方的就不在單獨去寫。正好.NET Core SignalR已經出來好久了,於是乎趕緊對接上。可以先看一下之前的文章:.Net Core SignalR初體驗。
Hub設計
Hub我采用了 Hub<T>,然后只定義了一個 Receive方法。
namespace LayIM.AspNetCore.IM.SignalR
{
public interface ILayIMClient
{
Task Receive(object message);
}
}
// Hub端代碼
public Task SendMessage(string targetId, string message)
{
//這里就可以調用 Receive方法
return Clients.Caller.Receive(message);
}
那么這里我們要做的就是,先連接上服務器在實現詳細業務。下面我們要做兩件事情:
- 修改Startup,注冊SignalR
- 增加
Javascript客戶端
由於是將SignalR拆分到LayIM.AspNetCore.IM.SignalR項目中,所以注冊服務端代碼做了小小封裝。在SignalRServiceExtensions文件中:
/// <summary>
/// 使用SignalR通信
/// </summary>
/// <param name="services"></param>
/// <param name="setConfig"></param>
public static IServiceCollection AddSignalR(this IServiceCollection services, Action<LayIMHubOptions> configure)
{
var options = new LayIMHubOptions();
configure?.Invoke(options);
var signalRServerBuilder = services.AddSignalR(options.HubConfigure);
//增加Redis配置
if (options.UseRedis)
{
signalRServerBuilder.AddRedis(options.RedisConfiguration, options.RedisConfigure);
}
//AddSignalR must be called before registering your custom SignalR services.
services.AddSingleton<ILayIMAppBuilder, SignalRAppBuilder>();
//獲取用戶ID
services.AddSingleton<IUserIdProvider, LayIMUserIdProvider>();
LayIMServiceLocator.SetServiceProvider(services.BuildServiceProvider());
return services;
}
那么在客戶端 Startup 調用的時候就可以這么寫了:
//注冊LayIM的默認服務
services.AddLayIM(() =>
{
return new MyUserFactory();
}).AddSignalR(options =>
{
options.HubConfigure = hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(5);
};
//使用Redis
options.RedisConfiguration = "192.168.1.225:6379"
})
.AddSqlServer(connectionString);
然后Configure方法中:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....其他代碼
//使用LayIM,自定義配置
app.UseLayIM(options =>
{
options.ServerType = ServerType.SignalR;
});
....其他代碼
}
到這里可能大家有疑問,沒有看到添加 AddSignalR方法。由於是封裝了很多細節,所以,這一部分已經寫到了UselayIM代碼中。
public class SignalRAppBuilder : ILayIMAppBuilder
{
public void Build(IApplicationBuilder builder)
{
builder.UseSignalR(route => {
route.MapHub<LayIMHub>("/layimHub", connectionOptions =>
{
});
});
}
}
其實也就對應了上文中services.AddSingleton<ILayIMAppBuilder, SignalRAppBuilder>();這句代碼。那么到這里呢,SignalR的服務該注冊的也注冊了,該添加的也添加了,下面就編寫(JS)客戶端代碼。
SignalR Javascript客戶端
這里我們根據官方文檔里寫就可以。連接部分核心代碼:
let hubRoute = "layimHub";
let protocol = new signalR.JsonHubProtocol();
var options = {};
connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl(hubRoute, options)
.withHubProtocol(protocol)
.build();
//receive message
connection.on('Receive', im.handle);
connection.onclose(function (e) {
if (e) {
}
log('連接已關閉' + e ? e : '');
});
connection.start()
.then(function () {
//連接成功
})
.catch(function (err) {
log('服務器連接失敗:' + err);
});
運行一下程序。沒問題

那么到這里,我們就可以對接LayIM的實際業務了.這一段其實和融雲思路差不多。首先,我們要確保消息能夠發送到后端,那么我們修改一下監聽LayIM發送消息部分的代碼:
layim.on('sendMessage', function (data) {
//調用socket方法,發送消息
im.sendMsgWithQueue(data);
});
調用服務端發送方法:
if (im.connected) {
this.invoke(connection, 'SendMessage', targetId, msg);
}
invoke方法
invoke: function () {
if (!im.connected) {
return;
}
var argsArray = Array.prototype.slice.call(arguments);
connection.invoke.apply(connection, argsArray.slice(1))
.then(function (result) {
if (result) {
log(result);
}
}).catch(function (err) {
log(err);
});
},
可以看到,調用了服務端的 SendMessage方法,那么這里就要回到Hub代碼部分了。我們在Hub端新增方法SendMessage,然后定義好接收變量。如下:
public class LayIMMessage
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("avatar")]
public string Avatar { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
[JsonProperty("username")]
public string UserName { get; set; }
}
public Task SendMessage(string targetId, LayIMMessage message)
{
if (string.IsNullOrEmpty(targetId) || message == null)
{
return Task.CompletedTask;
}
var toClientMessage = LayIMToClientMessage<LayIMMessage>.Create(message, LayIMMessageType.ClientToClient);
//如果消息類型是群聊,調用OthersInGroup方法
if (message.Type == LayIMConst.TYPE_GROUP)
{
return Clients.OthersInGroup(targetId).Receive(toClientMessage);
}
else
{
//如果消息類型是單聊,直接調用User
//或者 Clients.Client([connectionId])
return Clients.User(targetId).Receive(toClientMessage);
}
}
這里有兩個細節要注意,第一:用戶連接成功之后需要加入到Group,第二,自定義UserIdProvider。 那么第一個,就是我們要在用戶連接成功之后調用一下加入群組的方法,同樣,用戶下線之后要移除掉。IGroupManager中定義了如下兩個方法:
namespace Microsoft.AspNetCore.SignalR
{
//
// 摘要:
// A manager abstraction for adding and removing connections from groups.
public interface IGroupManager
{
Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default(CancellationToken));
Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default(CancellationToken));
}
}
至於自定義用戶ID,很簡單,我們實現接口IUserIdProvider即可。細心的同學可能在前文的代碼中看到這一段了。為什么要使用重寫呢?因為SignalR默認使用ConnectionId。而且每次刷新頁面之后,它都是會變化的,那么如果我們改成使用綁定用戶ID的話,對於直接定點推送,刷新頁面是沒有問題的,直接根據User對象推送即可。下面演示一下:

群聊的圖就不貼了,一樣的。那么至此SignalR的對接就結束了。是不是比Demo也難不了多少。
推送服務分離
到這里呢,我們就可以融雲,SignalR自由切換了。具體細節可以查看 LayIM.AspNetCore.Demo.RongCloud,LayIM.AspNetCore.Demo.SignalR兩個項目。
總結
給大家大體介紹了一下對接思路,其實有很多細節也沒有展示,畢竟貼的代碼已經夠多了。如果小伙伴們有興趣,可以移步:源碼地址,今天就到這里啦,再見,祝大家中秋快樂
