Sample Programs Using Asynchronous Sockets



Sample Programs Using Asynchronous Sockets

Now that you have studied all the pieces, it’s time to put them together and create a set of real Windows asynchronous network programs. The following two programs recreate the SimpleTcpSrvr and SimpleTcpClient programs (Listings 5. 1 and 5.2, respectively) introduced in Chapter 5, "Connection-Oriented Sockets," but this time they use asynchronous network methods.

The Client Program

The AsyncTcpClient.cs program (Listing 8.2) uses the .NET Windows Forms library to create a Windows GUI environment for a simple TCP client. In a Windows environment, it is important that the program respond to Windows events from the user, as well as from the network

Listing 8.2: The AsyncTcpClient.cs program
Start example
using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
class AsyncTcpClient Form:
{
  private TextBox newText;
  private TextBox conStatus;
  private ListBox results;
  private Socket client;
  private byte[] data = new byte[1024];
  private int size = 1024;
  public AsyncTcpClient()
  {
   Text = "Asynchronous TCP Client";
   Size = new Size(400, 380);
   
   Label label1 = new Label();
   label1.Parent = this;
   label1.Text = "Enter text string:";
   label1.AutoSize = true;
   label1.Location = new Point(10, 30);
   newText = new TextBox();
   newText.Parent = this;
   newText.Size = new Size(200, 2 * Font.Height);
   newText.Location = new Point(10, 55);
   results = new ListBox();
   results.Parent = this;
   results.Location = new Point(10, 85);
   results.Size = new Size(360, 18 * Font.Height);
   Label label2 = new Label();
   label2.Parent = this;
   label2.Text = "Connection Status:";
   label2.AutoSize = true;
   label2.Location = new Point(10, 330);
   conStatus = new TextBox();
   conStatus.Parent = this;
   conStatus.Text = "Disconnected";
   conStatus.Size = new Size(200, 2 * Font.Height);
   conStatus.Location = new Point(110, 325);
   Button sendit = new Button();
   sendit.Parent = this;
   sendit.Text = "Send";
   sendit.Location = new Point(220,52);
   sendit.Size = new Size(5 * Font.Height, 2 * Font.Height);
   sendit.Click += new EventHandler(ButtonSendOnClick);
   Button connect = new Button();
   connect.Parent = this;
   connect.Text = "Connect";
   connect.Location = new Point(295, 20);
   connect.Size = new Size(6 * Font.Height, 2 * Font.Height);
   connect.Click += new EventHandler(ButtonConnectOnClick);
   Button discon = new Button();
   discon.Parent = this;
   discon.Text = "Disconnect";
   discon.Location = new Point(295,52);
   discon.Size = new Size(6 * Font.Height, 2 * Font.Height);
   discon.Click += new EventHandler(ButtonDisconOnClick);
  }
  void ButtonConnectOnClick(object obj, EventArgs ea)
  {
   conStatus.Text = "Connecting...";
   Socket newsock = new Socket(AddressFamily.InterNetwork,
              SocketType.Stream, ProtocolType.Tcp);
   IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);
   newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
  }
  void ButtonSendOnClick(object obj, EventArgs ea)
  {
   byte[] message = Encoding.ASCII.GetBytes(newText.Text);
   newText.Clear();
   client.BeginSend(message, 0, message.Length, SocketFlags.None,
          new AsyncCallback(SendData), client);
  }
  void ButtonDisconOnClick(object obj, EventArgs ea)
  {
   client.Close();
   conStatus.Text = "Disconnected";
  }
  void Connected(IAsyncResult iar)
  {
   client = (Socket)iar.AsyncState;
   try
   {
     client.EndConnect(iar);
     conStatus.Text = "Connected to: " + client.RemoteEndPoint.ToString();
     client.BeginReceive(data, 0, size, SocketFlags.None,
            new AsyncCallback(ReceiveData), client);
   } catch (SocketException)
   {
     conStatus.Text = "Error connecting";
   }
  }
  void ReceiveData(IAsyncResult iar)
  {
   Socket remote = (Socket)iar.AsyncState;
   int recv = remote.EndReceive(iar);
   string stringData = Encoding.ASCII.GetString(data, 0, recv);
   results.Items.Add(stringData);
  }
  void SendData(IAsyncResult iar)
  {
   Socket remote = (Socket)iar.AsyncState;
   int sent = remote.EndSend(iar);
   remote.BeginReceive(data, 0, size, SocketFlags.None,
          new AsyncCallback(ReceiveData), remote);
  }
  public static void Main()
  {
   Application.Run(new AsyncTcpClient());
  }
}
End example

The class constructor creates the window objects for the program. For this simple client program, the following objects are created:

  • A Label object and a TextBox object; these allow the customer to input text messages to send to the remote host

  • A ListBox object to display the messages returned by the remote host

  • A Label object and a TextBox object to display the connection status of the client program

  • Three Button objects; one for establishing a new TCP connection to a remote host, one for sending the text message and receiving a reply, and one for disconnecting the TCP session.

Each object is placed using the Point class and sized using the Size class; both of these classes are from the System.Drawing namespace. You can experiment with these values to get a feel for manually locating the objects on the Window form.

Note 

If you have one of the Microsoft Visual products, such as Visual Studio or Visual C#, you can place all the Window objects using the graphical editing environment and allow the Form code to be generated automatically for you.

The Client Program Flow

You may notice that there is no network programming code at all in the class constructor. All of the network functions happen within EventHandler() methods. To get the AsyncTcpClient program to perform the same way as the original SimpleTcpClient program, you must first determine how to link the program protocol with the asynchronous events that will be triggered in the program. Then you must program them into the Windows event code methods. The diagram in Figure shows each of the required methods and how they interact with the Windows event code. The following paragraphs describe the steps accomplished by the sample client program: connecting, receiving, sending, and disconnecting.

Click To expand
Figure: Diagramming the asynchronous events
Connecting

As seen in the figure, when the customer clicks the Connect button, the method used to start the connection is performed. In the program code, this is the ButtonConnectOnClick() method:

void ButtonConnectOnClick(object obj, EventArgs ea)
{
  conStatus.Text = "Connecting...";
  Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
   ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);
  newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
}

The ButtonConnectOnClick() method creates a new Socket object for the communication. Then it starts the BeginConnect() method with the address information of the remote host, the name of the associated AsyncCallback method, and the newly created Socket object. When this method finishes processing, Windows waits for the BeginConnect() method to trigger its event, which indicates that a connection is established with the remote host. When this occurs, program control is passed to the Connected() method:

void Connected(IAsyncResult iar)
{
  client = (Socket)iar.AsyncState;
  try
  {
   client.EndConnect(iar);
   conStatus.Text = "Connected to: " + client.RemoteEndPoint.ToString();
   client.BeginReceive(data, 0, size, SocketFlags.None,
    new AsyncCallback(ReceiveData), client);
  } catch (SocketException)
  {
   conStatus.Text = "Error connecting";
  }
}

The first thing that must be done in the Connected() method is to retrieve the original Socket object used for the connection. The AsyncState property of the IAsyncResult class returns the object passed to the AsyncCallback method, which in the case of the BeginConnect() method was the newly created Socket object.

After the Socket object is recreated, the EndConnect() method can be performed. Because it is possible that the EndConnect() method could fail (for instance, if the remote host is unavailable), it is a good idea to place it in a try-catch block. Should EndConnect() fail, you can notify the customer through the conStatus TextBox object.

In this program protocol, the first thing the server does after the connection is established is to send a welcome banner. To accommodate this, the AsyncTcpClient program must be prepared to accept an incoming message immediately after establishing the connection. This is done by using a BeginReceive() method call at the end of the Connected() method.

Receiving Data

After a new connection, and after every sent message, a ReceiveData() method is performed. The BeginReceive() method declares the ReceiveData() method, the AsyncCallback method that’s used when the data is received:

void ReceiveData(IAsyncResult iar)
{
  Socket remote = (Socket)iar.AsyncState;
  int recv = remote.EndReceive(iar);
  string stringData = Encoding.ASCII.GetString(data, 0, recv);
  results.Items.Add(stringData);
}

Again, the first statement recreates the communication socket. After the original socket is recreated, the EndReceive() method is used, referencing the original IAsyncResult object, which pairs it with the original BeginReceive() method call. When the message is received from the remote host, it is placed in the data buffer referenced in the BeginReceive() method call. Because the data variable was defined as a global variable, it is used within this method to place the message in the results ListBox.

Sending Data

After entering a message in the TextBox object to send to the remote host, the customer clicks the Send button, and the SendData() method is performed. The EventHandler for the button points to the ButtonSendOnClick() method:

void ButtonSendOnClick(object obj, EventArgs ea)
{
  byte[] message = Encoding.ASCII.GetBytes(newText.Text);
  newText.Clear();
  client.BeginSend(message, 0, message.Length, SocketFlags.None,
   new AsyncCallback(SendData), client);
}

The message is extracted from the TextBox object, converted to a byte array, and sent out the socket. Because the ButtonSendOnClick() method does not have the original Socket object passed to it, it needs to use a class member to reference the connected socket. The BeginSend() method is used on the connected socket. It specifies the message and the AsyncCallback method to call when the BeginSend() function is ready to complete, along with an object to pass to the EndSend() method. In this case, it is the connected socket.

The AsyncCallback method, SendData(), is triggered when the socket indicates it is ready to send the message:

void SendData(IAsyncResult iar)
{
  Socket remote = (Socket)iar.AsyncState;
  int sent = remote.EndSend(iar);
  remote.BeginReceive(data, 0, size, SocketFlags.None,
   new AsyncCallback(ReceiveData), remote);
}

Once again, you see the familiar behavior: the original connected socket is recreated using the AsyncState of the state object passed from the BeginSend() method. When the original socket is recreated, the EndSend() method completes the data transmission. The IAsyncResult object pairs the EndSend()to the original BeginSend() and EndSend()returns the number of bytes that were actually sent out from the socket.

In this application, after a message is sent, the server is expected to echo it back. Knowing this, the BeginReceive() method is called to start the receiving process. BeginReceive()again calls the same ReadData() method, just as it did to receive the original connection’s welcome banner.

Disconnecting

Now that you have the wonders of event programming at your disposal, you can finally create a button to control the disconnect from the remote host (rather than having to define a control word to stop the client). Here’s how that works:

void ButtonDisconOnClick(object obj, EventArgs ea)
{
  client.Close();
  conStatus.Text = "Disconnected";
}

By issuing the Close() method for the client socket, the connection is disconnected. To establish a new connection, the customer can click the Connect button.

Warning 

There are lots of things that can go wrong with this simple client example, including the customer’s clicking the wrong button at the wrong time. A real-world production application should check each time a button is clicked to ensure that the function can be performed.

Testing the Asynchronous Client

Because the AsyncTcpClient program followed the same protocol model as the SimpleTcpClient program in Chapter 5, you can use the SimpleTcpSrvr program to test the new asynchronous version.

  1. Start SimpleTcpSrvr in a command-prompt window on the appropriate server.

  2. Start the AsyncTcpClient program, either in a separate command prompt window or on another network device.

  3. Click the Connect button. The status window should show that the client has connected to the server, and the server’s welcome banner should be echoed back to the list box, as shown in Figure.

    Click To expand
    Figure: The AsyncTcpClient program, with server’s welcome banner showing in the list box

  4. After the original connection, try sending messages from the text box to the server program by clicking the Send button. Each message will be displayed in the SimpleTcpSrvr console on the server and echoed in the list box in the AsyncTcpClient program.

  5. To stop the connection, click the Disconnect button. The SimpleTcpSrvr program will properly terminate, and the status text box in the AsyncTcpClient program will show that the client is disconnected from the server.

The Server Program

Now that you have moved the client program into the Windows world, it is time to tackle the server program. Listing 8.3 shows the AsyncTcpSrvr.cs program, which mimics the functionality of the SimpleTcpSrvr program (Listing 5.1) from Chapter 5 but uses a Windows Forms environment and asynchronous sockets.

Listing 8.3: The AsyncTcpSrvr.cs program
Start example
using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
class AsyncTcpSrvr    Form:
{
  private TextBox conStatus;
  private ListBox results;
  private byte[] data = new byte[1024];
  private int size = 1024;
  private Socket server;
  public AsyncTcpSrvr()
  {
   Text = "Asynchronous TCP Server";
   Size = new Size(400, 380);
   results = new ListBox();
   results.Parent = this;
   results.Location = new Point(10, 65);
   results.Size = new Size(350, 20 * Font.Height);
   Label label1 = new Label();
   label1.Parent = this;
   label1.Text = "Text received from client:";
   label1.AutoSize = true;
   label1.Location = new Point(10, 45);
   Label label2 = new Label();
   label2.Parent = this;
   label2.Text = "Connection Status:";
   label2.AutoSize = true;
   label2.Location = new Point(10, 330);
   conStatus = new TextBox();
   conStatus.Parent = this;
   conStatus.Text = "Waiting for client...";
   conStatus.Size = new Size(200, 2 * Font.Height);
   conStatus.Location = new Point(110, 325);
   Button stopServer = new Button();
   stopServer.Parent = this;
   stopServer.Text = "Stop Server";
   stopServer.Location = new Point(260,32);
   stopServer.Size = new Size(7 * Font.Height, 2 * Font.Height);
   stopServer.Click += new EventHandler(ButtonStopOnClick);
   server = new Socket(AddressFamily.InterNetwork,
          SocketType.Stream, ProtocolType.Tcp);
   IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
   server.Bind(iep);
   server.Listen(5);
   server.BeginAccept(new AsyncCallback(AcceptConn), server);
  }
  void ButtonStopOnClick(object obj, EventArgs ea)
  {
   Close();
  }
  void AcceptConn(IAsyncResult iar)
  {
   Socket oldserver = (Socket)iar.AsyncState;
   Socket client = oldserver.EndAccept(iar);
   conStatus.Text = "Connected to: " + client.RemoteEndPoint.ToString();
   string stringData = "Welcome to my server";
   byte[] message1 = Encoding.ASCII.GetBytes(stringData);
   client.BeginSend(message1, 0, message1.Length, SocketFlags.None,
         new AsyncCallback(SendData), client);
  }
  void SendData(IAsyncResult iar)
  {
   Socket client = (Socket)iar.AsyncState;
   int sent = client.EndSend(iar);
   client.BeginReceive(data, 0, size, SocketFlags.None,
         new AsyncCallback(ReceiveData), client);
  }
  void ReceiveData(IAsyncResult iar)
  {
   Socket client = (Socket)iar.AsyncState;
   int recv = client.EndReceive(iar);
   if (recv == 0)
   {
     client.Close();
     conStatus.Text = "Waiting for client...";
     server.BeginAccept(new AsyncCallback(AcceptConn), server);
     return;
   }
   string receivedData = Encoding.ASCII.GetString(data, 0, recv);
   results.Items.Add(receivedData);
   byte[] message2 = Encoding.ASCII.GetBytes(receivedData);
   client.BeginSend(message2, 0, message2.Length, SocketFlags.None,
          new AsyncCallback(SendData), client);
  }
  public static void Main()
  {
   Application.Run(new AsyncTcpSrvr());
  }
}

Just as the client program does, the AsyncTcpSrvr program uses basic Windows Forms objects to create the customer graphical environment:

  • A Label object and a ListBox object to identify the data received from the connected client

  • A Label object and a TextBox object to display the status of the socket and the connected client

  • A Button object to allow the customer to stop the server and exit the program

After creating the customer interface, you must code the network programming part. Unlike the client program, the AsyncTcpSrvr program includes network programming code that must be done in the class constructor.

End example

The Server Program Flow

Like its counterpart SimpleTcpSrvr in Chapter 5, the AsyncTcpSrvr program must follow a set procedure for communicating with remote clients. Figure diagrams the steps required for the server.

Click To expand
Figure: The AsyncTcpSrvr processes
Waiting for New Client Connections

Because you know that the server must immediately listen for incoming connections, the required network programming code is added to the class constructor after the Forms objects have been created:

server = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
 ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
server.Bind(iep);
server.Listen(5);
server.BeginAccept(new AsyncCallback(AcceptConn), server);

By now, you should recognize this as standard server socket code: creating a new Socket object, binding it to a local IPEndPoint object, and listening for new connection attempts.

The BeginAccept() method specifies the AsyncCallback method to use when a connection is received, and the object to pass to the AsyncCallback method.

When a connection attempt is detected, the AsyncCallback method registered with the BeginAccept() method is performed:

void AcceptConn(IAsyncResult iar)
{
  Socket oldserver = (Socket)iar.AsyncState;
  Socket client = oldserver.EndAccept(iar);
  conStatus.Text = "Connected to: " + client.RemoteEndPoint.ToString();
  string stringData = "Welcome to my server";
  byte[] message1 = Encoding.ASCII.GetBytes(stringData);
  client.BeginSend(message1, 0, message1.Length, SocketFlags.None,
   new AsyncCallback(SendData), client);
}

The AsyncCallback method recreates the original Socket object using the AsyncState property of the IAsyncResult object. When the EndAccept() method is called, it uses the IAsyncResult object to pair it up with the calling BeginAccept() method. The EndAccept() method returns a new Socket object to be used for all communication with the remote client.

As reflected in the program flow diagram, the next step for the server is to send a welcome banner to the client. This requires the BeginSend() method, which registers an AsyncCallback method to complete the send operation when the socket is ready to send out data.

Warning 

Note that the Socket object passed to the AsyncCallback method is the newly created client socket, not the original server socket. This is the socket that is connected to the remote client, and it should be used for all communications with that client.

Sending Data

The EndSend() method is found in the AsyncCallback method registered in the BeginSend() method:

void SendData(IAsyncResult iar)
{
  Socket client = (Socket)iar.AsyncState;
  int sent = client.EndSend(iar);
  client.BeginReceive(data, 0, size, SocketFlags.None,
   new AsyncCallback(ReceiveData), client);
}

This sample program uses the standard EndSend() format. A good-quality production program would also compare this value with the original byte count of the sent message and perform the BeginSend() method again if some of the message were not properly sent.

The server program flow model specifies that after the welcome banner is sent, the server should wait for an incoming message from the client. This is accomplished with the BeginReceive() method, which specifies a data buffer in which to place the received data, along with the AsyncCallback method to call when the data is received.

Receiving Data

The EndReceive() method is placed in the AsyncCallback method for the BeginReceive() method:

void ReceiveData(IAsyncResult iar)
{
  Socket client = (Socket)iar.AsyncState;
  int recv = client.EndReceive(iar);
  if (recv == 0)
  {
   client.Close();
   conStatus.Text = "Waiting for client...";
   server.BeginAccept(new AsyncCallback(AcceptConn), server);
   return;
  }
  string receivedData = Encoding.ASCII.GetString(data, 0, recv);
  results.Items.Add(receivedData);
  byte[] message2 = Encoding.ASCII.GetBytes(receivedData);
  client.BeginSend(message2, 0, message2.Length, SocketFlags.None, new AsyncCallback(SendData), client);
}

The ReceiveData() method is a little lengthier than the others because it has to accommodate two scenarios in the program flow. First, as always, the client socket is recreated using the standard AsyncState property. Next, the EndReceive() method is called using the IAsyncResult object to pair it up with the appropriate BeginReceive() method. EndReceive()returns the number of bytes received from the socket. As stated earlier, a real-time production program would check the number of bytes received against an expected message size.

If the received message size is zero bytes, it is assumed that the remote client has disconnected the TCP session. In that case, the client socket is closed, and the server can start listening for a new client connection by using the BeginAccept() method and starting the whole program flow all over again.

If a message has been received from the remote client, it must be displayed in the ListBox object and echoed back to the client. This is done by using the BeginSend() method again. Because the program flow now duplicates the original flow from sending the welcome banner, the original SendData() method is used for this AsyncCallback method as well.

Testing the Programs Together

The AsyncTcpSrvr program can be tested with either the AsyncTcpClient program or the original SimpleTcpClient program from Chapter 5:

  1. Start both the AsyncTcpClient and SimpleTcpClient programs, either from command-prompt windows or from Windows Explorer.

  2. When AsyncTcpClient starts, click the Connect button. Both the server and client programs will indicate that a connection has been established and show the address of the remote host. Figure shows the output window of AsyncTcpSrvr after a client has connected.

    Click To expand
    Figure: The AsyncTcpSrvr program window

  3. Send a message from the AsyncTcpClient. Each time a message is sent using the AsyncTcpClient program, it should appear in the AsyncTcpSrvr list box and be echoed back to the AsyncClient list box.

  4. To close the connection, click the Disconnect button on the client program. Immediately after disconnecting the current client, the AsyncTcpSrvr program waits for a new client connection attempt.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows