由于项目需要,笔者最近需要实现Web客户端之间的消息的即时推送功能。
功能描述如下:
假设A,B,C用户登陆,内存记录下已登录的用户的信息,这时A在所在的客户端(SendInfo.aspx)页面向B发消息,则在B所在客户端页面(SendInfo.aspx)将弹出消息框。
关键点有两个:
1.保证客户端和服务端的连接
2.保证服务端能够向客户端广播消息
笔者是第一次做这样的实现,所以Google了一些资料,了解到可使用Comet,ajax轮询,WebSocket等技术实现,
由于时间关系,发现有些技术不是很容易理解,这里做了一个简单Demo.希望能够达到抛砖引玉的作用,与大家分享,共同提高。
笔者做了两个框架下的实现,ASP.NET Web Form和ASP.NET MVC 下的尝试。
ASP.NET Web Form版:
AsyncHandler.cs
using System;using System.Collections.Generic;using System.Web;using System.Threading;namespace CometSample{ public class WebIMAsyncHandler : IHttpAsyncHandler { #region IHttpAsyncHandler 成员 public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { string _UID = context.Request.Params["uid"]; WebIMClientAsyncResult _AsyncResult = new WebIMClientAsyncResult(context, cb, extraData); string _Content = context.Request.Params["content"]; string _Action = context.Request.Params["action"]; if (_Action == "login") { _UID = context.Request.Params["uid"]; _AsyncResult.LoginID = _UID; WebIMMessageHandler.Instance().Login(_UID, _AsyncResult); } else if (_Action == "logout") { _AsyncResult.LoginID = _UID; WebIMMessageHandler.Instance().Logout(_UID, _AsyncResult); } else if (_Action == "connect") { _AsyncResult.LoginID = _UID; WebIMMessageHandler.Instance().Connect(_AsyncResult); } else if (_Action == "getuserlist") { _AsyncResult.LoginID = _UID; WebIMMessageHandler.Instance().GetUserList(_AsyncResult); } //增加消息发送 else if (_Action == "sendmsg") { _AsyncResult.LoginID = _UID; //WebIMMessageHandler.Instance().GetUserList(_AsyncResult); //调用 WebIMMessageHandler.Instance().AddMessage(_Content, _AsyncResult); } //调用 //WebIMMessageHandler.Instance().AddMessage(_Content, _AsyncResult); return _AsyncResult; } public void EndProcessRequest(IAsyncResult result) { } #endregion #region IHttpHandler 成员 public bool IsReusable { get { return false; ; } } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } #endregion } public class WebIMClientAsyncResult : IAsyncResult { bool m_IsCompleted = false; private HttpContext m_Context; private AsyncCallback m_Callback; private object m_ExtraData; private string m_Content; private string m_LoginID = string.Empty; public WebIMClientAsyncResult(HttpContext p_Context, AsyncCallback p_Callback, object p_ExtraData) { this.m_Context = p_Context; this.m_Callback = p_Callback; this.m_ExtraData = p_ExtraData; } ////// 用户编号 /// public string LoginID { get { return m_LoginID; } set { m_LoginID = value; } } ////// 发送消息的内容,暂时未使用到 /// public string Content { get { return m_Content; } set { m_Content = value; } } #region IAsyncResult 成员 public object AsyncState { get { return null; } } public WaitHandle AsyncWaitHandle { get { return null; } } public bool CompletedSynchronously { get { return false; } } public bool IsCompleted { get { return m_IsCompleted; } } #endregion ////// 向客户端响应消息 /// /// public void Send(object data) { try { m_Context.Response.Write(this.Content); if (m_Callback != null) { m_Callback(this); } } catch { } finally { m_IsCompleted = true; } } }}
MessageHandler.cs
using System;using System.Collections;using System.Collections.Generic;using System.Web;using System.Text;namespace CometSample{ public class WebIMMessageHandler { private static readonly WebIMMessageHandler m_Instance = new WebIMMessageHandler(); //记录所有请求的客户端 Listm_Clients = new List (); //Dictionary m_Clients=new Dictionary (); string m_Users = string.Empty; public WebIMMessageHandler() { } public static WebIMMessageHandler Instance() { return m_Instance; } /// /// 登录 /// /// /// public void Login(string p_LoginID, WebIMClientAsyncResult p_ClientAsyncResult) { bool _Logined = false; foreach (WebIMClientAsyncResult _Item in m_Clients) { if (_Item.LoginID == p_LoginID) { p_ClientAsyncResult.Content = "你已登录"; _Logined = true; break; } } if (!_Logined) { //m_Clients.Add(p_ClientAsyncResult); p_ClientAsyncResult.Content = "OK"; } p_ClientAsyncResult.Send(null); } private string GetUsers() { /* string _Users = string.Empty; foreach (WebIMClientAsyncResult _Item in m_Clients) { _Users += _Item.LoginID + ","; } return _Users; */ var sbUsers = new StringBuilder(); sbUsers.Append("Users:"); foreach (WebIMClientAsyncResult _Item in m_Clients) { sbUsers.Append(_Item.LoginID); sbUsers.Append(","); } return sbUsers.ToString(); } public void Logout(string p_LoginID, WebIMClientAsyncResult p_ClientAsyncResult) { foreach (WebIMClientAsyncResult _Item in m_Clients) { if (_Item.LoginID == p_LoginID) { m_Clients.Remove(_Item); break; } } p_ClientAsyncResult.Content = "退出成功"; p_ClientAsyncResult.Send(null); //UpdateUserList(); string _Users = GetUsers(); foreach (WebIMClientAsyncResult _Item in m_Clients) { _Item.Content = _Users; _Item.Send(null); } m_Clients.Clear(); } public void GetUserList(WebIMClientAsyncResult p_ClientAsyncResult) { Connect(p_ClientAsyncResult); string _Users = GetUsers(); foreach (WebIMClientAsyncResult _Item in m_Clients) { _Item.Content = _Users; _Item.Send(null); } m_Clients.Clear(); } public void Connect(WebIMClientAsyncResult p_Client) { bool _Exists = false; foreach (WebIMClientAsyncResult _Item in m_Clients) { if (_Item.LoginID == p_Client.LoginID) { _Exists = true; break; } } if (!_Exists) { m_Clients.Add(p_Client); } } /* public void UpdateUserList() { string _Users = GetUsers(); foreach (WebIMClientAsyncResult result in m_Clients) { result.Content = m_Users; result.Send(null); } m_Clients.Clear(); }*/ ////// 广播消息 /// /// /// public void AddMessage(string p_Message, WebIMClientAsyncResult p_ClientAsyncResult) { //保持连接 if (p_Message == "-1") { m_Clients.Add(p_ClientAsyncResult); } else { //将当前请求的内容输出到客户端 p_ClientAsyncResult.Content = p_Message; p_ClientAsyncResult.Send(null); //否则将遍历所有已缓存的client,并将当前内容输出到客户端 foreach (WebIMClientAsyncResult result in m_Clients) { //发送给所有已经登录用户 var strMsg = string.Format("{0}{1}{2}{3}{4}",p_ClientAsyncResult.LoginID,"发送给",result.LoginID,"的消息:",p_Message); //result.Content = p_Message; result.Content = strMsg; result.Send(null); //发送给指定用户 /* if (string.Equals(result.LoginID, "ZhangShan") && !string.Equals(p_ClientAsyncResult.LoginID, "ZhangShan")) { var strMsg = string.Format("{0}{1}{2}{3}{4}{5}","Msgs:", p_ClientAsyncResult.LoginID, "发送给", result.LoginID, "的消息:", p_Message); //result.Content = p_Message; result.Content = strMsg; result.Send(null); } */ } //清空所有缓存 m_Clients.Clear(); } } }}
Login.js
///$(document).ready(function () { //登录,登录成功后,获取在线用户列表, function login() { //增加页面跳转 var strUrl = '/SendInfo.aspx?strUid=' + $("#txtLoginID").val(); window.open(strUrl); } $("#btnLogin").click(function () { if ($("#txtLoginID").val() == '') alert('空'); login(); });})
WebIM.js
/////$(document).ready(function () { //状态,代表是否登录 //var _logined = false; //登录,登录成功后,获取在线用户列表, function login() { //$.post("comet_broadcast.asyn", { action: 'login', uid: $("#txtLoginID").val() }, $.post("comet_broadcast.asyn", { action: 'login', uid: strUid }, function (data, status) { if (data == "OK") { _logined = true; getuserlist(); //增加页面跳转 /*var strUrl = '\SendInfo.aspx?strUid=' + $("#txtLoginID").val(); window.open(strUrl); */ } else { alert(data); } }); } //获取在线用户列表,获取列表后,进入消息等待 function getuserlist() { //$.post("comet_broadcast.asyn", { action: 'getuserlist', uid: $("#txtLoginID").val() }, $.post("comet_broadcast.asyn", { action: 'getuserlist', uid: strUid }, function (data, status) { //alert('getuserlist' + data); var result = $("#divResult"); result.html(result.html() + " " + "用户列表:" + data); wait(); }); } //退出 function logout() { //$.post("comet_broadcast.asyn", { action: 'logout', uid: $("#txtLoginID").val() }, $.post("comet_broadcast.asyn", { action: 'logout', uid: strUid }, function (data, status) { _logined = false; alert(data); } ); } //消息等待,接收到消息后显示,发起下一次的消息等待 function wait() { //$.post("comet_broadcast.asyn", { action: 'connect', uid: $("#txtLoginID").val() }, $.post("comet_broadcast.asyn", { action: 'connect', uid: strUid }, function (data, status) { /* var result = $("#divResult"); result.html(result.html() + " " + "用户列表:" + data); */ //2.窗口 new Ext.ux.ToastWindow({ title: '提示窗口', html: data, iconCls: 'error' }).show(document); //服务器返回消息,再次建立连接 if (_logined) { wait(); } }, "html"); } /*********** *********************消息发送部分*************************** ************/ function send() { //$.post("comet_broadcast.asyn", { action: 'sendmsg', uid: $("#txtLoginID").val(), content: $("#content").val() }, $.post("comet_broadcast.asyn", { action: 'sendmsg', uid: strUid, content: $("#content").val() }, function (data, status) { /* var result = $("#divResult"); result.html(result.html() + " " + "已发消息:" + data); */ //发送方页面提示 //潜规则:暂时不处理 /* //2.窗口 new Ext.ux.ToastWindow({ title: '提示窗口', html: data, iconCls: 'error' }).show(document); */ }, "html" ); //向comet_broadcast.asyn发送请求,消息体为文本框content中的内容,请求接收类为AsnyHandler //$.post("comet_broadcast.asyn", { content: $("#content").val() }); //清空内容 $("#content").val(""); }; /** * 获取字符串中某个特殊字符首次出现的位置之前的子串 */ function GetSubStrBySpecChar(strConnStr,strSplit){ var arrStr = strConnStr.split(strSplit); var strSubStr = arrStr[0]; return strSubStr; }
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CometSample._Default" %>
SendInfo.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SendInfo.aspx.cs" Inherits="CometSample.SendInfo" %>SendInfo
最后还需要关注的是配置文件中的路径
在web.config 文件的system.web之间加上
好了运行程序,
效果如下:
登陆之后,跳转到sendinfo页面
笔者打开连个浏览器,模拟两个客户端登陆,并且模拟广播消息(能够广播,那么向指定客户端发消息也就很容易了)
我们可以看到在页面右下角,有消息弹出
在.NET WebForm下笔者实现了客户端之间即时消息的推送,
但是在.NET MVC 2 下遇到了一些问题,因为mvc框架下对 ,NET WebForm中某些东西不支持。
各位看官,若有在MVC2下的实现,多多交流和分享哈!
源码下载