Audience for this article
C# beginners with little knowledge about Networks and Protocols. Some knowledge of threads is required. Microsoft Development environment is required (Visual Studio 2003).
Brief description
Through this article I want to present readers with a TCP/IP Server which is started by a Windows Service and can be stopped when the Windows service is stopped. This TCP/IP server listens for client connection requests. When a request comes from a client, the job of the server is to read the data sent by the client and store it in a file (Name of the file also specified by the client) in C:\\TCP directory.
Hard words used
(Experienced readers can ignore/skim through this section)
Network: A group of computers connected ("Physically connected with a wire"/ "Connected without a wire using satellite communication i.e. via Internet")
Protocol: Set of rules for a communications
Example:
Let us say there are two things (Computers/people/anything) A and B.
A first says to B: Hello B.
B then says back to A: Hello A.
A then Says: How are you?
B then says: Good. How about you?
A then says: Good.
A again Says: Bye B.
B says: Bye A
If the above communication happens always, whenever A and B meet, then it can be termed as a greeting protocol between them. If A turns his face to the other side when he sees B (May be he fought with B) and ignores him; Then above communication can't be termed as protocol because above communication is not happening always.
TCP/IP (Transmission Control Protocol / Internet Protocol): It is a communication protocol used by many networks for communication.
I/P Address: Each machine in the internet/intranet world has been given an unique number in the form xxx.xxx.xxx.xxx, where xxx can be a integer ranging from 0 to 127.
Windows Service: On Windows 2000/XP go to Start/Control Panel/Administrative Tools/ We can see Windows Services Icon. A service is a computer program run by OS automatically whenever OS is started (normally without any intervention by users). A service normally has Start, Stop, Pause states. Also a service start up can be Automatic (By the OS when computer is booted), Manual( Has to be started by the users manually) or disabled (not functioning).
Server: A program waits for some one for communications (does both Listening and Talking).
Client: Some one trying to communicate (Again both Listening and Talking) with a server.
(You might wonder then what is difference between then. The difference is waiting. Let us take a restaurant as an example. Servers take orders and deliver food for customers (Clients) and clients go away once their eating is over. But Servers are ready for some other client until their duty is over.)
Socket: A connecting point for communication between The Server and a Client. Normally by saying Socket we mean we are talking about a connection point on the Server. But a client also needs a socket to talk with their Server. Now the important point to remember is that there is only one Server serving many clients. All clients are connected to server on the same point (Socket) on the server but have different individual connecting points on them. Let us take the example of telephones. When several people ("Clients") calling the same guy "Server" through a single telephone line where that guy called "Server" uses flash/switch mechanism that he has on his phone, for switching and talking to different "Clients" independently so that no one listens what "Server" is taking to the other people.
Port (Number): It is basically a positive integer number used to identify a point of communication.
Difference between Socket and Port: Socket is an abstract concept defined by an IP address along with its port number.
How this program works :-
This program works like this.
- This program starts when a windows service called "TCPService" Starts.
- When the Windows service is started a TCP/IP server is also started and that server is always be in a waiting mode for various client connections.
- When ever a client connection comes in, then Server creates a "Client Socket Listening" object to handle the communication between the client socket and server socket and again goes to waiting state for another connection request.
- The "Client Socket Listening" object has a thread started by the server, which takes care of the communication with the client.
- This communication is done as per a predefined protocol (See next section).
- The summary of protocol is, Client first send a file name and then some data, server has to store that in the server machine in C:\\TCP directory in a filename sent by the client.
- Once client sends every thing it wanted to send, then it will tear the connection with the "Client Socket Listening" object i.e. with the server. So this "Client Socket Listening" created by the server is ready for deletion.
- Server has a another low priority thread running which takes care of removing such "Ready For Deletion" "Client Socket Listening" objects.
- Once the Windows service is stopped server also stopped and all client connections goes away.
This Program's Protocol
The protocol works like this.
- Client first sends a file name that ends with "CRLF" (Carriage Return and Line Feed="\r\n")
- Then Server has to return, the length of the file name to the client for validation. If the length matches with the length what client had sent earlier, then client starts sending lines of information. Otherwise socket will be closed by the client.
- Each line of information that client sends will end with "CRLF".
- This Server has to store each line of information in a text file whose file name has been sent by the client earlier.
- As a last line of information client sends "[EOF]" line which also ends with "CRLF" ("\r\n"). This signals this Server for an end of file and intern this Server sends the total length of the data (lines of information excludes file name that was sent earlier) back to client for validation.
Since Server has to do this for many clients as soon as he receives a connection request from a client he instantiate a separate thread for implementing above protocol for each client request.
Program Notes
I am not going to explain how to write a windows Service using C# on Microsoft Development Environment. The best resource would be
The Service code has following methods OnStart
and OnStop
.
I have coded them as follows:
protected override void OnStart(string[] args)
{
server = new TCPServer();
server.StartServer();
}
protected override void OnStop()
{
server.StopServer();
server=null;
}
TCPServer is the Server class. Its constructor has following code.
try
{
m_server = new TcpListener(ipNport);
if (!Directory.Exists(TCPSocketListener.DEFAULT_FILE_STORE_LOC))
{
Directory.CreateDirectory(
TCPSocketListener.DEFAULT_FILE_STORE_LOC);
}
}
catch(Exception e)
{
m_server=null;
}
TCPListener
is the .NET framework class for creating a Server Socket. Following code is to create "C:\\TCP" directory if it is not present. The port on which server is listening is "ipNport" (30001). If we see TCPService
code, it is calling StartServer
method of TCPServer
. It is given as follows:
public void StartServer()
{
if (m_server!=null)
{
m_socketListenersList = new ArrayList();
m_server.Start();
m_serverThread = new Thread(new ThreadStart(ServerThreadStart));
m_serverThread.Start();
m_purgingThread = new Thread(new ThreadStart(PurgingThreadStart));
m_purgingThread.Priority=ThreadPriority.Lowest;
m_purgingThread.Start();
}
}
Server (TCPLi
stener object) is not started until StartServer
is called. See m_server.Start()
in the above code. Also we can observe that a thread is started for listening client requests. It is represented by the method "ServerThreadStart
" We can notice that another low priority thread (purging thread) is started for deleting "Client Socket Listening" objects if they become obsolete i.e. communication is completed between the Server and a client. There can be a different approach for doing the above purging process. But I have chosen this approach at this moment. Another interesting thing here is in C# (in C/C++ also) a thread is associated with a method. So the method associated with Server thread is "ServerThreadStart
" and the method associated with Purging thread is "PurgingThreadStart
".
private void ServerThreadStart()
{
Socket clientSocket = null;
TCPSocketListener socketListener = null;
while(!m_stopServer)
{
try
{
clientSocket = m_server.AcceptSocket();
socketListener = new TCPSocketListener(clientSocket);
lock(m_socketListenersList)
{
m_socketListenersList.Add(socketListener);
}
socketListener.StartSocketListener();
}
catch (SocketException se)
{
m_stopServer = true;
}
}
}
I am not explaining what is going to happen in the purging thread. If see above code first interesting spot is "m_server.AcceptSocket()
". This is a blocked statement. That means unless a server is closed or unless a client request comes in, code execution is blocked at the point. If a client request comes in we can see that a "Client Socket Listening" class object, (here it is 'TCPSocketListener
' class) is created and added to a hash table. It is added to the hash table because purging thread should delete it ('TCPSocketListener
' object) once communication is completed. Another important line of code is "socketListener.StartSocketListener();
". This is to start a separate thread in "Client Socket Listening" object (i.e. 'TCPSocketListener
' class object).
Last (but not the least) point to mention is the lock block on 'm_socketListenersList
' hash table. This is required because, there may a time where a new connection is being added and an expired connection has to deleted. lock makes sure that both addition and deletion happen with out any problem.
public void StopServer()
{
if (m_server!=null)
{
m_stopServer=true;
m_server.Stop();
m_serverThread.Join(1000);
if (m_serverThread.IsAlive)
{
m_serverThread.Abort();
}
m_serverThread=null;
m_stopPurging=true;
m_purgingThread.Join(1000);
if (m_purgingThread.IsAlive)
{
m_purgingThread.Abort();
}
m_purgingThread=null;
m_server = null;
StopAllSocketListers();
}
}
public TCPSocketListener(Socket clientSocket)
{
m_clientSocket = clientSocket;
}
public void StartSocketListener()
{
if (m_clientSocket!= null)
{
m_clientListenerThread =
new Thread(new ThreadStart(SocketListenerThreadStart));
m_clientListenerThread.Start();
}
}
private void SocketListenerThreadStart()
{
int size=0;
Byte [] byteBuffer = new Byte[1024];
m_lastReceiveDateTime = DateTime.Now;
m_currentReceiveDateTime = DateTime.Now;
Timer t= new Timer(new TimerCallback(CheckClientCommInterval),
null,15000,15000);
while (!m_stopClient)
{
try
{
size = m_clientSocket.Receive(byteBuffer);
m_currentReceiveDateTime=DateTime.Now;
ParseReceiveBuffer(byteBuffer, size);
}
catch (SocketException se)
{
m_stopClient=true;
m_markedForDeletion=true;
}
}
t.Change(Timeout.Infinite, Timeout.Infinite);
t=null;
}
There are few important points to worth mention.
'CheckClientCommInterval
' is a another thread method which takes care of termination 'SocketListenerThreadStart()
' thread method if there is no response from the client for more than 15 seconds.
- '
m_clientSocket.Receive(byteBuffer)
' this is blocking receive for data from the client.
'ParseReceiveBuffer
' method takes care of parsing data sent by the client whose details I am not describing here because it implements the protocol described above.
class TCPClient
{
[STAThread]
static void Main(string[] args)
{
TCPClient client = null;
client = new TCPClient("SatyaTest.txt\r\n");
client = new TCPClient("SatyaTest1.txt\r\n");
client = new TCPClient("SatyaTest2.txt\r\n");
client = new TCPClient("SatyaTest3.txt\r\n");
client = new TCPClient("SatyaTest4.txt\r\n");
client = new TCPClient("SatyaTest5.txt\r\n");
}
private String m_fileName=null;
public TCPClient(String fileName)
{
m_fileName=fileName;
Thread t = new Thread(new ThreadStart(ClientThreadStart));
t.Start();
}
private void ClientThreadStart()
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"),31001));
clientSocket.Send(Encoding.ASCII.GetBytes(m_fileName));
byte [] data = new byte[128];
clientSocket.Receive(data);
int length=BitConverter.ToInt32(data,0);
clientSocket.Send(Encoding.ASCII.GetBytes(m_fileName+":"+
"this is a test\r\n"));
clientSocket.Send(Encoding.ASCII.GetBytes(m_fileName+":"+
"THIS IS "));
clientSocket.Send(Encoding.ASCII.GetBytes("ANOTHRER "));
clientSocket.Send(Encoding.ASCII.GetBytes("TEST."));
clientSocket.Send(Encoding.ASCII.GetBytes("\r\n"));
clientSocket.Send(Encoding.ASCII.GetBytes(m_fileName+":"+
"TEST.\r\n"+m_fileName+":"+"TEST AGAIN.\r\n"));
clientSocket.Send(Encoding.ASCII.GetBytes("[EOF]\r\n"));
clientSocket.Receive(data);
length=BitConverter.ToInt32(data,0);
clientSocket.Close();
}
}
I hope I explained all the important parts of the program. If any one of you feel I did some thing terribly wrong or not clear, please haul at me. I will try to respond.