首先明确需求,我现在有很多个直播间,每个直播间内需要存在一个聊天室,每个聊天室内可以存在很多人聊天,当然,只有登陆系统的会员才能聊天,没有登陆的,干看着吧!
根据以上需求,可以做出三个简单的页面:登陆页面、直播列表页面、直播和聊天室页面。
一、登陆页面
登陆页面如下所示:
好简洁,有没有?
当用户成功登录之后,将用户信息保存到Session中或其他缓存中,本案例用的是Session,简单异常啊!
二、直播列表页面
直播列表页面如下所示:
本案例的侧重点在聊天室,至于直播,见他的大爷去,用图片代替!
点击任一图片,可以跳转到直播和聊天的页面!
三、直播和聊天室页面
直播和聊天室页面如下:
这个页面才是本次的重点!
接下来,主讲的就是这个页面。
我先把GroupChatHub类的代码全部贴出来,如下所示:
using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Threading.Tasks; using MyProject.Entity; using SignalRTest.BLL; namespace SignalRTest.Utils { [HubName("groupChatHub")] public class GroupChatHub:Hub { [HubMethodName("joinRoom")] //创建聊天的房间 public void JoinRoom(string liveId) { try { //查询出该房间是否开放 LiveChatRoomBLL roomBiz = new LiveChatRoomBLL(); LiveChatRoom room = roomBiz.Find(it => it.LiveID == Convert.ToInt32(liveId) && it.Status == 1); //如果房间为空,则创建该聊天房间 if (room == null) { room = new LiveChatRoom { LiveID = Convert.ToInt32(liveId), Status = 1 }; room.ID = roomBiz.Add(room); } if (room != null && room.ID > 0) { //将ConnectionID发送给自己 Clients.Client(Context.ConnectionId).intoRoom(Context.ConnectionId); } } catch (Exception ex) { } } public override Task OnConnected() { return base.OnConnected(); } public override Task OnReconnected() { return base.OnReconnected(); } public override Task OnDisconnected(bool stopCalled) { LiveChatRoomMemberBLL biz = new LiveChatRoomMemberBLL(); LiveChatRoomMember member = biz.Find(it => it.ConnectionID == Context.ConnectionId); if (member != null && member.ID > 0) { //从该房间清除该人员 if (biz.Delete(member.ID)) { //发送退出消息 Clients.Groups(new List<string> { member.RoomID.ToString() }).publishMsg(FormatMsg("系统消息", member.User.UserName + " 退出聊天", 0)); //从组中移除该ConnectionID Groups.Remove(Context.ConnectionId, member.RoomID.ToString()); } } return base.OnDisconnected(stopCalled); } /// <summary> /// 发送消息,供客户端调用 /// </summary> /// <param name="user">用户名,如果为0,则是发送给所有人</param> /// <param name="msg">消息</param> public void SendMsg(string user,string msg) { LiveChatRoomMember member = new LiveChatRoomMemberBLL().Find(it => it.ConnectionID == Context.ConnectionId); if (member != null && member.ID > 0) { Clients.Groups(new List<string> { member.RoomID.ToString() }).publishMsg(FormatMsg(member.User.UserName, msg, 1, member.User.HeadPic)); } } //type 0:系统消息 1:用户消息 public static dynamic FormatMsg(string name, string msg, int type=1,string pic="") { return new {IType=type, Name=name,Msg=HttpUtility.HtmlEncode(msg),Pic= HttpUtility.HtmlEncode(pic), Time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}; } } }
1、创建聊天室
当每个用户进入该页面的时候,在客户端页面中调用GroupChatHub的JoinRoom方法,JoinRoom方法的作用是创建聊天室数据并将ConnectionID发送给自己当前的页面。为何需要这样做?因为在SignalR中,每一个新连接到来,程序就会自动创建一个ConnectionID,一般的做法是将ConnectionID和UserID绑定。可是Hub不支持Session,因此在GroupChatHub类中就无法获取Session,也就无法获取当前登陆用户的信息。那怎么办呢?哼哼,简单至极!直接获取不行,我们可以进行迂回呀!反正客户端页面可以通过js调用GroupChatHub的方法,那我就将ConnectionID回传到前端页面,然后再通过ajax调用自定义的IHttpHandler接口,将ConnectionID传到接口中去和UserID进行绑定!是不是非常简单!有人会说了,IHttpHandler接口中也无法获取Session呀,你不会自己再次继承IRequiresSessionState接口哇?
上代码:
(1) 前端页面连接hub
// 链接hub var ticker = $.connection.groupChatHub; $.connection.hub.start().done(function () { //调用GroupChatHub的JoinRoom方法,创建聊天房间 ticker.server.joinRoom(QueryString("sid")).done(function () { }); });
(2) 创建房间,并将ConnectionID回传到当前页面
[HubMethodName("joinRoom")] //创建聊天的房间 public void JoinRoom(string liveId) { try { //查询出该房间是否开放 LiveChatRoomBLL roomBiz = new LiveChatRoomBLL(); LiveChatRoom room = roomBiz.Find(it => it.LiveID == Convert.ToInt32(liveId) && it.Status == 1); //如果房间为空,则创建该聊天房间 if (room == null) { room = new LiveChatRoom { LiveID = Convert.ToInt32(liveId), Status = 1 }; room.ID = roomBiz.Add(room); } if (room != null && room.ID > 0) { //将ConnectionID发送给自己 Clients.Client(Context.ConnectionId).intoRoom(Context.ConnectionId); } } catch (Exception ex) { } }
(3) 当前页面接收回传的ConnectionID,并上送接口中
// 接收服务端发送的消息 $.extend(ticker.client, { intoRoom: function (data) { //打印出当前连接的ConnectionID //alert(data); //调用ajax接口,将当前用户的ID(Session中)与ConnectionID关联起来 var param = { action: 'joinroom', liveId: QueryString("sid"), connectionId: data }; $.ajax({ type: 'POST', dataType: 'json', url: 'Index.ashx', data: JSON.stringify(param), success: function (data) { if (data && data.returnValue == 0) { console.log(data.returnMsg); } else alert(data.returnMsg); } }); } });
(4) 在接口中将ConnectionID与UserID绑定
public class JoinRoomHandler : LiveHandler<JoinRoomReq> { public JoinRoomHandler() : base("JoinRoomHandler") { } protected override BaseResponseResult DoWork(JoinRoomReq param) { BaseResponseResult rc = new BaseResponseResult((int)Code.OperationError, "操作失败!"); if (Index.User != null) { if (param.liveId > 0) { //找到当前直播对应的房间号 LiveChatRoom room = new LiveChatRoomBLL().Find(it => it.LiveID == param.liveId && it.Status == 1); //如果房间存在 if (room != null && room.ID > 0) { //将ConnectionID与UserID绑定到当前直播的房间中 LiveChatRoomMember member = new LiveChatRoomMember { ConnectionID = param.connectionId, RoomID = room.ID, UserID = Index.User.UserID }; member.ID = new LiveChatRoomMemberBLL().Add(member); //如果当前登陆的人员 成功 加入到直播聊天室 if (member.ID > 0) { //这里的代码很重要,这是在外部调用GroupChatHub var context = GlobalHost.ConnectionManager.GetHubContext<GroupChatHub>(); //将当前的ConnectionID加入到 以房间ID为名称的组中 context.Groups.Add(param.connectionId, room.ID.ToString()); //向客户端发送新加入人员信息 context.Clients.Group(room.ID.ToString()).publishMsg(GroupChatHub.FormatMsg("系统消息", Index.User.UserName + " 加入聊天", 0,Index.User.HeadPic)); rc.SetResult(0,"成功加入聊天室!"); } else rc.SetResult(3, "加入聊天房间失败!"); } else rc.SetResult(1, "当前聊天房间不存在!"); } else rc.SetResult(1, "当前聊天房间不存在!"); } else rc.SetResult(2,"未登录!"); return rc; } }
至此,成功将当前页面的ConnectionID、登陆人员的UserID和房间号绑定了起来。
2、发送消息
发送消息就简单了,在客户端页面调用GroupChatHub类的SendMsg方法。调用如下所示:
$("#btnSend").click(function () { //获取文本框内容 var tbxInput = $(this).parent().children(".msgs"); if (tbxInput) { var msg = tbxInput.val() || ''; if (msg.length > 0) { // 主动发送消息,传入直播ID,和发送的内容。 ticker.server.sendMsg(QueryString("sid"), msg); tbxInput.val(''); } else tbxInput.focus(); } }); $(".msgs").bind("keydown", event, function () { if (event.keyCode == 13) $("#btnSend").click(); });
SendMsg方法如下:
public void SendMsg(string user,string msg) { //通过ConnectionID找到当前聊天室的信息 LiveChatRoomMember member = new LiveChatRoomMemberBLL().Find(it => it.ConnectionID == Context.ConnectionId); if (member != null && member.ID > 0) { //向当前聊天室发送消息 Clients.Groups(new List<string> { member.RoomID.ToString() }).publishMsg(FormatMsg(member.User.UserName, msg, 1, member.User.HeadPic)); } }
客户端接收消息代码如下:
// 接收服务端发送的消息 $.extend(ticker.client, { // 接收聊天消息 publishMsg: function (data) { if (data) { var html = ''; //系统消息 if (data.IType == 0) { html = '<div class="im-systeminfo">'+ '<p class="im-sititle">' + data.Name + ' ' + data.Time + '</p>' + '<p class="im-sicontent">' + data.Msg + '</p>' + '</div>'; } //群聊消息 else if (data.IType == 1) { html = '<div class="im-contents">' + '<img class="im-headpic" src="' + data.Pic + '"/>' + '<div>' + '<p class="im-nickname">' + data.Name + ' ' + data.Time + '</p>' + '<p class="im-msgs">' + data.Msg + '</p>' + '</div>' + '</div>'; } } $(".im-list").append(html); $(".im-list").scrollTop($(".im-list")[0].scrollHeight); }, intoRoom: function (data) { //打印出当前连接的ConnectionID //alert(data); //调用ajax接口,将当前用户的ID(Session中)与ConnectionID关联起来 var param = { action: 'joinroom', liveId: QueryString("sid"), connectionId: data }; $.ajax({ type: 'POST', dataType: 'json', url: 'Index.ashx', data: JSON.stringify(param), success: function (data) { if (data && data.returnValue == 0) { console.log(data.returnMsg); } else alert(data.returnMsg); } }); } });
消息收发界面如下:
3、退出聊天室
退出聊天室很简单!当用户关闭当前聊天室页面,系统就会自动调用GroupChatHub的OnDisconnected方法,我们只需在OnDisconnected方法中写入逻辑代码即可。如下所示:
public override Task OnDisconnected(bool stopCalled) { LiveChatRoomMemberBLL biz = new LiveChatRoomMemberBLL(); //根据ConnectionID找到当前的聊天室信息 LiveChatRoomMember member = biz.Find(it => it.ConnectionID == Context.ConnectionId); if (member != null && member.ID > 0) { //从该房间清除该人员 if (biz.Delete(member.ID)) { //发送退出消息 Clients.Groups(new List<string> { member.RoomID.ToString() }).publishMsg(FormatMsg("系统消息", member.User.UserName + " 退出聊天", 0)); //从组中移除该ConnectionID Groups.Remove(Context.ConnectionId, member.RoomID.ToString()); } } return base.OnDisconnected(stopCalled); }
界面如下所示:
至此,整个流程如上所述,一个简单的聊天室就搭建完毕啦!
源代码:https://github.com/wsjun2016/SignalRTest
演示地址:http://101.201.234.177:8090/
备注:共预配了5个账号,每个账户名都是单个数字【1,2,3,4,5】,密码都为1