65.9K
CodeProject 正在变化。 阅读更多。
Home

COMET(或反向 AJAX)基于的 ASP.NET Web 应用程序网格控件 - 可扩展、高性能、低延迟的网格控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (45投票s)

2009年3月30日

CPOL

8分钟阅读

viewsIcon

262132

downloadIcon

5085

一个基于 COMET/Reverse Ajax 的 Web 网格控件,可用于 ASP.NET Web 应用程序。此控件基于服务器端事件将更新发布到客户端,从而减少网络往返次数。

引言

比我说的更好,维基百科的解释值得在此提及

“Comet 是一种编程技术,它允许 Web 服务器在无需客户端请求的情况下向客户端发送数据。它能够创建托管在浏览器中的事件驱动型 Web 应用程序。”

例如,假设您的 Web 应用程序中有一个网格/表格。该网格显示动态数据(例如股票行情或投资组合详情)。您希望在客户端浏览器中显示此不断变化的网格数据。但是,您不希望每秒都刷新页面。此外,您不希望使用 AJAX 进行轮询(每秒或两秒一次),因为这会因为客户端数量的增加而导致服务器停滞。您感兴趣的是显示此动态内容,但仅在必要时(即网格数据发生更改时)更新客户端,避免客户端和服务器之间不必要的通信。这种情况的解决方案是在您的 Web 应用程序中“使用此基于 COMET 的网格控件”。

为什么基于 COMET 的网格控件有用?

考虑上述情况。容易选择的方案是使用基于计时器的常规 AJAX 调用服务器。这将根据计时器到期(例如一两秒)向服务器发出调用。此方法存在缺点:

  1. 即使可能没有更新,也会向服务器发出不必要的调用。
  2. 随着客户端数量的增加,服务器可能会呈指数级过载,即客户端数量与服务器负载成正比。这会导致服务器因几百个客户端而停滞。

为避免这些缺点,我们需要一种在服务器端数据更改时更新客户端的方法,即一种事件驱动模型。此外,我们需要以异步模式更新客户端,而不阻塞 ASP.NET 线程。此原则已应用于此基于 COMET 的网格控件。因此,如果您的 ASP.NET 应用程序使用了此控件,您将获得以下优势:

  1. 在服务器端数据更改时将数据推送给客户端,避免了不必要的客户端往返。结果是:提高了网络、IIS 服务器和 ASP.NET 应用程序的性能。
  2. 异步处理客户端避免了阻塞 ASP.NET 线程,从而显著提高了应用程序的性能。这将在“COMET 网格控件设计说明”部分进行详细介绍。

这当然*不是*说 AJAX 不好而 COMET 好。AJAX 是解决某些问题的绝佳解决方案模式,而 COMET 则非常适合解决其他问题。在一个地方使用另一个地方的解决方案有时是灾难性的。

在您的 ASP.NET 应用中使用代码

必备组件

Visual Studio 2008 和 .NET 3.5

快速演示

要观看快速演示,只需在 Visual Studio 2008 中打开解决方案。运行应用程序。

使用此 COMET 网格控件是一个简单的 3 步过程

  1. 将此控件添加到您的项目中

    如何操作

    1. 右键单击工具箱 | 选择“选择项...”| 单击浏览。然后浏览以选择下载的(在本文第一行下载的)程序集(GridControlCometAjax.dll)。
    2. 从工具箱中,将新添加的控件拖放到您的网页上。
  2. 在您的 `web.config` 文件中的 `httpHandlers` 部分添加一个异步处理程序,如下所示:
    add verb="GET, POST" path="GridControlCometAjax.ashx" 
    	type="BK.Util.GridAsynchHandler, GridControlCometAjax" validate="false"
  3. 在您的代码隐藏文件(例如 `Default.aspx.cs`)中,在“`Page_Load`”方法中添加以下行:
    protected void Page_Load(object sender, EventArgs e)
    {
       // Your code goes here.  
       // Assuming the control instance's name is GridControlCometAjax1, 
       // add the below line:
       GridControlCometAjax1.LoadControl(this);
    }

好了,就是这样。现在开始使用该控件。您通过任何以“Dyn”为前缀的方法在此网格控件中进行的任何更新都将自动传播到所有客户端。此外,这些方法是线程安全的。因此,您可以从多个线程调用这些方法。

让我们提供几个示例,以帮助我们入门。

用例 1:如何初始化

考虑您想用初始值(及其颜色)填充网格,完成后需要将这些值传播到所有客户端 - 使用以下代码示例。像往常一样,代码解释内嵌。

// Iterate through each item in the grid to fill them with some random values.
for (int Rowitem = 0; Rowitem < 5; Rowitem++)
{
   for (int ColItem = 0; ColItem < 5; ColItem++)
   {
      // Get a random value to put in side each cell
      double ValueToModify = randomNo.NextDouble();

      // Update the Grid with the Random value we got just above.
      GridControlCometAjax1.DynModifyTableItem
		(Rowitem, ColItem, ValueToModify.ToString("N2"), false);
      // Update the Grid's text color to be black.
      GridControlCometAjax1.DynModifyTableItemColor
		(Rowitem, ColItem, Color.Black, false);	
   }
}

// Now, we are finished with our initialization.  Send updates to all the clients.
GridControlCometAjax1.DynUpdateAllClients();

用例 2:如何修改网格内容并立即传播到客户端

您希望在服务器端更新网格视图中的某个单元格,并立即将其更新发送到客户端。幸运的是,这只需一行代码。

GridControlCometAjax1.DynModifyTableItem
	(RowToModify, ColToModify, StringToUpdate, true);

 

请注意,在上面的行中,最后一个参数需要设置为 `true`。

用例 3:如何修改网格文本颜色并传播到客户端

您希望更新网格视图中的单元格文本颜色,并立即将此更改传播到客户端。

GridControlCometAjax1.DynModifyTableItemColor
	(RowToModify, ColToModify, Color.Red, true);

此网格控件中还有其他有用的方法,可能会派上用场。但是,所有动态方法(连接到客户端的方法)都以“Dyn”为前缀。

性能结果

我使用 Microsoft 的 Web 应用程序压力测试工具进行了压力测试。完整的报告作为可下载文件附在上面的链接中。但这里提供了一个概述。我使用了 100 个线程,每个线程有 10 个套接字,这意味着 1000 个并发连接。我使用了测试应用程序,该应用程序通过两个网格控件更新客户端。以下是结果:

COMET 网格控件设计说明

如果您只对使用此控件感兴趣,可以跳过此部分。但是,如果您想了解此控件的设计,请继续阅读。

首先,HTTP 是一种请求-响应协议。即,客户端(可能来自浏览器)请求,服务器响应。相对于这个众所周知的概念,AJAX 是非常有意义的。Ajax 客户端(可能来自浏览器)发出 `XmlHttp` 请求,服务器响应。然而,区别在于整个网页不会被重新初始化(或刷新),而只是其中的 Ajax 部分。简单明了。现在,考虑相反的情况,服务器在任何时候想要更新客户端时都这样做。这被称为 COMET 或反向 Ajax。一开始可能不太容易理解。

那么,我们如何实现这一点呢?考虑一场足球比赛。希望参赛的足球队向组织机构注册。此后,组织机构将继续发布足球队的任何活动,例如日期更改、场地更改等。这里也一样——只需将“足球队”替换为浏览器客户端,将“组织机构”替换为服务器(托管您正在使用此网格控件的 ASP 应用程序)。

简化的解释

考虑一个客户端浏览器向您的 ASP.NET 服务器发出调用。ASP.NET 服务器根据您在网站初始页面(可能为 `default.aspx`)中填写的内容渲染页面。由于您有一个 Ajax 组件(此 COMET 网格控件),因此会向服务器发出 Ajax 调用。这是我们在 COMET 中与常规 Ajax 调用不同的地方。常规 Ajax 调用会立即处理,但在这里我们将调用标记为挂起。请注意,我们没有响应客户端,而是保持连接活动。我们对任何连接的客户端都这样做。我们一直等到发生服务器端事件,例如网格内容发生更改。当这种情况发生时,我们完成之前挂起的调用。如果服务器事件长时间未发生,会向客户端发出维护调用以保持连接活动。否则,客户端浏览器可能会使我们的响应超时。这就是 COMET 相对于常规 Ajax 能够服务于大量客户端的原因。

在理解了初始概念之后,让我们深入了解此 COMET 网格 Web 控件的具体细节。下图概述了该控件的主要设计。

如上图所示,首先客户端浏览器向服务器注册以获取通知。服务器保持对该 HTTP 请求的响应。当发生事件时,例如网格值发生更改,服务器将使用更新的值完成请求。客户端浏览器显示此新更新,而无需刷新整个页面。这是因为这是一个 Ajax 请求响应。如果您想了解更多关于 Ajax 请求的信息,请阅读“参考 3”。下面的源代码执行此任务。代码解释内嵌。

// The below method is called by ASP.NET request thread to begin 
// asynchronous processing for the request.
public IAsyncResult BeginProcessRequest
	(HttpContext Context, AsyncCallback Callback, object ExtraData)
{
   // We get the Query string to figure out the Grid's instance
   string ClientId = Context.ApplicationInstance.Request.QueryString[0];

   // Using the clientId we obtained above, we get the details of the Grid from Multimap
   ResponseDetails respDet = clientDetails.GetFirstItem(ClientId);

   // Create new Asynch result 
   GridAsynchResult asyncResultToAdd = 
	new GridAsynchResult(respDet, Context, Callback, ClientStatus.Updated);
   // Add the Asynch result to the response details
   respDet.AddClient(asyncResultToAdd);

   // This will return the ASP.NET thread to return to thread pool.
   return asyncResultToAdd;
}

现在,有些人可能会担心上面段落中使用的“保持请求”一词。因为保持 ASP.NET 线程的请求是一项昂贵的工作。这是因为随着客户端数量的增加,我们很快就会耗尽服务器资源。这通过使用一个不错的接口 `IHttpAsynchHandler` 来解决。

如上图所示,客户端请求被保存在挂起队列中。这使得 ASP.NET 线程可以返回到池中。这一步极大地提高了服务器性能。当对控件请求更改时(例如要发布到网格中的客户端的更改),我们会完成挂起的客户端请求。此外,如果长时间未发生事件,我们会对客户端执行维护调用。下面的源代码执行此任务。这是由线程池的线程执行的方法。代码解释内嵌。

public static void WorkerThreadProc(object ThreadParam)
{
   // Get the Response details
   ResponseDetails respDetails = (ResponseDetails)ThreadParam;

   // Run in infinite loop (however it has break statement inside)   
   while (true)
   {
       // Remove the first client to update
       GridAsynchResult asyncResult = respDetails.RemoveClient();
                      
      // If there is no more client to update, break and return.
      if (asyncResult == null)
      {
         break;
      }
     // Else, if the client's status is updated, then add it back to Queue and break
     else if (asyncResult.Status == ClientStatus.Updated)
     {
         respDetails.AddClient(asyncResult);
         break;
      }

      // Send an update to the client.
      asyncResult.SetCompleted();	
      }
}

关注点

  1. 该控件在 Internet Explorer 中进行了测试。尽管我为其他浏览器编写了代码,但并未在其他浏览器中进行测试。
  2. 该控件使用 PUSH 而非轮询/拉取。这提高了服务器性能。
  3. 需要使用以“Dyn”为前缀的函数才能在客户端看到更新。这些函数是线程安全的。
  4. 该控件使用 `IHttpAsynchHandler` 接口,该接口显著提高了服务器端性能,能够为数千个客户端提供服务。
  5. 您无需对代理或防火墙设置进行任何特殊更改即可支持此控件。

重要提醒!

我想听听大家的反馈。即使您评价为 1 星或更低,我也很乐意听到您的详细反馈。所以,请写下您的留言。谢谢!

参考文献

  1. 反向 Ajax
  2. Comet(编程)
  3. XMLHttpRequest

历史

  • 版本 1.0 - 2009 年 3 月 29 日
  • 版本 1.1 - 2009 年 3 月 31 日 - 添加了性能报告
© . All rights reserved.