views:

235

answers:

0

Hi all,
This is my first foray into asynchronous socket development, so I'm open to any and all suggestions - thought I do have a specific q. My objective is to make a series of httpRequests using raw async sockets, I need to grab the http response headers that the server replies with. I adapted the code from this MSDN article: http://msdn.microsoft.com/en-us/library/bew39x2a.aspx -- my code is below.

It seems to work fine [I test against localhost]...except for one thing. In the StartClient() method, if I don't do the Thread.Sleep(), the contents of my dictionary, responseResults, contains the correct number of requests that I provide to the method, however the values are duplicates (IE, the headers are identical for all the http requests I make). With the Thread.Sleep(), however, everything seems to be honky dory in the dictionary (E.g., I get the correct response headers in my dictionary). I'm sure there is a correct way to do this w/o Thread.Sleep - I just want to know what it is and how I go about implementing it!

Thanks in advance!

namespace Async2
{
public class Program
{
    public static int Main(String[] args)
    {
        List<string> hitUrls = new List<string>() 
        { 
            "http://www.someUrl.com",
            "http://www.someOtherUrl.com"
        };

        AsynchronousClient asyncClient = new AsynchronousClient();
        asyncClient.StartClient(hitUrls, AsynchronousClient.Response.HttpCode);

        Console.WriteLine("Press enter key to exit...");
        Console.ReadLine();

        return 0;
    }
}

// State object for receiving data from remote device.
public class StateObject
{
    // Client socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder responseContent = new StringBuilder();
    public string responseHeaders = string.Empty;
}

public class AsynchronousClient
{

    public enum Response
    {
        HttpCode = 0,
        HttpContent = 1
    }
    // Indicates whether you want the full server response (headers + content) or only the http response code (200, etc.)
    private Response responseType { get; set; }

    // ManualResetEvent instances signal completion.
    private ManualResetEvent connectDone = new ManualResetEvent(false);
    private ManualResetEvent sendDone = new ManualResetEvent(false);
    private ManualResetEvent receiveDone = new ManualResetEvent(false);

    // The response from the remote device.
    private String response = String.Empty;
    private StringBuilder httpResponseCode = new StringBuilder();
    private StringBuilder httpResponseContent = new StringBuilder();

    private Dictionary<string, string> responseResults;

    static IPHostEntry ipHostInfo = null;
    static IPAddress ipAddress = null;
    static IPEndPoint remoteEP = null;
    static Socket client = null;
    static Uri uri = null;


    private static Uri getUrl(string url)
    {
        Uri uri = null;
        try
        {
            uri = new Uri(url);
        }
        catch (Exception e)
        {
            throw e;
        }
        return uri;
    }

    public void StartClient(IEnumerable urls, Response responseType)
    {
        this.responseType = responseType;
        responseResults = new Dictionary<string, string>();
        try
        {
            IEnumerator urlEnumerator = urls.GetEnumerator();
            string currentUrl = string.Empty;
            while (urlEnumerator.MoveNext())
            {
                connectDone.Reset();                     
                currentUrl = urlEnumerator.Current.ToString();
                uri = getUrl(currentUrl);


                ipHostInfo = Dns.GetHostEntry(uri.Host);
                ipAddress = ipHostInfo.AddressList[0];
                remoteEP = new IPEndPoint(ipAddress, uri.Port);

                client = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);

                client.BeginConnect(remoteEP,
                        new AsyncCallback(ConnectCallback), client);
                connectDone.WaitOne();

                Send(client, "GET " + uri.AbsolutePath + " HTTP/1.1\r\n Host:" + uri.Host + "\r\n\r\n");
                sendDone.WaitOne();

                Receive(client);
                receiveDone.WaitOne();

                Thread.Sleep(500);
                responseResults.Add(currentUrl, responseType == Response.HttpContent ?
                                                httpResponseContent.ToString() :
                                                httpResponseCode.ToString());

                // Release the socket if this is a request for all content
                if (responseType == Response.HttpContent)
                {
                    client.Shutdown(SocketShutdown.Both);
                    client.Close();
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            // Complete the connection.
            client.EndConnect(ar);

            Console.WriteLine("Socket connected to {0}",
                client.RemoteEndPoint.ToString());

            // Signal that the connection has been made.
            connectDone.Set();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private void Receive(Socket client)
    {
        try
        {
            // Create the state object.
            StateObject state = new StateObject();
            state.workSocket = client;

            client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                GetCallbackType(), state);

        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private void ReceiveCallbackContent(IAsyncResult ar)
    {
        try
        {
            StateObject state = (StateObject)ar.AsyncState;
            Socket client = state.workSocket;

            int bytesRead = 0;
            if (client.Connected)
            {
                bytesRead = client.EndReceive(ar);
            }

            //if (bytesRead > 0)
            if (bytesRead == state.buffer.Length)
            {
                // There might be more data, so store the data received so far.
                state.responseContent.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));

                // Get the rest of the data.
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallbackContent), state);
            }
            else
            {
                // All the data has arrived; put it in response.
                if (state.responseContent.Length > 1)
                {
                    state.responseContent.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    httpResponseContent = new StringBuilder();
                    httpResponseContent.Append(state.responseContent);
                    Console.WriteLine(httpResponseContent.ToString());
                }
                // Signal that all bytes have been received.
                receiveDone.Set();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private void ReceiveCallbackHeaders(IAsyncResult ar)
    {
        try
        {
            StateObject state = (StateObject)ar.AsyncState;
            Socket client = state.workSocket;

            int bytesRead = 0;
            if (client.Connected)
            {
                bytesRead = client.EndReceive(ar);
            }

            if (bytesRead > 0)
            {
                state.responseContent.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));

                if (client.Connected)//just to be safe
                {
                    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                        new AsyncCallback(ReceiveCallbackHeaders), state);
                }

                System.Text.RegularExpressions.MatchCollection matches = 
                    new System.Text.RegularExpressions.Regex("[^\r\n]+").Matches(state.responseContent.ToString()
                                                                        .Substring(0, state.responseContent.ToString()
                                                                                                            .IndexOf("\r\n\r\n"))
                                                                                                            .TrimEnd('\r', '\n'));
                httpResponseCode = new StringBuilder();
                for (int n = 0; n < matches.Count; n++)                                                                        
                {
                    httpResponseCode.Append(matches[n].Value.ToString() + "\r\n");
                }

                //Buffer is allocated to 1024, we should have headers + some content
                //should be able to safely end requests to this url if you just want headers
                receiveDone.Set();
                client.Shutdown(SocketShutdown.Both);
                client.Disconnect(false);
            }
            else
            {
                // All the data has arrived; put it in response.
                if (state.responseContent.Length > 1)
                {
                    response = state.responseContent.ToString();
                }
                // Signal that all bytes have been received.
                receiveDone.Set();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }

    }

    private AsyncCallback GetCallbackType()
    {
        if (responseType == Response.HttpContent)
            return new AsyncCallback(ReceiveCallbackContent);

        return new AsyncCallback(ReceiveCallbackHeaders);
    }

    private void Send(Socket client, String data)
    {
        // Convert the string data to byte data using ASCII encoding.
        byte[] byteData = Encoding.ASCII.GetBytes(data);

        // Begin sending the data to the remote device.
        client.BeginSend(byteData, 0, byteData.Length, 0,
            new AsyncCallback(SendCallback), client);
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            int bytesSent = 0;
            if (client.Connected)
                bytesSent = client.EndSend(ar);

            Console.WriteLine("Sent {0} bytes to server.", bytesSent);

            // Signal that all bytes have been sent.
            sendDone.Set();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

}