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.