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.asmx, Dispatcher.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: 你好,我是李四!”
基本上我们的示例到这里已经成功了!如有写的不好的地方欢迎大家指出。