public class MvcHandler : IHttpHandler, IRequiresSessionState
{
internal ControllerBuilder ControllerBuilder
{
get
{
if (_controllerBuilder == null)
{
_controllerBuilder = ControllerBuilder.Current;
}
return _controllerBuilder;
}
set
{
_controllerBuilder = value;
}
}
}
有个默认实例,进去看看。
public class ControllerBuilder
{
private static ControllerBuilder _instance = new ControllerBuilder();
private Func<IControllerFactory> _factoryThunk;
public ControllerBuilder()
{
SetControllerFactory(new DefaultControllerFactory());
}
public static ControllerBuilder Current
{
get { return _instance; }
}
很简单,不是吗?在构造方法里面我们看到了 Factory。
public void SetControllerFactory(IControllerFactory controllerFactory)
{
if (controllerFactory == null)
{
throw new ArgumentNullException("controllerFactory");
}
_factoryThunk = () => controllerFactory;
}
public class DefaultControllerFactory : IControllerFactory
{
protected internal virtual IController CreateController(…)
{
// … 省略部分代码 …
RequestContext = requestContext;
Type controllerType = GetControllerType(controllerName);
IController controller = GetControllerInstance(controllerType);
return controller;
}
protected internal virtual Type GetControllerType(string controllerName)
{
// … 省略部分代码 …
// 检查缓存标记,利用反射将未缓存的相关控制器类型添加到缓存
if (!ControllerTypeCache.Initialized)
{
// … 省略部分代码 …
}
// Once the master list of controllers has been created we can quickly index into it
Type controllerType;
// 用控制器名称从缓存中提取控制器类型,为空则抛出异常。
if (ControllerTypeCache.TryGetControllerType(controllerName, out controllerType))
{
if (controllerType == null)
{
// A null value indicates a conflict for this key (i.e. more than one match)
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
MvcResources.DefaultControllerFactory_DuplicateControllers,
controllerName));
}
return controllerType;
}
return null;
}
protected internal virtual IController GetControllerInstance(Type controllerType)
{
// … 省略部分代码 …
try
{
return Activator.CreateInstance(controllerType) as IController;
}
catch (Exception ex)
{
// … 省略部分代码 …
}
}
What else is burried down in the depth’s of Google’s amazing JavaScript?
So the new GTalk interface in GMail is pretty rad. Congrats to Dan and the rest of the team that made it “go”.
The talk feature is cool not just from a UI perspective as the code is also chock full of little gems. I’m kind of a dork about low-latency data transport to the browser. HTTP wasn’t meant to be used this way…so of course I’m interested! Ever since Joyce got me involved in the rewrite of mod_pubsub I’ve had my eye on the various ways that servers can push data to browsers and the kinds of technology that will prevent a server that’s doing this from melting down (hellooooooooo Twisted). Using just what’s available to the browser, it’s possible to have the server push data encapsulated in <script> blocks and rely on a progressive rendering behavior that every modern browser implements to dispatch events in near real-time (compared to full page refresh or polling delay). There are a mountain of browser quirks that of course play into this process. The least desirable of these to the user are the “phantom click” and the “throbber of doom” that afflict IE users.
When a page (or an iframe it hosts) is loading content, your browser usually shows some sort of “I’m working” indicator. In the bottom “taskbar” there is usually some sort of progress meter. In the upper right (on IE) the “throbber” will continue to animate until the work is done. Of course in the scenario I’m describing the sent page is never done. The whole point is that the server keeps the connection open. Combine this with the IE behavior of producing a “click” like sound when an iframe is navigated to a different URL, and you’ve got a pretty poor user experience.
But couldn’t you do something with XMLHTTP? Short answer: yes, but not as portably and it won’t get you around IE’s 2-connection limit either so there’s not much of a win. For the long answer, see my talk at ETech or wait for me to post the slides. At the end of the day, the hidden <iframe> hack scales best and is the most portable. Especially if you can lick the UX problems.
Which Google has.
How? By cleverly abusing another safe-for-scripting ActiveX control in IE. Here’s the basic structure of the hack:
// we were served from child.example.com but
// have already set document.domain to example.com
var currentDomain = "http://exmaple.com/";
var dataStreamUrl = currentDomain+"path/to/server.cgi";
var transferDoc = new ActiveXObject("htmlfile"); // !?!
// make sure it's really scriptable
transferDoc.open();
transferDoc.write("<html>");
transferDoc.write("<script>document.domain='"+currentDomain+"';</script>");
transferDoc.write("</html>");
transferDoc.close();
// set the iframe up to call the server for data
var ifrDiv = transferDoc.createElement("div");
transferDoc.appendChild(ifrDiv);
// start communicating
ifrDiv.innerHTML = "<iframe src='"+dataStreamUrl+"'></iframe>";
This is the kind of fundamental technique that is critical to making the next generation of interactive experiences a reality. Server tools like mod_pubsub and LivePage (and perhaps even JMS buses) are starting to come into their own and the benefits of event-driven IO are starting to become well understood by server-side devs. It’s only a matter of time before server-push data hits an inflection point in the same way that background single-request/single-response data transfer did with Ajax. Dojo will, of course, have infrastructure to support this kind of thing when the borader developer community is ready (most if it is already in place).
From long and painful experience and amazingly deep respect, I take my hat off and bow to whoever it was on the GMail/GTalk team that figured this out. It’s a hell of a hack. It’s no wonder that Google has been able to attract and develop the bestDHTMLhackersin theworld.
Update: so just to be *very* clear, I worked on the rewrite of the mod_pubsub *client*. The server rewrite was handled by some folks who are much smarter than I am.
Web pages and ASP.NET are wonderful tools, but as the old proverb goes, "if all you have is a hammer, everything seems like a nail". ASP.NET is a web technology, all of which exists to serve up resources to Web Browsers which request them. That's great, and it works for the vast majority of things currently on the Internet. In fact, when people think of the Internet, they mostly associate it with the ubiquitous World Wide Web model or its HTTP protocol. But, what if we want more…what if instead of Web Browsers just requesting resources, we want to push information actively out to them, without being asked first? Examples of this include: live wiki's, polls, chat, stock tickers, real-time auctions, and games.
This is where the existing paradigm fails, and we have to fall back to older technologies, the roots on which the Internet is founded….yes, I'm talking about actual two-way communication, interactivity, *gasp* sockets, TCP/IP!
Taking a step back, web requests can roughly be compared to a student asking a teacher questions. However, only the student is allowed to ask questions, the teacher cannot actively prompt the student with facts. This article sets out one experimental approach to breaking that model. Most of the current solutions, collectively called "Comet", involve the Web Browser keeping an active HTTP connection to the Web Server, like a student always keeping the teacher's attention. Presented here is another aproach, utilizing a "side-channel", where the "teacher" (that's the Web Server) doesn't spend as much attention or resources responding to pesky questions (web requests), and we can push information out instead, or engage in a more interactive way.
Background
This article is inspired and partially based on the work and code by Alex MacCaw, the developer who created the Juggernaut extension to Ruby on Rails. But, why leave all the fun to those dynamic language people? Can't we have that in ASP.NET too? The answer is "yes we can".
How do we communicate with the Web Browser without it making a request first? One solution is to have the Web Browser keep open a socket connection directly to the server using a small light-weight Flash component.
What are the benefits of this aproach?
It consumes much less Web Server resources. We are not dedicating any Web Server threads to responding to a "long-polling" request.
It's simple and easy to implement.
Good cross-platform support (any modern Web Browser that supports Flash 8, e.g., IE, FireFox, Safari, Opera).
Lower latency, no re-establishing connections, or other HTTP protocol overheads.
And yes, it does have drawbacks:
We need to install something on the Web Server or the network domain to accept these socket connections.
The embedded Flash component making the connection to the Comet server will not work behind firewalls or proxies that block non-HTTP ports.
It needs Flash and JavaScript enabled on the Web Browser.
It's a hack (though the same thing could be said about many, now ubiquitous, Web Browser technologies).
Using the Code
Comet applications can be divided into two categories:
"Server-push" applications where the Web Server pushes out information on updates and changes based on user events, e.g., live wiki's, polls, chat etc.
"Interactive" applications where an external process pushes information to the web browser in response to an event, e.g., stock tickers, real-time auctions, games etc.
Server-push
The work flow for using Comet in a server-push scenario is:
The Web Browser requests a page from the Web Server, which includes the Flash Comet component, Comet JavaScript, and a unique authentication string.
The Web Server registers the Comet connection with the "Comet Server", specifying its channel, its authentication string, and any initial state.
The Web Browser opens a socket connection to the Comet Server, and sends its authentication string.
The Web Browser sends data to the Web Server using postbacks.
The Web Server responds to any postbacks or AJAX calls and publishes any updates to the Comet Server.
The Web Browser responds to any updates from its Comet connection.
Finally, the Web Browser receives the data published on the Web Server and executes it:
Collapse
guestbook_addComment = function(name, comment)
{
var commentsDiv = document.getElementById ("comments");
var e = document.createElement("blockquote");
e.innerHTML = comment + " - " + name + "";
commentsDiv.insertBefore (e, commentsDiv.firstChild);
}
Interactive
The interactive scenario is very similar, but instead of the Web Browser doing postbacks or AJAX calls to the Web Server, the Web Browser uses its Comet connection to send data directly to an Application Client and vice versa.
The work flow for using Comet in an interactive scenario is:
The Web Browser requests a page of the Web Server, which includes the Flash Comet component, Comet JavaScript, and a unique authentication string.
The Web Server registers the Comet connection with the "Comet Server", specifying its channel, its authentication string, and any initial state.
The Web Browser opens a socket connection to the Comet Server, and sends its authentication string, and the Comet Server tells any Application Clients whether a connection has been made on a channel they have subscribed to.
The Application Client sends data to the Comet Server, which passes it onto any Web Browser which has connected on that channel.
The Web Browser sends data to the Comet Server over its Comet connection, which passes it onto any Application Client which has subscribed to that channel.
The Web Browser code is still very similar, but now, instead of doing AJAX calls or postbacks, we can just send data to our actively listening Application Client over the Comet connection.
E.g.: in response to a button click in a "sound player" application:
Collapse
function sounds_click(soundId)
{
comet.send (soundId);
}
The Comet Server and Clients
The Comet Server is a simple event handling and messaging component that accepts two types of connections:
Comet connections from Web Browsers, these are always pre-registered and associated with a single channel.
Client connections from the Web Server, Comet Controls, or Client Applications.
It responds to the following Client events:
Register – A Client wants to allow a Web Browser to make a Comet connection.
Subscribe – A Client wants to listen to events on a specific channel.
Publish/Push – A Client wants to send data to an entire channel (Publish) or a specific Comet connection (Push).
It sends the following events to any Client that has subscribed to the related channel.
Connect – A Web Browser has successfully connected and authenticated with the Comet Server.
Disconnect – An authenticated Web Browser has disconnected from the Comet Server.
Send – A Web Browser has sent data to the Comet Server.
Both the Comet Server and the Client component, which Application Clients can be written with, are simple components. The Comet Server component is self-contained, the only interaction needed is to get it to start and stop, and it can be easily hosted in a Windows application, a Service, or even in the Web Server itself. Similarly, the Client is a simple component (which the CometControl already encapsulates) which exposes some simple methods and events (on a single thread) which can easily be extended to write your own Application Clients which can also be hosted in a variety of ways.
Summary
Web browsers opening socket connections to a server isn't actually that new. In their isolated little sandboxes, Java applets have been doing it since they were invented more then a decade ago, and Flash has had the capability for a while too. Microsoft's Silverlight will eventually be getting it in version 2.0. All that the Comet approaches are trying to do is get a similar capability directly within the web browser itself.
I've had fun with this project, exploring a Comet solution for ASP.NET. I don't pretend that it's anything more than it is, a toy project; so consume and eat it with a grain of salt (and credit me if it tastes nice). For this author however, it gave an interesting insight to what's possible.
Im a Software Developer working in Auckland, New Zealand. When i was a lot shorter, i started programming in Atari Basic, though these days its mostly C#, and a bit of java (mostly the caffinated kind).
This code is not production ready; it is designed to demonstrate a theoretical solution to using COMET in ASP.NET. This article covers the server-side implementation of COMET and how to combat the scalability problems. To demonstrate the client code, I will release a smaller article soon which demonstrates a small tic-tac-toe game using the COMET thread pooling mechanism I mention below, which should give you some idea about using it in a real world application.
Introduction
Over the past six months, I have been putting together an online chess application where players can register, login, and play chess in real-time. One of the obstacles I had to overcome was how to implement a real-time communication between the client and the server. For this to be successful, a number of factors need to be addressed:
Scalability – I wanted it to function in a load-balanced environment, and not consume huge amounts of server resources.
Compatibility – I wanted it to function with many different flavours of browsers, hopefully without the need for a plug-in.
Performance – I needed responses from players to be available to the opponents as soon as possible so I could manage time controls and provide a better experience.
Simplicity – I wanted to implement the communication layer without installing a third party server application. Mainly, so it would work on hosted environments such as www.discountASP.NET.
So, I evaluated up all of my options. My first prototype used a standard AJAX mechanism which polled the server; this created too much latency and too much traffic, so I quickly moved on from this. I researched other mechanisms of transport, such as using socket communication via a hidden Flash applet; this required a browser plug-in, and so I moved on. I then found the COMET idea, and thought, bingo, this is what I want to use, so I did some more research and built a prototype.
The idea behind COMET
COMET uses a persistent connection between the client (Web Browser, using XmlHttpRequest) and the server. This persistent connection is held open on the server for a predefined period of time (let's say, 5 seconds) and will only respond back to the client with either a timeout message, or when some part of the server's application logic wants to send a message. Once the client receives the message, it is processed using whatever application logic is implemented on the client. The persistent connection is then reopened, and the process starts again.
This mechanism solves the Performance requirement; it means that whenever a message is needed to be sent to the client, and if a persistent connection is open, the client should receive it with very little latency, almost instantly.
A second connection is used to send messages to the server; this connection is not persistent, and typically returns immediately after it is processed. From the point of view of a chess game, the persistent connection would be waiting for my opponent’s move, while the non-persistent connection would send my move.
Real world use implementation of COMET
So far, everything looks great on paper; we have a mechanism that can deliver messages to a browser in real-time without a plug-in, but in practice, this is much more difficult. Many articles that describe the features of using a persistent connection comment about how much of a "hack" this is. I tend to disagree with these statements.
It is true that COMET can run into issues when running on some browsers (mainly because HTTP imposes a two connection per browser, per host limitation). This limitation of the HTTP protocol was implemented to provide better performance for normal browsing over low bandwidth connections (e.g., dialup modems), and can cause performance issues when running COMET (there are ways around this!). This issue is only really noticeable in Internet Explorer (I think IE is strict with this standard up until IE8); Firefox 2 allows more connections, and manages them better, and Firefox 3 allows even more, meaning the future for COMET style applications is bright.
The second issue comes from the scalability of the technology, and this is mainly what this article is trying to remedy. This issue is present because the platforms lack the support for a COMET style protocol, and presently do not scale well when using persistent connections. I would say this is not a failure of the overall idea of COMET, rather a failure of the implementation of a specific COMET server.
Other developers have put together servers that sit in front of the platforms we develop on, these allow us to separate the COMET request mechanism from the web server, and allow the solution to scale by managing their own persistent connections. What I will demonstrate in this article is how you should not use COMET in ASP.NET, and the possible solution.
Load testing COMET
The major drawback with persistent connections to ASP.NET is that each connection consumes an ASP.NET worker thread for the five seconds that the connection is open. Therefore, each client that connects will hold a thread from the ASP.NET thread pool and, eventually, under load, the server will stop responding.
To demonstrate this, I put together a very simple application to simulate persistent connections to an ASP.NET application, with a handler that holds the requests open for 5 seconds before returning to the client:
Collapse
publicclass CometSyncHandler : IHttpHandler
{
#region IHttpHandler Members
publicbool IsReusable
{
get { returntrue; }
}
publicvoid ProcessRequest(HttpContext context)
{
int workerAvailable = 0;
int completionPortAvailable = 0;
ThreadPool.GetAvailableThreads(out workerAvailable,
out completionPortAvailable);
Debug.WriteLine("CometSyncHandler.ProcessRequest Start");
Debug.WriteLine(string.Format("Worker Threads Available: {0}",
workerAvailable));
Debug.WriteLine(string.Format("Completion Port Threads Available: {0}",
completionPortAvailable));
DateTime now = DateTime.Now;
while (true)
{
Thread.Sleep(50);
if (DateTime.Now.Subtract(now).TotalSeconds >= 5)
break;
}
Debug.WriteLine("CometSyncHandler.ProcessRequest End");
}
#endregion
}
This handler is very simple, it holds the execution of the request up to for 5 seconds, then returns. This simulates a COMET request that would eventually timeout and return the client.
I also wrote a console application that ramps up WebRequest calls to the CometSyncHandler handler. The results were pretty much as expected; each client used an ASP.NET worker thread, and eventually after 40 or so connections, the website started to underperform, and page requests started to respond very slowly.
The screenshot below shows this happening:
I ramped up approximately 50 connections with two instances of the CometClientSimulator application, and as the website performance dropped, it started to drop connections from the simulator. To repeat this test, you can open and Debug the CometAsync website, which would open default.aspx and set everything running, then open an instance of the CometClientSimulator console application and type addsyncclients; this will then ramp up 25 clients, adding one every two seconds into the ASP.NET application.
Clearly, this is no good for any real world application, so I did some digging and devised a solution.
IHttpAsyncHandler
This is the first part of the solution. This little piece of magic allows us to run code asynchronously on the web server when we service a request to a handler. If you are not familiar with IAsyncHttpHandler, then read my brief explanation of how it works below:
IHttpAsyncHandler exposes two main methods that require implementation; these are BeginProcessRequest and EndProcessRequest. The general idea behind this is we service the start of our request in BeginProcessRequest; we then hand execution off to some sort of asynchronous method, e.g., Database Query Execution, or indeed any asynchronous .NET method. The asynchronous method completes, and then the response to the client is processed in EndProcessRequest.
The sequence diagram below shows how this would work:
CometThreadPool
The sequence diagram above introduces a custom thread pool for servicing the COMET requests that sits around on the server. This is required because we don't want ASP.NET to use one of its threads while waiting around for a COMET request to timeout.
The code for this pooling mechanism is located in the website in the CometAsync folder. It contains the following files:
CometAsyncHandler – This is the IHttpAsyncHandler implementation.
CometAsyncResult – This is a custom IAsyncResult implementation that contains the state of a COMET asynchronous operation.
CometThreadPool – This is a static class that manages the COMET thread pool.
CometWaitRequest – This is an object that represents the request from the client. These are queued to be processed in the custom thread pool.
CometWaitThread – This is a thread that processes CometWaitRequest objects from the queue.
This implementation works by first creating a set of background CometWaitThread objects that each contain a single thread that processes the CometWaitRequest queue items. In our web application, we would initialize the thread pool in Application_Start in the global.asax file.
Collapse
protectedvoid Application_Start(object sender, EventArgs e)
{
//// queue 5 threads to run// the comet requests
CometThreadPool.CreateThreads(5);
}
This fires up five threads that idle along in the background, waiting for CometWaitRequest instances to service.
Our CometAsyncHandler than waits for requests from the client; its responsibility is to queue the requests in the thread pool.
Collapse
public IAsyncResult BeginProcessRequest(HttpContext context,
AsyncCallback cb, object extraData)
{
int workerAvailable = 0;
int completionPortAvailable = 0;
ThreadPool.GetAvailableThreads(out workerAvailable,
out completionPortAvailable);
Debug.WriteLine(
string.Format(
"BeginProcessRequest: {0} {1} out of {2}/{3} ({4} Requests Active)",
Thread.CurrentThread.IsThreadPoolThread,
Thread.CurrentThread.ManagedThreadId,
workerAvailable,
completionPortAvailable,
CometWaitRequest.RequestCount));
// get the result here
CometAsyncResult result =
new CometAsyncResult(context, cb, extraData);
result.BeginWaitRequest();
// ok, return itreturn result;
}
The BeginProcessRequest outputs some Debug information so we can keep track exactly of what threads are available, and then creates an instance of a CometAsyncResult class that tracks the HttpContext and is used to return to ASP.NET to indicate it has started an asynchronous process. Before returning, it calls BeginWaitRequest, which adds the request into the thread pool.
This code creates a new instance of the CometWaitRequest object and queues it in the thread pool.
Collapse
internalstaticvoid QueueCometWaitRequest(CometWaitRequest request)
{
CometWaitThread waitThread;
lock (state)
{
// else, get the next wait thread
waitThread = waitThreads[nextWaitThread];
// cycle the thread that we want
nextWaitThread++;
if (nextWaitThread == maxWaitThreads)
nextWaitThread = 0;
CometWaitRequest.RequestCount++;
}
// queue the wait request
waitThread.QueueCometWaitRequest(request);
}
This logic picks a CometWaitThread to assign the CometWaitRequest to, based on a round robin approach (e.g., if thread 1 received the previous request, thread 2 will receive the second).
The request is added into an internal list of CometWaitRequest objects in the chosen thread.
At this point, the CometAsyncHandler has returned the ASP.NET thread to the pool, and is waiting for the CometWaitThread to complete the asynchronous process so it can complete the client's request. Our CometWaitThread code looks like this:
Collapse
privatevoid QueueCometWaitRequest_WaitCallback()
{
// here we are...// in a loopwhile (true)
{
CometWaitRequest[] processRequest;
lock (this.state)
{
processRequest = waitRequests.ToArray();
}
Thread.Sleep(100);
for (int i = 0; i < processRequest.Length; i++)
{
// timed out so remove from the queueif (DateTime.Now.Subtract
(processRequest[i].DateTimeAdded).TotalSeconds >= 5)
{
//// queue anotehr wait callback, so// we tell close handler down// the endRequest will exist on a // different thread to this// one and not tear down this thread
processRequest[i].Result.ResponseObject =
this.CheckForServerPushEvent(processRequest[i], true);
this.QueueCometWaitRequest_Finished(processRequest[i]);
}
else
{
object serverPushEvent =
this.CheckForServerPushEvent(processRequest[i], false);
if (serverPushEvent != null)
{
// we have our event, which is good// it means we can serialize it back to the client
processRequest[i].Result.ResponseObject =
serverPushEvent;
// queue the response on another // ASP.NET Worker threadthis.QueueCometWaitRequest_Finished
(processRequest[i]);
// dequeue the request
DequeueCometWaitRequest(processRequest[i]);
}
}
Thread.Sleep(100);
}
}
}
The QueueCometWaitRequest_WaitCallback is the entry point for this thread, and was started way back in Application_Start. It has been cycling around in a loop, waiting for a CometWaitRequest item to appear in its queue; once a client requests the CometAsyncHandler handler, one will be present.
It processes each item in the queue in a sequenced order in each loop iteration, e.g., if there were three requests pending, it would check request 1, 2, then 3, then continue the loop and process request 1, 2, and 3 again. This ensures that each request is processed as soon as possible without having to wait for a previous request to finish its 5 second timeout.
The loop checks to see if the CometWaitRequest has been in the queue for longer than the predefined timeout (in this case, 5 seconds); otherwise, it checks to see if there is an event waiting to be sent back to the client. If either case is true, it completes the CometWaitRequest, returning the desired response object, and then removes it from the queue.
Collapse
privatevoid QueueCometWaitRequest_WaitCallback()
{
.
.
// queue the response on another ASP.NET Worker threadthis.QueueCometWaitRequest_Finished(processRequest[i]);
// dequeue the request
DequeueCometWaitRequest(processRequest[i]);
.
.
}
.
.
privatevoid QueueCometWaitRequest_Finished(object target)
{
CometWaitRequest request = target as CometWaitRequest;
request.Result.SetCompleted();
}
.
.
The QueueCometWaitRequest_Finished method completes the asynchronous operation by calling SetCompleted on the CometAsyncResult object, which then calls the callback delegate in the CometAsyncResult which is pointing to EndProcessRequest on the CometAsyncHandler. The following code is then executed:
Collapse
publicvoid EndProcessRequest(IAsyncResult result)
{
int workerAvailable = 0;
int completionPortAvailable = 0;
ThreadPool.GetAvailableThreads(
out workerAvailable, out completionPortAvailable);
Debug.WriteLine(string.Format("EndProcessRequest: {0} {1}" +
" out of {2}/{3} ({4} Requests Active)",
Thread.CurrentThread.IsThreadPoolThread,
Thread.CurrentThread.ManagedThreadId,
workerAvailable,
completionPortAvailable,
CometWaitRequest.RequestCount));
CometAsyncResult cometAsyncResult = result as CometAsyncResult;
if (cometAsyncResult != null &&
cometAsyncResult.ResponseObject != null)
{
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(
cometAsyncResult.ResponseObject.GetType());
serializer.WriteObject(
cometAsyncResult.HttpContext.Response.OutputStream,
cometAsyncResult.ResponseObject);
}
cometAsyncResult.HttpContext.Response.End();
}
This method responds to the client by serializing whatever response object we set in the completion into the response stream of the request's HttpContext.
One thing to mention here is what threads are actually doing the processing of the request. When it arrives in BeginProcessRequest, it is an ASP.NET worker process that is executing, and when the CometWaitThread has completed, either with a timeout or a message, the EndProcessRequest method is executed on one of the CometThreadPool threads, meaning that ASP.NET has only used one of its thread pool thread for the initialization of the COMET request, the remaining 5 seconds were processed without an ASP.NET thread.
We can see that in action in the following screenshot:
At this point, it is also worth mentioning that the response from the website is very good, considering there are 200 persisted connections, and the CPU/memory usage for the box is good (it is also running the clients).
To also double check everything is working smoothly, I log a counter for each request and response pair to ensure that each request is met with a response. The screenshot below shows this output from running the test with 200 clients for five minutes:
This shows that all the requests were complete successfully, (apart from 1, but I put that down to a small race condition in the shutdown code, you can see it completes after!).
Conclusion
By implementing a custom thread pool, we can leverage a COMET mechanism in our ASP.NET server code without implementing a custom server, or even implementing any complicated messaging routines, just a simple thread pool to manage multiple requests (e.g., we had five threads managing all 200 COMET requests).
Using the code
The website project CometAsync will execute and provide a default.aspx page that lists the amount of available threads available to ASP.NET when that request executes.
The CometClientSimulator application will simulate the client connections; it should be executed as follows:
Collapse
CometClientSimulator [websitehost]
websitehost = the host name of the website CometSync app is running e.g. http://localhost:2251
I am currently using a similar engine on my chess website, but it is undergoing vigorous testing right now. By the way, if anyone’s interested in playing chess on there and wants to help out with the testing, drop me an email at imebgo@gmail.com. Thanks!