Introduction
Performance is the main concern for most server application developers. That’s why many of them use .NET platform to develop high performance server applications.
Microsoft Windows provides a high performance model that uses the I/O completion port (IOCP) to process network events. IOCP provides the best performance, but it is difficult to use due to a lack of good code samples and the obligatory use of the C/C++ language. From the other side, if the server’s business logic is not trivial, then it becomes difficult to support and test it. Finding a bug in C++ code may become a serious challenge for most developers.
What is the solution?
Ideally, we would like to have a server application that has a performance comparable to a C++ server and which is a well designed, bugs-free, unit tested business logic written in C#. Yeah, that would be great!
Let’s look at what the .NET platform offers for network operations.
The Socket
class which supports asynchronous operations should be great for the purpose of writing a scalable high performance server. According to the documentation, the asynchronous network operation methods (BeginConnect
, BeginReceive
, BeginSend
) support the I/O completion port, but simple benchmarking showed that the performance is still very low. The Socket
class is very resource consuming when used asynchronously.
In addition to the design and implementation issues[i] of several .NET classes, a server written completely in .NET has a performance lack due to lots of managed/unmanaged code transitions and data marshaling which happen each time an underlying system call is made (network I/O, threads synchronization, etc.). The only solution is to minimize those transitions. This approach has been used in the XF.Server component which was recently released for community preview and available for public download.
In the XF.Server component, managed/unmanaged code transition happens only when a message should be passed between the business logic and the transport logic layer. XF.Server handles all low level network I/O by providing the client with an interface to work with network operations.
Now, let’s write a high performance simple web server using C#, so that we could compare the performance with another web server, for example IIS or Apache (written in C/C++).
What will our web server do?
This simple web server that we are creating will accept client connections, read HTTP requests, and reply with a response containing the client’s request. As soon as a request is served, the connection is closed. The web server response will be formatted in the following way:
private static readonly string responseFormat =
"HTTP/1.1 200 OK\r\n"+
"Server: XF.HTTP/0.1\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Length: {0}\r\n"+
"Connection: Close\r\n\r\n{1}";
Please note that XF.HTTP is a planned open source project of Kodart Technologies that may leave the competitor behind if it includes the features of other products without degradation in performance. Actually, this article contains the source code of the XF.HTTP server prototype, which was gladly provided by Kodart Technologies.
Code review
OK, let’s return to the code. The XF.Server component was developed to support unit testing of the server’s business logic. That’s why the server and connection objects are passed using the interfaces, which you can easily mock to test the logic.
internal static IServer server;
static void Main()
{
server = new Server();
StartServer();
Console.WriteLine("Web Server is Ready. Press any key to exit.");
Console.ReadKey(true);
StopServer();
}
internal static void StartServer()
{
server.OnConnect += OnClientConnect;
server.Start(80);
}
internal static void StopServer()
{
server.Stop();
}
Because it is only a demo HTTP server, we don’t expect a request to be longer than 1024 bytes. If it is longer, then it will be truncated.
internal static void OnClientConnect(IConnection conn)
{
byte[] buffer = new byte[1024];
conn.ReadAsync(buffer, buffer.Length, OnReadComplete, null);
}
The completion routine mechanism in XF.Server has a great optimization; if you recall how an asynchronous network programming model works in .NET, you will remember that in the completion routine, you would repeat the asynchronous call. But, that would again involve many operations with buffers locking, getting the function pointer for the callback delegate, etc.
In XF.Server, you just say that you do/don’t want to repeat the operation, using the Repeat
flag in the OperarionArgs
object.
internal static void OnReadComplete(OperationArgs args)
{
if (args.Bytes != 0)
{
string content = Encoding.ASCII.GetString(args.Buffer).Substring(0, args.Bytes);
string response = String.Format(responseFormat, content.Length, content);
args.Connection.WriteAsync(Encoding.ASCII.GetBytes(response),
response.Length, OnWriteComplete, null);
args.Repeat = false;
}
}
internal static void OnWriteComplete(OperationArgs args)
{
args.Connection.Close();
}
That's it. Just a few lines of code, and a web server is ready.
Performance results
Our web server application and IIS/6.0 were tested using the same platform (Intel Core2 Duo, 2GB RAM) with the Microsoft Web Application Stress Tool. The client and the server were on different machines connected using a 100MB/S LAN.
In order to make the performance testing fair, IIS/6.0 was requested to return a static content, a short text file. IIS is supposed to cache frequently used content, that’s why we don’t consider the disk I/O overhead. If IIS does not cache, then it is a big question to IIS developers ;)
The test has been run for one minute, and the following results were received:
|
IIS/6.0 |
XF.HTTP |
Requests/Second |
1338.14 |
3841.92 |
Socket Errors |
Connect |
0 |
0 |
Send |
1 |
0 |
Recv |
3 |
0 |
Timeout |
0 |
0 |
The results are very surprising. Please also check the CPU usage during the tests, and you will notice that IIS’s CPU usage is not stable and always jumps between 20-80%. In contrast, XF.HTTP’s CPU usage is very stable and changes between 70-80%, that denotes a better server design.
Conclusion
Regardless that XF.Server is still in development and has been only CTP released, you should consider it if you are writing server applications or want to improve existing .NET server applications. Using the XF.Server component and several lines of code, we managed to write a simple web server that could handle thousands of concurrent connections effectively.
The XF.Server component provides performance that no one has offered before. Even socket components that could offer 300-500 requests/sec are far behind, even when compared to the CTP release of XF.Server.
Please post your questions, and comments, and if you know a component that can provide better performance. Please do benchmarking and comment here if you have different results.