Académique Documents
Professionnel Documents
Culture Documents
(Page 1 of 5 )
This article will get you started on building a chat application based on the client/server
model. It will be able to handle all of the basics found in chat applications like Microsoft
Messenger and others.
A downloadable zip of the COMPLETE source code for the application is available
here.
Introduction
Most chat applications are based on a client/server model, i.e. the client logs on to a
server, gets a list of other clients who are logged on and sends messages to them. When
you decide to join a chat, the software program that you use connects to a server
somewhere and allows you to talk to other people who are connected to that server.
Our chat application is also going to be based on client/server model. What we are going
to do in this tutorial is look at how the server enables clients to communicate. We are
also going to look at how the server sends files to other clients, take screenshots and
sends these back to your computer. We will set up our own little communication
protocol.
Requirements for the Server Application:
We want the server to be able to keep a record of every client on the chat.
We want it to be able to send files and take screenshots of connected clients.
We want it to send text to everybody or a single person (private chat), with the
possibility of doing a three way chat.
The port number is used by the server application to listen for clients who want to join or
log on to the chat. There are over 650000 port numbers available, but I would
recommend you use any number from 4000 as some port numbers are reserved for other
services such as SMTP on port 25, POP3 on port 110, HTTP on 80, and so on; be careful
when choosing a port number for your server. Go to: http://www.i386.info/ports.htm for
a list of known ports and further information.
My server app, so far:
TMyContext=class(TIdContext)
public
IP:String;
Nick:String;
Con:TDateTime;
//compname:string;
procedureSendMsg(constANick:String;constAMsg:
String);
procedureBroadcastMsg(constbmsg:String);
procedureBroadcastMsgAll(constANick:String;const
bmsg:String);
procedureSendNicks;
procedureSendFile(constANick,Fn:string);
end;
The code below is responsible for displaying messages. It frees up the main VCL thread.
Without it, I found that the server system usually crashes.
TLog=class(TIdSync)
protected
FMsg:String;
procedureDoSynchronize;override;
public
constructorCreate(constAMsg:String);
classprocedureAddMsg(constAMsg:String);
end;
Add this line of code above the private section of the form:
constructorCreate(AOwner:TComponent);override;
Also, add this under the var section of the form:
names:string;
and add in the implementation section:
usesjpeg;
listnames=requestlistofnamesofconnectedclients
all=sendmessagetoallconnectedclients
takeshot=takesascreenshotofanyclient
name=sendsaprivatemessagetonamedclient
file=sendsafile
functionParseString(s:string;varstr1,str2,str3:string):
boolean;
var
P1,P2:integer;
begin
P1:=Pos('@',s);
P2:=Pos(';',s);
//Testifbothdelimitersarepresent,intherightorder
and
//atleast1charapart
if((P1>0)and(P2>0)and(P2>P1)and(Abs(P2P1)>
1))
thenbegin
str1:=Copy(s,1,P11);
str2:=Copy(s,P1+1,P2P11);
str3:=Copy(s,P2+1,Length(s)P2);
Result:=True;//validstring
end
elseResult:=False;//invalidstring
end;
All that this function does is take in a string - s : string and break it down into three
parts, based on two delimiters which are - @ AND : - So, if I send a message saying
hello mate, the server will receive: fromme@hello mate:toyou. The parsestring function
will take that message and break it down into sender, message, recipient.
procedureTMyContext.SendNicks;
var
List:TList;
Context:TMyContext;
I:Integer;
begin
List:=FContextList.LockList;
try
ifList.Count>1then
begin
//Connection.IOHandler.WriteLn('Currently
Connected:');
forI:=0toList.Count1do
begin
Context:=TMyContext(List[I]);
ifContext<>Selfthen
Connection.IOHandler.WriteLn('list@'+Context.Nick);
end;
endelse
Connection.IOHandler.WriteLn('list@Nooneelse
is
connected');
finally
FContextList.UnlockList;
end;
end;
Command: all
When the server receives this command it sends the message to all connected clients.
Heres how:
procedureTMycontext.BroadcastMsgAll(constANick:String;const
bmsg:String);
var
List:TList;
Context:TMyContext;
I:Integer;
begin
List:=FContextList.LockList;
try
forI:=0toList.Count1do
begin
Context:=TMyContext(List[I]);
ifContext<>Selfthentry
Context.Connection.IOHandler.WriteLn(ANick
+
'>'+bmsg);
exceptend;
end;
finally
FContextList.UnlockList;
end;
end;
Command: takeshot:
Next, I needed a procedure to convert bitmap images to jpeg format, because Delphi
only works in the bitmap format. Also, when you send a picture over the Internet it is
always best to use a format that does not take up a lot of space and that is Internet
friendly.
ProcedureBmpToJpg(constFilename:String;Quality:
TJPEGQualityRange=100);
varBmp:TBitmap;
Jpg:TJpegImage;
begin
Bmp:=TBitmap.Create;
Jpg:=TJpegImage.Create;
try
Bmp.LoadFromFile(Filename);
Jpg.CompressionQuality:=Quality;
Jpg.Assign(Bmp);
Jpg.SaveToFile(ChangeFileExt(Filename,'.jpg'));
deletefile(filename);
finally
Jpg.Free;
Bmp.Free;
end;
end;
Heres the procedure that actually takes the screenshot:
procedurescreenshot;
varDCDesk:HDC;//hDCofDesktop
bmp:TBitmap;
begin
{Createabitmap}
bmp:=TBitmap.Create;
{Setabitmapsizes}
bmp.Height:=Screen.Height;
bmp.Width:=Screen.Width;
{GetadesktopDChandlehandleofadisplaydevice
context}
DCDesk:=GetWindowDC(GetDesktopWindow);
{Copytoanycanvas,herecanvasofanimage}
BitBlt(bmp.Canvas.Handle,0,0,Screen.Width,Screen.Height,
DCDesk,0,0,SRCCOPY);
{Savethebitmap}
bmp.SaveToFile('scrnshot.bmp');
BMPtoJPG('scrnshot.bmp');
{ReleasedesktopDChandle}
ReleaseDC(GetDesktopWindow,DCDesk);
{Releaseabitmap}
bmp.Free;
end;
As the name suggests, the procedure takes a screenshot and saves it in a file called
'scrnshot.bmp'. This file will then be converted to jpeg format, before it is sent off to the
requesting client. So, it is essential that this procedure is called before the bmptojpg
procedure.
procedureTMyContext.SendMsg(constANick:String;const
AMsg:String);
var
List:TList;
Context:TMyContext;
I:Integer;
begin
//lockthelist,sonothingisaddedwhilesearchingforthe
name
List:=FContextList.LockList;
try
forI:=0toList.Count1do
begin
//startthesearch
Context:=TMyContext(List[I]);
ifContext.Nick=ANickthen
begin
try
//iffoundsentthemessage
Context.Connection.IOHandler.WriteLn(AMsg);
except
end;
Exit;
end;
end;
finally
FContextList.UnlockList;
end;
//ifnotfoundsentanerrormsg
Self.Connection.IOHandler.WriteLn('Thenameyousend
the
messagetodoesnotexist.Pleaseclickon''GetlistofNames
on
Chat''buttontogetafulllistofnames.');
end;
This procedure searches for the recipient name and sends the message on. If it does not
find the name on the list, it sends an error msg back to the sender.
Command: file
procedureTMyContext.SendFile(constANick,Fn:string);
var
List:TList;
Context:TMyContext;
I:Integer;
FStream,fstream2:TFileStream;
IdStream,idstream2:TIdStreamVCL;
MStream:TMemoryStream;
begin
//lockthelist
List:=FContextList.LockList;
try
forI:=0toList.Count1do
begin
//searchfortherecipientname
Context:=TMyContext(List[I]);
ifContext.Nick=ANickthen
begin
try
//foundit,nowcreatethefile
FStream:=TFileStream.Create(fn,fmOpenReador
fmShareDenyWrite);
try
IdStream:=TIdStreamVCL.Create(fstream);
try
//sendit!
Context.Connection.IOHandler.WriteLn
('pic@'+fn+';Sendingfile...');
Context.Connection.IOHandler.Write(IdStream,0,
True);
Context.Connection.IOHandler.WriteLn('done!');
finally
IdStream.Free;
end;
finally
FStream.Free;
end;
//ifrecipientnamenotfound
except
end;
Exit;
end;
end;
finally
FContextList.UnlockList;
end;
Self.Connection.IOHandler.WriteLn(Thenameyousend
the
messagetodoesnotexist.Pleaseclickon''GetlistofNames
on
Chat''buttontogetafulllistofnames.');
end;
This procedure basically sends the file to the intended recipient, like so:
Sender -->-----server---->----------->Recipient
As long as the recipient's name is on the list, the file will be sent to them.
This is not the world's most sophisticated chat application, but it can handle all of the
basics found in chat applications like Microsoft Messenger and others. And it will be very
easy to develop an image based chat by tweaking the procedures in this code.
Next
In the next installment we will finish off this part of the chat application.
Introduction
In this part we are going to finish off the server side coding of our Chat Application. In
the previous part - Server Side Chat Application with Borland Delphi/Indy - we discussed
how the server actually carries out all its communication functions as well as how it
interacts with the client application. We also defined and implemented all of the
procedures and commands needed to run our custom communication protocol.
Lets continue
Im not going to go in detail about how the code works, however, I will make sure to
comment the code heavily, so that it makes sense when you read it.
Add the following three procedures in the implementation section:
procedureTLog.DoSynchronize;
begin
form1.Memo1.Lines.Add(FMsg);
end;
classprocedureTLog.AddMsg(constAMsg:String);
begin
withCreate(AMsg)dotry
Synchronize;
finally
Free;
end;
end;
constructorTForm1.Create(AOwner:TComponent);
begin
inheritedCreate(AOwner);
ts.ContextClass:=TMyContext;
end;
As I said in the previous tutorial, this code basically helps to keep the server working
flawlessly by synchronizing messages that need to be displayed by components on the
server form.
Next, double click on the connect button and add the following code:
procedureTForm1.btconnectClick(Sender:TObject);
begin
ts.DefaultPort:=strtoint(edport.Text);
ts.Active:=true;
memo1.Lines.Add('Serverisnowconnectedandreadytoaccept
clients');
end;
This code just gets the server to listen on the said port number as well as display a
message.
Server Side Chat Application with Borland Delphi/Indy, concluded - Code for
connecting clients
(Page 2 of 4 )
The next bit of code gets fired when a client connects. Click on the idtcpserver
component and then go to the object inspectors events tab and double click on the
OnConnect procedure. Now add the following code:
procedureTForm1.tsConnect(AContext:TIdContext);
begin
//tmycontextistheclassthatholdsclientinfosuchas
nickname,ipetc
withTMyContext(AContext)do
begin
//gettimeanddatethattheclientconnected
Con:=Now;
if(Connection.Socket<>nil)then
//getclientipaddress
IP:=Connection.Socket.Binding.PeerIP;
//getclientnickname
Nick:=Connection.IOHandler.ReadLn;
ifNick<>''then
begin
//sendawelcomemsgtoclient
Connection.IOHandler.WriteLn('Welcome'+Nick
+'!');
//telleverybodyelsethathejoined
BroadcastMsg(Nick+'justjoined!');
endelse
begin
//ifnonicknameisnotprovided,endconnection
Connection.IOHandler.WriteLn('NoNickprovided!
Goodbye.');
Connection.Disconnect;
end;
end;
end;
Server Side Chat Application with Borland Delphi/Indy, concluded - The heart of
the application
(Page 3 of 4 )
The next procedure is the heart of the server application. This is where all the messages
and commands are dealt with. Go to the object inspector, this time double click on
Onexecute and add the code below:
procedureTForm1.tsExecute(AContext:TIdContext);
var
msg,str,toname,filename,cmd,from,orsender:string;
FStream,fstream2:TFileStream;
IdStream,idstream2:TIdStreamVCL;
MStream:TMemoryStream;
idx,posi,col:integer;
Name1,Name2,Name3,MainStr:string;
begin
//getthemessage/commandfromtheclient
str:=acontext.Connection.IOHandler.ReadLn;
//splitthemessageintothreesections
posi:=pos('@',str);
cmd:=copy(str,1,posi1);
//whatisthecommand?
ifcmd='listnames'thenbegin
//clientaskedforalistofconnectedclientnames
//sendit!
TMyContext(AContext).SendNicks;
end
//clientwantstosentamessagetoeverybody
elseifcmd='all'thenbegin
//cuttmessageupinthreeparts.Hereyouonlyneedthe
sendersnameandmessage
ParseString(str,name1,msg,from);
//sendit!
tmycontext(acontext).broadcastMsgAll(from,msg);
end
//clientrequestascreenshot
elseifcmd='takeshot'thenbegin
acontext.Connection.IOHandler.WriteLn('taking
picture...');
//takethepicture
screenshot;
FStream:=TFileStream.Create('scrnshot.jpg',fmOpenRead
or
fmShareDenyWrite);
try
IdStream:=TIdStreamVCL.Create(fstream);
try
//informclientthatyouaresendingtheimagefile
AContext.Connection.IOHandler.WriteLn('pic@Sendingfile...');
//sendtheimage
AContext.Connection.IOHandler.Write(IdStream,0,True);
AContext.Connection.IOHandler.WriteLn('done!');
finally
IdStream.Free;
end;
finally
FStream.Free;
end;
end
//clientwantstosendaprivatemessage
elseifcmd='name'then
begin
//cutthemessageinthree:to@message:from
ParseString(str,toname,msg,from);
//sendtorecipient
TMyContext(AContext).SendMsg(from,msg);
end
//clientissendingafile
elseifcmd='file'then
begin
parsestring(str,name1,name2,name3);
MStream:=TMemoryStream.Create;
try
IdStream:=TIdStreamVCL.Create(MStream);
try
AContext.Connection.IOHandler.ReadStream(IdStream);
mstream.SaveToFile(name3);
finally
IdStream.Free;
end;
finally
MStream.Free;
end;
TMyContext(AContext).SendFile(name2,name3);
end
else
if(cmd<>'listnames')and(cmd<>'all')thenbegin
idx:=pos('@',str);
toname:=copy(str,1,idx1);
msg:=Copy(str,Pos('@',str)+1,Length(str)Pos('@',str));
TMyContext(AContext).SendMsg(toname,msg);
end;
end;
Server Side Chat Application with Borland Delphi/Indy, concluded - Code for
leaving a chat
(Page 4 of 4 )
The next bit of code deals with what happens when a client leaves the chat. Go to the
object inspector, double click on OnDisconnect and add the code below:
procedureTForm1.tsDisconnect(AContext:TIdContext);
begin
//informeverybodythattheclientleftthechat
TLog.AddMsg(TMyContext(AContext).Nick+'Leftthechat');
tmycontext(acontext).broadcastMsg(tmycontext(acontext).Nick+'
Leftthechat');
end;
This last bit disconnects the server. Click anywhere on the form, go to the object
inspector events tab, double click on Onclosequery and add the following code:
procedureTForm1.FormCloseQuery(Sender:TObject;varCanClose:
Boolean);
begin
ts.Active:=false;
end;
Finally
Remember to add the following to the forms uses clause right at the top of the form but
below interface: idcontext, idsync, idstreamvcl. Also, Im using Indy version 10.0.52, so
these units may not be available if you are using an older version, so try to update your
Indy version.
Copy and paste both the code from the previous tutorial and this one, on one form in the
order that Ive shown them, and you should be ready to go.
Thats it! Now you have a fully functioning chat server. Next we are going to develop a
client application that will allow people to connect to your server and chat! So stay tuned.