客户端启动

  客户端启动主要做三件事情,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. 【机器学习 Azure Machine Learning】Azure Machine Learning 访问SQL Server 无法写入问题 (使用微软Python AML Core SDK)

    问题情形 使用Python SDK在连接到数据库后,连接数据库获取数据成功,但是在Pandas中用 to_sql 反写会数据库时候报错.错误信息为:ProgrammingError: ('42000' ...

  2. elasticsearch启动报错

    1,max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536] 查看 ...

  3. mapstruct 快速使用

    mapstruct 快速使用 mapstruct 主要的作用则是用来复制对象字段使用,功能非常的强大.在没有使用 mapstruct 之前可能都在使用 BeanUtils ,但是 BeanUtils ...

  4. 码农会锁,synchronized 对象头结构(mark-word、Klass Pointer)、指针压缩、锁竞争,源码解毒、深度分析!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 感觉什么都不会,从哪开始呀! 这是最近我总能被问到的问题,也确实是.一个初入编程职场 ...

  5. Github优质库分享-01算法小抄 基于LeetCode

    Github 优质库分享-01 算法小抄 该库总共 60 多篇原创文章,都是基于 LeetCode 的题目,涵盖了所有题型和技巧,而且一定要做到举一反三,通俗易懂,绝不是简单的代码堆砌. 目前 sta ...

  6. NB-IoT窄带物联网技术的四大优势

      NB-IoT是指窄带物联网(Narrow Band -Internet of Things)技术,是IoT领域一个新兴的技术,支持低功耗设备在广域网的蜂窝数据连接,也被叫作低功耗广域网(LPWA) ...

  7. 敲黑板:InnoDB的Double Write,你必须知道

    世界上最快的捷径,就是脚踏实地,本文已收录[架构技术专栏]关注这个喜欢分享的地方. 前序 InnoDB引擎有几个重点特性,为其带来了更好的性能和可靠性: 插入缓冲(Insert Buffer) 两次写 ...

  8. trie 树(字典树)

    目录 简述 trie 实现 前言 初始化 插入 检索 代码实现 例题 · 前缀统计 异或对 前言 贪心证明 代码实现 例题 · 最长异或值路径 可持久化 trie 树 简介 原理 代码实现 例题 总结 ...

  9. Git--gitLab远程仓库分支代码回退的两种方案

    事由:作为仓库的master,一时老眼昏花,把同事说的不合并看成了合并,直接合并了. 解决方法: 一.粗鲁的代码回退--直接在远程仓库合并 1. 在gitLab远程仓库中,基于想回退的代码的节点(co ...

  10. 记载idea创建spring-boot项目时“Spring Initalizr Error”的问题处理