1. 소개
이전 글에서는 SignalR에 대해 자세히 소개했고, Asp.net MVC와 WPF에서의 적용에 대해서도 간략하게 소개했습니다. 지난 블로그 포스팅에서 그룹 메시징 구현에 대해 소개했습니다. 하지만 SignalR은 실시간 채팅을 위해 탄생했기 때문에 당연히 QQ와는 달리 엔드투엔드 채팅 기능이 부족합니다. 이번 블로그 포스팅에서는 SignalR을 사용하여 QQ 채팅과 유사한 기능을 구현하는 방법을 소개합니다.
2. SignalR을 이용한 엔드투엔드 채팅 구현 아이디어
구체적인 구현을 소개하기에 앞서 먼저 SignalR을 활용한 구현 아이디어를 소개했습니다. 엔드투엔드 채팅. 이전 기사에서 모든 클라이언트의 SendMessage를 호출하는 것을 의미하는 Clients.All.sendMessage(name, message);와 같은 코드를 본 적이 있을 것입니다. SignalR의 허브는 클라이언트와 서버 간의 실시간 통신을 가능하게 합니다. 엔드투엔드 채팅을 달성하려면 당연히 모든 클라이언트에게 메시지를 보낼 수 없으며 특정 클라이언트에게만 메시지를 보낼 수 있습니다. 그렇지 않으면 혼란스럽고 개인 정보 보호가 없습니다. 그렇다면 특정 클라이언트에게 메시지를 어떻게 보낼 수 있습니까? 이 문제는 우리가 엔드 투 엔드 채팅 기능을 구현하는 데 핵심입니다.
우리가 보내는 Clients 객체에는 다른 속성도 있습니다. VS에서 F12를 누르면 Clients 객체의 모든 속성이나 메서드를 볼 수 있습니다.
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); // 参数中的用户 }
SignalR에서는 고유성을 표시하기 위해 SignalR이 각 클라이언트에 ConnnectionId를 할당하므로 ConnnectionId를 통해 특정 클라이언트를 찾을 수 있습니다. 이런 식으로 클라이언트에 메시지를 보낼 때 메시지를 전달하는 것 외에도 상대방에게 보낸 ConnectionId도 입력해야 서버가 해당 메시지를 기반으로 해당 클라이언트에 전달할 수 있습니다. 들어오는 연결 ID입니다. 이것으로 종단 간 채팅 기능이 완료됩니다. 또한, 사용자가 온라인 상태가 아니면 서버는 메시지를 데이터베이스에 저장할 수 있으며, 해당 클라이언트가 온라인 상태가 되면 클라이언트에 푸시해야 할 메시지가 있는지 여부를 데이터베이스에서 확인할 수 있습니다. 데이터베이스의 데이터를 클라이언트에 푸시합니다. (단, 이 블로그 포스팅에서는 서버측에서 데이터를 캐싱하는 기능이 구현되어 있지 않습니다. 여기서 소개하는 것은 QQ의 구현 원리 중 하나를 여러분에게 이해시키기 위한 것입니다.)
이제 종단 간 채팅 기능의 구현 아이디어를 정리하겠습니다.
클라이언트가 로그인하면 클라이언트의 ConnnectionId를 기록하고 사용자를 정적 배열에 추가합니다. 모든 온라인 사용자를 녹음하기 위한 것입니다.
사용자는 온라인 사용자 간의 사용자 채팅을 클릭할 수 있습니다. 메시지를 보낼 때 ConnectionId가 서버에 전달되어야 합니다.
서버는 들어오는 메시지 내용과 ConnectionId를 기반으로 Clients.Client(connection).sendMessage 메서드를 호출하여 해당 클라이언트에 전달합니다.
3. 쿨챗 기능의 핵심 코드 구현
구현 아이디어로 기능 구현을 쉽게 해보자. at 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); } }
위는 서버의 주요 구현입니다. 다음으로 클라이언트의 구현 코드를 살펴보겠습니다. 🎜>
<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은 SignalR을 사용하여 멋진 엔드투엔드 채팅 기능을 구현합니다." ><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은 SignalR을 사용하여 멋진 엔드투엔드 채팅 기능을 구현합니다." >'; } else { return '<img src="' + param.face + '" alt="Asp.net은 SignalR을 사용하여 멋진 엔드투엔드 채팅 기능을 구현합니다." >' + '<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>위에는 일부 핵심 코드 구현만 나열되어 있습니다. 또한 멋진 효과를 얻기 위해 여기에서 Jquery 플러그인이 사용됩니다. 공식 웹사이트는 http://layer.layui.com/입니다. 이 플러그인은 주로 팝업 상자 및 팝업 레이어 효과를 구현하는 데 사용됩니다. 멋진 채팅 효과를 구현하려면 JS 코드를 직접 작성해야 합니다. 저는 이 JS라는 프론트엔드에 익숙하지 않습니다. 특수 효과 코드도 인터넷 구현을 기반으로 합니다. 실행해서 효과를 보고 싶으시다면, 글 마지막 부분에 있는 소스코드를 다운받아서 실행해 보시는 걸 추천드립니다. 4. 최종 효과