企业级工作流解决方案(九)--微服务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 [定义一个消息检查器对象,该对象可以添加到 ...
随机推荐
- NLP任务梳理总结
NLP任务 前处理任务 前处理任务的结果可作为下游任务输入的额外特征. POSTa(词性标注) 往模型中输入句子,对每一个token进行词性的识别. 识别出的词性可以用于下游任务. Word Segm ...
- 64位Ubuntu14.04配置adb后提示No such file or directory
配置好SDK的环境变量后,输入adb提示 No such file or directory. 原因:由于是64位的linux系统,而Android SDK只有32位的,需要安装一些支持包才能使用 1 ...
- 对于72种常用css3的使用经验
对于72种常用css3的使用经验 保存网站源码 目的 保证有足够的css和js文件源码 拿到当前网页的整体布局 本地化 创建web项目 将web项目创建出来 在项目中创建一个文件夹 将所有的js和cs ...
- 实战四:Gateway网关作全局路由转发
Gateway网关的作用主要是两个:路由转发,请求过滤.此篇讲的是路由转发,下篇介绍请求过滤. 一,创建网关module,添加依赖 1,new -> module -> maven 或直接 ...
- Python ( 学习基础篇 第二部 )
目录 运算符 算数运算符 比较运算符 赋值运算符 位运算符 逻辑运算符 成员运算符 身份运算符 Python 中运算符的优先级 运算符总结基础语法 判断类型 isinstence 代码块 流程控制 w ...
- LruCache缓存bitmap(二)
Lrucache缓存程序关闭缓存自动清除,所以要在onstart方法中调用,只要不关闭程序缓存就在,除以1024是以kb为单位 public class MainActivity extends Ap ...
- 走在深夜的小码农 Fifth Day
Css3 Fifth Day writer:late at night codepeasant 学习大纲: 一.css三大特性 1.层叠性 相同选择器给设置相同的样式,此时一个样式就会覆盖(层叠) ...
- 4g工业路由器的覆盖范围分析
4G工业路由器通常覆盖范围在60-80米之间,而影响4G工业路由器的覆盖范围有以下几个因素. 影响4g工业路由器覆盖范围的因素一:环境 空旷的环境下4g工业路由器的信号覆盖范围必然更加广阔,如果传输环 ...
- Python爬虫之线程池
详情点我跳转 关注公众号"轻松学编程"了解更多. 一.为什么要使用线程池? 对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初 ...
- think PHP5.1使用时 session重定向丢失问题
查了很多资料,也看了redirect底层代码,具体来说,还是多个用的地方不太对.做个笔记防忘记: 遇重定向后丢失session时: 1.php.ini配置文件,不要自动启动,默认是0,session. ...