My previous article which you can find at http://blog.xploiter.com/index.php/2009/01/16/socket-programming-in-c-using-the-built-in-libraries-a-fully-working-production-example-part-1/ covered the creation of a solid production ready Socket Server written in C#. I promised to follow up with part 2 which would be a suitable client so without further ado, here we go:
App.Config
<?xml version=“1.0“ encoding=“utf-8“ ?>
<configuration>
<appSettings>
<add key=“ServerIP“ value=“127.0.0.1“ />
<add key=“ServerPort“ value=“10001“ />
</appSettings>
</configuration>
This time we define the server IP Address we wish to connect to as well as the port to use.
ExceptionHandler.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Windows;
namespace SocketClient
{
///
/// Handles displaying error messages
///
public class ExceptionHandler
{
///
/// Takes the exception message and displays a meaningful message to the user
///
public static string DisplayMessage(Exception ex)
{
return DisplayMessage(ex, “”);
}
///
/// Takes the exception message and displays a meaningful message to the user
///
/// The exception to display.
/// Current User
//[System.Diagnostics.DebuggerStepThrough()]
public static string DisplayMessage(Exception ex, string userName)
{
StringBuilder sb = new StringBuilder();
if (ex is DBConcurrencyException)
sb.Append(“Concurrency Error: One or more people have updated this data since your last request.”);
else if (ex is SqlException)
{
sb.Append(“Database Error: “);
switch (((SqlException)ex).Number)
{
case 547:
sb.Append(“There is a constraint on the items you tried to modify. Please try again.”);
break;
case 2601:
// Unique Index
sb.Append(“Cannot insert duplicate values into the database.”);
break;
case 2627:
// Unique Constraint
sb.Append(“Cannot insert duplicate values into the database.”);
break;
default:
sb.Append(ex.Message);
break;
}
}
else
{
sb.Append(“Exception Handler Unexpected Error: “ + ex.Message);
}
string nl = “nn”;
sb.Append(nl + “Exception Information:” + nl);
sb.Append(“Message: “ + ex.Message + nl);
sb.Append(“Source: “ + ex.Source + nl);
sb.Append(“Stack Trace: “ + ex.StackTrace + nl);
if (ex.InnerException != null)
{
sb.Append(nl + “Inner Exception Info:” + nl);
sb.Append(“Message: “ + ex.InnerException.Message + nl);
sb.Append(“Source: “ + ex.InnerException.Source + nl);
sb.Append(“Stack Trace: “ + ex.InnerException.StackTrace + nl);
}
return sb.ToString();
}
}
}
Same as the SocketServer code.
SocketClient.cs
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using Microsoft.VisualBasic;
using System.Configuration;
namespace SocketClient
{
/// <summary>
/// Description of SocketClient.
/// </summary>
public class Client : System.Windows.Forms.Form
{
#region private members
byte[] m_dataBuffer = new byte[10];
IAsyncResult m_result;
public AsyncCallback m_pfnCallBack;
private System.Windows.Forms.Timer timerAlive;
private System.ComponentModel.IContainer components;
private int _ServerSocketID = –1;
public Socket m_clientSocket;
bool isClosing = false;
Int16 _Port = 10001; // default port we listen on
string _ServerIP = “127.0.0.1”;
private GroupBox groupBox1;
private Button buttonConnect;
private Button button1;
private TextBox textBoxMsg;
private Button buttonDisconnect;
private Button btnSendMessage;
private Label label4;
private RichTextBox richTextRxMessage;
private System.Windows.Forms.Timer tmrServerCheck;
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void AppendTextCallback(string text);
delegate void DisconnectCallback();
#endregion private members
#region constructor
public Client()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
_ServerIP = ConfigurationManager.AppSettings[“ServerIP”];
_Port = Convert.ToInt16(ConfigurationManager.AppSettings[“ServerPort”]);
}
[STAThread]
public static void Main(string[] args)
{
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.Run(new Client());
}
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
WriteToLogFile(ExceptionHandler.DisplayMessage(e.Exception));
MessageBox.Show(ExceptionHandler.DisplayMessage(e.Exception));
}
#endregion constructor
#region Windows Forms Designer generated code
/// <summary>
/// This method is required for Windows Forms designer support.
/// Do not change the method contents inside the source code editor. The Forms designer might
/// not be able to load this method if it was changed manually.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timerAlive = new System.Windows.Forms.Timer(this.components);
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.buttonConnect = new System.Windows.Forms.Button();
this.button1 = new System.Windows.Forms.Button();
this.textBoxMsg = new System.Windows.Forms.TextBox();
this.buttonDisconnect = new System.Windows.Forms.Button();
this.btnSendMessage = new System.Windows.Forms.Button();
this.label4 = new System.Windows.Forms.Label();
this.richTextRxMessage = new System.Windows.Forms.RichTextBox();
this.tmrServerCheck = new System.Windows.Forms.Timer(this.components);
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// timerAlive
//
this.timerAlive.Interval = 10000;
this.timerAlive.Tick += new System.EventHandler(this.timerAlive_Tick);
//
// groupBox1
//
this.groupBox1.Controls.Add(this.buttonConnect);
this.groupBox1.Controls.Add(this.button1);
this.groupBox1.Controls.Add(this.textBoxMsg);
this.groupBox1.Controls.Add(this.buttonDisconnect);
this.groupBox1.Controls.Add(this.btnSendMessage);
this.groupBox1.Controls.Add(this.label4);
this.groupBox1.Controls.Add(this.richTextRxMessage);
this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill;
this.groupBox1.Location = new System.Drawing.Point(0, 0);
this.groupBox1.Name = “groupBox1”;
this.groupBox1.Size = new System.Drawing.Size(446, 159);
this.groupBox1.TabIndex = 18;
this.groupBox1.TabStop = false;
this.groupBox1.Text = “Client Setup”;
//
// buttonConnect
//
this.buttonConnect.BackColor = System.Drawing.SystemColors.HotTrack;
this.buttonConnect.Font = new System.Drawing.Font(“Tahoma”, 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.buttonConnect.ForeColor = System.Drawing.Color.Yellow;
this.buttonConnect.Location = new System.Drawing.Point(12, 19);
this.buttonConnect.Name = “buttonConnect”;
this.buttonConnect.Size = new System.Drawing.Size(85, 48);
this.buttonConnect.TabIndex = 38;
this.buttonConnect.Text = “Connect To Server”;
this.buttonConnect.UseVisualStyleBackColor = false;
this.buttonConnect.Click += new System.EventHandler(this.ButtonConnectClick);
//
// button1
//
this.button1.Location = new System.Drawing.Point(139, 124);
this.button1.Name = “button1”;
this.button1.Size = new System.Drawing.Size(49, 24);
this.button1.TabIndex = 35;
this.button1.Text = “Clear”;
this.button1.Click += new System.EventHandler(this.btnClear_Click);
//
// textBoxMsg
//
this.textBoxMsg.Location = new System.Drawing.Point(7, 98);
this.textBoxMsg.Name = “textBoxMsg”;
this.textBoxMsg.Size = new System.Drawing.Size(181, 20);
this.textBoxMsg.TabIndex = 33;
this.textBoxMsg.Tag = “”;
//
// buttonDisconnect
//
this.buttonDisconnect.BackColor = System.Drawing.Color.Red;
this.buttonDisconnect.Font = new System.Drawing.Font(“Tahoma”, 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.buttonDisconnect.ForeColor = System.Drawing.Color.Yellow;
this.buttonDisconnect.Location = new System.Drawing.Point(12, 19);
this.buttonDisconnect.Name = “buttonDisconnect”;
this.buttonDisconnect.Size = new System.Drawing.Size(85, 48);
this.buttonDisconnect.TabIndex = 32;
this.buttonDisconnect.Text = “Disconnect From Server”;
this.buttonDisconnect.UseVisualStyleBackColor = false;
this.buttonDisconnect.Visible = false;
this.buttonDisconnect.Click += new System.EventHandler(this.ButtonDisconnectClick);
//
// btnSendMessage
//
this.btnSendMessage.Enabled = false;
this.btnSendMessage.Location = new System.Drawing.Point(80, 124);
this.btnSendMessage.Name = “btnSendMessage”;
this.btnSendMessage.Size = new System.Drawing.Size(49, 24);
this.btnSendMessage.TabIndex = 31;
this.btnSendMessage.Text = “Send”;
this.btnSendMessage.Click += new System.EventHandler(this.ButtonSendMessageClick);
//
// label4
//
this.label4.Location = new System.Drawing.Point(9, 79);
this.label4.Name = “label4”;
this.label4.Size = new System.Drawing.Size(120, 16);
this.label4.TabIndex = 30;
this.label4.Text = “Message To Server”;
//
// richTextRxMessage
//
this.richTextRxMessage.BackColor = System.Drawing.SystemColors.InactiveCaptionText;
this.richTextRxMessage.Font = new System.Drawing.Font(“Microsoft Sans Serif”, 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.richTextRxMessage.Location = new System.Drawing.Point(194, 19);
this.richTextRxMessage.Name = “richTextRxMessage”;
this.richTextRxMessage.ReadOnly = true;
this.richTextRxMessage.Size = new System.Drawing.Size(247, 129);
this.richTextRxMessage.TabIndex = 27;
this.richTextRxMessage.Text = “”;
//
// tmrServerCheck
//
this.tmrServerCheck.Enabled = true;
this.tmrServerCheck.Interval = 5000;
this.tmrServerCheck.Tick += new System.EventHandler(this.tmrServerCheck_Tick);
//
// Client
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(446, 159);
this.Controls.Add(this.groupBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
this.Name = “Client”;
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.SocketClient_FormClosed);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
#region event handlers
private void tmrServerCheck_Tick(object sender, EventArgs e)
{
// check to see if the server is available, if so connect to it automatically
// See if we have text on the IP and Port text fields
if (_ServerIP != “”)
{
try
{
UpdateControls(false);
CreateSocket();
UpdateControls(true);
buttonConnect.Visible = false;
buttonDisconnect.Visible = true;
btnSendMessage.Enabled = true;
}
catch (SocketException se)
{
WriteToLogFile(“Cannot connect to server, is it available?”);
UpdateControls(false);
}
}
}
void ButtonDisconnectClick(object sender, System.EventArgs e)
{
timerAlive.Enabled = false;
SendMessageToServer(“id=client,closing,socket=” + _ServerSocketID);
if (m_clientSocket != null)
{
m_clientSocket.Close();
m_clientSocket = null;
UpdateControls(false);
buttonConnect.Visible = true;
buttonDisconnect.Visible = false;
btnSendMessage.Enabled = false;
}
}
private void btnClear_Click(object sender, System.EventArgs e)
{
richTextRxMessage.Clear();
}
private void timerAlive_Tick(object sender, EventArgs e)
{
SendMessageToServer(“id=client,available,socket=” + _ServerSocketID);
}
private void ExitApplication()
{
ClosingApplication();
Close();
}
private void ClosingApplication()
{
SendMessageToServer(“id=client,shutdown,socket=” + _ServerSocketID);
isClosing = true;
if (m_clientSocket != null)
{
m_clientSocket.Close();
m_clientSocket = null;
}
}
private void SocketClient_FormClosed(object sender, FormClosedEventArgs e)
{
if (!isClosing)
ClosingApplication();
}
void ButtonConnectClick(object sender, System.EventArgs e)
{
// See if we have text on the IP and Port text fields
if (_ServerIP == “”)
{
MessageBox.Show(“Port Number is required to connect to the Servern”);
return;
}
try
{
UpdateControls(false);
CreateSocket();
UpdateControls(true);
buttonConnect.Visible = false;
buttonDisconnect.Visible = true;
btnSendMessage.Enabled = true;
}
catch (SocketException se)
{
string str;
str = “Connection failed, is the server running?n” + se.Message;
WriteToLogFile(str);
UpdateControls(false);
}
}
void ButtonSendMessageClick(object sender, System.EventArgs e)
{
SendMessageToServer(textBoxMsg.Text + “,socket=” + _ServerSocketID);
}
private void button1_Click(object sender, EventArgs e)
{
richTextRxMessage.Clear();
}
#endregion event handlers
#region private methods
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// AppendTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void AppendRxText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.richTextRxMessage.InvokeRequired)
{
AppendTextCallback d = new AppendTextCallback(AppendRxText);
this.Invoke(d, new object[] { text });
WriteToLogFile(text);
}
else
{
richTextRxMessage.AppendText(text + “r”);
WriteToLogFile(text);
}
}
private void ClosedownSocket()
{
if (this.buttonDisconnect.InvokeRequired)
{
DisconnectCallback d = new DisconnectCallback(ClosedownSocket);
this.Invoke(d, null);
}
else
this.ButtonDisconnectClick(this, null);
}
private void CreateSocket()
{
// Create the socket instance
m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Cet the remote IP address
IPAddress ip = IPAddress.Parse(_ServerIP);
int iPortNo = System.Convert.ToInt16(_Port);
// Create the end point
IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);
// Connect to the remote host
m_clientSocket.Connect(ipEnd);
if (m_clientSocket.Connected)
{
//Wait for data asynchronously
WaitForData();
}
}
/// <summary>
/// Allows us to send a string command to our server
/// </summary>
/// <param name=”msg”>string command to send (comma delimited format)</param>
private void SendMessageToServer(string msg)
{
try
{
if (m_clientSocket != null)
{
if (m_clientSocket.Connected)
{
// New code to send strings
NetworkStream networkStream = new NetworkStream(m_clientSocket);
System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
streamWriter.WriteLine(msg);
streamWriter.Flush();
}
}
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
/// <summary>
/// Allows us to send a Byte Array to our server
/// </summary>
/// <param name=”objData”>Byte array to send</param>
private void SendMessageToServer(byte[] objData)
{
try
{
// Use the following code to send bytes
byte[] byData = System.Text.Encoding.ASCII.GetBytes(objData.ToString());
if (m_clientSocket != null)
{
m_clientSocket.Send(byData);
}
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
private void UpdateControls(bool connected)
{
buttonConnect.Enabled = !connected;
buttonDisconnect.Enabled = connected;
string connectStatus = connected ? “Connected” : “Not Connected”;
AppendRxText(connectStatus + “r”);
if (connected)
{
timerAlive.Enabled = true;
tmrServerCheck.Enabled = false;
}
else
{
timerAlive.Enabled = false;
tmrServerCheck.Enabled = true;
}
}
//—————————————————-
// This is a helper function used (for convenience) to
// get the IP address of the local machine
//—————————————————-
private String GetIP()
{
String strHostName = Dns.GetHostName();
// Find host by name
IPHostEntry iphostentry = Dns.GetHostEntry(strHostName);
// Grab the first IP addresses
String IPStr = “”;
foreach (IPAddress ipaddress in iphostentry.AddressList)
{
IPStr = ipaddress.ToString();
return IPStr;
}
return IPStr;
}
private void Processcommand(string[] ClientCommand, SocketPacket socket)
{
// get our command string, parse it, pass through case statement
// and perform the relevant update i.e. label status
//int count = ClientCommand.Length; // how many elements in our array
if (ClientCommand[0].ToLower() != “server reply”)
{
switch (ClientCommand[0].ToLower())
{
case “shutdown”:
{
// server has closed the connection, ensure we disconnect
ClosedownSocket();
}
break;
case “servermsg:killclient”:
{
// shutdown this application
ClosedownSocket();
Close();
}
break;
case “hello”: // we have connected to our server, get our socketid to use in all our comms
// this ensures that our server knows which client has sent the command
{
string[] Args = SplitQuoted(ClientCommand[1], “trn”);
_ServerSocketID = Convert.ToInt32(Args[0]);
SendMessageToServer(“hello,” + _ServerSocketID + “,id=client”);
}
break;
default:
break;
}
}
}
/// <summary>
/// Here we decode the string sent from the client
/// </summary>
/// <param name=”szData”></param>
private string[] DecodeCommandString(string szData)
{
string[] command = SplitQuoted(szData, “;rn”);
return command;
}
private void WaitForData()
{
try
{
if (m_clientSocket != null)
{
if (m_pfnCallBack == null)
{
m_pfnCallBack = new AsyncCallback(OnDataReceived);
}
SocketPacket theSocPkt = new SocketPacket();
theSocPkt.thisSocket = m_clientSocket;
// Start listening to the data asynchronously
m_result = m_clientSocket.BeginReceive(theSocPkt.dataBuffer,
0, theSocPkt.dataBuffer.Length,
SocketFlags.None,
m_pfnCallBack,
theSocPkt);
WriteToLogFile(“Connected to “ + m_clientSocket.RemoteEndPoint.ToString());
}
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
private void OnDataReceived(IAsyncResult asyn)
{
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
try
{
int iRx = theSockId.thisSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
string[] ClientCommand = SplitQuoted(szData, “,rn”);
Processcommand(ClientCommand, theSockId);
AppendRxText(szData + “r”);
WaitForData();
}
catch (ObjectDisposedException)
{
WriteToLogFile(“Socket has been closed”);
System.Diagnostics.Debugger.Log(0, “1”, “nOnDataReceived: Socket has been closedn”);
}
catch (SocketException se)
{
AppendRxText(se.Message + “: “ + theSockId.thisSocket.RemoteEndPoint);
if (se.Message == “An existing connection was forcibly closed by the remote host”)
{
WriteToLogFile(“Connection closed by remote host”);
ClosedownSocket();
}
else
MessageBox.Show(se.Message);
}
}
#endregion private methods
#region public methods
/// <summary>
/// Action to be performed by RetryOpen
/// </summary>
/// <returns></returns>
public delegate T RetryOpenDelegate<T>();
/// <summary>
/// Perform an action until succeeds without throwing IOException
/// </summary>
/// <typeparam name=”T”>object returned</typeparam>
/// <param name=”action”>action performed</param>
/// <returns></returns>
public static T RetryOpen<T>(RetryOpenDelegate<T> action)
{
while (true)
{
try
{
return action();
}
catch (IOException)
{
System.Threading.Thread.Sleep(50);
}
}
}
public static void WriteToLogFile(string msg)
{
// create our daily directory
string dir = Application.StartupPath + “\logs\” + DateTime.Now.ToString(“ddMMyyyy”);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
string LogFile = dir + “\” + DateTime.Now.ToString(“ddMMyyyy”) + “_Client_log.txt”;
TextWriter tw = null;
try
{
// create a writer and open the file
tw = RetryOpen<StreamWriter>(delegate()
{
return new StreamWriter(LogFile, true);
});
// write a line of text to the file
tw.WriteLine(DateTime.Now + “: ‘” + Environment.MachineName + “‘ – “ + msg);
}
catch { }
finally
{
// close the stream
if (tw != null)
{
tw.Close();
tw.Dispose();
}
}
}
/// <summary>
/// Splits any string using seperators string. This is different from the
/// string.Split method as we ignore delimiters inside double quotes and
/// will *ignore multiple delimiters in a row (i.e. “One,,,,two” will split
/// to two fields if comma is a delimiter).
/// Example:
/// Delims: ” t,” (space, tab, comma)
/// Input: “one two” three four,five
/// Returns (4 strings):
/// one two
/// three
/// four
/// five
/// </summary>
/// <param name=”text”>The string to split.</param>
/// <param name=”delimiters”>The characters to split on.</param>
/// <returns></returns>
public static string[] SplitQuoted(string text, string delimiters)
{
// Default delimiters are a space and tab (e.g. ” t”).
// All delimiters not inside quote pair are ignored.
// Default quotes pair is two double quotes ( e.g. ‘””‘ ).
if (text == null)
throw new ArgumentNullException(“text”, “text is null.”);
if (delimiters == null || delimiters.Length < 1)
delimiters = ” t”; // Default is a space and tab.
ArrayList res = new ArrayList();
// Build the pattern that searches for both quoted and unquoted elements
// notice that the quoted element is defined by group #2 (g1)
// and the unquoted element is defined by group #3 (g2).
string pattern =
@”””([^””\]*[\.[^””\]*]*)””” +
“|” +
@”([^” + delimiters + @”]+)”;
// Search the string.
foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(text, pattern))
{
//string g0 = m.Groups[0].Value;
string g1 = m.Groups[1].Value;
string g2 = m.Groups[2].Value;
if (g2 != null && g2.Length > 0)
{
res.Add(g2);
}
else
{
// get the quoted string, but without the quotes in g1;
res.Add(g1);
}
}
return (string[])res.ToArray(typeof(string));
}
#endregion public methods
}
#region socketpacket class
public class SocketPacket
{
public System.Net.Sockets.Socket thisSocket;
public byte[] dataBuffer = new byte[1024];
}
#endregion socketpacket class
}
This is the main body of the application. Compile and run the code and at the same time have the server running.
The client will automatically try to connect to the server (see the timer code to see how) and once established send keep alive messages to let the server know it’s there. These will also be returned to the client and displayed accordingly.
Try sending some text, you will see it appear in the server and then be returned to the client.
Try this, add the following line into the text box on the client and send it:
showmessage,Hello from client
You should get a message box popup with your ‘Hello from client’ text.
That’s all there is to it. Simple eh!
Have fun and let me know if you make any modifications to improve it all
Si