Internet, Socket Programming in
C#.Net
The primary programming mechanism by which
two programs communicate is a socket. Even though all communication
between two programs located on two different machines takes place in terms of
IP packets at the lowest level, every operating system provides an API called sockets
to facilitate the exchange of data between two programs. For example on a
Windows operating system, there is WinSock API for low level socket
programming, and in Unix, you can use the socket library via the –lsocket
option when compiling C programs.
.Net library makes socket programming fairly simple. There are two levels of types available in .Net, one at a higher level (easier to accomplish socket related tasks) and the other at the conventional low level of detailed socket programming. The namespaces for socket programming are “System.Net” and “System.Net.Sockets”.
C# provides several useful types for simplifying internet and socket programming. These include IPAddress for IP addresses, IPHostEntry for containing a collection of IPAddresses and a corresponding HostName. Note that one host name (e.g., www.cnn.com) can have multiple IP addresses assigned to it. There is a Dns class for obtaining IP addresses from domain names (GetHostByname method), and doing a reverse DNS lookup by the Reverse method. WebClient, HttpWebRequest and HttpWebResponse classes for communicating with web servers, and TcpClient and TcpServer classes for creating general client server programs. We will examine the above Network programming capabilities through several examples.
Internet
Programming:
Example 1: Obtaining IP addresses from domain names and reverse DNS lookup.
Create a C# windows application. Name the project SocketEx1. Add a label, a text box, a button and a list box as shown below. Name the button as “btnFindIPAddress”, the text box ss “txtDomainName” and the listbox as “lbIP”. Add the following declaration in the top of the form.
using System.Net;
Type the following code in the button handler.
private void
btnFindIPAddress_Click(object sender,
System.EventArgs e)
{
string hname = txtDomainName.Text;
IPAddress
ipadd = (Dns.GetHostByName(hname)).AddressList[0];
// MessageBox.Show(ipadd.ToString());
//
there can be multiple addresses for one host
// fill the list box with all IP addresses for a given
domain name
lbIP.Items.Clear();
IPAddress
[] ipAddrList = (Dns.GetHostByName(hname)).AddressList;
foreach (IPAddress ip in
ipAddrList)
lbIP.Items.Add(ip.ToString());
}
Add another label, a text box and a button to the bottom of the form as shown below. Name the text box “txtIP” and the button as “btnReverseDnsLookup”. Type the following code in the button handler.
private void
btnReverseDnsLookup_Click(object sender,
System.EventArgs e)
{
//---reverse DNS lookup can be done by the Resolve
method---
MessageBox.Show(Dns.Resolve(txtIP.Text).HostName);
}
Build and run the above program. If you are connected to the internet, you will see an output like:
AsynchCallback:
Since the reverse DNS lookup can take considerable amount of time, it makes sense to use an asynchronous approach in making the “Dns.reverse()” call. .Net provides an excellent mechanism for asynchronous (non-blocking) calls so that the calling code does not have to block and can continue to do useful things. Once the call has been completed, the delegate passed to the asynchronous call can trigger the call to the callback function. Many socket related methods have the asynchronous capabilities built into them via the BeginMethodName and the EndMethodName calls.
Example: Add another button
next to the “Reverse DNSLookup” button. Name this button “btnReverseDnsAsynch” and give it a Text
property of “Reverse DNS Asynch”.
Add
the following declaration to the top of the form.
private AsyncCallback
MyResolveDel;
Type the following code in the form’s constructor.
MyResolveDel = new
AsyncCallback(MyResolveCallback);
Type the following code in the “reverse DNS Asynch” button’s handler.
private void btnReverseDnsAsynch_Click(object sender, System.EventArgs e)
{
object obj = new object();
Dns.BeginResolve(txtIP.Text,
MyResolveDel, obj);
// when reolve is done, it will callback MyResolveCallback
}// via the MyResolveDel
Add the following function in the Form1 class.
private void
MyResolveCallback(IAsyncResult res)
{
IPHostEntry
hostent = Dns.EndResolve(res);
MessageBox.Show(hostent.HostName);
}
WebClient Class: .Net library provides a higher level class called WebClient which is very useful for making socket connections to web servers, uploading and downloading files etc.. The following example will demonstrate how to make a socket connection to the Nasdaq web server, and request Intel’s stock price using the Get method.
Example: Add another button and a label to the above form. Name the button “btnGetIntelPrice” and the label as “lblPrice”. Write the following code in the button’s handler.
private void
btnGetIntelPrice_Click(object sender,
System.EventArgs e)
{
WebClient
wbc = new WebClient();
byte[] bytes = wbc.DownloadData
("http://quotes.nasdaq.com/Quote.dll?mode=stock&symbol=intc&quick.x=23&quick.y=10");
string str1 = new
UTF8Encoding().GetString(bytes);
int pos = str1.IndexOf("$ ");
if(pos>0)
{
string s2 = str1.Substring(pos+7, 5);
lblPrice.Text
= s2 + " : " + System.DateTime.Now.ToString();
}
}
Add the following namespace declaration to the top of the form.
using System.Text;
In the above example, the name of stock is hard coded in the URL. To create a parametrized query, the WebClinet class allows us to add parameters via the QueryString collection’s Add method. To see this kind of example, add another label, a button and a text box to the bottom left side of the form as shown in the figure below. Name the button “btnGetStockPrice”, the textbox as “txtSymbol” and the label as “LblStockPrice”. Write the following code in the “get Stock Price” button handler.
private void
btnGetStockPrice_Click(object sender,
System.EventArgs e)
{
string url =
"http://quotes.nasdaq.com/Quote.dll" ;
WebClient
wbc1 = new WebClient();
wbc1.QueryString.Add("mode","stock");
wbc1.QueryString.Add("symbol",txtSymbol.Text);
wbc1.QueryString.Add("quick.x","23");
wbc1.QueryString.Add("quick.y","10");
byte [] bytes = wbc1.DownloadData(url);
string str1 = new
UTF8Encoding().GetString(bytes);
int pos = str1.IndexOf("$ ");
if(pos>0)
{
string s2 = str1.Substring(pos+7, 5);
lblStockPrice.Text
= s2 + " : " + System.DateTime.Now.ToString();
}
}
Build and test the application.
Examining Response Headers and Content: While WebClient class is very useful in connecting to a web server and exchanging information i.e., downloading or uploading a file or content, sometimes we need to examine the response headers to find out the “Content-Type” or “last-Modified” date or “Content-Length” etc.. For these type of tasks, the WebRequest and WebResponse classes are better suited. The WebRequest class allows us to make a request to a web site. The response is stored in a WebResponse type of object from where we can examine the content and the headers in a straight forward manner.
Example: Add two text boxes and a button to the previous form. Name the button “btnWebResponse” with a text property of “Web Response and Headers”. The two text boxes should have their Multilane property set to true and Scrollbars property to both. Name the first text box as txtHeader and the second one as txtContent. Type the following code in the button handler.
private void
btnWebResponse_Click(object sender,
System.EventArgs e)
{
HttpWebRequest
webReq =
(HttpWebRequest)WebRequest.Create("http://kiwi.bridgeport.edu/cs400");
webReq.Referer="http://www.bridgeport.edu"; //optional
HttpWebResponse
webResp =(HttpWebResponse)webReq.GetResponse();
// opens URL and gets the response.
// Now you can examine the content as well as headers
Stream
strResp = webResp.GetResponseStream(); // binary stream
StreamReader
strRead = new StreamReader(strResp); // Text stream
Char[]
buff = new Char[256];
StringBuilder
pageContent = new StringBuilder();
int cnt = strRead.Read(buff, 0, 256 );
while (cnt > 0)
{
pageContent.Append(new String(buff, 0, cnt));
cnt =
strRead.Read(buff, 0, 256);
}
txtContent.Text
= pageContent.ToString();
txtRespHeaders.Text
= "Number of Response Headers=" + webResp.Headers.Count.ToString();
txtRespHeaders.Text
+= webResp.Headers.ToString();
MessageBox.Show(webResp.Headers["Content-Type"].ToString());
strRead.Close();
strResp.Close();
// Release the response object resources.
webResp.Close();
}
Add the following namespace declaration to the top of the form.
using System.IO;
Socket
Programming:
There are two kinds of sockets that need to be created in implementing client server programs. One is referred to as a server socket and the other a client socket. Since it is possible that a large amount of data (requiring more than one IP packet) may need to be transferred from one program to the other, TCP is used in the actual transfer of data from one program to the other. Thus in a client server type of environment, often the server program is referred to as the TCP server and the client as TCP client. Or in other words, you can think of the socket mechanism as a higher level of abstraction of the TCP layer. The socket API has a number of steps that need to be followed in order to create the TCP server and the TCP client. These are shown in the following diagram.
TCP Client Program TCP Server Program
create a socket create
a socket
connect to an IP address bind to an IP address
and port number and
port number
send data listen
recv data accept
recv data
send data
Socket class in .Net (available in the System.Net.Sockets namespace) is used to make TCP clients. Its constructor identifies the TCP server (by its domain name or IP address) and the port number on which to connect to.
Example: Build a windows application that connects to the port 43 (Whois port on the internic server to find out if a given domain is registered or not and if so information about it).
Create a new Windows C# project. Name the project SocketEx2. Add a label, a text box, a button and another text box with multilane property set to true, as shown below. Name the top text box as “txtDomain”, the button as “btnWhoisInternic”, and the last text box that is in multilane mode as “txtResults”. Give a text property of “Find out from Internic Whois Port”. The label has a text property of “Domain Name”.
Add the following namespaces to the top of the form code.
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
Type the following code in the button’s handler.
private void
btnWhoisInternic_Click(object sender,
System.EventArgs e)
{
try
{
TcpClient
client = new TcpClient();
client.Connect("internic.net",
43);
Stream
inoutStr = client.GetStream();
// socket is bidirectional, binary stream
String
domainName = txtDomain.Text;
byte [] sendBuff =
ASCIIEncoding.ASCII.GetBytes(domainName+"\n");
// "\n" is how the server will know to start
responding
inoutStr.Write(sendBuff,0,sendBuff.Length);
StreamReader
strRead = new StreamReader(inoutStr); // Text stream
Char[]
recvBuff = new Char[256];
StringBuilder
recvData = new StringBuilder();
int cnt = strRead.Read(recvBuff, 0, 256 );
while (cnt > 0)
{
recvData.Append(new String(recvBuff, 0, cnt));
cnt = strRead.Read(recvBuff, 0, 256);
}
txtResults.Text
= recvData.ToString();
inoutStr.Close();
client.Close();
strRead.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Build and test the program, your output will look as.
Example: If you modify the above program to connect to a web server (most web servers are listening on port 80), then by sending the following string:
“GET /default.htm HTTP/1.0 \n\n” to the socket that is connected to the web server, you will get the page back on the socket. Add another button to the form with a name of “btnClientSocketKiwi” and a text property of “Client Socket to Kiwi”. Write the following code in the button’s handler.
private void
btnClientSocketKiwi_Click(object sender,
System.EventArgs e)
{
try
{
TcpClient
client = new TcpClient();
client.Connect("kiwi.bridgeport.edu",
80);
Stream
inoutStr = client.GetStream();
// socket is bidirectional, binary stream
byte []
sendBuff = ASCIIEncoding.ASCII.GetBytes("GET /cs400/default.htm
HTTP/1.0\n\n");
// "\n\n" is important now for the server to
start responding
inoutStr.Write(sendBuff,0,sendBuff.Length);
StreamReader
strRead = new StreamReader(inoutStr); // Text stream
Char[]
recvBuff = new Char[256];
StringBuilder
recvData = new StringBuilder();
int cnt = strRead.Read(recvBuff, 0, 256 );
while (cnt > 0)
{
recvData.Append(new String(recvBuff, 0, cnt));
cnt
= strRead.Read(recvBuff, 0, 256);
}
txtResults.Text
= recvData.ToString();
/* the following code will also work
StreamReader
inStr = new StreamReader(inoutStr); // text stream for input reading
StringBuilder
recvData = new StringBuilder();
string
line = inStr.ReadLine();
while
(line != null)
{
recvData.Append(line);
line
= inStr.ReadLine();
}
txtResults.Text
= recvData.ToString();
*/
inoutStr.Close();
client.Close();
strRead.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
In the previous examples, we used the TcpClient class to make a socket connection to the server. .Net library also provides the low level Socket class that implements the Berkeley sockets. We will use a plain socket to connect to a server in our next example. In some situations the client may take a while to read the data from a socket. In such cases, the socket reading code can be invoked in a separate thread. Further the .Net delegate mechanism can be used to have the thread (reading from socket) call a callback function once the data has been read by the thread. The following example illustrates the use of client socket to contact a Daytime server on port 13, and also the related thread creation and callback once the data from the socket has been read.
Example: Add a label, a text box and a button to the form as shown below. The textbox has a name of “txtHostname”, and the button name is “btnContactDayTimeServer”. The label has a text property of “Host Name”, and the button’s text property is “Contact Day Time Server”.
Add another class to the SocketEx2 project by right clicking on the project name and choosing “Add New Item”. Select the item to be C# class with a name of MyDayTime.cs.
Type the following code for the MyDayTime class.
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;
namespace SocketEx2
{
public class
MyDayTime
{
Socket
mSockdt; //
client socket for day time server
public string msDaytime="";
CallBackDel
mcbDel;
public MyDayTime(string
hostname, CallBackDel mc)
{
try
{
IPAddress
ipadd = (Dns.GetHostByName(hostname)).AddressList[0];
System.Net.IPEndPoint
ipepdt = new System.Net.IPEndPoint(ipadd,13); // daytime port
mSockdt =
new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
mSockdt.Connect(ipepdt);
mcbDel =
mc; // now
mcbDel is pointing to the callback in form1
}
catch(Exception e)
{
throw new
Exception("Error: "+e.Message);
}
}
public void
GetDayTime() //
will be called in a separate thread
{ // reads the data from socket
try
{
byte [] buff = new byte[256]; // Day time server sends one line
mSockdt.Receive(buff);
mSockdt.Close();
lock (this)
{
msDaytime
= Encoding.ASCII.GetString(buff);
}
mcbDel(); // call the
callback function in form1 so that it can get } // dtat
obtained by this thread from the socket
catch(Exception e)
{
throw new Exception(e.Message);
}
}
}
}
Add the following declarations to the top of form1 class (shown in bold).
using
System.Threading;
…
namespace SocketEx2
{
public delegate void
CallBackDel();
public class Form1 : System.Windows.Forms.Form
{
private
CallBackDel mcb; // delegate mcb of CallBackDel type
MyDayTime dt;
Type the following code for the “Contact Day Time Server” button’s handler.
private void
btnContactDayTimeServer_Click(object sender,
System.EventArgs e)
{
try
{
mcb = new CallBackDel(this.CallBackFromThread);
dt = new MyDayTime(txtHostname.Text, mcb);
Thread t1
= new Thread(new
ThreadStart(dt.GetDayTime));
t1.Start(); // thread will
call back on the function in this
//
form once it gets the data
//Thread.Sleep(5000);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void
CallBackFromThread() // thread will call back this function
{ //
once it has read all the data from the socket
txtResults.Text
= dt.msDaytime;
}
Build and test the application.
Note: A socket is a two way communication mechanism. If you contact a TCP server using a buffered stream, you need to flush the data that is to be written to the output stream.
Example: Contacting an echo server on port 7. The echo server simply echoes back the data that was sent to it on the socket. A ‘\n’ on the input to the socket indicates to the echo server to start responding with the echo.
Add three labels, three text boxes and two buttons to the existing form in SocketEx2 project as shown below. The labels have text properties of “host name”, “input” and “response”. The first text box has a name of “txthost”. The second text box has a name of “txtInputEcho” and its multiline property set to true. The third text box has a name of “txtResponseEcho” and its multiline property also set to true. The first button has a name of “btnContactEchoServer” and a text property of “Contact Echo Server”. The second button has a name of “btnDisconnectEcho” and a text property of “Disconnect Echo Server”. Add another class to the project called “MyEchoClient”. The code for this class is shown below.
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;
namespace SocketEx2
{
public class
MyEchoClient
{
Socket
mSockEcho; //
client socket for echo server
public MyEchoClient(string
hostname)
{
try
{
IPAddress
ipadd = (Dns.GetHostByName(hostname)).AddressList[0];
System.Net.IPEndPoint
ipepdt = new System.Net.IPEndPoint(ipadd,7); // Echo port
mSockEcho
= new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
mSockEcho.Connect(ipepdt);
}
catch(Exception e)
{
throw new
Exception("Error: "+e.Message);
}
}
public string
SendReceiveEcho(string inp)
{ // sends and reads data back from socket
try
{
byte [] buff = ASCIIEncoding.ASCII.GetBytes(inp);
mSockEcho.Send(buff);
byte [] respbuff = new
byte[256];
mSockEcho.Receive(respbuff);
return ASCIIEncoding.ASCII.GetString(respbuff);
}
catch(Exception e)
{
throw new
Exception(e.Message);
}
}
public void
DisconnectEcho()
{
mSockEcho.Close();
}
}
}
Add the following declaration in the form class.
MyEchoClient echoClient;
int mpos = 0;
Type the following code for the “Contact Echo Server” button handler.
private void btnContactEchoServer_Click(object sender, System.EventArgs e)
{
mpos = 0;
echoClient = new
MyEchoClient(txthost.Text);
}
Type the following code for the “disconnect Echo” button handler.
private void
btnDisconnectEcho_Click(object sender,
System.EventArgs e)
{
txtInputEcho.Text
= "";
txtResponseEcho.Text
= "";
echoClient.DisconnectEcho();
}
Add a “Text changed” handler for the “txtInputEcho” text box. Type the following code in it.
private void
txtInputEcho_TextChanged(object sender,
System.EventArgs e)
{
if (txtInputEcho.Text == "")
return;
if (txtInputEcho.Text[txtInputEcho.Text.Length-1] ==
'\n')
{ // \n tells the echo
server to start responding
int lastpos = txtInputEcho.Text.Length;
txtResponseEcho.Text
= txtResponseEcho.Text +
echoClient.SendReceiveEcho(txtInputEcho.Text.Substring(mpos,lastpos-mpos));
mpos =
lastpos;
}
}
Build and test the application. Type in a host name in the host name text box, then click on the “Contact Echo Server” button. Now type in some text in the “txtInputEcho” textbox. Whenever you hit enter, the line is sent to the echo server, which responds by the same text on the same socket (remember socket is bi-directional). When you are done sending and receiving lines from the echo server, click on the “Disconnect Echo” button to close the client socket.
As you have seen from the previous examples, the “Socket” class on the client-side provides “Send(byte [])” and “Receive(byte [])” methods for sending and receiving bytes. You can also use the TCPClient class instead of a Socket class. The TCPClient allows for a stream-based sending and receiving of data. Since you can also use text streams (StreamReader and StreamWriter classes) for those servers that you know exchange text based information such as Web Servers, the TCPClient class is often preferred over the low level Socket class.
Creating TCP servers: .Net library provides the TCPListener class to create
TCP servers in an easy manner. You can also use the Socket class for this
purpose. However, just like the TCPClient is easier to use than a Socket class
on the client side, a TCPListener has a higher level interface for creating
servers. The TCPListener class constructor creates the server socket, binds it
to an IP and a port number, then goes into listening mode. The class provides
an accept method which is a blocking call and waits for the incoming client.
Example: Line length server.
A simple server that
accepts a client connection and sends back the length of the line in terms of
number of characters it receives from the client. Create a C# Windows
application with the project name as “TCPLineLengthServer”. Create the
following user interface in the default form.
The text boses in the left
hand corner have names of “txtIP” and “txtPort” respectively. The two buttons
have names of “btnStartServer” and “btnStopServer”. The label at the bottom has
a name of “lblStatus”. The list box on the right has a name of “lbClients”.
Add the following
namespaces statements to the top of the form.
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using
System.IO;
Add the following code to
the top of the Form1 class.
TcpListener tcpServer;
Thread srvTh;
Type the following code for
the Start Server button.
private void
btnStartServer_Click(object sender,
System.EventArgs e)
{
try
{
tcpServer
= new TcpListener(IPAddress.Parse(txtIP.Text),int.Parse(txtPort.Text));
tcpServer.Start();
srvTh = new Thread(new
ThreadStart(ServeClients));
// handle client requests in a separate thread
srvTh.Start();
lblStatus.Text
= "Line Length server ready, Listening on\n" +
tcpServer.LocalEndpoint.ToString();
btnStartServer.Enabled
= false;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
As you can see from the above code, the TcpListener creates a server. The IP address and port number are assigned in its constructor. Once the server is started, it can “Accept” incoming connections from clients. The above code starts a new thread to handle the client request. Add the following function that will be invoked in a separate thread in the Form1 class.
private void
ServeClient() //
can only serve a single client
{
tcpSrv =
tcpServer.AcceptTcpClient();
// thread will block here until client connects
Stream
strm = tcpSrv.GetStream(); // binary stream
StreamReader
strmRead = new StreamReader(strm); // text stream
while (true) // allows client
to submit multiple line length requests
{
string recvString = strmRead.ReadLine();
byte [] sendData;
if (recvString != "DONE")
sendData
= Encoding.ASCII.GetBytes((recvString.Length).ToString()+"\n");
else
sendData
= Encoding.ASCII.GetBytes("Server closed socket\n");
strm.Write(sendData,0,sendData.Length);
// strm.Flush();
if (recvString == "DONE") // client is done
and is closing connection
break;
}
strmRead.Close();
strm.Close();
tcpSrv.Close();
}
The above code is capable
of serving only one client. To handle multiple concurrent clients, we can
modify the above “ServeClient” function, so that it creates a new thread each
time a client connects to it: Recall the server blocks at the “AcceptTcpClient(
)” call until a client connects to it.
We can make a TCP server more responsive by having it create a separate
thread for each client as the server does an accept of an incoming connection.
Since we may want to keep track of each client ID, and the thread that is used
for each client, add a new class to the project with a name of "HandleClient".
The code for the HandleClient class is shown below.
using System;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Threading;
namespace TCPLineLengthServer
{
public class
HandleClient
{
public TcpClient tcpClientSocket = null;
int clientID;
// issued by the TCP server
public int ClientID
{
get { return clientID; }
}
public Thread clientThread; // for send, recv of data to/from
client
Form1
pForm; //
parent form
public HandleClient(TcpClient tcpClient, int id, Form1 fm)
{
this.tcpClientSocket = tcpClient;
this.clientID = id;
pForm =
fm;
}
public void
SendReceiveClientData()
{// will execute in a separate thread to send receive data
to/from client
Stream
strm = null;
StreamReader
strmRead = null;
try
{
strm =
tcpClientSocket.GetStream(); // binary stream
strmRead
= new StreamReader(strm); // text stream
while (true) // allows client
to submit multiple line length requests
{
string recvString = strmRead.ReadLine();
byte [] sendData;
if (recvString != "DONE")
sendData = Encoding.ASCII.GetBytes((recvString.Length).ToString()+"\n");
else
sendData
= Encoding.ASCII.GetBytes("Server closed socket\n");
strm.Write(sendData,0,sendData.Length);
// strm.Flush();
if (recvString == "DONE") // client is done
and is closing connection
break;
}
}
catch(Exception ex)
{
throw new
Exception(ex.Message);
}
finally
{
strmRead.Close();
strm.Close();
// remove self from array list of active clients
lock (pForm.syncobj)
{
foreach (HandleClient hc in
pForm.activeClients)
{
if (hc.clientID == this.clientID)
pForm.activeClients.Remove(hc);
}
}
}
}
}
}
Add the following code to
the top of the Form1 class.
TcpClient tcpSrv;
int clientNum = 0;
public ArrayList activeClients
= new ArrayList();
public object
syncobj = new object();
// for multithreading synchronization
The array list
“activeClients” keeps a list of the clients that are currently connected to
this server. Because we are creating a multithreaded server, any modifications
to the activeClients array list should be protected by a lock. The syncobj
object above will be used to provide the synchronization lock.
Type the following code for
the ServeClients( ) function in the Form1 class.
private void
ServeClients() //
can server multiple clients in a multithreaded manner
{
try
{
while (true)
{
TcpClient
tcpClient = tcpServer.AcceptTcpClient();
// thread will block here until client connects, at that
time
// the AcceptTcpClient will return a client socket
Interlocked.Increment(ref clientNum);
// important because of multithreading
HandleClient
hc = new HandleClient(tcpClient,clientNum, this);
lock (syncobj)
{
activeClients.Add(hc);
}
hc.clientThread
= new Thread(new
ThreadStart(hc.SendReceiveClientData));
hc.clientThread.IsBackground
= true;
// otherwise thread cleaning does not work
hc.clientThread.Start();
}
}
catch (ThreadAbortException ex)
{
throw new
Exception(ex.Message);
}
}
Type the following code for the Form load event.
private void Form1_Load(object
sender, System.EventArgs e)
{
txtIP.Text = "192.168.0.100";
txtPort.Text = "5000";
lblStatus.Text = "server not
started..";
activeClients.Clear();
}
Type the following code for
the “Stop Server” button.
private void
btnStopServer_Click(object sender,
System.EventArgs e)
{
// ------close all client sockets, stop client threads
foreach
(HandleClient hc in this.activeClients)
{
if (hc.clientThread != null)
{
if (hc.clientThread.IsAlive)
{
hc.tcpClientSocket.Close();
hc.clientThread.Abort();
}
}
}
// ------wait for all client threads to finish
foreach (HandleClient hc in
this.activeClients)
{
if (hc.clientThread != null)
{
hc.clientThread.Join(0); // proper way to
abort a thread, some prob. so 0
}
}
//------stop TCP server
--this should be before aborting main server thread
if (tcpServer != null)
tcpServer.Stop();
lblStatus.Text
= "Line Length server Stopped" ;
btnStartServer.Enabled
= true;
// ------stop the main server thread
if (srvTh != null)
{
if (srvTh.IsAlive)
{
srvTh.Abort();
srvTh.Join();
}
}
}
Type the following code for the Form closing event.
private void
Form1_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
btnStopServer_Click(null,null);
// GC.Collect(); //trigger
GC
// GC.WaitForPendingFinalizers();
}
Add a timer to the form.
Set its enabled property to true and the interval property to 500. Type the
following code in the timer handler (double click on the timer1 to write the
following handler). The code below checks the active clients list and updates
the listbox showing the currently connected clients.
private void timer1_Tick(object
sender, System.EventArgs e)
{
lbClients.Items.Clear();
lock
(syncobj)
{
foreach
(HandleClient hc in this.activeClients)
lbClients.Items.Add(hc.ClientID);
}
}
The server is ready. You
can build and run it. Pick a port number and an IP corresponding to your
computer, and then clcik on the “Start Server” button. You can find your IP
address by typing “ipconfig” from the command prompt. If you are not connected
to the internet, then choose the loopback localhost IP address of 127.0.0.1.
Building the client for Line Length Server:
Create a windows C# application called
“TCPLineLengthClient”. Create the following user interface in the default form.
The two text boxes in the
top left have names of “txtIP” and “txtPort”. The middle label has a name of
“lblStatus”. The top right button has a name of “ConnectLLServer”. The middle
text box has a name of “txtSendString”. The “Send Data” has a button of
“btnSenddata”. The text box below it has a name of “txtLineLength”. The button
at the bottom has a name of “btnDisconnect Client”.
The important code for the
Form1 class including the different event handlers is shown below. The client
uses the TcpClient class to connect to the LineLengthServer.
..
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.IO;
using System.Threading;
namespace TCPLineLengthClient
{
public class Form1 :
System.Windows.Forms.Form
{
private TcpClient tcpClient = null;
private Thread thClient;
….
….
private void
btnConnectLLServer_Click(object sender,
System.EventArgs e)
{
try
{
IPEndPoint
ipLLS = new IPEndPoint(IPAddress.Parse(txtIP.Text),
int.Parse(txtPort.Text));
tcpClient
= new TcpClient();
tcpClient.Connect(ipLLS); // blocking call
lblStatus.Text
= "Connected to Server at:\n " + ipLLS.ToString();
thClient
= new Thread(new
ThreadStart(this.SendReceiveData));
thClient.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void
Form1_Load(object sender, System.EventArgs e)
{
txtIP.Text
= "192.168.0.100";
txtPort.Text
= "5000";
lblStatus.Text
= "Not Connected";
}
void SendReceiveData()
{
try
{
tcpClient.SendTimeout
= 200; // 200
milliseconds
tcpClient.ReceiveTimeout
= 200;
Stream
strm = tcpClient.GetStream();
byte [] sendData =
Encoding.ASCII.GetBytes(txtSendString.Text+"\n");
strm.Write(sendData,0,sendData.Length);
StreamReader
strmRead = new StreamReader(strm); // text stream
string lineLength = strmRead.ReadLine();
txtLineLength.Text
= lineLength;
}
catch (Exception ex)
{
lblStatus.Text
= "problem connecting.." ;
MessageBox.Show(ex.Message);
}
}
private void
btnSendData_Click(object sender,
System.EventArgs e)
{
SendReceiveData();
}
private void
btnDisconnectClient_Click(object sender,
System.EventArgs e)
{
if (thClient != null)
{
if (thClient.IsAlive)
{
thClient.Abort();
}
}
if (tcpClient != null)
tcpClient.Close();
tcpClient
= null;
lblStatus.Text
= "Disconnected..";
}
private void
Form1_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
btnDisconnectClient_Click(null, null);
}
}
}
Run the server, then the
client. Make sure the server has a valid IP and port number before you click on
the “Start Server” button. The client should connect to the same IP address and
port number the server has been started on.
You can run multiple clients by clicking on the exe file in the bin/debug folder of the TCPLineLengthClient project directory.
Each time you type in a line in the middle text box and click on “Send Data” button, the TCPLineLengthServer returns the length of the line which the client displays in the bottom text box. You can type the “DONE” message in the client to indicate that the server should close the socket to this client.
Asynchronous
Programming:
You have already seen the use of delegates as “call back”
functions in the case of event handlers, message communication between modeless
dialogs, and having another thread block while making a synchronous call, then
once the call is returned invoke the “call back” method via the delegate.
Rather than creating the thread yourself that (blocks on the synchronous call)
and have it call back via a delegate, the standard delegate mechanism can be
used to create asynchronous calls in a straightforward manner. Here is the
general procedure to accomplish this.
1. Declare an appropriate delegate corresponding to the
method you wanted to call (i.e., the method that blocks).
2. Use the BeginInvoke( ) method from the above
delegate to invoke the intended method. The BeginInvoke(.) method takes
two extra parameters in addition to the parameters required in the original
call. The additional parameters are a delegate to the callback function (has to
be of type AsyncCallback) and a context object for state information.
Both of these additional parameters can be null if they are not needed. The BeginInvoke(.)
method is nonblocking and returns an “IAsyncResult” type object that can
be used to determine the status of the call.
3. Once BeginInvoke(.) is called, you have several
options to wait on the completion of the actual call.
a. Call EndInvoke(IAsyncResult ar) to see if the
call has completed and collect the result from the call via the IAsyncResult
parameter. Note that EndInvoke is a blocking call, so you can first do
some work and then make this call. Also, you should not make this call from a
user interface thread as it will block the user interface.
b. Obtain a wait handle using the IAsyncResult.AsyncWaitHandle
property. Then use wait handle’s WaitOne method to block execution until
the wait handle is signaled. Then call EndInvoke to collect the result.
c. Poll the IAsyncResult object returned by the BeginInvoke
call to see when the call gets actually completed. Then call EndInvoke
to collect the result.
d. Pass a delegate for a callback function to the BeginInvoke
method. This delegate has to be of AsyncCallback type. This method will
be called back automatically when the actual call finishes. Note that the
invoked function and the call back method automatically execute in another
thread provided by the thread pool. The code in the call back method should
call EndInvoke to collect the result.
NOTE: You should always call EndInvoke to collect
the result from the call. Even if result is not needed, EndInvoke should
be called to do the proper clean up in an asynchronous call.
Example: Create a C#
windows application. Name the project “AsyncCalls”. Create the following user
interface in the default form.
Put four buttons on the
form with text properties as shown above. The names of the four buttons are:
“btnSyncCall”, “btnAsyncPoll”, “btnAsyncDelegate” and “btnAsyncWaitHandle”. Put
five 10 labels on the form as shown above. The labels with rectangles around
them have their border style property set to “FixedSingle”. Name these labels
as “lblCallTime”, “lblEndTime”, “lblthUI”, “lblthComputeit”, and
“lblthEndInvoke”.
Add another class to the
project. Name this class “MyCompute”. Type the following code in it.
using
System;
using
System.Threading;
using
System.Windows.Forms;
namespace
AsyncCalls
{
/// <summary>
/// Summary description for MyCompute.
/// </summary>
public class MyCompute
{
public
MyCompute()
{
}
public
System.DateTime Computeit(int data, out int thID)
{
data = data + 5;
// dummy
computation
Thread.Sleep(5000); // pretend
computation takes 5 seconds
thID =
System.AppDomain.GetCurrentThreadId();
return
System.DateTime.Now;
}
}
}
Add the following
declaration after the namespace in Form1.cs file (but before the Form1 class).
delegate System.DateTime MyDelAsyn(int a, out int ret);
As you can see, the above
declaration defines a delegate for the function that we intend to call in an
asynchronous manner (i.e., the Computeit function in MyCompute class).
Add the following line in
the beginning of the Form1 class.
MyDelAsyn delComputeit; // delegate
for async call
Type the following code for
the “Synchronous Call” button.
private void btnSyncCall_Click(object
sender, System.EventArgs e)
{
MyCompute mc = new
MyCompute();
System.DateTime t1 = System.DateTime.Now;
lblCallTime.Text = t1.ToString();
lblthUI.Text =
System.AppDomain.GetCurrentThreadId().ToString();
int
thIDComputeit;
System.DateTime t2 = mc.Computeit(5, out thIDComputeit) ;
lblEndTime.Text = t2.ToString();
lblthComputeit.Text =
thIDComputeit.ToString();
}
If you run the above
program, you will see that nothing shows up in the labels in the form for about
five second (because of the synchronous call that takes five seconds), and then
the following results appear.
The best technique for
asynchronous calls is where we pass a delegate to the callback function in the
“BeginInvoke” method. Type the following code for the “Asynchronous Call
(delegate callback)” button’s handler.
private void
btnAsyncDelegate_Click(object sender,
System.EventArgs e)
{
MyCompute mc = new
MyCompute();
delComputeit = new
MyDelAsyn(mc.Computeit);
// set delegate
for async call
AsyncCallback MyCallBackDel = new AsyncCallback(this.MyCallback); // callback
delegate
System.DateTime
t1 = System.DateTime.Now;
lblCallTime.Text = t1.ToString();
lblthUI.Text =
System.AppDomain.GetCurrentThreadId().ToString();
int
thIDComputeit;
IAsyncResult ar =
delComputeit.BeginInvoke(5, out thIDComputeit,
MyCallBackDel, delComputeit);
// asynchronous call, function pointed to MyCallBackDel
// will be automatically invoked, when the call to
Computeit finishes
// Computeit is being pointed to by delComputeit delegate,
last
// param is the context. First two parameters are given to
Computeit.
}
Add the following callback function in the Form1
class. This function will be automatically invoked when the actual call
(Computeit method) via the BeginInvoke finishes.
private void
MyCallback(IAsyncResult ar)
{
try
{
int thIDComputeit;
AsyncCalls.MyDelAsyn
delComp = (AsyncCalls.MyDelAsyn) ar.AsyncState;
// retrieve async delegate from state
System.DateTime
t2 = delComp.EndInvoke(out thIDComputeit, ar);
// the ar parameter has to be the same as that received in
this call
// the above call could be through delComputeit in which
case you will // not need
state object.
lblEndTime.Text
= t2.ToString();
lblthEndInvoke.Text
= System.AppDomain.GetCurrentThreadId().ToString();
lblthComputeit.Text
= thIDComputeit.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
If you run the program and click on the “Asynchronous
Call” button, you will see that the “Call Time” and “UI Thread” labels are filled
right away, as the button’s handler immediately returns because of the
asynchronous BeginInvoke call. After about five seconds, the callback delegate
(MyCallback) that was passed to the BeginInvoke method is automatically
triggered, and the rest of the labels are filled by the MyCallback method. Note
that the called function via BeginInvoke(i.e., Computeit), and the call back
function (i.e., MyCallback) are automatically executed in a separate thread.
In short, the
benefit of the asynchronous calls using the built-in delegate mechanism
(BeginInvoke, EndInvoke etc..) is that you do not have to create threads
yourself, the .Net framework automatically does this for us so that there is no
blocking involved.
Very similarly, the other two asynchronous techniques
- “Polling” and “Wait Handle” are demonstrated by the two button handlers as
shown below.
private void
btnAsyncPoll_Click(object sender,
System.EventArgs e)
{
MyCompute
mc = new MyCompute();
delComputeit
= new MyDelAsyn(mc.Computeit); // set delegate
for async call
System.DateTime
t1 = System.DateTime.Now;
lblCallTime.Text
= t1.ToString();
lblthUI.Text
= System.AppDomain.GetCurrentThreadId().ToString();
int thIDComputeit;
IAsyncResult
ar = delComputeit.BeginInvoke(5, out thIDComputeit,
null, null);
// we will use polling technique
while (ar.IsCompleted == false)
{
Thread.Sleep(100); // wait 100
milliseconds, then check again
}
System.DateTime
t2 = delComputeit.EndInvoke(out thIDComputeit,
ar);
// the ar parameter has to be the same as that received in
this call
// the above call could be through delComputeit in which
case you will // not need state
object.
lblEndTime.Text
= t2.ToString();
lblthEndInvoke.Text
= System.AppDomain.GetCurrentThreadId().ToString();
lblthComputeit.Text
= thIDComputeit.ToString();
}
private void
btnAsyncWaitHandle_Click(object sender,
System.EventArgs e)
{
MyCompute
mc = new MyCompute();
delComputeit
= new MyDelAsyn(mc.Computeit); // set delegate
for async call
System.DateTime
t1 = System.DateTime.Now;
lblCallTime.Text
= t1.ToString();
lblthUI.Text
= System.AppDomain.GetCurrentThreadId().ToString();
int thIDComputeit;
IAsyncResult
ar = delComputeit.BeginInvoke(5, out
thIDComputeit, null, null);
// we will use wait handle technique
ar.AsyncWaitHandle.WaitOne(); // wait for this
handle to get signaled
System.DateTime
t2 = delComputeit.EndInvoke(out thIDComputeit,
ar);
// the ar parameter has to be the same as that received in
this call
// the above call could be through delComputeit in which
case you will
// not need state object.
lblEndTime.Text
= t2.ToString();
lblthEndInvoke.Text
= System.AppDomain.GetCurrentThreadId().ToString();
lblthComputeit.Text
= thIDComputeit.ToString();
}