Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Bi-directional HTTP Connection

Rate me:
Please Sign up or sign in to vote.
5.00/5 (17 votes)
22 Jan 2004CPOL4 min read 107.9K   865   86   11
An article about a bi-directional communication using a single open connection.

Abstract

Implementing a peer to peer application requires either end to be set up as both a client and a server. The simplest approach would be to let the parties connect to each other at their respective end-points. But this approach does not usually work if the parties are separated by a fire-wall where the network administrator is usually reluctant to permit outgoing connections. So, maintaining and reusing an established connection for bi-directional traffic is the only option.

The essential concept

To accomplish this, we must distinguish between an incoming request and an incoming response, explained like this. In one case, we expect the other party to send us a request to which we simply return a response, and in the other case it is we who shall send a request and wait for the other party to send us a response. We shall make this distinction with the help of out-of-band information that we can pass along, employing the HTTP protocol.

So, we shall communicate peer to peer using HTTP messages. Once the listening socket accepts a connection, we will assume and completely receive an HTTP message which may then be easily identified and processed as either a request or a response. Here is some code to illustrate the basic idea:

C#
// create and start the listener
TcpListener listener = new TcpListener(7070);
listener.Start();

// wait and accept a client connection
Socket socket = listener.AcceptSocket();

// create an HTTP message object and receive the HTTP message
HttpMessage msg = new HttpMessage();
msg.Receive(_socket);

// process the HTTP message as either a request or a response
if(msg.IsResponse)
  ProcessResponse(msg);
else
  ProcessRequest(msg);  

The HttpMessage object simply reads from the socket stream the essential parts of the HTTP protocol, the first line, the list of optional headers and the message body. A distinction between request and response is made by inspecting the first token in the first line. In the case of a response, the first line must start with "HTTP". That is what msg.IsResponse ascertains.

Our keenest interest is about the method ProcessResponse(msg). How do we co-relate a response to a request we have previously made? Let us examine the method of sending a request.

C#
void SendRequest(Socket socket, HttpMessage msg)
{
  // create a correlation id and assign it to the message
  msg.CorrelationID = Guid.NewGuid().ToString();

  // send the message
  msg.Send(socket);

  // retain the message for future reference in a hash table
  _requests[msg.CorrelationID] = msg;
}

The central idea is to create and attach a unique identifier to the outgoing HTTP message. We expect that this identifier also be present in the returning HTTP response message. Here is an illustration of the essential HTTP protocol exchange.

// outgoing request
GET / HTTP/1.1
Correlation-ID: 0B83745D-2AAB-4bce-8AC9-B8A590F07768

// incoming response
HTTP/1.1 200 Ok
Correlation-ID: 0B83745D-2AAB-4bce-8AC9-B8A590F07768

We can now turn our attention to the method ProcessResponse(msg). Here is the model:

C#
void ProcessResponse(HttpMessage response)
{
  // use the correlation id to match the response to the request
  HttpMessage request = _requests[response.CorrelationID];
  
  // use the request and the response for further processing
}

A bi-directional communication manager

The central idea of the bi-directional communication has now been described. Let us proceed to develop a module that we can practically deploy. What we want is a class that can manage the details of a bi-directional communication and that we may deploy like so:

C#
// create and start the listener
TcpListener listener = new TcpListener(7070);
listener.Start();

while(true)
{
  // wait for and accept a client connection
  Socket socket = listener.AcceptSocket();
  
  // create the connection object that will
  // manage the bi-directional communication
  Connection conn = new Connection(socket);
  
  // spawn a worker thread for this connection
  // and loop back to accept another client
  new Thread( new ThreadStart(conn.ThreadProc) ).Start();
}

The class Connection is responsible for managing the bi-directional communication. We pass to it the accepted socket and rely upon the connection object to use that same socket for sending and receiving HTTP messages. In order to wait for and receive additional connections, we spawn a worker thread to manage the established connection. Here is the connection's thread procedure:

C#
// the connection object's thread procedure
void ThreadProc()
{
  while(Continue())
  {
    // create and receive the HTTP message
    HttpMessage msg = new HttpMessage();
    msg.Receive(_socket);
    
    // process the message as either a response or request
    if(msg.IsResponse)
      ProcessResponse(msg);
    else
      ProcessRequest(msg);
  }
}

The code inside the thread procedure should be familiar. Let us re-examine the method of sending a request. We would like to send a request and synchronously wait for the response like so:

C#
HttpMessage request = new HttpMessage();
request.Verb = "GET";
request.RequestUri = "/";
request.Version = "HTTP/1.1";

HttpMessage response = conn.SendMessage(request);

This implies that the method SendMessage(request) must wait until the response has been received. We need a way to signal the arrival of the response. The best approach to this problem is to implement the complementary asynch methods, BeginSendMessage and EndSendMessage.

C#
public IAsyncResult BeginSendMessage(HttpMessage request)
{
  // create a correlation id and assign it to the message
  request.CorrelationID = Guid.NewGuid().ToString();

  // send the request on the assigned socket
  request.Send(_socket);
  
  // create the waitable object
  IAsyncResult async = new HttpAsyncResult();
  
  // map the correlation id to the waitable object
  _requests[request.CorrelationID] = async;
  return async;
}

public HttpMessage EndSendMessage(IAsyncResult async)
{
  // wait until the signal is set, e.g the response has been received
  if(!async.IsCompleted)
    async.AsyncWaitHandle.WaitOne();

  // get the matching response
  HttpMessage response = (HttpMessage)_requests[async];
  
  // clear the requests table
  _requests.Remove(async);
  
  // return the response
  return response;
}

Before discussing this code, let us show how the synchronous version is implemented. It is very simple.

C#
public HttpResponse SendRequest(HttpRequest request)
{
  IAsyncResult async = BeginRequest(request);
  return EndRequest(async); 
}

Let's discuss the asynchronous version. We are mapping the correlation ID of the outgoing message to a waitable object that implements the IAsyncResult interface. Obviously, the waitable object will need to be set, the moment the response to the outgoing message arrives. That must happen inside the ProcessResponse method. Here is its implementation:

C#
void ProcessResponse(HttpMessage response)
{
  // use the correlation id to get the waitable object
  HttpAsyncResult async = (HttpAsyncResult)_requests[response.CorrelationID];
  
  // remove the correlation id from the requests table
  _requests.Remove(response.CorrelationID);
  
  // map the waitable object to the response
  _requests[async] = response;
  
  // set the signal so that the response becomes availabe
  async.Set();
}

You need to closely compare the methods EndSendMessage and ProcessResponse. It is understood that once we have send a request, we must wait until the response has arrived.

Now, let's turn our attention to the case where we need to process an incoming request, as per ProcessRequest. Our Connection here is firstly about managing a bi-directional communication. So, it only makes sense to delegate the processing of the HTTP request to some external agent. We can best accomplish it by defining an appropriate delegate: public delegate HttpMessage ProcessRequestDelegate(HttpMessage request);. So, here is the simple implementation of the PrecessRequest method.

C#
// delegate member
public ProcessRequestDelegate DelegateRequest;

// private method
void ProcessRequest(HttpMessage request)
{
  // let an external agent process the request
  HttpMessage response = DelegateRequest(request);
  
  // Take the response and set the correlation id
  response.CorrelationID = request.CorrelationID;
  
  // now send the response on the assigned socket
  response.Send(_socket);
}

We just want to be sure that request processing is accomplished in parallel. We cannot afford to wait as we seek to be ready for any incoming HTTP message. Let us therefore refine the ProcessRequest method.

C#
// temporary queue for storing the request
Queue _queue = Queue.Synchronized( new Queue() );

void ProcessRequest(HttpMessage request)
{
  // store request temporarily in a queue
  _queue.Enqueue(request);
  new Thread( new ThreadStart(this.ProcessRequestThreadProc) ).Start();
}

// delegate member
public ProcessRequestDelegate DelegateRequest;

// private method
void ProcessRequestThreadProc()
{
  // get request from temporary queue
  HttpMessage request = (HttpMessage)_queue.Dequeue();
  
  // let an external agent process the request
  HttpMessage response = DelegateRequest(request);
  
  // Take the response and set the correlation id
  response.CorrelationID = request.CorrelationID;
  
  // now send the response on the assigned socket
  response.Send(_socket);
}

Using the source code

You may download the source and try it out. The zip file contains a Visual Studio 2003 solution. The connection manager rests in a separate assembly, 'Connection'. In addition, there is a 'client' and a 'server' project for you to try.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I am a consultant, trainer, software archtect/engineer, since the early 1980s, working in the greater area of Boston, MA, USA.

My work comprises the entire spectrum of software, shrink-wrapped applications, IT client-server, systems and protocol related work, compilers and operating systems, and more ....

I am currently focused on platform development for distributed computing in service oriented data centers.

Comments and Discussions

 
QuestionHow can one contact Wytek Szymanski, the author of this arcticle? Pin
YossiMimon2-May-08 0:03
YossiMimon2-May-08 0:03 
GeneralProblem with Location Header Pin
tothm5-Dec-06 22:48
tothm5-Dec-06 22:48 
Hi!

If you have a http response code 302 you get a http header "Location" which has the value of the url you should go.

this won't work with the HttpMessage.cs Header extraction method.

Yours:
// read all the headers<br />
String[] tokens;<br />
line = reader.ReadLine();<br />
while(line != null && line != String.Empty) {<br />
	tokens = line.Split(':'); // location header ex: http://goto.this.url/xxx.xx<br />
	String name = tokens[0].Trim();<br />
	Headers[name] = tokens[1].Trim(); // you get only the "http" string<br />
	line = reader.ReadLine();<br />
}


Better:
// read all the headers<br />
String[] tokens;<br />
char[] separatorcolon = new char[] { ':' };<br />
line = reader.ReadLine();<br />
while (line != null && line != String.Empty)<br />
{<br />
          tokens = line.Split(separatorcolon, 2, StringSplitOptions.RemoveEmptyEntries);<br />
          if (tokens != null && tokens.Length == 2)<br />
          {<br />
              String name = tokens[0].Trim();<br />
              SetHeader(name, tokens[1].Trim());<br />
          }<br />
	line = reader.ReadLine();<br />
}

GeneralGracefully close Pin
Hennoj3-Jan-06 20:41
Hennoj3-Jan-06 20:41 
GeneralProblem in creating a socket behind a proxy server in C# .net Pin
Nachiket K. Tarate24-Aug-05 20:45
Nachiket K. Tarate24-Aug-05 20:45 
GeneralQuestion on sending data Pin
DeepToot12-Jul-05 12:12
DeepToot12-Jul-05 12:12 
GeneralServer behind the firewall Pin
zoomba23-Jul-04 10:56
zoomba23-Jul-04 10:56 
GeneralRe: Server behind the firewall Pin
Wytek Szymanski24-Jul-04 3:58
Wytek Szymanski24-Jul-04 3:58 
GeneralRe: Server behind the firewall Pin
zoomba25-Jul-04 18:24
zoomba25-Jul-04 18:24 
GeneralRe: Server behind the firewall Pin
Wytek Szymanski26-Jul-04 17:25
Wytek Szymanski26-Jul-04 17:25 
GeneralWeb server implementation Pin
koolb3-Jun-04 2:59
koolb3-Jun-04 2:59 
GeneralRe: Web server implementation Pin
ShyAlon20-Oct-04 22:26
ShyAlon20-Oct-04 22:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.