ASP.NET 实现 Comet 反向 Ajax

概念:

           Comet 是基于 HTTP长连接的“服务器推”技术,是一种新的 Web 应用架构。基于这种架构开发的应用中,服务器端会主动以异步的方式向客户端程序推送数据,

而不需要客户端显式的发出请求。Comet 架构非常适合事件驱动的 Web 应用,以及对交互性和实时性要求很强的应用,如股票交易行情分析、聊天室和 Web 版在线游戏等。

//////////////////////////////////////////////////////////////////////////////

演示示例: 1.分别在两个不同的浏览器打开Sender.aspx页面,分别输入不同的用户名登录。 2.输入收件人的名字和消息内容,点击发送按钮。

//////////////////////////////////////////////////////////////////////////////

好了,废话不多说了,我们开始建项目,首先建立一个文件夹重命名为“CSASPNETReverseAJAX”,打开VS 2012 >文件>新建>网站>ASP.NET 空网站,Web位置选择刚才创建的文件夹CSASPNETReverseAJAX


1,添加一个Message.cs类,代码如下:

/****************************** 模块标题 ******************************\
* 模块名称:    Message.cs
* 项目:        CSASPNETReverseAJAX
* 消息类包含所有必需的字段在一个消息包。
*
\*****************************************************************************/

namespace CSASPNETReverseAJAX {  
  /// <summary>    
  /// 这是一个实体类,代表一个信息项。    
  /// </summary>    
      public class Message     {        
       /// <summary>        
       /// 这个名字谁将接收这个消息。        
       /// </summary>        
           public string RecipientName { get; set; }

              /// <summary>       
              /// 消息内容。        
             /// </summary>      
           public string MessageContent { get; set; }   
     }
}

2. 添加Client.cs类,Client.cs类代表一个客户端,可以从服务器接收消息。同时服务器可以发送消息给客户端。这个类包含两个私有成员和两个公共方法。
代码如下:

/****************************** 模块标题 ******************************\
* 模块名称:    Client.cs
* 项目:        CSASPNETReverseAJAX
* 客户端类用于同步消息发送和接收的消息。
* DequeueMessage方法被调用时,该方法将等到一个新消息
*
\*****************************************************************************/

using System.Collections.Generic;
using System.Threading;

namespace CSASPNETReverseAJAX {    
/// <summary>    
/// 这个类代表一个web客户端可以接收消息。    
/// </summary>     
     public class Client     {       
              private ManualResetEvent messageEvent = new ManualResetEvent(false);        
              private Queue<Message> messageQueue = new Queue<Message>();
              /// <summary>        
              /// //EnqueueMessage方法是专为发送方发送一条信息客户端。        
              /// </summary>        
              /// <param name="message">新消息</param>       
             public void EnqueueMessage(Message message)       
              {     
               lock (messageQueue)           
                 {               
                   messageQueue.Enqueue(message);
                   // 设置一个新的消息事件。            
                   messageEvent.Set();        
                 }      
             }
             /// <summary>        
             /// DequeueMessage方法是专为收件人接收消息。        
             /// 如果没有消息,它会等待,直到一个新的消息插入。         
             /// </summary>        
             /// <returns>未读消息</returns>        
             public Message DequeueMessage()        
              {    
                // 等到一个新消息。         
                messageEvent.WaitOne();
                  lock (messageQueue)           
                   {
                     if (messageQueue.Count == 1)          
                        {
                           messageEvent.Reset();            
                        }                
                     return messageQueue.Dequeue();       
                   }        
               }    
          }
     }

3.添加 ClientAdapter.cs类,ClientAdapter.cs类是用于管理多个客户。表示层可以很容易地调用其方法来发送和接收消息。
代码如下:

/****************************** 模块标题 ******************************\
* 模块名称:    ClientAdapter.cs
* 项目:        CSASPNETReverseAJAX
* ClientAdapter类管理多个客户端实例。 表示层
* 调用它的方法来轻松地发送和接收消息。
*
\*****************************************************************************/

using System.Collections.Generic;

namespace CSASPNETReverseAJAX {    
/// <summary>    
/// 这个类是用来发送消息到多个客户端事件。    
/// </summary>    
 public class ClientAdapter    
 {        
   /// <summary>        
   /// 收件人列表。        
   /// </summary>        
   private Dictionary<string, Client> recipients = new Dictionary<string,Client>();
        /// <summary>
        /// 发送一个消息到一个指定的收件人。
        /// </summary>
        public void SendMessage(Message message)
        {
            //判断如果收件人列表包含收件人就调用client.EnqueueMessage方法发送給他
            if (recipients.ContainsKey(message.RecipientName))
            {
                Client client = recipients[message.RecipientName];

                client.EnqueueMessage(message);
            }
        }

        /// <summary>
        /// 被一个收件人等和接收一个消息。
        /// </summary>
        /// <returns>消息内容</returns>
        public string GetMessage(string userName)
        {
            string messageContent = string.Empty;
            //判断收件人列表是否包userName
            if (recipients.ContainsKey(userName))
            {
                Client client = recipients[userName];

                messageContent = client.DequeueMessage().MessageContent;
            }

            return messageContent;
        }

        /// <summary>
        /// 加入一个用户到收件人列表。
        /// </summary>
        public void Join(string userName)
        {
            recipients[userName] = new Client();
        }

        /// <summary>
        /// 单例模式.
        /// 这种模式将确保只有一个这个类的实例的系统。
        /// </summary>
        public static ClientAdapter Instance = new ClientAdapter();
        private ClientAdapter(){ }
    }
}

4. 添加一个Web服务Dispatcher.asmxDispatcher.asmx 是web服务设计的调用Ajax来接收消息。
Dispatcher.asmx.cs代码如下:

/****************************** 模块标题 ******************************\
* 模块名称:    Dispatcher.asmx.cs
* 项目:        CSASPNETReverseAJAX
* 这个web服务的设计被称为Ajax客户端。
*
\*****************************************************************************/

using System.Web.Services;

namespace CSASPNETReverseAJAX
{
    /// <summary>
    /// 这个web服务包含的方法帮助事件分发给客户端。
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    public class Dispatcher : System.Web.Services.WebService
    {
        /// <summary>
        /// 调度新消息事件。
        /// </summary>
        /// <param name="userName">The loged in user name</param>
        /// <returns>消息内容</returns>
        [WebMethod]
        public string WaitMessage(string userName)
        {
            return ClientAdapter.Instance.GetMessage(userName);
        }
    }
}
5. 添加Sender.aspx页面,
Sender.aspx前台页面代码如下:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Sender.aspx.cs" Inherits="CSASPNETReverseAJAX.Sender" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head> <body>
    <form id="form1" runat="server">
    <!-- 登录 -->     <asp:Label ID="Label1" runat="server" ForeColor="Red" Text="请先登录:"></asp:Label><br />
    用户名<asp:TextBox ID="tbUserName" runat="server"></asp:TextBox>
    <asp:Button ID="btnLogin" runat="server" Text="登录" onclick="btnLogin_Click" />

    <!-- 接收信息 -->     <asp:ScriptManager ID="ScriptManager1" runat="server" AsyncPostBackTimeout="2147483647">
        <Services>
            <asp:ServiceReference Path="~/Dispatcher.asmx" />
        </Services>
    </asp:ScriptManager>
    <script type="text/javascript">

        // 这个方法将会持续一个http请求,等待消息。
        function waitEvent() {

            CSASPNETReverseAJAX.Dispatcher.WaitMessage("<%= Session["userName"] %>",
            function (result) {

                displayMessage(result);

                // 保持循环.
                setTimeout(waitEvent, 0);
            }, function () {

                // 保持循环.
                setTimeout(waitEvent, 0);
            });
        }

        // 附加一个消息内容到结果面板.
        function displayMessage(message) {
            var panel = document.getElementById("<%= lbMessages.ClientID %>");

            panel.innerHTML += currentTime() + ": " + message + "<br />";
        }

        // 返回当前时间字符串.
        function currentTime() {
            var currentDate = new Date()
            return currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
        }
    </script>

    <h3>消息:</h3>     <asp:Label ID="lbMessages" runat="server" ForeColor="Red"></asp:Label>     <br /><br />

    接收人姓名:<br />     <asp:TextBox ID="tbRecipientName" runat="server" Width="100px"></asp:TextBox><br />

    消息:<br />     <asp:TextBox ID="tbMessageContent" runat="server" Width="300px"></asp:TextBox><br />

    <asp:Button ID="btnSend" runat="server" Text="发送" onclick="btnSend_Click" />
        <br />
        <br /> <asp:Label ID="lbNotification" runat="server" ForeColor="Red"></asp:Label>
    </form>
  </body>
</html>

Sender.aspx.cs后台代码

/****************************** 模块标题 ******************************\
* 模块名称:    Sender.aspx.cs
* 项目:        CSASPNETReverseAJAX
*
* 用户将使用这个页面送一条消息给一个特定的收件人。
*
*
\*****************************************************************************/

using System;

namespace CSASPNETReverseAJAX {
    public partial class Sender : System.Web.UI.Page
    {
        //发送消息事件
        protected void btnSend_Click(object sender, EventArgs e)
        {
            // 创建一个消息实体包含所有必要的数据
            Message message = new Message();
            //从前台页面文本框获取姓名
            string Name = tbRecipientName.Text.Trim();
            //判断姓名不为空或不为null
            if (!string.IsNullOrEmpty(Name))
            {
                //将姓名赋值給message.RecipientName
                message.RecipientName = Name;
                //从前台页面获取文本框消息内容并赋值給message.MessageContent
                message.MessageContent = tbMessageContent.Text.Trim();
                //判断消息接收人姓名不为Null或空白字符 且 消息内容不为空或不为null
                if (!string.IsNullOrWhiteSpace(message.RecipientName) && !string.IsNullOrEmpty(message.MessageContent))
                {
                    // 调用客户端适配器立即发送消息給特定收件人。
                    ClientAdapter.Instance.SendMessage(message);

                    // 用lable显示一个消息发送的当前时间.
                    lbNotification.Text += DateTime.Now.ToLongTimeString() + ": 消息已发送!<br/>";
                }
            }
        }
        //登录事件
        protected void btnLogin_Click(object sender, EventArgs e)
        {
            //获取页面用户名文本框
            string userName = tbUserName.Text.Trim();

            // 判断用户名不为空或null
            if (!string.IsNullOrEmpty(userName))
            {
                //加入一个用户名到收件人列表
                ClientAdapter.Instance.Join(userName);
                //用Session保存用户名
                Session["userName"] = userName;
            }
            else
            {
                //否则调用js弹窗提示“用户名不正确!”
                ClientScript.RegisterStartupScript(this.GetType(), "msg", "alert('用户名不正确!')", true);
            }
        }

        protected void Page_PreRender(object sender, EventArgs e)
        {
            // 激活JavaScript等待循环。
            if (Session["userName"] != null)
            {
                string userName = (string)Session["userName"];

                //后台调用JavaScript方法waitEvent()开始等待循环。
                ClientScript.RegisterStartupScript(this.GetType(), "ActivateWaitingLoop", "waitEvent();", true);
                //前台lable显示正在等待新的消息
                lbNotification.Text = string.Format("你的用户名是: <b>{0}</b>. 现在正在等待新的消息.", userName);

                // 禁用登录。
                tbUserName.Visible = false;
                btnLogin.Visible = false;
            }
        }
    }
}

重新生成一下,没有报错的话我们就可以开始测试了。在IE浏览器打开Sender.aspx页面,打开HttpWatch开始监测浏览器, 输入用户名“张三” 点击登录,
可以从HttpWatch看到已经开始运行js方法waitEvent()等待消息、,不要关闭页面,
再从火狐浏览器打开Sender.aspx页面,输入用户名“李四” 点击登录,输入接收人姓名“张三”,输入消息“你好,我是李四!”,点击发送,
我们再回到IE浏览器的Sender.aspx页面就可以收到“17:13:21: 你好,我是李四!”   
基本上我们的示例到这里已经成功了!如有写的不好的地方欢迎大家指出。

posted @ 2013-04-09 17:21  无缺~  阅读(993)  评论(0编辑  收藏  举报