客户端启动

  客户端启动主要做三件事情,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消息传输模型之客户端处理的更多相关文章

  1. 企业级工作流解决方案(七)--微服务Tcp消息传输模型之消息编解码

    Tcp消息传输主要参照surging来做的,做了部分裁剪和改动,详细参见:https://github.com/dotnetcore/surging Json-rpc没有定义消息如何传输,因此,Jso ...

  2. 企业级工作流解决方案(八)--微服务Tcp消息传输模型之服务端处理

    服务端启动 服务端启动主要做几件事情,1. 从配置文件读取服务配置(主要是服务监听端口和编解码配置),2. 注册编解码器工厂,3. 启动dotnetty监听端口,4. 读取配置文件,解析全局消息处理模 ...

  3. 庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署

    庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署 一.简介      我们在上一篇文章<庐山真面目之八微服务架构 NetCore 基于 Dockerfile ...

  4. .net core ——微服务内通信Thrift和Http客户端响应比较

    原文:.net core --微服务内通信Thrift和Http客户端响应比较 目录 1.Benchmark介绍 2.测试下微服务访问效率 3.结果 引用链接 1.Benchmark介绍 wiki中有 ...

  5. 企业级工作流解决方案(六)--微服务消息处理模型之与Abp集成

    身份认证传递 对于Abp比较熟悉的朋友应该对他里面的用户身份认证比较熟悉,他是通过实现微软提供的权限认证方式实现的,用户登录身份信息存储在System.Security.Claims.ClaimsPr ...

  6. eShopOnContainers 看微服务⑤:消息通信

    1.消息通信 传统的单体应用,组件间的调用都是使用代码级的方法函数.比如用户登录自动签到,增加积分.我们可以在登录函数调用积分模块的某个函数,为了解耦我们使用以来注入并放弃new Class()这种方 ...

  7. [手把手教你] 用Swoft 搭建微服务(TCP RPC)

    序言 Swoft Framework 基于 Swoole 原生协程的新时代 PHP 全栈式协程框架 Swoft 是什么? Swoft 框架是首个基于Swoole 原生协程的新时代 PHP高性能协程全栈 ...

  8. 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务

    权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...

  9. WCF 客户端与服务端消息传输

    WCF很多需要认证信息,保证服务的安全,可以使用消息来实现 WCF 实现消息的方式: WCF中有两个接口: IClientMessageInspector [定义一个消息检查器对象,该对象可以添加到 ...

随机推荐

  1. C# 面试前的准备_基础知识点的回顾_05

    1.谈谈ViewState 这个问题,回答的好,工资翻一级 基本上浮现在出来的是,它是什么, 具体谈论的东西我就不一一细说了,只能说根据自己的学识去探讨,千万不要背书,很容易露馅,露馅的话给人一种不懂 ...

  2. Anderson《空气动力学基础》5th读书笔记 第1记——流动相似性

    在飞机真正上天之前,我们常常需要制作出缩小版的模型放在风洞中吹呀吹,尽可能地模拟真实飞行中的参数,这时我们就需要实现流动相似性,这便是本记要讲的. 文章目录 一.流动相似性的标准 二.流动相似性的应用 ...

  3. ResultSet 处理方法

    结果集(ResultSet)是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等. 结果集读取数据 ...

  4. 云计算管理平台之OpenStack块存储服务cinder

    一.cinder简介 cinder是openstack环境中的块存储服务,主要为运行在openstack之上的虚拟机提供块存储服务的:所谓块存储就是我们经常用的硬盘呀,U盘啊,SD卡等等这些块设备的, ...

  5. python栈、队列、文件目录遍历

    一. 栈与队列 关注公众号"轻松学编程"了解更多. 1. 栈 stack 特点:先进先出[可以抽象成竹筒中的豆子,先进去的后出来] 后来者居上 mystack = [] #压栈[向 ...

  6. [Luogu P1462] 通往奥格瑞玛的道路 (二分答案+最短路径)

    题面 传送门:https://www.luogu.org/problemnew/show/P1462 Solution 这道题如果去除掉经过城市的收费.那么就是裸的最短路 但是题目要求经过城市中最多的 ...

  7. ACM已刷题

    ZOJ: 1001.1002.1003.1004.1005.1006.1037.1045.1048.1049.1067.1087.1091.1016. swjtuoj: 2433 注:没链接的应该是最 ...

  8. Codeforce算法题 | 你能想出解法,让你的基友少氪金吗?

    在TechFlow学长的公众号里发现一道挺有意思的CF算法题,现在利用学长的思路学习一下 题目链接:https://codeforces.com/contest/1418/problem/C 题意 这 ...

  9. ElasticSearch 集群基本概念及常用操作汇总(建议收藏)

    内容来源于本人的印象笔记,简单汇总后发布到博客上,供大家需要时参考使用. 原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: Elas ...

  10. php抽奖程序

    //php概率抽奖算法 1.获取总的概率数 2.随机从1到总概率数 3.判断获取的随机数是否在小于等于(就是你随机的数是否在数组值得范围中比如数组为array(1,2,3,4,5,6)则随机出了一个数 ...