在上一篇 signalr 文章中,演示了如何通过 signalr 实现了简单的聊天室功能;本着简洁就是美的原则,这一篇我们也来聊聊在 signalr 中的用户和组的概念,理解这些基础知识有助于更好的开发基于 signalr 的应用,通过对用户和分组的理解,进一步扩展出对用户和分组的管理,以及消息推送的各种方式,为全面接入 signalr 做准备。

1. 用户

在 signalr 中,用户表示连接,一个用户代表一个连接,一个“系统用户”可以创建多个连接身份,通过函数集线器,可以给一个用户的所有连接发送消息;比如一个“系统用户”拥有多个连接,这些连接分别是 web连接、android手机客户端连接,ios手机客户端连接、或者其它客户端连接,“系统用户”分别登录了这些客户端,同时创建了多个连接;默认情况下这些连接都通过 claimtypes.nameidentifier 在 claimsprincipal 于用户标识进行关联。

** 注意:用户标识符是区分大小写的,为了实现一个客户多个连接,本例还简单实现了一个基于 claimsidentity 登录接口,算是意外惊喜。

1.1 用户连接管理

为了直观的观察到用户是可以拥有多连接的,需要建立一个本地静态对象,用于存储用户连接

public class wechathub : hub
 {
  public dictionary<string, list<string>> userlist { get; set; } = new dictionary<string, list<string>>();

  public void send(chatmessage body)
  {
   clients.all.sendasync("recv", body);
  }

  public override task onconnectedasync()
  {
   var username = this.context.user.identity.name;
   var connectionid = this.context.connectionid;
   if (!userlist.containskey(username))
   {
    userlist[username] = new list<string>();
    userlist[username].add(connectionid);
   }
   else if (!userlist[username].contains(connectionid))
   {
    userlist[username].add(connectionid);
   }
   console.writeline("哇,有人进来了:{0},{1},{2}", this.context.useridentifier, this.context.user.identity.name, this.context.connectionid);
   return base.onconnectedasync();
  }

  public override task ondisconnectedasync(exception exception)
  {
   var username = this.context.user.identity.name;
   var connectionid = this.context.connectionid;
   if (userlist.containskey(username))
   {
    if (userlist[username].contains(connectionid))
    {
     userlist[username].remove(connectionid);
    }
   }

   console.writeline("靠,有人跑路了:{0}", this.context.connectionid);
   return base.ondisconnectedasync(exception);
  }
 }

上面的代码包含了一个内部成员 userlist,用于存储用户的每个连接,在用户进行 signalr 连接时,将当前连接存储到 userlist 中,当连接断开的时候,将当前连接从 userlist 中删除。这样就实现了一个简单的用户连接管理。

在上面的代码中,当前用户昵称是根据 var username = this.context.user.identity.name; 这行代码获取的,为了取得这个用户昵称,我们实现了一个简单的 useridentity 登录,然后将 user 信息写入到 cookie 中,最后才可以通过 var username = this.context.user.identity.name; 获得当前登录用户昵称(熟悉 id 登录流程的同学应该不会感到陌生,实际上我也很少使用 id 验证)

1.2 给单个用户发送消息

  [authorize(roles = "user")]
  [httppost("sendtouser")]
  public async task<iactionresult> sendtouser([frombody] userinfoviewmodel model)
  {
   chatmessage message = new chatmessage()
   {
    type = 1,
    content = model.content,
    username = model.username
   };

   if (this.chathub.userlist.containskey(model.username))
   {
    var connections = this.chathub.userlist[model.username].first();
    await this.chathub.clients.client(connections).sendasync("recv", new object[] { message });
   }

   return json(new { code = 0 });
  }

在 usercontroller 中,定义了上面的接口 sendtouser ,客户端传入用户昵称和消息,然后服务端就会去根据 chathub.userlist 成员查找目标用户的连接信息,最后,通过 sendasync 将消息推送到目标客户端连接中。

2. 分组

分组的概念类似于聊天室,每个房间就是一个独立的分组,用户可以选择加入 a 房间,也可以选择加入 b 房间,如果业务允许,一个用户还可以加入多个分组(房间),通过使用分组对用户进行管理,可以实现一个或者多个聊天房间,用户可以加入分组,也可以将用户从分组中删除(类似离开房间),这里的用户并发真正意义上的“系统用户”,而是指系统用户创建的那些 signalr连接。

** 注意:当连接断开后重新发起连接的时候,signalr 不会保留组成员身份,必须重新加入分组。

下面的代码演示了如何对分组进行操作,要对分组进行操作,主要包含三个方面:

2.1 加入分组

 public async task addtogroupasync(string groupname)
  {
   await groups.addtogroupasync(this.context.connectionid, groupname);
  }

2.2 离开分组

 public async task removefromgroupasync(string groupname)
  {
   await groups.removefromgroupasync(this.context.connectionid, groupname);
  }

2.3 发送消息到指定分组

 public async task sendtogroupasync(string groupname, chatmessage message)
  {
   await clients.group(groupname).sendasync(groupname, new object[] { message });
  }

对分组的操作非常的简单,几乎都是一行代码的事情,不得不说,微软的封装实在是太好了。

3. signalr的推送消息的其它方式

通过上面对用户和分组的学习,再去扩展学习其它推送消息的方式,就非常的好理解和上手,在 signalr 内部还有多种推送消息的方式,他们分别是

3.1 all(全站推送)

3.2 others(全站推送排除自己)

3.3 othersingroup(指定分组推送,排除自己)

3.4 allexcept(除指定列表外的所有人)

3.5 演示代码

 list<string> blacklist = new list<string>();
  public async task othersendasync(chatmessage body)
  {
   // 给当前连接到 hub 上的所有连接发送消息,相当于广播
   await clients.all.sendasync("recv", body);

   // 给当前连接对象发送消息
   await clients.caller.sendasync("recv", body);

   // 给其它所有连接的客户端发送消息,除了当前正在连接的客户端
   await clients.others.sendasync("recv", body);

   // 查找当前所有连接的客户端(排除自己),如果是已加入此分组,则给他们推送消息
   await clients.othersingroup("groupname").sendasync("recv", body);

   // 给除了 blacklist(黑名单)之外的所有人发送消息
   await clients.allexcept(blacklist).sendasync("recv", body);
  }

4. 一个简单的示例

本示例代码包含两个简单的界面

4.1 登录

4.2 各种方式发送消息

结束语

最近在做一个开源项目,还处于试用阶段,准备写个使用的 wiki 出来,看看大家是否感兴趣,此 singalr 系列只能不定期更新了,抱歉。

演示代码下载

已托管到 github 仓库

https://github.com/lianggx/examples/tree/master/signalr/ron.signalrlesson2

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持www.887551.com。