Introduction
The original article proposed to demonstrate elementary network programming
by designing a very simple SMTP server class. It had no support for sending
attachments and was quite uncomplicated in nature. Since there are much better
classes available within the BCL for sending emails, the exercise seemed to be
quite futile. Therefore I have updated the article so that it provides a decent
introduction to network programming. There are already several introductions to
network programming on the web and I wanted this to be slightly different from
all of them. Whether I was successful in that endeavor is up to the end-readers
to judge.
Basically with the advent of .NET, remoting and web services are the
preferred means of network communication. Both mechanisms are object oriented
and allows the programmer to work from a high level of abstraction. But there
are often situations, where you might want to communicate with native programs
that were written prior to .NET, or for some reason you cannot expect all
clients to be running .NET enabled programs. That's where we can use the
slightly lower level network classes for our network-based programs. There is a
Socket
class available but I prefer to use the TcpListener
and
TcpClient
classes which offer a higher level of abstraction.
In this article we'll design a multi-threaded TCP server using Managed C++.
This server is very simple in functionality. It accepts a line of text and
returns the reversed version of the same. We'll write an MFC dialog based
application that will use the MFC CSocket
class to chat with our Managed C++ TCP
server. Since we have used basic TCP protocol, it does not matter what language
the client program is written in. Just to complete the wheel, I also include a
Managed C++ version of the TCP client, which also serves to demonstrate the
TcpClient
class.
TCP server - MC++
We write two classes here - SpookServer
which basically starts
our TCP server and listens for connections, and SpookClient
which
is a class for handling individual client connections. The SpookServer
class itself spawns off a new thread in which it waits for connections. And for
each connection, it instantiates a SpookClient
object which
immediately spawns off a new thread to handle the particular client connection.
This system of one thread per client is quite acceptable in our case because
each TCP chat involves so little process flow. It's just a matter of accepting a
line of text, reversing it and sending it back.
The SpookServer class
__gc class SpookServer
{
private:
TcpListener* lis;
Thread* t;
ManualResetEvent* mre;
public:
SpookServer(int port)
{
mre = new ManualResetEvent(true);
lis = new TcpListener(port);
t = new Thread(new ThreadStart(this,&SpookServer::ServerThread));
t->Start();
}
void CloseServer()
{
mre->Reset();
lis->Stop();
}
private:
void ServerThread()
{
lis->Start();
while(mre->WaitOne(100,false))
{
try
{
TcpClient* c = lis->AcceptTcpClient();
SpookClient* sc = new SpookClient(c);
}
catch(Exception* e)
{
}
}
}
};
Well, the class is rather simple, but I'll still give a quick run-through of
how it works. We use a ManualResetEvent
object for synchronization.
This is basically so that the calling class or method has a means of stopping
the server anytime, in our case this is _tmain
. As you can see, we
spawn off a new thread where we listen for connections. This is so that we can
return control to the calling class. We use the AcceptTcpClient
method of the TcpListener
class which returns a TcpClient
object that is a reference to the connected client. Immediately we instantiate a
new SpookClient
object passing the returned TcpClient
object to it's constructor. And we go back to AcceptTcpClient
, thus allowing a
seemingly innumerable number of clients to be able to connect at any one time.
The SpookClient class
__gc class SpookClient
{
private:
TcpClient* m_c;
Thread* t;
public:
SpookClient(TcpClient* c)
{
m_c = c;
t = new Thread(new ThreadStart(this,&SpookClient::StartChat));
t->IsBackground = true;
t->Start();
}
private:
void StartChat()
{
NetworkStream* ns = m_c->GetStream();
unsigned char buffer __gc[] = new unsigned char __gc[128];
String* str = "";
while(true)
{
int d = ns->Read(buffer,0,127);
str = String::Concat(str,Encoding::ASCII->GetString(buffer,0,d));
if(str->LastIndexOf('\n') != -1)
break;
}
str = str->Trim();
str = String::Concat("\n",str);
buffer = Encoding::ASCII->GetBytes(str);
Array::Reverse(buffer);
ns->Write(buffer,0,buffer->Length);
ns->Flush();
ns->Close();
m_c->Close();
}
};
The SpookClient
class has a TcpClient
member and we
assign the TcpClient
object passed to the constructor to this
member object. And we start off a new thread. Inside the thread, we first call
TcpClient.GetStream
to get the underlying NetworkStream
object. Once we have the NetworkStream
object we can use Write
and
Read
on it to send and receive data, respectively. We simply accept a line of
text, by looping till a \n
is encountered and then we reverse it
and send it back. Remember to Flush
and Close
the
NetworkStream
object and then call Close
on the
TcpClient
object as well.
TCP client - MFC
void CTcpClientMFCDlg::OnBnClickedButton1()
{
UpdateData(TRUE);
m_text += "\n";
CSocket s;
s.Create();
if(s.Connect("127.0.0.1",20248))
{
s.Send(m_text,m_text.GetLength());
m_text = "";
char buff[17];
int i;
while(m_text.Find('\n') == -1)
{
i=s.Receive(buff,16);
buff[i]=0;
m_text += buff;
}
m_text.Trim();
UpdateData(false);
}
s.Close();
}
Well, we don't do anything spectacular here. We simply send the text using
CSocket::Send()
and receive back the reversed line of text using
CSocket::Receive
. You'd think that with using network streams and
all, the data would have been slightly personalized for .NET purposes, but
apparently the raw data is preserved. This indicates that you can use the
TcpListener
and TcpClient
classes for any kind of Tcp
programming including popular protocols like HTTP, SMTP, POP3 etc.
TCP client - MC++
__gc class SpookClient
{
public:
String* Reverse(String* s)
{
TcpClient* tl = new TcpClient("127.0.0.1",20248);
NetworkStream* nw = tl->GetStream();
unsigned char buffer __gc[] = new unsigned char __gc[128];
buffer = Encoding::ASCII->GetBytes(s);
nw->Write(buffer,0,buffer->Length);
unsigned char retbuffer __gc[] = new unsigned char __gc[128];
String* str = "";
while(true)
{
int d = nw->Read(retbuffer,0,127);
str = String::Concat(str,Encoding::ASCII->GetString(retbuffer,0,d));
if(str->LastIndexOf('\n') != -1)
break;
}
nw->Close();
tl->Close();
return str;
}
};
I have written another class here called SpookClient
, not to be
confused with the other class in the server program that has the same name. This
uses a TcpClient
object to connect to the server, gets the
underlying NetworkStream
using GetStream
and uses
Write
and Read
for sending and receiving data, to and
from the server. This code snippet would look very similar to our client handler
class in the server program because they both demonstrate the use of the
NetworkStream
class.