 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Nicolai Waniek Guest
|
Posted: Wed Dec 07, 2005 1:24 pm Post subject: designing a server |
|
|
Hi!
I am to develop a server. At the moment we develop/build an ERP solution
that includes customer related stuff as well as distributor stuff. Apart
from that, the solution ships with a complete todo management layer. As the
todo stuff is pretty old (and buggy), we decided to transfer the todo
handling (creating new todo items and so forth). At the moment, we have a
form displaying all todo items a person has and he or she has to hit the
"refresh" button when a new todo item arrives to get noticed because we
don't have a client/server structure. Now everything related with the todo
list should be taken from the client up to the new server. Apart from that,
it should be easy to extend the server on new functions. And that's on
where i'm stuck:
- the server should be possible of TCP/IP related stuff like client
handling
- it should trigger processes in the background that are not related to the
TCP/IP layer
- it should do things that are related to the TCP/IP layer
I thought of the following concept because the server should work within a
service but within a regular executable as well:
There's a main server DLL that is loaded when the executable/service
starts. Within this loading, i create a TCP/IP "server" listening on the
TCP/IP connection. This tcp/ip server only listenes to the incoming streams
and does not work on them. Apart from that, the tcp/ip server provides an
interface to hook to like:
ITCPServer = interface
procedure RegisterTCPCommand(Command: Integer; IntfIID: TGUID);
procedure RegisterConnectionListener(const Intf: IInterface);
procedure RegisterStreamListener(const Intf: IInterface);
procedure SendStream(ClientID: Something; const Intf: ITCPStream);
end;
where ITCPStream could be something like
ITCPStream = interface
property Command: Integer;
property Stream: SomeStream;
end;
the extension modules now should implement something like this:
ITCPConnectionListener = interface
procedure OnConnect;
procedure OnDisconnect;
end;
ITCPStreamListener = interface
procedure OnNewStream(const StreamRef: ITCPStream);
end;
With this way I could easily develop all extension to the server within
different DLLs and if there's a new extension to be built, i simply deploy
the new dll, not a complete new version of the server itself.
The problem now is: The TCP/IP stuff could be handled this way, but what
about the other things? How to implement the server related stuff that does
not require the TCP/IP layer? We have source code of a few tryouts for the
new server where the other stuff is directly implemented within the service
and triggered by TTimer, but I don't really like this solution. I'd rather
prefer something in complete like this:
TTCPServer = class(TObject, ITCPServer)
{ all the TCP server procedures }
end;
TThreadManager = class()
{ all the thread related stuff goes here }
end;
TMainServerClass = class(TObject, ITCPServer)
TCPServer: TTCPServer
ThreadMgr: TThreadManager;
{ forwards ITCPServer stuff to TCPServer }
end;
The extension would then get loaded by some procedure like
LoadExtension(const ServerIntf: IInterface) where the MainServerClass gives
itself to the library. those librarys then register themselves to the
serverparts they require. As you may notice, i was inspired by the Delphi
Open Tools API.
The questions I have follow:
- Is it a good or bad idea to do it this way?
- Would there be a more better way to design it this way?
- How to handle the inter-thread operations with the interface instances?
for example, some module works within its own thread and composes an
instance of ITCPStream and wants the server to send this stream. Should the
implementors of ITCPStream implement a critical section within the get and
set methods? or how would i have to do those things.
- is there any literature/websites on such design decitions?
Best regards,
Nicolai Waniek
|
|
| Back to top |
|
 |
Martin James Guest
|
Posted: Wed Dec 07, 2005 7:38 pm Post subject: Re: designing a server |
|
|
"Nicolai Waniek" <"Nicolai dot Waniek at sphere71 dot com"> wrote
| Quote: | Hi!
I am to develop a server. At the moment we develop/build an ERP solution
that includes customer related stuff as well as distributor stuff. Apart
from that, the solution ships with a complete todo management layer. As
the
todo stuff is pretty old (and buggy), we decided to transfer the todo
handling (creating new todo items and so forth). At the moment, we have a
form displaying all todo items a person has and he or she has to hit the
"refresh" button when a new todo item arrives to get noticed because we
don't have a client/server structure. Now everything related with the todo
list should be taken from the client up to the new server. Apart from
that,
it should be easy to extend the server on new functions. And that's on
where i'm stuck:
- the server should be possible of TCP/IP related stuff like client
handling
- it should trigger processes in the background that are not related to
the
TCP/IP layer
|
HTTP? Then you can use CGI, ASP etc.
| Quote: | - it should do things that are related to the TCP/IP layer
|
Up to you and your protocol.
| Quote: | I thought of the following concept because the server should work within a
service but within a regular executable as well:
|
Should work as either - not a problem.
| Quote: | There's a main server DLL that is loaded when the executable/service
starts. Within this loading, i create a TCP/IP "server" listening on the
TCP/IP connection. This tcp/ip server only listenes to the incoming
streams
and does not work on them.
|
Well, at some point, the server has to submit client data to some form of
Protocol Handler, but I guess you are trying to seperate the PH from the
rest of the server. This seems like a reasonable idea.
| Quote: | Apart from that, the tcp/ip server provides an
interface to hook to like:
ITCPServer = interface
procedure RegisterTCPCommand(Command: Integer; IntfIID: TGUID);
procedure RegisterConnectionListener(const Intf: IInterface);
procedure RegisterStreamListener(const Intf: IInterface);
procedure SendStream(ClientID: Something; const Intf: ITCPStream);
end;
|
Such an interface implies a command-based TCP protocol. IMHO, this
compromises your design flexibility somewhat. The command parser should be
part of the plug-in PH, not part of the server. The server should call into
the PH with an instance of your stream that can be read and interpreted.
| Quote: | where ITCPStream could be something like
ITCPStream = interface
property Command: Integer;
property Stream: SomeStream;
end;
|
Well, something like that, anyway. You may find it better to pass the
ITCPStream, (IS) into the PH as a var parameter. This gives the PH the
option of hanging on to the IS instance across calls by storing it in its
own context and setting the passed var to nil. The calling thread can check
the var after the call and only free/repool it if the var is still assigned.
| Quote: | the extension modules now should implement something like this:
ITCPConnectionListener = interface
procedure OnConnect;
procedure OnDisconnect;
end;
ITCPStreamListener = interface
procedure OnNewStream(const StreamRef: ITCPStream);
end;
With this way I could easily develop all extension to the server within
different DLLs and if there's a new extension to be built, i simply deploy
the new dll, not a complete new version of the server itself.
|
I think this is a fine idea. I use a different 'coupling', (I'm struggling
for terms, now - all the usual ones for 'coupling' one bit of s/w to another
are used up for specifics:). My PH class is passed an array of TNotifyEvent
handlers to which it can attach. The handlers are all called with an
'IS-like' object that may contain client data and has a socket object linked
that has methods for replies to the client.
| Quote: | The problem now is: The TCP/IP stuff could be handled this way, but what
about the other things? How to implement the server related stuff that
does
not require the TCP/IP layer? We have source code of a few tryouts for the
new server where the other stuff is directly implemented within the
service
and triggered by TTimer, but I don't really like this solution.
|
Nah - TTimer - yech! Needs either the main thread or a thread with a WMQ.
Either best avoided, if possible.
| Quote: | I'd rather
prefer something in complete like this:
TTCPServer = class(TObject, ITCPServer)
{ all the TCP server procedures }
end;
TThreadManager = class()
{ all the thread related stuff goes here }
end;
TMainServerClass = class(TObject, ITCPServer)
TCPServer: TTCPServer
ThreadMgr: TThreadManager;
{ forwards ITCPServer stuff to TCPServer }
end;
The extension would then get loaded by some procedure like
LoadExtension(const ServerIntf: IInterface) where the MainServerClass
gives
itself to the library.
|
Yes. I have a similar design - the server passes a class called TSCG, which
has all the 'global' resources, classes, methods etc. that the PH plugin
needs and the PH plugin supplies a port on which the server should listen,
accept connections and create an instance of the PH class to process.
| Quote: | those librarys then register themselves to the
serverparts they require.
|
Hmm - I give them everything in the TSCG.
| Quote: | As you may notice, i was inspired by the Delphi
Open Tools API.
|
Not looked at that...
| Quote: | The questions I have follow:
- Is it a good or bad idea to do it this way?
|
I think it's not too bad <g>
| Quote: | - Would there be a more better way to design it this way?
|
There are a lot of issues, but it's probably better if you try and implement
some form of your server system now.
| Quote: | - How to handle the inter-thread operations with the interface instances?
|
Hehe....
| Quote: | for example, some module works within its own thread and composes an
instance of ITCPStream and wants the server to send this stream. Should
the
implementors of ITCPStream implement a critical section within the get and
set methods?
|
Well a CS, or the equivalent, will surely be needed somewhere. When
deciding on how to transport data around servers, look at both ends. How
will the data get into the server and how will the PH want it delivered.
With care, you can then design one class that satisfies both requirements
without copying the data a half-dozen times. Take an overlapped/IOCP server
for example: WSARecv wants an array of buffer/pointer pairs, your PH wants
to read a string/integer/whatever. The fun, (and, ultimately,
performance/efficiency), is designing a class that allows both without
avoidable copying.
On the paticular issue above, I would suggest that the CS gets involved when
the thread gets a ITCPStream instance from a pool queue, (a
producer-consumer queue containing a general pool of IS). The performance
of a CS is very dependent on the chances of contention. If there is no
contention, a CS is very quick. The CS that protects the pointers of a P-C
queue is locked for only as much time as is required to get the instance
from the queue. Once the thread has its IS instance, it can faff about with
it as much as it wants - nothing else has access to that particular instance
and so no further protection is required.
If you have one thread loading an IS while another can read the same
instance, you have to lock a CS for long periods. This is a disaster for
performance and invites deadlocks.
Once the IS is loaded up with stuff, it can be dispatched to the client.
How you do this within your interface is up to you, but you may well find
that the socket method that dispatches it will also require some locking to
implement a state-machine that executes the socket operation logic, (eg.
just as you are calling out to dispatch the IS, the socket class is called
from another thread because it has just disconnected, so changing its state.
If the disconnect gets in first, the IS dispatch call can return an
error/exception, if the IS dispatch gets in first, the later 'disconnect'
action has to clean up etc).
Rgds,
Martin
|
|
| Back to top |
|
 |
Nicolai Waniek Guest
|
Posted: Fri Dec 09, 2005 8:04 am Post subject: Re: designing a server |
|
|
Hi Martin,
Thank you for your detailed answer. It was very inspiring for me.
Unfortunately management - facing a server that complex not required just
in time - decided that i shouldn't develop the server now (but some time
later). Well, that won't stop me dealing with that subject at home
broadening my horizon. So maybe (but not in the next few days) i will come
back with another bunch of questions ;)
Regards,
Nicolai
|
|
| Back to top |
|
 |
Martin James Guest
|
Posted: Sat Dec 10, 2005 2:43 pm Post subject: Re: designing a server |
|
|
| Quote: | Hi Martin,
Thank you for your detailed answer. It was very inspiring for me.
Unfortunately management - facing a server that complex not required just
in time - decided that i shouldn't develop the server now (but some time
later). Well, that won't stop me dealing with that subject at home
broadening my horizon. So maybe (but not in the next few days) i will come
back with another bunch of questions ;)
|
It may be of some inspiration to hear how I manage my IS/buffers and
server-client sockets:
IS class: with a 'classic' memory-stream class, like TMemoryStream, there
are problems with the dynamic allocation of memory. Unless special
memory-manager sharing is provided, space allocated in the DLL cannot be
safely deallocated in the main app and vice-versa. 'Sharemem' and FastMM
options can be used to allow this to work in DLLs and packages, but it would
be nice to avoid this, so allowing PH DLL plugins written in any language to
be used. I get round this by separating the actual buffer space from the
IS. The IS contains an array of buffer references and this array is
initially empty. This array can be filled up from pools of fixed-size
buffer objects, all created at server startup in the main program space.
After startup, these buffer objects are not created or freed, merely
obtained from, and released back to, their pools. So, the PH DLL does not
have to make constructor calls, memory-management calls,
NewInstance/InitInstance etc etc. to get/release buffers, so getting around
the DLL MM issue and improving overall performance, (depooling a buffer from
the P-C queue that forms the pool takes about 100 asm instructions,
including those in the CS protecting the queue. 'Release' is similar).
There are six pools in the TSCG, containing buffers of various sizes - lots
of small ones, fewer large ones, eg [30000,4096,1024,512,128] buffers of
size [64,512, 2048,8192,32k,64k). There are calls in the TSCG to get the
buffers and, when 'done with' the buffer objects can be released back to
their pool with a 'release' method, rather than having to free them, (each
buffer object contains a private reference to its own pool, so 'release'
needs no parameters).
The IS instances and socket objects are also pooled.
When a 'receive' call is issued, (eg. WSARecv), the smallest-size buffer is
initially used, (64 bytes). PH for, say, telnet, ony ever get to use the
small buffer because only one/few chars come in at a time from human users.
If a receive call comes back with all 64 bytes filled, the next receive call
is issued with a buffer from the pool containing buffers of the next size
up - this allows the transfer rate to increase rapidly for a PH
implementing, say, a large HTTP upload. This allows a server to have 20,000
clients connected and provide good performance for them all without
requiring huge amounts of memory, (assuming that the duty-cycle for each
client is low:). Another advantage is that memory-use is capped. If a
massive load causes the pool to empty, threads requesting buffers are
blocked until buffers are released. This causes the server to slow up and
servicing clients takes longer until the load drops This is bad, but
nowhere near as bad/fatal as an 'Out of memory' exception.
When a PH wants to send stuff, calls write methods of the socket object. The
write methods do not, in general, do any actual writing. The first write
call causes an IS to be depooled, a buffer object of an appropriate size to
be loaded into the IS array and the data copied into it. Subsequent writes
just add to this buffer until it is full - another buffer is then depooled
into the next position in the array. Even this copying can be sometimes
avoided - a method is available that can depool a complete, new buffer into
the array and return a pointer to it, so allowing the buffer to be filled
directly. There are only 16 elements in the bffer array in the IS, so, if a
write fills the IS, it is dispatched and another IS depooled. When the PH
has finished writing, it calls a 'IOwriteCompleted' method that dispatches
the current IS. 'Dispatching' an IS causes it's internal array record data
to be set so that it conforms to that required for a 'WSASend' API call, (no
data copying is required for this). This call can then be made and
winsock/OS then sends all the buffers in one call. Assuming this call is
successful, the overlapped completion routine releases all the buffers and
the IS, ready for re-use.
Socket state management: The socket objects, (30,000 of them), are all
created at startup and pooled. As well as the winsock socket and methods
associated with it, the socket object also contains write calls for use by
the PH, sequence number methods for received buffers and a CS-protected
state-machine for socket management. This state-machine is neessary because
several threads may make calls into the socket object, eg. the thread that
implements timeouts may call in 'at the same time' as a PH is trying to
dispatch an IS 'at the same time' as a receive thread is calling in to say
that the socket has disconnected. All these 'events' must be handled
correctly for any combination, hence the CS and state-machine.
Just food for thought...
Rgds,
Martin
|
|
| Back to top |
|
 |
Nicolai Waniek Guest
|
Posted: Mon Dec 12, 2005 11:03 am Post subject: Re: designing a server |
|
|
Wow and thank you a lot!
Do you know of any good literature about the subject? I ordered one of the
last copies of "programming server-side applications for windows 2000" but
this is a bit out of date I think.
Best regards,
Nicolai
|
|
| Back to top |
|
 |
Danijel Tkalcec [RTC] Guest
|
Posted: Wed Dec 14, 2005 7:56 pm Post subject: Re: designing a server |
|
|
Hi,
you can use the RTC SDK, which uses the HTTP protocol and makes it possible
to write one code and compile as a stand-alone Server application, Windows
Service or as ISAPI extension (dll).
http://www.deltasoft.hr/rtc
--
Danijel Tkalcec
http://www.deltasoft.hr/rtc/author.htm
RealThinClient components
-------------------------------------------
* The Easiest way to build Internet-enabled applications
- Clients, Stand-alone Servers, ISAPI extensions -
| Quote: | Write and Call Remote Functions
Download and Upload Files
Single- and Multi-Threaded mode
Firewall friendly - all over HTTP
Stress-tested for highest stability
|
http://www.deltasoft.hr/rtc
http://www.deltasoft.hr/rtc/forum
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|