The C++ framework for developing highly scalable, high performance servers on Windows platforms.

Programming Concepts

Collaboration diagram for Programming Concepts:

Detailed Description

It's easier to understand how to use this code if you try and understand a little about the way it has been written...

This section contains links to the code that implements some of the various programming concepts that are fully detailed here.

Some reasoning behind some of the decisions that were made during the development of this code.


Modules

 Admin Library
 The Admin library is a collection of header files that all other libraries in the JetByte Tools suite use. Header files from the Admin library control things such as which warnings are disabled (Warnings.h) and which version of windows you're targetting the code for (see here for details of just how complex that simple thing could be...).

This section contains links to the code that implements this concept.
 Warnings level 4
 All code builds at warning level 4. The Admin library contains a header file, Warnings.h that disables warnings that "don't matter", things like C4786 - "identifier was truncated to '255' characters in the debug information" and C4511 - "copy ctor could not be generated", etc.

This section contains links to the code that implements this concept.
 Unicode and Non-Unicode Builds
 All of the code builds as either Unicode or Non-Unicode. This allows us to be maximally efficient on our primary platform (Windows NT/XP, etc) and yet still be able to use the code on older platforms (Windows 95) if required.

This section contains links to the code that implements this concept.
 Precompiled Headers
 All of the code builds using precompiled headers for speed of compilation using pragma hdrstop to detail where the precompiled header stops. There's also always a 'no precompiled header' build which is used to make sure that include file dependencies are 'correct' and kept to a minimum. In a nutshell, putting pragma hdrstop in a source file that isn't compiled with /Yc or /Yu has no effect at all. If you have /Yu set for the file then hdrstop tells the compiler to throw away everything before the line on which hdrstop appears and insert the precompiled header instead. If /Yc is set for the file then hdrstop means save all of the compiled state for everything up to the line on which hdrstop appears as the precompiled header. The trick is using /Yc and /Yu without the optional header file name; just check the 'use' or 'create' radio button and leave the 'through header' edit box blank (or edit the dsp). See here for more details.

This section contains links to the code that implements this concept.
 Debug logging and tracing
 We use minimal debug tracing in the form of calls to JetByteTools::Core::Output and (even less often) JetByteTools::Core::OutputEx. If an application requires a trace log for support purposes then we implement one, probably using asynchronous file I/O to write the log using the JetByteTools::IO::CAsyncFileLog logger. A support log is NOT, in our opinion, the same as a debug trace, it's part of the application and should be designed with as much care as the rest of the application. Debug trace files tend to accumulate rubbish over time as trace statements are put in to help debug problems. See: here and here for more details.

This section contains links to the code that implements this concept.
 Log files and the pluggable logging system
 Having explained our lack of enthusiasm for Debug tracing you might be surprised to find that there is a fairly complex pluggable logging system underneath our basic JetByteTools::Core::Output calls. This can be customised to provide a high performance trace log for supporting software that's been written with the framework. You can either use one of the many implementations of JetByteTools::Win32::ILogMessages or write your own and plug it in. Alternatively you can take a look at JetByteTools::Win32::CDebugTrace and write something similar to provide a separate system trace and debug log system, or use the log writers for other purposes...

This section contains links to the code that implements this concept.
 Interfaces
 These abstract base classes define Interfaces that concrete classes implement. They exist so that we can reduce coupling in the system. Since objects interact with each other through abstract interfaces and they don't care about the implementation of those interfaces we're free to plug in any object that implements the interface, such as a Mock Object, or alternative implementation. This allows us to evolve the system in a gradual manner; isolated objects can be replaced by new objects that implement the same interfaces without causing changes to ripple through the entire system due to unexpected coupling. See: here and here for more details.

This section contains links to the code that implements this concept.
 Protected destructors on abstract base classes
 Many of the abstract base classes that we use as interfaces have protected destructors rather than the more usual public virtual destructors. This is because we never delete an instance of the object through a pointer to the interface concerned and by making the destructor protected we make such deletion impossible). See here for more details.

This section contains links to the code that implements this concept.
 Exceptions
 Most of this code uses exceptions for the reporting of errors. Most functions validate their input and throw exceptions on bad input. Most fuctions throw exceptions if there are errors that they can't handle. There are a surprisingly small number of exception classes used, this is by design. Usually there isn't a great deal that the calling code will want to do that depends on why the code failed, if there's that kind of choice available then the code that's being called usually exposes that choice. This means that if an exception occurs it's often not caught by anything except the final exception handler in the system, the one that lives at process or thread boundaries and, generally, pretty much all that will do is log the failure. This is all by design and all works well for writing reliable code. See here, here, here and here for more details.

This section contains links to the code that implements this concept.
 Exception Specifications
 Whilst exception specifications in C++ can, at first glance, seem like a good idea, they're not. The problem is that, unlike Java, the exception specification is not enforced at compile time, they're just a runtime thing. If an exception that isn't in the exception specification is thrown by a function then unexpected() may (or may not if you're Visual C++) be called. As such we view exception specifications as of limited use in C++. However: we do use a 'no throw' (throw()) exception specification to indicate that a function does not throw exceptions. We rarely use a 'no throw' specification to indicate that YOU should not throw exceptions, simply because we don't trust you ;)

This section contains links to the code that implements this concept.
 Iteration
 

This section contains links to the code that implements this concept.
 Monitoring
 Many classes support a monitoring interface. This allows us to see what's going on and, perhaps, hook these classes up to performance counters that can be viewed by perfmon, etc.

This section contains links to the code that implements this concept.
 Patterns with a small 'p'
 Patterns are a great way of being able to communicate clearly about a programming concept, but some people get a little too hung up on them. We use patterns sparingly and, hopefully, only where appropriate...
 Lock Free Algorithms
 We've toyed with using lock free data structures to improve the performance of the socket and buffer allocators but our initial performance profiling didn't show that great an improvement so we haven't done a great deal of work in this direction... See here for more details.

This section contains links to the code that implements this concept.
 Templates
 We don't use templates very often (apart from the STL, of course!). This is deliberate. We feel that often templates are overused and often the code using them can be more complex than need be. And, we're constrained by the fact that we need to support old compilers that often don't allow us to use templates when we'd like to... We do use templates where we can and where we feel that they add value to the construction and maintenance of the code.
 Unit Tests
 Where possible we've tried to make all code in the system testable. The idea is that rather than testing the system as a whole, you can test it in pieces. This can make debugging easier as you don't have to manipulate the entire system to get it into the state you want, you can just set up a test that gets the piece of code that you need to test into the correct state... Of course, testing requires that you write more code, and writing code takes time, so we've tried to focus our tests on the places that give us the best 'bang for the buck'. This means that coverage isn't as good as we'd like it to be, but the important test, the first test, is there, in most cases. Moving forward it would be good if each time a bug is located a test is first constructed that duplicates the bug and then the bug is fixed so the test passes. Working in this way quickly builds a useful set of regression tests for the code.
Note: just because the code that you have in front of you doesn't seem to have any unit tests included it doesn't mean that they don't exist. The normal licensed version of the code doesn't ship with our unit tests. If you'd like to license them, get in touch.
 Mock Objects
 These objects implement various Interfaces in a way that's useful for testing. Rather than using a real implementation you use a Mock which allows you to instrument and manipulate the object in ways that are useful for testing. See here for more details.


Generated on Sun Sep 12 19:06:46 2021 for The Server Framework - v7.4 by doxygen 1.5.3