企业级工作流解决方案(九)--微服务Tcp消息传输模型之客户端处理
客户端启动
客户端启动主要做三件事情,1. 从配置文件读取服务调用配置,存储到全局对象中。2. 指定客户端编解码器工厂。3. 预连接,即预先建立与服务端的通信Chanel。
[DependsOn(typeof(AbpKernelModule))]
public class JsonRpcClientModule : AbpModule
{
public override void PreInitialize()
{
// 注册客户端配置,固定从Xml文件读取
SocketClientConfiguration socketClientConfiguration = XmlConfigProvider.GetConfig<SocketClientConfiguration>("SocketClientConfiguration.xml");
IocManager.IocContainer.Register(
Component
.For<ISocketClientConfiguration>()
.Instance(socketClientConfiguration)
);
switch (socketClientConfiguration.MessageCode)
{
case EMessageCode.Json:
IocManager.RegisterIfNot<ITransportMessageCodecFactory, JsonTransportMessageCodecFactory>(Dependency.DependencyLifeStyle.Singleton);
break;
case EMessageCode.MessagePack:
IocManager.RegisterIfNot<ITransportMessageCodecFactory, MessagePackTransportMessageCodecFactory>(Dependency.DependencyLifeStyle.Singleton);
break;
case EMessageCode.ProtoBuffer:
IocManager.RegisterIfNot<ITransportMessageCodecFactory, ProtoBufferTransportMessageCodecFactory>(Dependency.DependencyLifeStyle.Singleton);
break;
}
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(JsonRpcClientModule).GetAssembly());
var dotNettyTransportClientFactory = new DotNettyTransportClientFactory(IocManager.Resolve<ITransportMessageCodecFactory>(), Logger); IocManager.IocContainer.Register(
Component
.For<ITransportClientFactory>()
.Instance(dotNettyTransportClientFactory)
);
} public override void PostInitialize()
{
var socketClientConfiguration = Configuration.Modules.RpcClientConfig();
var transportClientFactory = IocManager.Resolve<ITransportClientFactory>();
try
{
foreach (var clientConnectServerInfo in socketClientConfiguration.ClientConnectServerInfos) // 预连接
{
if (clientConnectServerInfo.ConnectServerType == EConnectServerType.Tcp)
{
var tcpAddress = clientConnectServerInfo.Url.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
transportClientFactory.CreateClient(new IpAddressModel(tcpAddress[0], int.Parse(tcpAddress[1])).CreateEndPoint());
}
}
}
catch(Exception ex) // 预连,出错不处理
{ }
}
}
客户端全局Chanel设计
每一个服务连接创建一个TransportClient与之对应,存储在全局变量中private readonly ConcurrentDictionary<EndPoint, Lazy<ITransportClient>> _clients = new ConcurrentDictionary<EndPoint, Lazy<ITransportClient>>();
TransportClient即处理客户端传输消息对象,每当发起客户端调用时,创建transportClient对象,并存储到_clients集合中,下次对同一个服务端调用时,直接复用此对象,如果与服务器的通信Chanel断开,则从_clients对象中移除,达到了复用Chanel的作用。
/// <summary>
/// 创建客户端。
/// </summary>
/// <param name="endPoint">终结点。</param>
/// <returns>传输客户端实例。</returns>
public ITransportClient CreateClient(EndPoint endPoint)
{
var key = endPoint;
_logger.Debug($"准备为服务端地址:{key}创建客户端。");
try
{
return _clients.GetOrAdd(key
, k => new Lazy<ITransportClient>(() =>
{
var bootstrap = _bootstrap;
var channel = bootstrap.ConnectAsync(k).Result;
var messageListener = new MessageListener();
channel.GetAttribute(messageListenerKey).Set(messageListener);
var messageSender = new DotNettyMessageClientSender(_transportMessageEncoder, channel);
channel.GetAttribute(messageSenderKey).Set(messageSender);
channel.GetAttribute(origEndPointKey).Set(k);
var client = new TransportClient(messageSender, messageListener, _logger);
return client;
}
)).Value;
}
catch
{
_clients.TryRemove(key, out var value);
var ipEndPoint = endPoint as IPEndPoint;
throw;
}
}
protected class DefaultChannelHandler : ChannelHandlerAdapter
{ public override void ChannelInactive(IChannelHandlerContext context)
{
_factory._clients.TryRemove(context.Channel.GetAttribute(origEndPointKey).Get(), out var value);
}
}
TransportClient
默认的客户端传输实现,Rpc调用时,直接组装请求参数,调用SendAsync方法。注意里面的ManualResetValueTaskSource的设计。
/// <summary>
/// 一个默认的传输客户端实现。
/// </summary>
public class TransportClient : ITransportClient, IDisposable
{
#region Field private readonly IMessageSender _messageSender;
private readonly IMessageListener _messageListener;
private readonly ILogger _logger; private readonly ConcurrentDictionary<string, ManualResetValueTaskSource<TransportMessage>> _resultDictionary =
new ConcurrentDictionary<string, ManualResetValueTaskSource<TransportMessage>>(); #endregion Field #region Constructor public TransportClient(IMessageSender messageSender, IMessageListener messageListener, ILogger logger)
{
_messageSender = messageSender;
_messageListener = messageListener;
_logger = logger;
messageListener.Received += MessageListener_Received;
} #endregion Constructor #region Implementation of ITransportClient /// <summary>
/// 发送消息。
/// </summary>
/// <param name="message">远程调用消息模型。</param>
/// <returns>远程调用消息的传输消息。</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public async Task<JsonResponse> SendAsync(JsonRequest message, NameValueCollection contextNameValueCollection, CancellationToken cancellationToken)
{
try
{
_logger.Debug("准备发送消息。"); var transportMessage = TransportMessage.CreateInvokeMessage(message, contextNameValueCollection); //注册结果回调
var callbackTask = RegisterResultCallbackAsync(transportMessage.Id, cancellationToken); try
{
//发送
await _messageSender.SendAndFlushAsync(transportMessage);
}
catch (Exception exception)
{
throw new CommunicationException("与服务端通讯时发生了异常。", exception);
} _logger.Debug("消息发送成功。"); return await callbackTask;
}
catch (Exception exception)
{
_logger.Error("消息发送失败。");
throw;
}
} #endregion Implementation of ITransportClient #region Implementation of IDisposable /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
(_messageSender as IDisposable)?.Dispose();
(_messageListener as IDisposable)?.Dispose();
foreach (var taskCompletionSource in _resultDictionary.Values)
{
taskCompletionSource.SetCanceled();
}
} #endregion Implementation of IDisposable #region Private Method /// <summary>
/// 注册指定消息的回调任务。
/// </summary>
/// <param name="id">消息Id。</param>
/// <returns>远程调用结果消息模型。</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private async Task<JsonResponse> RegisterResultCallbackAsync(string id, CancellationToken cancellationToken)
{
_logger.Debug($"准备获取Id为:{id}的响应内容。"); var task = new ManualResetValueTaskSource<TransportMessage>();
_resultDictionary.TryAdd(id, task);
try
{
var result = await task.AwaitValue(cancellationToken);
return result.GetContent<JsonResponse>();
}
finally
{
//删除回调任务
ManualResetValueTaskSource<TransportMessage> value;
_resultDictionary.TryRemove(id, out value);
value.SetCanceled();
}
} private async Task MessageListener_Received(IMessageSender sender, TransportMessage message)
{
_logger.Debug("服务消费者接收到消息。"); ManualResetValueTaskSource<TransportMessage> task;
if (!_resultDictionary.TryGetValue(message.Id, out task))
return; if (message.IsInvokeResultMessage())
{
var content = message.GetContent<JsonResponse>();
if (content.Error != null)
{
task.SetException(content.Error);
}
else
{
task.SetResult(message);
}
}
} #endregion Private Method
}
企业级工作流解决方案(九)--微服务Tcp消息传输模型之客户端处理的更多相关文章
- 企业级工作流解决方案(七)--微服务Tcp消息传输模型之消息编解码
Tcp消息传输主要参照surging来做的,做了部分裁剪和改动,详细参见:https://github.com/dotnetcore/surging Json-rpc没有定义消息如何传输,因此,Jso ...
- 企业级工作流解决方案(八)--微服务Tcp消息传输模型之服务端处理
服务端启动 服务端启动主要做几件事情,1. 从配置文件读取服务配置(主要是服务监听端口和编解码配置),2. 注册编解码器工厂,3. 启动dotnetty监听端口,4. 读取配置文件,解析全局消息处理模 ...
- 庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署
庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署 一.简介 我们在上一篇文章<庐山真面目之八微服务架构 NetCore 基于 Dockerfile ...
- .net core ——微服务内通信Thrift和Http客户端响应比较
原文:.net core --微服务内通信Thrift和Http客户端响应比较 目录 1.Benchmark介绍 2.测试下微服务访问效率 3.结果 引用链接 1.Benchmark介绍 wiki中有 ...
- 企业级工作流解决方案(六)--微服务消息处理模型之与Abp集成
身份认证传递 对于Abp比较熟悉的朋友应该对他里面的用户身份认证比较熟悉,他是通过实现微软提供的权限认证方式实现的,用户登录身份信息存储在System.Security.Claims.ClaimsPr ...
- eShopOnContainers 看微服务⑤:消息通信
1.消息通信 传统的单体应用,组件间的调用都是使用代码级的方法函数.比如用户登录自动签到,增加积分.我们可以在登录函数调用积分模块的某个函数,为了解耦我们使用以来注入并放弃new Class()这种方 ...
- [手把手教你] 用Swoft 搭建微服务(TCP RPC)
序言 Swoft Framework 基于 Swoole 原生协程的新时代 PHP 全栈式协程框架 Swoft 是什么? Swoft 框架是首个基于Swoole 原生协程的新时代 PHP高性能协程全栈 ...
- 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务
权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...
- WCF 客户端与服务端消息传输
WCF很多需要认证信息,保证服务的安全,可以使用消息来实现 WCF 实现消息的方式: WCF中有两个接口: IClientMessageInspector [定义一个消息检查器对象,该对象可以添加到 ...
随机推荐
- Linux基础命令之.命令
. 点命令 .命令等同source 可以让配置文件被读到进程中,立刻生效
- JSONObject与JSONArray,转换为字符串
public class TestArrayToList { public static void main(String[] args) { // TODO Auto-generated metho ...
- codevs1228 (dfs序+线段树)
1228 苹果树 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 在卡卡的房子外面,有一棵苹果树.每年的春天,树上总会结 ...
- 李志杰的C语言程序设计第一次作业
这个作业属于C语言程序设计课程 : https://edu.cnblogs.com/campus/zswxy/CST2020-2 这个作业要求在哪里: https://edu.cnblogs.com/ ...
- Docker的介绍与安装教程
基于Windows系统下docker的介绍与安装教程以及更换docker镜像源教程 目录 基于Windows系统下docker的介绍与安装教程以及更换docker镜像源教程 Docker的核心概念 D ...
- 赛门铁克和DigiCert证书有什么区别?
在众多国人眼里,赛门铁克Symantec名气更胜于DigiCert证书.但是,我们知道2017年赛门铁克因一系列原因被DigiCert收购,品牌名称也被更新为DigiCert Secure Site. ...
- 撸个反向代理,激活JRebel~
持续原创输出,点击上方蓝字关注我 目录 前言 本地反向代理 服务器反向代理[个人推荐] IDEA安装JRebel并激活 服务器安装JRebel并激活 总结 前言 热部署相信大家都听说过,比如Sprin ...
- Django之富文本(获取内容,设置内容)
富文本 1.Rich Text Format(RTF) 微软开发的跨平台文档格式,大多数的文字处理软件都能读取和保存RTF文档,其实就是可以添加样式的文档,和HTML有很多相似的地方 图示 2.tin ...
- MySQL查询这一篇就够了
1. 条件 使用where子句对表中的数据筛选,结果为true的行会出现在结果集中 语法如下: select * from 表名 where 条件; 例: select * from students ...
- 编码风格:Mvc模式下SSM环境,代码分层管理
本文源码:GitHub·点这里 || GitEE·点这里 一.分层策略 MVC模式与代码分层策略,MVC全名是ModelViewController即模型-视图-控制器,作为一种软件设计典范,用一种业 ...