- abstract : The architecture of the ScktComp socket CLASSes
- key words : Sockets, WinSock, Windows Socket, ScktComp, socket UML class diagram, socket file transfer, tClientSocket, tServerSocket, HTTP server
- software used : Windows XP, Delphi 6
- hardware used : Pentium 1.400Mhz, 256 M memory, 140 G hard disc
- scope : Delphi 1 to 8 for Windows
- level : Delphi developer
- plan :
1 - Socket Programming in Delphi
Sockets were first introduced under Unix. Windows later, much later, followed. Since Windows has no easy multitasking library, a consortium defined a Windows Socket specification which could use the Windows messages for socket scheduling. Third parties offered libraries (Trumpet was one of those). Later Windows included its own WinSock version. And later Windows offered threading, which allows blocking sockets without message notifications.Therefore it is a small wonder that Delphi had to change its VCL components several times to allow networking. To make a long story short:
- NetManage components were first offered. This component set
- allows the whole TCP/IP gamut: basic client and server, but also HTTP client, FTP client and server, POP3 reader etc
- was licensed by Borland but the source never provided
- many bugs were reported
- allows the whole TCP/IP gamut: basic client and server, but also HTTP client, FTP client and server, POP3 reader etc
- at some time, we heard about John KASTER writing a book about socket programming. I never saw this book (and I believe nobody else did). I seem to remember that he then was hired by Borland, and there were rumors about rewriting the Socket component suite. John KASTER then managed the very useful Borland Community site, and we could watch him proudly grinning at us every time we visited this site. Anyway, he sure had plenty of web programming and administration on his hands. A pitty that the Delphi programs used for this site were not published. But John KASTER also published nice papers available on the community site.
- in any case, NetManage remained on the Palette, but Delphi added tClientSocket and tServerSocket components, with source code this time
- thereafter, NetManage was quietly put aside, and the Indy component suite placed on the component palette
2 - The ScktComp unit
2.1 - Required Classes
Communication between a Server and a Client needs:- on the Client side, a socket for connecting, reading and writing
- on the Server side
- a socket for welcoming the Clients
- a new socket for each Client which has established a connection
- a socket for welcoming the Clients
In any case, a component library will naturally include:
- a Client class, with all the connection, read and write functionality
- a Server class, with the welcoming socket and a list of sockets handling the connected clients
- the windows message mechanism is the same for the Server and the Client. So it seems natural to place the whole event processing in a base class
- the library users should be able to choose between several possibilities:
- use windows messaging asynchronous mode, or blocking mode
- in blocking mode, threading should be offered
- reception and emission could be encapsulated in Streams
- use windows messaging asynchronous mode, or blocking mode
2.2 - The ScktComp Unit
The tClientSocket and tServerSocket have been place in a single 90 K unit, which contains 12 classes. Splitting those into 6 units helped us better understand the relationships between those classes. Basically there are two layers:- the first layer is concerned with the Winsock encapsulation:
- start the wsa library (Windows Sockets Asynchronous= Winsock.Dll)
- call the functions (Socket, Bind, Send etc)
- handle the notifications
- start the wsa library (Windows Sockets Asynchronous= Winsock.Dll)
- the second layer handles the user point of view:
- store the parameters (IP address, blocking or asynchronous mode ...)
- call the main functions (Connect, Send, Recv)
- receive the events
- store the parameters (IP address, blocking or asynchronous mode ...)
2.3 - The WinSock encapsulation
- tCustomWinsocket (40 K)is the base CLASS. All functions, whether Client or Server are included. Some functions are only included to update internal states but the calls to the WinSock library is performed in the derived Client or Server CLASS. This is the case, for instance, for Accept, where:
- this CLASS contains only an empty method
- the derived tServerWinSocket contains the real Winsock.Accept call
Those Windows message are handled by a Window created by the tCustomWinsocket using the Delphi AllocateHwnd call. This simply calls the RegisterClass, CreateWindow routines, which any reader of Charles PETZOLD Windows 3 programming book certainly remembers (THE book that really started Windows).
- this CLASS contains only an empty method
- tClientWinSocket (2 K) adds the Client specific part (is the Client blocking or not, call Connect)
- tServerWinSocket (13 K) add the Server specific part: Listen, Accept.
The tServerWinSocket also includes a tList of tServerClientWinSocket (which is very small), and all ServerClientEvents are delegated to the tServerWinSocket. This allows the final user to only deal with the tServerWinsocket, using events with the specific tServerClientWinsocket parameter.
Each tServerClientWinSocket has a back link to the tServerWinSocket, which allows to manage the tList addition and deletion from the tServerClientWinSocket side
To add threading capabilities, the following CLASSES (17 K) have been added:
- tServerAcceptThread: this thread is used to isolate the tServerWinSocket in a thread. This is normally used only if the Server is in blocking (not Windows Asynchronous) mode
- tServerClientThread: each incoming Client can be handled in its own thread. Each tServerClientWinSocket has a link to its thread. And the tServerWinSocket contains a tList of all those threads
And to read and write socket as streams, the following tStream descendent is included:
- tWinSocketStream (4 K)is a tStream descendent. It contains a tCustomWinSocket member, and the Read and Write stream methods. Read and Write use the Windows Overlapped mode. This CLASS is not used anywhere else in ScktComp, but used in the SvrHTTP HTTP Web server unit.
Also not that the base tCustomWinSocket has Stream possibilities (the tCustomWinSocket.SendStream function) but the stream is just used to hand over the bytes to send (not to perform read write synchronization like the tWinSocketStream)
- the tCustomWinSocket contains an FSocketLock critical section which is used
- when we access socket properties (local or remote Address, Host, Port
- when we Send or Recv
- when we Disconnect
- when we access socket properties (local or remote Address, Host, Port
- the tServerWinSocket protects the tServerClientWinSocket list with a FListLock critical section
2.4 - The User classes
The user is mainly concerned with- initializing the connection parameters,
- connect, read and write data,
- and get events if the asynchronous mode.
- tAbstractSocket and its associated tCustomSocket (10 K), essentially containing the parameters, and Open and Close methods
- tClientSocket (3 K) essentially promotes the visibility of the client tCustomSocket properties, methods and events
- tCustomServer and tServerSocket (7 K) also promote the server tCustomSocket fields
2.5 - The UML class diagram
Base on the previous analysis, we can draw the following diagram:
|
Note that
- the blue classes are Client specific
- the green classes are Server specific
- we framed in red the classes placed on the Delphi Palette
2.6 - ScktComp usage
The ScktComp is used in the following VCL units:- ScktMain which is some kind of socket monitoring application
- sConnect used by the n-tier database architecture (Midas)
- SvrHTTP which is an HTTP server
- SvrLog which journals the HTTP activities
There is a Chat demo application in the DEMO directory of Delphi. This is a very elegant sample, since all the people taking part in a chat use the same application. The user either start with "Listen" or "Connect" and the tForm1 contains an IsServer Boolean which tells whether the application should use the tServerSocket or the tClientSocket. And all is contained in 7 K. I would be reluctant though to use this application as a Socket programming first example, since most Socket application are disymmetric by nature: there is a Server application handling server tasks, and a separate Client application used by all the clients dedicated to client business. The server receives an HTML page requests and uses CGI, ISAPI, ASP etc to build the page and send it over to the Client. On the other side, the Client receives the page and renders it, analyzing Style Sheets parameters and monkeying around with Visual Basic Script, Java Script or ActiveX components. Quite different jobs.
The tClientSocket and tServerSocket are also published on the Palette to let us build Socket application which:
- implement any standard TCP/IP protocol (HTTP, SMTP, POP3, FTP etc)
- implement our own protocols (sending and receiving files etc) or protocols which are not implemented by Indy, or special protocols (CVS, Spidering, Peer to Peer etc)
We will now build a simple file transfer application as an example.
3 - Socket File Transfer
3.1 - The Goal
The Client will send a file name, and the Server will send the file back. To avoid typing errors, the Client selects the file names in a tFileListBox (so in effect he has already access to the files, but let's pretend that he cannot grab them directly).The Server loads the file and uses Send to transfer the file.
3.2 - The Client
The Client application uses a tClientSocket. We have the following possibilities:- "connect" starts the connection
- "disconnect" stops it
- clicking on a filename in the tListBox will send the file name to the Server, using SendText
3.3 - The Server
The "listen" and "disconnect" buttons allow to start or stop the Server.The requested file will be read using a tFileStream and sent over to the client with tServerSocket.SendStream or tServerSocket.SendBuff. Since several Clients transfer might overlap, we have to use a separate tFileStream for each incoming Client. We chose to save those tFileStreams as pointers in the tListBox.Objects. The key we chose to identify each Client is the ServerClientSocket handle.
Here is the tServerSocket.OnAccept handler:
function f_socket_key(p_c_server_client_socket: TCustomWinSocket): String; begin Result:= IntToStr(p_c_server_client_socket.SocketHandle) end; // f_socket_key
procedure Tserver_form.ServerSocketAccept(Sender: TObject; |
And when the fd_read notification arrives, we use the following tServerSocket.OnRead handler:
procedure Tserver_form.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); const k_buffer_size= 4096; var l_text: String; l_file_name: String; l_listbox_index: Integer; l_c_file_stream: tFileStream; l_size, l_start_position: Integer; l_sleep: Integer; l_buffer: array[0..k_buffer_size- 1] of byte; // -- now locate the file // -- find or create the stream if l_listbox_index< 0 with l_c_file_stream do // -- send the bytes if l_sleep= 0 l_to_send:= l_c_file_stream.Read(l_buffer, k_buffer_size); Application.ProcessMessages; |
This procedure is unnecessarily complex, because we wanted to use packet transfers with delay in order to display several Clients in action. If we use a zero delay, only SendStream is called. Notice however that Delphi frees the tFileStream when the transfer is over. We personally prefer to let the unit which calls Create also call Free, but this is not the case here.
3.4 - Transfer example
Here is a snapshot of 2 clients downloading files from the server:
In this example
- we started the Server, with a 100 ms delay to be able to visualize the packet transfers
- we started the first client
- while the transfer was still in progress, we started a second client
- the second request triggers a recursive OnRead call
- the second transfer request will send all its packets
- the first transfer will then send the remaining packets
- either use threads
- or use some kind of round robin sending, which would be effective as long as there are more than one alive client requests
4 - Download the Sources
You can download the Client and Server projects (not very useful, but anyway...):- file_transfer_client.zip: requesting and saving files (21 K)
- file_transfer_server.zip: sending the files to clients (21 K)
Those .ZIP files contain:
- the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters
- all units (.PAS) for units
- are self-contained: you will not need any other product (unless expressly mentioned).
- can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
As usual:
- please tell us at fcolibri@felix-colibri.com if you had some problem downloading the file, or found some bug. Resulting corrections will be helpful for other readers
- we welcome any comment, criticism, enhancement. Just send an e-mail to fcolibri@felix-colibri.com.