SignalR循序渐进(二)

接上一篇,文章末尾抛出了2个问题:

  1. 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错。

  2. 同样的,能不能让服务端声明一个强类型的方法列表给客户端调用呢?

如果要让客户端的方法以强类型出现在服务端,同样的,服务端的方法也以强类型出现在客户端,那就必须声明类似契约一样的载体。比如:


public interface IChatClient
{
void broadcast(string name, string message);
}


public interface IChatHub
{
void Send(string name, string message);
}

分别建立ChatClient接口和ChatHub的接口。


public class ChatHub : Hub<IChatClient>
{
...
}

这是最终的目标,一个泛型Hub。

好,现在需要进行一些分析,怎样才能让Hub支持泛型。

首先,看一下Hub是如何操作客户端方法的:


Clients.AllExcept(Context.ConnectionId).broadcast(name, message);

Hub通过Clients来操作所有客户端的行为。那么这个Clients又是什么类型的呢?


// 摘要:
// Gets a dynamic object that represents all clients connected to this hub (not
// hub instance).
IHubCallerConnectionContext Clients { get; set; }

通过IHub接口看到,Clients的类型是IHubCallerConnectionContext,点进去看:


// 摘要:
// Encapsulates all information about an individual SignalR connection for an
// Microsoft.AspNet.SignalR.Hubs.IHub.
public interface IHubCallerConnectionContext : IHubConnectionContext
{
[Dynamic]
dynamic Caller { get; }
[Dynamic]
dynamic Others { get; }

dynamic OthersInGroup(string groupName);
dynamic OthersInGroups(IList<string> groupNames);
}

IHubCallerConnectionContext又继承IHubConnectionContext,再点进去看:


// 摘要:
// Encapsulates all information about a SignalR connection for an Microsoft.AspNet.SignalR.Hubs.IHub.
public interface IHubConnectionContext
{
[Dynamic]
dynamic All { get; }

dynamic AllExcept(params string[] excludeConnectionIds);
dynamic Client(string connectionId);
dynamic Clients(IList<string> connectionIds);
dynamic Group(string groupName, params string[] excludeConnectionIds);
dynamic Groups(IList<string> groupNames, params string[] excludeConnectionIds);
dynamic User(string userId);
}

一目了然,所有Clients的操作方法都在这儿了,全是动态类型的,这也是为什么在Hub中写到Clients.All.xxx的时候已经是动态的了,那么运行时,这些操作都是什么类型的呢?试一下:

运行时,Clients的操作返回的是ClientProxy类型,从代码中扒出来:


public class ClientProxy : DynamicObject, IClientProxy
{
public ClientProxy(IConnection connection, IHubPipelineInvoker invoker, string hubName, IList<string> exclude);

public Task Invoke(string method, params object[] args);
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);
}


// 摘要:
// A server side proxy for the client side hub.
public interface IClientProxy
{
// 摘要:
// Invokes a method on the connection(s) represented by the Microsoft.AspNet.SignalR.Hubs.IClientProxy
// instance.
//
// 参数:
// method:
// name of the method to invoke
//
// args:
// argumetns to pass to the client
//
// 返回结果:
// A task that represents when the data has been sent to the client.
Task Invoke(string method, params object[] args);
}
}

可以看到,运行时如果以IClientProxy注入,就一个Invoke方法。

好,挖到这儿,可以有一些思路了。

  1. Clients所有的操作最终都是通过IClientProxy的Invoke来执行的。

  2. 如果让IChatClient通过某种方式和IClientProxy建立起非运行时的联系,就能实现强类型了。

  3. 这样的话,就需要有一个Hub<T>的类,然后把Clients里所有的操作在Hub<T>中重新实现一次。

  4. 然后T又是客户端的行为接口,因此,需要对Hub<T>进行静态扩展,让IClientProxy的Invoke方法能够被T的所有方法自动调用。

核心攻克点找到了,解决了4,就能一路解决1。怎样才能让IClientProxy的Invoke自动的被T的所有方法调用呢?AOP可以!可以用Castle对T进行动态织入。到这儿可以动手了,先建立一个Hub扩展类:


public static class HubExtensions
{
static readonly ProxyGenerator generator = new ProxyGenerator();

public static T GetClientBehavior<T>(this IClientProxy clientProxy) where T : class
{
return (T)generator.CreateInterfaceProxyWithoutTarget<T>(new ClientBehaviorInterceptor(clientProxy));
}
}

让所有的IClientProxy执行GetClientBehavior方法,然后内部进行拦截器装载,并将IClientProxy塞进拦截器。


public class ClientBehaviorInterceptor:IInterceptor
{
public ClientBehaviorInterceptor(IClientProxy clientProxy)
{
this.clientProxy = clientProxy;
}

IClientProxy clientProxy;

public void Intercept(IInvocation invocation)
{
clientProxy.Invoke(invocation.Method.Name, invocation.Arguments);
}
}

拦截器中,每当T执行方法的时候,clientProxy就执行Invoke方法,把T的方法名和T的参数传入,这就达到了原先动态调用客户端方法传入参数并执行的效果。

然后就是写一个Hub<T>了。


public abstract class Hub<T> : Hub where T : class
{
protected T All { get { return (Clients.All as IClientProxy).GetClientBehavior<T>(); } }

protected T Any(params string[] connectionIds)
{
return (Clients.Clients(connectionIds) as IClientProxy).GetClientBehavior<T>();
}

protected T Except(params string[] connectionIds)
{
return (Clients.AllExcept(connectionIds) as IClientProxy).GetClientBehavior<T>();
}

protected T Client(string connectionId)
{
return (Clients.Client(connectionId) as IClientProxy).GetClientBehavior<T>();
}

protected T Caller { get { return (Clients.Caller as IClientProxy).GetClientBehavior<T>(); } }
}

把Clients中所有的操作都在这儿写一遍,例子中就写了5个。通过刚才的扩展方法,返回的T已经是经过AOP的了。最后,把最初的ChatHub改一下:

让ChatHub继承Hub<T>,T为IChatClient,如图示,已经可以通过Except方法用强类型调用客户端方法了。执行一下看看:

到此,服务端改造结束。服务端已经可以接受强类型的客户端行为。

下一篇将对客户端部分进行强类型改造。

最后附上一个基于SignalR的聊天室玩具,绿色无毒:http://www.royarea.cn/chatroom

转载请注明出处:http://www.cnblogs.com/royding/p/3750412.html

时间: 2024-11-13 15:17:48

SignalR循序渐进(二)的相关文章

SignalR 循序渐进

SignalR 循序渐进(五)多个Hub服务器下的消息订阅 hellsoul86 2014-08-18 11:29 阅读:840 评论:7 SignalR 循序渐进(四) Hub的生命周期以及IoC hellsoul86 2014-07-29 16:55 阅读:953 评论:6 SignalR循序渐进(三)简易的集群通讯组件 hellsoul86 2014-06-27 01:39 阅读:1350 评论:7 SignalR循序渐进(二)泛型Hub hellsoul86 2014-05-24 21:

SignalR循序渐进(三)简易的集群通讯组件

上一篇演示了泛型Hub的实现,微软于6月17日更新了SignalR 2.1.0,然后自带了泛型Hub,于是就不需要自己去实现了…(微软你为啥不早一个月自带啊…).不过没关系,SignalR出彩之处不在泛型Hub,本篇为各位观众带来了基于SignalR的简易集群通讯组件Demo,可用于分布式定时任务. 说到集群,自然想到了NLB啊Cluster啊HPC啊等等.NLB受制于成员数量,Cluster用数量堆高可用性,HPC太复杂.本着SignalR的双向异步通讯的特点,其实是可以用来玩弹性计算的.初始

SignalR循序渐进(一)

前阵子把玩了一下SignalR,起初以为只是个real-time的web通讯组件.研究了几天后发现,这玩意简直屌炸天,它完全就是个.net的双向异步通讯框架,用它能做很多不可思议的东西.它基于Owin,可以脱离繁重的System.Web,随意寄宿在IIS,WindowsService,或者一个控制台程序,这样它即能用于b/s的Web应用,也能用在客户端程序或者服务之间的通讯上.对它的介绍网上早已铺天盖地,这而就不再啰嗦了,先来个小例子,一个聊天室程序. 服务端 新建一个叫SignalRDemo的

SignalR 循序渐进(四) Hub的生命周期以及IoC

有阵子没更新这个系列了,最近太忙了.本篇带来的是Hub的生命周期以及IoC. 首先,Hub的生命周期,我们用一个Demo来看看: public class TestHub : Hub { public TestHub() { Console.WriteLine(Guid.NewGuid().ToString()); } public void Hello() { } } static HubConnection hubConnection; static IHubProxy hubProxy;

SignalR 循序渐进(五)多个Hub服务器下的消息订阅

SignalR的通讯方式决定了其高性能,但是即便如此,当消息的并发量上来以后,单节点的Hub服务器依然可能无法承载总的消息吞吐量,那么如何对Hub服务器做水平扩展呢? 从微软官方的文档上看,SignalR是具有消息底板功能的,SignalR核心组件公开了一个IMessageBus的接口,只需要实现该接口,就能实现消息订阅功能.官网提供了3种解决方案:Azure.Redis.SqlServer,nuget平台上有更多的基于消息队列的第三方底板.本篇以Redis为例子展示一下以消息底板模式运作的Hu

SignalR 设计理念(二)

SignalR 设计理念(二) 实现客户端和服务器端的实时通讯. 前言: 客户端方法忽略大小写,主要原因基于是URL对大小写不敏感的问题,开发者之间为了更好的协同开发,定下的开发者协议. 问题阐述 客户端数量不确定! 同一个用户的客户端数量不确定(一个用户可以多处登陆)! 客户端连接的渠道不确定(应用程序连接.Web普通连接.WebSocket连接等)! 同一个用户的连接渠道不一定! 针对以上问题,你会如何设计服务器架构? SignalR 采用 管道通讯 作为连接消息通道,使用 分配器 对消息适

SignalR循序渐进(二)泛型Hub

position:static(静态定位) 当position属性定义为static时,可以将元素定义为静态位置,所谓静态位置就是各个元素在HTML文档流中应有的位置 podisition定位问题.所以当没有定义position属性时,并不说明该元素没有自己的位置,它会遵循默认显示为静态位置,在静态定位状态下无法通过坐标值(top,left,right,bottom)来改变它的位置. position:absolute(绝对定位) 当position属性定义为absolute时,元素会脱离文档流

Nginx+SignalR+Redis(二)windows

接上篇:此篇主要讲解signalr使用nginx后遇到的问题. 首先发布signalr服务端多个站点,为了简单只发布了两个站点类似:一个服务端端口8090一个8091 然后配置Nginx具体安装下载就不一一介绍,可以自行百度安装.现在只介绍配置nginx.config中的项,因为在此遇到了许多坑 先将ngixn.con中的代码贴出来. events { worker_connections 1024;} http { include mime.types; default_type applic

SignalR 教程二 服务端广播

转帖官方教程:Tutorial: Server Broadcast with SignalR 2 http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr This tutorial shows how to create a web application that uses ASP.NET SignalR 2 to provide server broadcast fu