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());
}
}
}
}