1. Introduction
In the previous article, SignalR has been introduced in detail, and its application in Asp.net MVC and WPF has been briefly introduced. The last blog post introduced the implementation of group messaging. However, SignalR was born for real-time chat, so it naturally lacks the end-to-end chat unlike QQ. This blog post will introduce how to use SignalR to implement functions similar to QQ chat.
2. The idea of using SignalR to implement end-to-end chat
Before introducing the specific implementation, I first introduced the idea of using SignalR to implement end-to-end chat. I believe you have seen code like Clients.All.sendMessage(name, message); in the previous article, which means calling SendMessage of all clients. SignalR's hub enables real-time communication between clients and servers. To achieve end-to-end chat, naturally you cannot send messages to all clients, but can only send messages to specific clients. Otherwise, it will be chaotic and there will be no privacy. So how can I send a message to a specific client? This problem is the key to our implementation of end-to-end chat function.
In addition to the All attribute, the Clients object we send also has other attributes. You can press F12 in VS to view all attributes or methods of the Clients object. The specific definition is as follows:
public interface IHubConnectionContext<T> { T All { get; } // 代表所有客户端 T AllExcept(params string[] excludeConnectionIds); // 除了参数中的所有客户端 T Client(string connectionId); // 特定的客户端,这个方法也就是我们实现端对端聊天的关键 T Clients(IList<string> connectionIds); // 参数中的客户端端 T Group(string groupName, params string[] excludeConnectionIds); // 指定客户端组,这个也是实现群聊的关键所在 T Groups(IList<string> groupNames, params string[] excludeConnectionIds); T User(string userId); // 特定的用户 T Users(IList<string> userIds); // 参数中的用户 }
In SignalR, every To mark a client's uniqueness, SignalR will assign it a ConnectionId, so that we can find the specific client through the ConnectionId. In this way, when we send a message to a client, in addition to passing in the message, we also need to enter the ConnectionId sent to the other party, so that the server can forward the corresponding message to the corresponding client based on the incoming ConnectionId. Served. This completes the end-to-end chat function. In addition, if the user is not online, the server can save the message to the database. When the corresponding client comes online, it can check from the database whether the client has any messages that need to be pushed. If so, retrieve the data from the database. , push the data to the client. (However, the function of caching data on the server side is not implemented in this blog post. The introduction here is to let everyone understand one of the implementation principles of QQ).
Let’s sort out the implementation ideas of the end-to-end chat function:
When the client logs in, it records the client’s ConnectionId and adds the user to a static array. This data is used to record all online users.
Users can click on the user chat among online users. When sending a message, the ConnectionId needs to be passed to the server.
The server calls the Clients.Client(connnection).sendMessage method based on the incoming message content and ConnectionId to forward it to the corresponding client.
3. Implement the core code of cool chat function
With the implementation idea, it is easy to implement the function. Next, let us first take a look at the code in the hub ChatHub:
public class ChatHub : Hub { // 静态属性 public static List<UserInfo> OnlineUsers = new List<UserInfo>(); // 在线用户列表 /// <summary> /// 登录连线 /// </summary> /// <param name="userId">用户Id</param> /// <param name="userName">用户名</param> public void Connect(string userId, string userName) { var connnectId = Context.ConnectionId; if (OnlineUsers.Count(x => x.ConnectionId == connnectId) == 0) { if (OnlineUsers.Any(x => x.UserId == userId)) { var items = OnlineUsers.Where(x => x.UserId == userId).ToList(); foreach (var item in items) { Clients.AllExcept(connnectId).onUserDisconnected(item.ConnectionId, item.UserName); } OnlineUsers.RemoveAll(x => x.UserId == userId); } //添加在线人员 OnlineUsers.Add(new UserInfo { ConnectionId = connnectId, UserId = userId, UserName = userName, LastLoginTime = DateTime.Now }); } // 所有客户端同步在线用户 Clients.All.onConnected(connnectId, userName, OnlineUsers); } /// <summary> /// 发送私聊 /// </summary> /// <param name="toUserId">接收方用户连接ID</param> /// <param name="message">内容</param> public void SendPrivateMessage(string toUserId, string message) { var fromUserId = Context.ConnectionId; var toUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == toUserId); var fromUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == fromUserId); if (toUser != null && fromUser != null) { // send to Clients.Client(toUserId).receivePrivateMessage(fromUserId, fromUser.UserName, message); // send to caller user // Clients.Caller.sendPrivateMessage(toUserId, fromUser.UserName, message); } else { //表示对方不在线 Clients.Caller.absentSubscriber(); } } /// <summary> /// 断线时调用 /// </summary> /// <param name="stopCalled"></param> /// <returns></returns> public override Task OnDisconnected(bool stopCalled) { var user = OnlineUsers.FirstOrDefault(u => u.ConnectionId == Context.ConnectionId); // 判断用户是否存在,存在则删除 if (user == null) return base.OnDisconnected(stopCalled); Clients.All.onUserDisconnected(user.ConnectionId, user.UserName); //调用客户端用户离线通知 // 删除用户 OnlineUsers.Remove(user); return base.OnDisconnected(stopCalled); } }
The above is The main implementation of the server, let’s take a look at the client’s implementation code:
<script type="text/javascript"> var systemHub = $.connection.chatHub; / 连接IM服务器成功 // 主要是更新在线用户 systemHub.client.onConnected = function (id, userName, allUsers) { var node = chatCore.node, myf = node.list.eq(0), str = '', i = 0; myf.addClass('loading'); onlinenum = allUsers.length; if (onlinenum > 0) { str += '<li class="ChatCore_parentnode ChatCore_liston">' + '<h5><i></i><span class="ChatCore_parentname">在线用户</span><em class="ChatCore_nums">(' + onlinenum + ')</em></h5>' + '<ul id="ChatCore_friend_list" class="ChatCore_chatlist">'; for (; i < onlinenum; i++) { str += '<li id="userid-' + allUsers[i].UserID + '" data-id="' + allUsers[i].ConnectionId + '" class="ChatCore_childnode" type="one"><img src="/Content/Images/001.jpg?' + allUsers[i].UserID + '" class="ChatCore_oneface" alt="Asp.net uses SignalR to implement cool end-to-end chat function" ><span class="ChatCore_onename">' + allUsers[i].UserName + '(' + ')</span><em class="ChatCore_time">' + allUsers[i].LoginTime + '</em></li>'; } str += '</ul></li>'; myf.html(str); } else { myf.html('<li class="ChatCore_errormsg">没有任何数据</li>'); } myf.removeClass('loading'); }; //消息传输 chatCore.transmit = function () { var node = chatCore.node, log = {}; node.sendbtn = $('#ChatCore_sendbtn'); node.imwrite = $('#ChatCore_write'); //发送 log.send = function () { var data = { content: node.imwrite.val(), id: chatCore.nowchat.id, sign_key: '', //密匙 _: +new Date }; if (data.content.replace(/\s/g, '') === '') { layer.tips('说点啥呗!', '#ChatCore_write', 2); node.imwrite.focus(); } else { //此处皆为模拟 var keys = chatCore.nowchat.type + chatCore.nowchat.id; //聊天模版 log.html = function (param, type) { return '<li class="' + (type === 'me' ? 'ChatCore_chateme' : '') + '">' + '<div class="ChatCore_chatuser">' + function () { if (type === 'me') { return '<span class="ChatCore_chattime">' + param.time + '</span>' + '<span class="ChatCore_chatname">' + param.name + '</span>' + '<img src="' + param.face + '" alt="Asp.net uses SignalR to implement cool end-to-end chat function" >'; } else { return '<img src="' + param.face + '" alt="Asp.net uses SignalR to implement cool end-to-end chat function" >' + '<span class="ChatCore_chatname">' + param.name + '</span>' + '<span class="ChatCore_chattime">' + param.time + '</span>'; } }() + '</div>' + '<div class="ChatCore_chatsay">' + param.content + '<em class="ChatCore_zero"></em></div>' + '</li>'; }; log.imarea = chatCore.chatbox.find('#ChatCore_area' + keys); log.imarea.append(log.html({ time: new Date().toLocaleString(), name: config.user.name, face: config.user.face, content: data.content }, 'me')); node.imwrite.val('').focus(); log.imarea.scrollTop(log.imarea[0].scrollHeight); // 调用服务端sendPrivateMessage方法来转发消息 systemHub.server.sendPrivateMessage(chatCore.nowchat.id, data.content); } }; node.sendbtn.on('click', log.send); node.imwrite.keyup(function (e) { if (e.keyCode === 13) { log.send(); } }); }; //用户离线 systemHub.client.onUserDisconnected = function (id, userName) { onlinenum = onlinenum - 1; $(".ChatCore_nums").html("(" + onlinenum + ")"); $("#ChatCore_friend_list li[data-id=" + id + "]").remove(); }; // 启动连接 $.connection.hub.start().done(function () { systemHub.server.connect(userid, username); // 调用服务端connect方法 }); </script>
The above only lists some core code implementations. In addition, in order to achieve cool effects, a Jquery plug-in is used here: layer, the official website is: http://layer.layui.com/. This plug-in is mainly used to achieve the effects of pop-up boxes and pop-up layers. To achieve cool chat effects, you need to write JS code yourself. Since I am not very familiar with the front-end, this JS special effects code is also based on the implementation on the Internet. If you want to run it and see the effect, it is recommended to download the source code at the end of the article and run it.
4. Final effect
After introducing the implementation ideas and implementation code, now that we have reached our exciting moment, that is to see if our implemented functions can meet the needs. In addition, in addition to meeting the basic chat functions, You also need to see if the interface is cool enough.
For more related articles about Asp.net using SignalR to implement cool end-to-end chat functions, please pay attention to the PHP Chinese website!