Enquiring Mind
2008-07-10 13:46:40 UTC
Hi,
I find it slightly bizarre that Delphi provides the native Delphi components
TTcpServer and TTcpClient for socket I/O, but Borland (aka Codegear aka
...) is said in books like Mastering Delphi 2005 to recommend the use of
Indy or other third party components instead. The criticism of the native
Delphi components goes back to the time they were first released (around the
time of Delphi 7 release), so surely Borland has had plenty of opportunities
to fix any real deficiencies in subsequent Delphi releases, or alternatively
explain to developers why the perceived deficiencies only exist due to
misunderstanding of the components. Misunderstanding is highly likely, due
to the sub-standard documentation. When I stepped through the on-line help
pages in the Contents section on socket components , I found that most had
general text book information about sockets but no Delphi-specific detail
and no examples, as if they had been written by someone not familiar with
the detail of the components. I also found that some pages stopped in mid
sentence, as if time had run out to do a proper job ! It makes it look as if
the developer of the components has left the company and there is nobody
left with the knowledge to fix the problem or produce adequate
documentation.
IMHO, since sockets are fundamental part of the operating system and of
applications, a rapid application development language that purports to
provide a high level interface to the operating system should cover Win32
sockets natively. There is a place for higher level components from third
parties, but the core library of the development language should provide a
high level interface to fundamental file-orientated socket operations that
is fully functional.
I have briefly studied the TTcpServer and TTcpClient components, and have
concluded that they can be used if only a few key points are explained (so
why are adequate explanations totally lacking in the D7 help files ? -
making these components possibly the worst covered in the whole of the D7
help files). Some of the issues that have come in for criticism in the NG's,
like the difficulty in overriding the TClientSocketThread.Execute method,
seem to me to be in fact non-issues. The fact that the Execute method is
declared in the public section of the TClientSocketThread class is a clear
indication that it is not intended to be overridden in descendent classes.
There are alternative ways of customising the Execute behaviour in a
descendent class, most notably by putting the work to be done in the
OnAccept event handler of the TTcpServer object.
Having said the TTcpServer and TTcpClient components are in actual fact
usable, I would nevertheless question the logic of their design.
1. The ownership relationship between the TClientSocketThread and
TCustomIpClient (client socket) objects seems to me to be the wrong way
round. The thread object owns the socket object, when it would make more
sense for the socket to own the thread. Why? Because when a new connection
is made, we need to create one and only one new client socket on the server,
which can then be used for file-orientated I/O just like a serial port. The
number of threads needed to handle the socket I/O depends on the application
and may need to be more than one - there are many situations where multiple
threads are needed to handle one socket. For example we could have one
thread to read messages from the socket and put them into a queue, another
to retrieve messages from the queue and write them to the socket, and a
third to handle the queue. However in the TtcpServer data structure the
list of current socket connections is represented by a list of
TClientSocketThread objects rather than by a list of TCustomIpClient
objects! A client socket object has to be retrieved from the
TClientSocketThread object that owns it.
2. In other languages (like Java) the Accept method of the server socket
returns a new client socket object, and it's then up to the application
program to create a thread object to handle the I/O through that socket, in
the same way that the application can create one or more threads to handle
I/O through a serial port file object. There is no need IMO for a socket to
be treated differently to any other asynchronous I/O channel. The Delphi
components seek to do too much thread management work behind the scenes, for
example fetching threads from a thread pool. The right place for this is in
a higher level class.
3. The TCustomTcpServer.OnAccept event handler runs in the
TClientSocketThread rather than in the TServerSocketThread thread where it
would seem to be more logical for it to run. I realise that this could block
the TServerSocketThread if the client socket that should be returned by the
OnAccept event contains some blocking code that is not in a separate
TClientSocketThread, but it would make the logic of the components much
clearer.
4. It would be preferable for a TCustomIpClient object to get a reference to
its associated TClientSocketThread object from a local field (assigned in
the constructor) rather than from a Threadvar variable. What's the reason
for using a threadvar variable?
5. The TServerSocketThread gives inadequate access to the collection of
threads it owns via the property:
property ThreadPool: TList read FThreadPool;
IMHO this is an example of very poor style for a property in a strongly
typed language, because a) an array rather than array component is returned,
thus creating opportunities for indexing errors; b) the array component type
is not defined, making a developer without access to the source code unable
to actually use the property safely. A much better interface would have
been:
property ThreadPoolComponentByIndex[Index]: TClientSocketThread read
GetThreadPoolComponentByIndex;
function GetThreadPoolComponentByIndex(Index: integer): TClientSocketThread;
begin
if (index<0) or Index>(FThreadPool.Count-1) then
raise exception.Create('...')
else
Result:= FThreadPool[Index] as TClientSocketThread;
end;
6. What does the following statement in TServerSocketThread.Destroy do?
while FThreadPool.Count > 0 do;
7. In TServerSocketThread.Execute what is the purpose of the statement?
Sleep(0);
8. In TCustomTcpServer.Open the function Listen, declared as taking one
parameter, is called without any parameters. Is this a valid Pascal
construct?
9. What is the purpose of the function TCustomTcpServer.Accept? It is not
called within the Sockets unit and internally creates a TCustomIpClient
object having an ownership that is incompatible with that of the
TCustomIpClient object created by the TClientSocketThread.Execute method.
10. The method TCustomTcpServer.Close contains the code
try
finally
LeaveCriticalSection(FThreadLock);
end;
What's the purpose of the empty try..finally construct?
TIA for any enlightenment on these tricky points.
EM
I find it slightly bizarre that Delphi provides the native Delphi components
TTcpServer and TTcpClient for socket I/O, but Borland (aka Codegear aka
...) is said in books like Mastering Delphi 2005 to recommend the use of
Indy or other third party components instead. The criticism of the native
Delphi components goes back to the time they were first released (around the
time of Delphi 7 release), so surely Borland has had plenty of opportunities
to fix any real deficiencies in subsequent Delphi releases, or alternatively
explain to developers why the perceived deficiencies only exist due to
misunderstanding of the components. Misunderstanding is highly likely, due
to the sub-standard documentation. When I stepped through the on-line help
pages in the Contents section on socket components , I found that most had
general text book information about sockets but no Delphi-specific detail
and no examples, as if they had been written by someone not familiar with
the detail of the components. I also found that some pages stopped in mid
sentence, as if time had run out to do a proper job ! It makes it look as if
the developer of the components has left the company and there is nobody
left with the knowledge to fix the problem or produce adequate
documentation.
IMHO, since sockets are fundamental part of the operating system and of
applications, a rapid application development language that purports to
provide a high level interface to the operating system should cover Win32
sockets natively. There is a place for higher level components from third
parties, but the core library of the development language should provide a
high level interface to fundamental file-orientated socket operations that
is fully functional.
I have briefly studied the TTcpServer and TTcpClient components, and have
concluded that they can be used if only a few key points are explained (so
why are adequate explanations totally lacking in the D7 help files ? -
making these components possibly the worst covered in the whole of the D7
help files). Some of the issues that have come in for criticism in the NG's,
like the difficulty in overriding the TClientSocketThread.Execute method,
seem to me to be in fact non-issues. The fact that the Execute method is
declared in the public section of the TClientSocketThread class is a clear
indication that it is not intended to be overridden in descendent classes.
There are alternative ways of customising the Execute behaviour in a
descendent class, most notably by putting the work to be done in the
OnAccept event handler of the TTcpServer object.
Having said the TTcpServer and TTcpClient components are in actual fact
usable, I would nevertheless question the logic of their design.
1. The ownership relationship between the TClientSocketThread and
TCustomIpClient (client socket) objects seems to me to be the wrong way
round. The thread object owns the socket object, when it would make more
sense for the socket to own the thread. Why? Because when a new connection
is made, we need to create one and only one new client socket on the server,
which can then be used for file-orientated I/O just like a serial port. The
number of threads needed to handle the socket I/O depends on the application
and may need to be more than one - there are many situations where multiple
threads are needed to handle one socket. For example we could have one
thread to read messages from the socket and put them into a queue, another
to retrieve messages from the queue and write them to the socket, and a
third to handle the queue. However in the TtcpServer data structure the
list of current socket connections is represented by a list of
TClientSocketThread objects rather than by a list of TCustomIpClient
objects! A client socket object has to be retrieved from the
TClientSocketThread object that owns it.
2. In other languages (like Java) the Accept method of the server socket
returns a new client socket object, and it's then up to the application
program to create a thread object to handle the I/O through that socket, in
the same way that the application can create one or more threads to handle
I/O through a serial port file object. There is no need IMO for a socket to
be treated differently to any other asynchronous I/O channel. The Delphi
components seek to do too much thread management work behind the scenes, for
example fetching threads from a thread pool. The right place for this is in
a higher level class.
3. The TCustomTcpServer.OnAccept event handler runs in the
TClientSocketThread rather than in the TServerSocketThread thread where it
would seem to be more logical for it to run. I realise that this could block
the TServerSocketThread if the client socket that should be returned by the
OnAccept event contains some blocking code that is not in a separate
TClientSocketThread, but it would make the logic of the components much
clearer.
4. It would be preferable for a TCustomIpClient object to get a reference to
its associated TClientSocketThread object from a local field (assigned in
the constructor) rather than from a Threadvar variable. What's the reason
for using a threadvar variable?
5. The TServerSocketThread gives inadequate access to the collection of
threads it owns via the property:
property ThreadPool: TList read FThreadPool;
IMHO this is an example of very poor style for a property in a strongly
typed language, because a) an array rather than array component is returned,
thus creating opportunities for indexing errors; b) the array component type
is not defined, making a developer without access to the source code unable
to actually use the property safely. A much better interface would have
been:
property ThreadPoolComponentByIndex[Index]: TClientSocketThread read
GetThreadPoolComponentByIndex;
function GetThreadPoolComponentByIndex(Index: integer): TClientSocketThread;
begin
if (index<0) or Index>(FThreadPool.Count-1) then
raise exception.Create('...')
else
Result:= FThreadPool[Index] as TClientSocketThread;
end;
6. What does the following statement in TServerSocketThread.Destroy do?
while FThreadPool.Count > 0 do;
7. In TServerSocketThread.Execute what is the purpose of the statement?
Sleep(0);
8. In TCustomTcpServer.Open the function Listen, declared as taking one
parameter, is called without any parameters. Is this a valid Pascal
construct?
9. What is the purpose of the function TCustomTcpServer.Accept? It is not
called within the Sockets unit and internally creates a TCustomIpClient
object having an ownership that is incompatible with that of the
TCustomIpClient object created by the TClientSocketThread.Execute method.
10. The method TCustomTcpServer.Close contains the code
try
finally
LeaveCriticalSection(FThreadLock);
end;
What's the purpose of the empty try..finally construct?
TIA for any enlightenment on these tricky points.
EM