企业级工作流解决方案(九)--微服务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 [定义一个消息检查器对象,该对象可以添加到 ...
随机推荐
- pyqt5为控件设置提示信息
# 显示控件提示消息 import sys from PyQt5.QtWidgets import QHBoxLayout,QMainWindow,QApplication,QToolTip,QPus ...
- Spring Boot 学习摘要--关于配置
date: 2019-12-27 09:00:00 updated: 2019-12-30 13:20:00 Spring Boot 学习摘要--关于配置 学习教程来自:B站 尚硅谷 1. 关于配置 ...
- 10 张图打开 CPU 缓存一致性的大门
前言 直接上,不多 BB 了. 正文 CPU Cache 的数据写入 随着时间的推移,CPU 和内存的访问性能相差越来越大,于是就在 CPU 内部嵌入了 CPU Cache(高速缓存),CPU Cac ...
- mongoDB之在windows下的安装
mongoDB官网http://www.mongodb.org/对mongoDB的描述: MongoDB (from "humongous") is an open-source ...
- GAMES101系列笔记一 图形学概述与线性代数入门
概述+线性代数 为什么学习图形学? Computer Graphics is AWESOME! 主要涉及内容: 光栅化 曲线和网格 光线追踪 动画与模拟 Differences between CG ...
- 面试题:对NotNull字段插入Null值 有啥现象?
Hi,大家好!我是白日梦. 今天我要跟你分享的话题是:"对NotNull字段插入Null值有啥现象?" 一. 推荐阅读 首发地址:https://mp.weixin.qq.com/ ...
- SPI的学习和ESP8266的SPI通讯测试
SPI简介: SPI是串行外设接口(Serial Peripheral Interface)的缩写.SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时 ...
- Mycat分库分表(一)
随着业务变得越来越复杂,用户越来越多,集中式的架构性能会出现巨大的问题,比如系统会越来越慢,而且时不时会宕机,所以必须要解决高性能和可用性的问题.这个时候数据库的优化就显得尤为重要,在说优化方案前,先 ...
- vbox复制虚拟机之后修改ethx号使得可以上网
vbox复制虚拟机之后修改ethx号使得可以上网: (1).改硬件mac编号[此项需要重启] vi /etc/udev/rules.d/70-persistent-net.rules 然后删掉上面一行 ...
- SSH个人小结
初学SSH的一些总结,主要来源于谷歌搜索和鸟叔的教程http://cn.linux.vbird.org/linux_server/0310telnetssh_2.php 以及阮一峰的博客http:// ...