Sunday, January 5, 2014

A History of Microsoft's HTTP Client Stacks

This article is a bit of history about the various Microsoft HTTP client stacks.

WININET


Originally, there was one - the WININET implementation, that was written for the Internet Explorer browser. The implementation for this was encapsulated in Wininet.dll. In it's original implementation (as shipped in IE 2.0/IE 3.0/IE 4.x), it supported the following protocols - HTTP, FTP, GOPHER. It also supported HTTP caching. For more information, see WININET Msdn Page


As client side HTTP stacks stood in those days, it was pretty good and usable. While it was originally targetted for Internet Explorer, a lot of third party applications began to be built around it. And as frequently happens, it was being put to uses for which it was not intended.


For eg, a lot of customers started using it for implementing Middle-tier applications, where the DLL was loaded in an ISAPI in the IIS process. Wininet was not really intended for such uses - it was heavily bound to a user context because of it's dependency on the user directories for storing the cached web pages. Also, it did not support delegated authentication. Also, some of the Wininet settings were only configurable through the IE interface.


Wininet still lives on - it is the HTTP stack being used by the Internet Explorer browser. In OS Versions from Windows7 onwards, it might have been replaced by WebIO.


WinHTTP


More and more customers were using Wininet in a non user-context - for eg, in middle-tier apps, and it was not working very well. As I mentioned above, Wininet was not designed to be used in a server environment. So, a separate team was spun off to come up with a solution for that.


The result of that was WinHTTP. This was a standalone DLL, that borrowed heavily from Wininet in terms of the interfaces it offered. However, it was designed to be usable in server applications as well. It was a robust implementation, that exposed it's functionality through exported functions, as well as a COM interface that could be used in a server scenario.


However, it was a subset of the Wininet implementation. It did not support cookies, caching, and automatic credential handling. Also, Wininet had a lot of code to handle buggy HTTP servers, and proxy servers, and that code was not supported by WinHTTP.


There were two version of WinHTTP. WinHTTP 5.0 was a redistributable version, and was downloadable as a standalong component from Microsoft. WinHTTP 5.1 was made a part of the operating system, and thus was not downloadable as a separate component.

System.Net


As part of Microsoft's effort to build a clean-room, managed execution environment like Java, a separate team was staffed to develop the networking library for the managed frameworks that were to ship as part of this. It was part of the project Codenamed Lightning.

The networking classes were encapsulated in the System.Net namespace. This team was staffed by ex-WININET developers, and led by Henry Sanders, who is now a Distinguished Engineer at Microsoft.

This library was written with a view to fix some of the problems with WININET. It was designed from the ground up to be usable in middle-tier scenarios. Also, it was designed to support asynchronous networking calls. Even the sync networking calls ended up calling the async implementations underneath, which caused the application to use up an extra Thread. This was later fixed in .Net Framework 2.0 (codename Whidbey). Also, it supported asynchronous operations on both WinNT and Win9x platforms, whereby it used Completion ports on WinNT, and Overlapped I/O on Win9x.

The class design (for the Http client stack) in the System.Net library is such that it support both beginners and advanced users seamlessly. For beginners, System.Net.WebClienrt offers an easy to use API to get web resources, and even to send POST requests to Web forms. For advanced and fine grained control, System.Net.HttpWebRequest class is the recommended entry point.

With a new design philosophy, the API designers decided to make usage consistent with other parts of the framework. For eg, doing asynchronous network I/O in Http was the same pattern as doing asynchronous I/O on a File handle in the System.IO.File class.

One extra thing that was available in System.Net, was a Socket implementation, similar to the facilities provided by Windows Sockets (Winsock) library on Windows. Again, keeping in mind the overall design philosophy of the .NET framework, later releases added Stream implementation ( for eg SSLStream) that could be hooked up to a Socket to provide higher level network stack functions, like encryption, authentication etc.

For debugging a Tracing/Logging functionality is provided that allows most network I/O to be logged using the .NET Runtimes logging functionality.

WebIO

In Windows7, a new native HTTP stack was written, called WebIO. This is not documented as a public API, and is internal only to the browser and the Operating System.

Conclusion

For developers who are using .NET, the primary and only choice for a HTTP client library is System.Net namespace. However, for use outside of the .NET Runtime, the options are Wininet and WinHTTP.  All three of these stacks should support most modern applications to varying degrees.

Sunday, September 4, 2011

How to share NTLM connection across HttpWebRequest objects

When writing an application that talks to a server using NTLM authentication, HttpWebRequest does a three legged authentication handshake that looks as follows:


Client Server
GET / HTTP/1.1 HTTP/1.1 401 Access Denied
Connection: close
WWW-Authenticate: NTLM <token>
GET / HTTP/1.1
Authorization: NTLM <token1>
HTTP/1.1 401 Access Denied
Connection: close
WWW-Authenticate: NTLM NTLM <token>
GET / HTTP/1.1
Authorization: NTLM <token2>
HTTP/1.1 200 OK
Connection: close

As you can see, by default, HttpWebRequest does not share connections. The reason is that NTLM is a connection authentication protocol, and not a request authentication protocol like BASIC or DIGEST.

In other words, once an NTLM handshake succeeds, the NTLM credentials stick with the connection. So, if you have a situation (for eg, a web server talking to another webserver using NTLM auth) where there are multiple users with different credentials accessing the front-end server, there is a high risk that an authenticated connection for one user might get used by another user due to connection pooling.

In order to force HttpWebRequest to share NTLM connections, you can do the following:

Setting UnsafeAuthenticatedConnectionSharing will cause all NTLM connections to the same host to be shared.

By setting ConnectionGroupName, you can cause connections to be shared only selectively. In this case, all requests having the same ConnectionGroupName will share connections. You can use this mechanism if you are on the middle-tier scenario.

Related:

System.NET Links and How To's
Tracing with System.Net
The case of multiple NTLM challenges with IIS7

Tuesday, August 9, 2011

Diagnosing RMI Exception: java.rmi.ConnectException: Connection refused to host: 127.0.0.1

I have a service that runs on Linux under JBOSS. This service uses JMX (RMI) to talk to a windows box running a java service.

Everything was working ok, until both the Linux and Windows boxes were moved to a different subnet. Then, they started failing with the following exception:

Caused by: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:
java.net.ConnectException: Connection refused
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:601)
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:198)
at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:184)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:110)
at javax.management.remote.rmi.RMIServerImpl_Stub.newClient(Unknown Source)
at javax.management.remote.rmi.RMIConnector.getConnection(RMIConnector.java:2312)
at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:277)
at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:248)
at com.zillow.core.jmx.ZillowJMXConnectorFactory.connect(ZillowJMXConnectorFactory.java:127)
at com.zillow.core.jmx.ZillowJMXConnectorFactory.connect(ZillowJMXConnectorFactory.java:53)
at com.zillow.bcpserver.BCPServerProxy.afterPropertiesSet(BCPServerProxy.java:123)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1369)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1335)
... 142 more
Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:519)
at java.net.Socket.connect(Socket.java:469)
at java.net.Socket.(Socket.java:366)
at java.net.Socket.(Socket.java:180)
at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:22)
at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:128)
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:595)
... 154 more


I searched for this exception in the search engines, and the only thing I found was people saying that if the local box (i.e linux) did not have a correct IP address for localhost, it would send the 127.0.0.1 address as the "ContactMe" endpoint to the remote destination, and that would fail.

For example, the following links explained that issue:

http://forum.springsource.org/showthread.php?33711-RMI-invocation-attempts-connecting-to-127.0.0.1


However, in my case, that turned out not to be the problem. My Linux boxes (even after the move) had the correct hostname, and the `hostname` command was giving back the correct hostname (and not 127.0.0.1 or localhost).

At this point, there was no more information available through the search engines to diagnose the problem.

So, I decided to debug it myself. I first restarted the service, and used TCPDUMP to do a network sniff on the linux box.


Here, .175 is the Linux box, and .45 is the windows box.

The following is the packet disassembly of the JRMI/ReturnData response being sent by the Windows box to the Linux box.


As you can see, the Windows server is sending back "127.0.0.1" as the CallMe endpoint to the Linux box. The linux box tries to connect to port 1099 on 127.0.0.1 and fails.

Since the machines had been moved to different networks, it is possible that the java service might have lost their IP address and network registration settings.

So, I restarted the java service on the windows box. And viola!, that fixed the problem.

Tuesday, March 22, 2011

Why isn't HTTPS used more on the web?

Ars Technica has a great article giving the reasons why HTTPS is not used more on the Web.

The summary is as follows:

1) HTTPS adds some latency to initial connection establishment.
2) HTTPS documents cannot be cached by the browser. While this is not a big issue when the client and server are on the same continents,
3) HTTPS websites cannot be virtual hosts on the same server, and this causes addition in operation costs.

It goes on to add that while these are practical reasons why HTTPS isn't widely deployed, the practical hurdles will fall away eventually leading to a wider adoption of HTTPS.

Friday, June 25, 2010

Via JonesBlog - Apples Retina Display

Brian Jones analyzes Apple's claim that the Iphone4's display resolution is better than a human eye. Interesting reading.

Sunday, March 21, 2010

Multipart Form Upload Helper


Implementing Multipart-MIME upload helper using WebClient



The WebClient class is a higher level abstraction on top of HttpWebRequest, that abstracts away the common tasks of uploading files, as well as doing Form Posts to HTTP servers. However, it does not provide an easy way to do multipart form uploads.

Multipart form upload is defined by the W3C specification.

Multipart HTML Form Design

Let us start off by designing the HTML Form that will be the target of the multipart upload. As per the HTML spec, the form should have an enctype attribute set to "multipart/form-data". Also, we are going to have a File input control embedded in the form.

Your name: Your id: What files are you sending?

MIME Part Requirements

In a multipart form upload, each mime part begins with some MIME headers. The following headers are mandatory:

  • Content-Disposition header
  • A name attribute that specifies the name of the corresponding control from the HTML form.
For eg, in the form above, if I specify "John" for the name, and "1" for the ID, then I will have the following in the MIME upload

--BD37EC1
Content-Disposition: form-data; name="fname";

john
--BD37EC1
Content-Disposition: form-data; name="id";
acme
--BD37EC1
Ideally, our helper class should be able to write out the boundaries itself, and delegate the task of writing out the headers to each MIME part that is part of the upload.

Our multipart form has two kinds of controls: normal Text input as well as FILE input controls. Therefore, our helper class needs to support both of these types of MIME parts.

Let us start off by thinking about the layour of the MIME part classes.

MIME Part Design

As discussed, we are going to have two types of MIME parts - a normal FORM upload type where we want to specify Name/Value pairs, as well as a FILE part where we want to embed the contents of a file.

In the previous section we saw the basic requirements of a MIME part. It has to have a "Content-Disposition" header. In addition to this, a MIME part that contains file contents should also have a "Content-Type" header.

With this, we can now write out the layout of the base class for these two MIME parts.


/// 
    /// MimePart
    /// Abstract class for all MimeParts
    /// 
    abstract class MimePart
    {
        public string Name { get; set; }

        public abstract string ContentDisposition { get; }

        public abstract string ContentType { get; }

        public abstract void CopyTo(Stream stream);

        public String Boundary
        {
            get;
            set;
        }
    }

This is a good abstraction of a MIME part. Derived classes can define the Content-Type and Content-Disposition, we well as write out the contents of the MIME part into the Stream that is provided. The MIME helper class will do the job of formatting the MIME part and uploading it to a HTTP endpoint.

With this, we can now write out a derived class that can upload textual input data (i.e not file contents)

class NameValuePart : MimePart
    {
        private NameValueCollection nameValues;

        public NameValuePart(NameValueCollection nameValues)
        {
            this.nameValues = nameValues;
        }

        public override void CopyTo(Stream stream)
        {
            string boundary = this.Boundary;
            StringBuilder sb = new StringBuilder();

            foreach (object element in this.nameValues.Keys)
            {
                sb.AppendFormat("--{0}", boundary);
                sb.Append("\r\n");
                sb.AppendFormat("Content-Disposition: form-data; name=\"{0}\";", element);
                sb.Append("\r\n");
                sb.Append("\r\n");
                sb.Append(this.nameValues[element.ToString()]);

                sb.Append("\r\n");

            }

            sb.AppendFormat("--{0}", boundary);
            sb.Append("\r\n");

            //Trace.WriteLine(sb.ToString());
            byte [] data = Encoding.ASCII.GetBytes(sb.ToString());
            stream.Write(data, 0, data.Length);
        }

        public override string ContentDisposition
        {
            get { return "form-data"; }
        }

        public override string ContentType
        {
            get { return String.Empty; }
        }
    } 
 


NameValuePart is an implementation of the MimePart abstract class. It does the job of serializing the Name/Value pairs as a MIME encapsulated part.

Next, we write the implementation of the class the encapsulates file uploads as a MIME part.


class FilePart : MimePart

{

private Stream input;

private String contentType;



public FilePart(Stream input, String name, String contentType)

{

this.input = input;

this.contentType = contentType;

this.Name = name;

}



public override void CopyTo(Stream stream)

{

StringBuilder sb = new StringBuilder();

sb.AppendFormat("Content-Disposition: {0}", this.ContentDisposition);

if (this.Name != null)

sb.Append("; ").AppendFormat("name=\"{0}\"", this.Name);

if (this.FileName != null)

sb.Append("; ").AppendFormat("filename=\"{0}\"", this.FileName);

sb.Append("\r\n");

sb.AppendFormat(this.ContentType);

sb.Append("\r\n");

sb.Append("\r\n");



// serialize the header data.

byte[] buffer = Encoding.ASCII.GetBytes(sb.ToString());

stream.Write(buffer, 0, buffer.Length);



// send the stream.

byte[] readBuffer = new byte[1024];

int read = input.Read(readBuffer, 0, readBuffer.Length);

while (read > 0)

{

stream.Write(readBuffer, 0, read);

read = input.Read(readBuffer, 0, readBuffer.Length);

}



// write the terminating boundary

sb.Length = 0;

sb.Append("\r\n");

sb.AppendFormat("--{0}", this.Boundary);

sb.Append("\r\n");

buffer = Encoding.ASCII.GetBytes(sb.ToString());

stream.Write(buffer, 0, buffer.Length);



}



public override string ContentDisposition

{

get { return "file"; }

}



public override string ContentType

{

get { return String.Format("content-type: {0}", this.contentType); }

}



public String FileName { get; set; }

}


According to the HTML Form upload specification, when the form defines more than one FILE INPUT field, then all the files in the form can be uploaded as one MIME type (of type multipart/mixed) that encapsulates each file, and each file will have it's own MIME encapsulation. In order to support this, we need to create a special class that can encapsulate FILE types.

/// 
        /// Helper class that encapsulates all file uploads
        /// in a mime part.
        /// 
        class FilesCollection : MimePart
        {
            private List files;

            public FilesCollection()
            {
                this.files = new List();
                this.Boundary = MultipartHelper.GetBoundary();
            }

            public int Count
            {
                get { return this.files.Count; }
            }

            public override string ContentDisposition
            {
                get
                {
                    return String.Format("form-data; name=\"{0}\"", this.Name);
                }
            }

            public override string ContentType
            {
                get { return String.Format("multipart/mixed; boundary={0}", this.Boundary); }
            }

            public override void CopyTo(Stream stream)
            {
                // serialize the headers
                StringBuilder sb = new StringBuilder(128);
                sb.Append("Content-Disposition: ").Append(this.ContentDisposition).Append("\r\n");
                sb.Append("Content-Type: ").Append(this.ContentType).Append("\r\n");
                sb.Append("\r\n");
                sb.AppendFormat("--{0}", this.Boundary).Append("\r\n");

                byte[] headerBytes = Encoding.ASCII.GetBytes(sb.ToString());
                stream.Write(headerBytes, 0, headerBytes.Length);
                foreach (FilePart part in files)
                {
                    part.Boundary = this.Boundary;
                    part.CopyTo(stream);
                }
            }

            public void Add(FilePart part)
            {
                this.files.Add(part);
            }
        }



This class does the job of encapsulating FILE parts inside a MIME container.

Now, we just need to write the code that can put this all together.

/// 
    /// Helper class to aid in uploading multipart
    /// entities to HTTP web endpoints.
    /// 
    class MultipartHelper
    {
        private static Random random = new Random(Environment.TickCount);

        private List formData = new List();
        private FilesCollection files = null;
        private MemoryStream bufferStream = new MemoryStream();
        private string boundary;

        public String Boundary { get { return boundary; } }

        public static String GetBoundary()
        {
            return Environment.TickCount.ToString("X");
        }

        public MultipartHelper()
        {
            this.boundary = MultipartHelper.GetBoundary();
        }

        public void Add(NameValuePart part)
        {
            this.formData.Add(part);
            part.Boundary = boundary;
        }

        public void Add(FilePart part)
        {
            if (files == null)
            {
                files = new FilesCollection();
            }
            this.files.Add(part);
        }

        public void Upload(WebClient client, string address, string method)
        {
            // set header
            client.Headers.Add(HttpRequestHeader.ContentType, "multipart/form-data; boundary=" + this.boundary);
            Trace.WriteLine("Content-Type: multipart/form-data; boundary=" + this.boundary + "\r\n");

            // first, serialize the form data
            foreach (NameValuePart part in this.formData)
            {
                part.CopyTo(bufferStream);
            }

            // serialize the files.
            this.files.CopyTo(bufferStream);

            if (this.files.Count > 0)
            {
                // add the terminating boundary.
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("--{0}", this.Boundary).Append("\r\n");
                byte [] buffer = Encoding.ASCII.GetBytes(sb.ToString());
                bufferStream.Write(buffer, 0, buffer.Length);
            }

            bufferStream.Seek(0, SeekOrigin.Begin);

            Trace.WriteLine(Encoding.ASCII.GetString(bufferStream.ToArray()));
            byte [] response = client.UploadData(address, method, bufferStream.ToArray());
            Trace.WriteLine("----- RESPONSE ------");
            Trace.WriteLine(Encoding.ASCII.GetString(response));
        }

        /// 
        /// Helper class that encapsulates all file uploads
        /// in a mime part.
        /// 
        class FilesCollection : MimePart
        {
            private List files;

            public FilesCollection()
            {
                this.files = new List();
                this.Boundary = MultipartHelper.GetBoundary();
            }

            public int Count
            {
                get { return this.files.Count; }
            }

            public override string ContentDisposition
            {
                get
                {
                    return String.Format("form-data; name=\"{0}\"", this.Name);
                }
            }

            public override string ContentType
            {
                get { return String.Format("multipart/mixed; boundary={0}", this.Boundary); }
            }

            public override void CopyTo(Stream stream)
            {
                // serialize the headers
                StringBuilder sb = new StringBuilder(128);
                sb.Append("Content-Disposition: ").Append(this.ContentDisposition).Append("\r\n");
                sb.Append("Content-Type: ").Append(this.ContentType).Append("\r\n");
                sb.Append("\r\n");
                sb.AppendFormat("--{0}", this.Boundary).Append("\r\n");

                byte[] headerBytes = Encoding.ASCII.GetBytes(sb.ToString());
                stream.Write(headerBytes, 0, headerBytes.Length);
                foreach (FilePart part in files)
                {
                    part.Boundary = this.Boundary;
                    part.CopyTo(stream);
                }
            }

            public void Add(FilePart part)
            {
                this.files.Add(part);
            }
        }
    }


Since the job of serializing to MIME has been delegated to each concrete implementation of MimePart class, the MultipartHelper only has to make sure that it uses the different parts and serializes them into a stream. The stream is then uploaded using the supplied WebClient.

Note that I have put the FilesCollection class as an inner class of the MultipartHelper class, because it is meant to be a helper class that should not be directly accessed by the clients.

The client program for this looks as follows:

class Program
    {
        static void Main(string[] args)
        {
            Trace.Listeners.Add(new ConsoleTraceListener());
            try
            {
                using (StreamWriter sw = new StreamWriter("testfile.txt", false))
                {
                    sw.Write("Hello there!");
                }

                using (Stream iniStream = File.OpenRead(@"c:\platform.ini"))
                using (Stream fileStream = File.OpenRead("testfile.txt"))
                using (WebClient client = new WebClient())
                {
                    MultipartHelper helper = new MultipartHelper();

                    NameValueCollection props = new NameValueCollection();
                    props.Add("fname", "john");
                    props.Add("id", "acme");
                    helper.Add(new NameValuePart(props));

                    FilePart filepart = new FilePart(fileStream, "pics1", "text/plain");
                    filepart.FileName = "1.jpg";
                    helper.Add(filepart);

                    FilePart ini = new FilePart(iniStream, "pics2", "text/plain");
                    ini.FileName = "inifile.ini";
                    helper.Add(ini);

                    helper.Upload(client, "http://localhost/form.aspx", "POST");
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e);
            }
        }
    }



If you want to understand what is going on at the wire level, you can create a system.net trace log which will show the details.

Sunday, December 6, 2009

Socket.SendFile - Implementing fast file transfer

Implementing fast file transfer with Socket.SendFile

When writing network applications, we usually have a need to implement file transfer between two hosts. For eg, imaging an FTP client, where the client is downloading or uploading a file from an FTP server. Similarly, you could be uploading an image file (probably a photo) as an attachment to a Blog or a website like Facebook or Flickr.

Usually, file transfer is implemented as a Read/Write pattern, where you read from the source stream and write into the destination stream. Here the source stream is the stream constructed from the Socket, and the target stream is the file, or vice versa if the file is being transferred to a destination server.

The simple Read/Write pattern for file transfer is implemented as follows.

using (FileStream fs = File.OpenRead(filename))
                {
                    byte[] buffer = new byte[1024];
                    int read = fs.Read(buffer, 0, buffer.Length);

                    while (read > 0)
                    {
                        ns.Write(buffer, 0, read);
                        read = fs.Read(buffer, 0, buffer.Length);
                    }
                }

In the .NET framework, there is a better way to do file uploads, which is exposed through Socket.SendFile method. This method exposes the underlying Winsock API TransmitFile. This API is much more powerful and faster in terms of performance.

In order to check the performance difference, I wrote an application that compares the difference in performance between using the Read/Write pattern and the Socket.SendFile method.

Here is the test program:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Cache;
using System.Threading;

namespace socket_sendfile
{
    delegate TimeSpan SendFileDelegate(Socket client, String filename, long fileSize);

    class Header
    {
        public long FileSize { get; set; }
        public int FileNumber { get; set; }

        public void Serialize(Stream stream)
        {
            byte[] buffer = BitConverter.GetBytes(this.FileNumber);
            stream.Write(buffer, 0, buffer.Length);

            buffer = BitConverter.GetBytes(this.FileSize);
            stream.Write(buffer, 0, buffer.Length);
        }

        public static Header Deserialize(Stream stream)
        {
            Header header = new Header();

            byte[] buffer = new byte[4];
            int read = stream.Read(buffer, 0, buffer.Length);
            header.FileNumber = BitConverter.ToInt32(buffer, 0);

            buffer = new byte[sizeof(long)];
            read = stream.Read(buffer, 0, buffer.Length);
            header.FileSize = BitConverter.ToInt64(buffer, 0);
            return header;
        }
    }

    class Program
    {
        private Random rand = new Random();

        static void Main(string[] args)
        {
            Program prog = new Program();

            try
            {
                prog.StartServer();

                using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
                {
                    client.Bind(new IPEndPoint(0, 0));
                    client.Connect(new IPEndPoint(IPAddress.Loopback, 8080));

                    prog.Run(client, new SendFileDelegate(prog.SendFile1));

                    Console.WriteLine();
                    prog.Run(client, new SendFileDelegate(prog.SendFile2));
                }
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
            }
        }

        void StartServer()
        {
            Thread serverThread = new Thread(new ThreadStart(this.Server));
            serverThread.Start();
        }

        void Run(Socket client, SendFileDelegate sendFileMethod)
        {
            foreach (long size in this.GetNextSize())
            {
                String filename = Path.GetTempFileName();

                this.CreateFile(filename, size);

                for (int i = 0; i < 10; i++)
                {
                    TimeSpan ts = sendFileMethod(client, filename, size);
                    Console.WriteLine("{0} {1} {2}", i, size, ts.TotalMilliseconds);
                }
            }
        }

        IEnumerable GetNextSize()
        {
            ulong[] sizes = { 1024, 4096, 8192, 16385, 65536, 1048576 };
            for (int i = 0; i < sizes.Length; i++)
            {
                yield return sizes[i];
            }
        }

        void CreateFile(string filename, long size)
        {
            byte[] buffer = new byte[16384];

            // first write out the file.
            using (FileStream tempStream = File.OpenWrite(filename))
            using (BinaryWriter bw = new BinaryWriter(tempStream))
            {
                long remaining = size;
                while (remaining > 0)
                {
                    rand.NextBytes(buffer);
                    int writeSize = buffer.Length;
                    if (writeSize > (int)remaining)
                    {
                        writeSize = (int)remaining;
                    }

                    bw.Write(buffer, 0, writeSize);
                    remaining -= writeSize;
                }
            }
        }

        TimeSpan SendFile1(Socket client, String filename, long fileSize)
        {
            Stopwatch timer = new Stopwatch();

            timer.Start();
            using (NetworkStream ns = new NetworkStream(client))
            {
                Header header = new Header();
                header.FileSize = fileSize;
                header.FileNumber = 1;

                // send the header
                header.Serialize(ns);

                using (FileStream fs = File.OpenRead(filename))
                {
                    byte[] buffer = new byte[1024];
                    int read = fs.Read(buffer, 0, buffer.Length);

                    while (read > 0)
                    {
                        ns.Write(buffer, 0, read);
                        read = fs.Read(buffer, 0, buffer.Length);
                    }
                }
            }

            timer.Stop();

            return timer.Elapsed;
        }

        TimeSpan SendFile2(Socket client, String filename, long fileSize)
        {
            Stopwatch timer = new Stopwatch();

            timer.Start();
            using (NetworkStream ns = new NetworkStream(client))
            {
                Header header = new Header();
                header.FileSize = fileSize;
                header.FileNumber = 1;

                byte[] headerBuffer = null;
                using(MemoryStream ms = new MemoryStream())
                {
                    header.Serialize(ms);
                    ms.Seek(0, SeekOrigin.Begin);
                    headerBuffer = ms.ToArray();
                }

                // send the header
                client.SendFile(filename, headerBuffer, null, TransmitFileOptions.UseDefaultWorkerThread);
                
            }

            timer.Stop();

            return timer.Elapsed;
        }

        void Server()
        {
            byte [] buffer = new byte[1024];
            TcpListener listener = new TcpListener(8080);
            listener.Start();
            using (TcpClient client = listener.AcceptTcpClient())
            using (NetworkStream ns = client.GetStream())
            {
                bool hasData = true;
                while (hasData)
                {
                    // first get the header. Header has the file size.
                    Header header = Header.Deserialize(ns);

                    long remaining = header.FileSize;

                    while (remaining > 0)
                    {
                        int readSize = buffer.Length;
                        if ((long)readSize > remaining)
                            readSize = (int)remaining;

                        int read = ns.Read(buffer, 0, readSize);
                        remaining -= read;
                    }
                }
            }
        }
    }
}

A couple of things to note about this implementation:

1) It uses Message framing to frame file transfers, since it uses the same socket for multiple file transfers. I have used the techniques in Serializing data from .NET to Java to do this. Even though there is no Java app that is involved here, the techniques are the same.

2) The server just drains the incoming stream. It does not save the incoming data to a file. Since we are just interested in benchmarking the performance between the two Send implementations, we should be ok here.

3) The program, which is basically a perf harness, uses a Strategy pattern to change the SendFile method used. That way everything else remains the same, and it just changes the SendFile method to get performance numbers.


Perf Comparison

The following graph shows the performance with the simple Read/Write pattern for file transfer.

performance of file transfer with Socket.BeginSend



The following chart shows the performance when Socket.SendFile is used.


performance of file transfer with Socket.SendFile



As you can see, there is a huge difference between the two, specially for 1M file size. With Socket.SendFile, it takes max 129ms for upload, whereas without this API, it takes 1000ms for upload. For smaller file sizes, there is not that much of a difference.

There is a huge variance in timings for the SendFile() method for 1M file size, but I havent been able to figure out the reason for that yet. Anyway, the fact that Socket.SendFile() is faster should not  be impacted by that.