Latest release of licensed socket server code: 6.2

| 6 Comments

The latest release of The Server Framework is now available. This release includes the following changes.

The following changes were made to the libraries.

Admin Library - 6.2

  • We now support Visual Studio 2010. At present the code has only been tested with the beta and release candidate versions.
  • New build configuration options. All of these are enabled by defining the option to 1 in Config.h and disabled by defining them to 0; the default state if you do not do anything in Config.h is shown for each option:
    • JETBYTE_TINY_XML_2_5_3 - enabled by default. From this release TinyXML version 2.5.3 is available and is used by default in XMLTools. If you would prefer to continue to use TinyXML 2.5.2 then define JETBYTE_TINY_XML_2_5_3 to 0 in your Config.h
    • You can enable the FILE_SKIP_SET_EVENT_ON_HANDLE flag of SetFileCompletionNotificationModes() for stream sockets, datagram sockets, JetByteTools::IO::CAsyncFileWriter or JetByteTools::IO::CAsyncFileReader using the following configuration options:
      • JETBYTE_PERF_STREAM_SOCKETS_SKIP_EVENT_ON_HANDLE - enabled by default.
      • JETBYTE_PERF_DATAGRAM_SOCKETS_SKIP_EVENT_ON_HANDLE - enabled by default.
      • JETBYTE_PERF_FILE_WRITER_SKIP_EVENT_ON_HANDLE - enabled by default.
      • JETBYTE_PERF_FILE_READER_SKIP_EVENT_ON_HANDLE - enabled by default.
      This potentially provides a very small performance improvement. You can turn off this functionality by defining the appropriate configuration option to 0 in your Config.h. See here for more details.
    • You can enable the FILE_SKIP_COMPLETION_PORT_ON_SUCCESS flag of SetFileCompletionNotificationModes() for stream sockets, datagram sockets, JetByteTools::IO::CAsyncFileWriter or JetByteTools::IO::CAsyncFileReader. using the following configuration options:
      • JETBYTE_PERF_STREAM_SOCKETS_SKIP_COMPLETION_PORT_ON_SUCCESS - enabled by default.
      • JETBYTE_PERF_DATAGRAM_SOCKETS_SKIP_COMPLETION_PORT_ON_SUCCESS - DISABLED by default.
      • JETBYTE_PERF_FILE_WRITER_SKIP_COMPLETION_PORT_ON_SUCCESS - DISABLED by default.
      • JETBYTE_PERF_FILE_READER_SKIP_COMPLETION_PORT_ON_SUCCESS - enabled by default.
      This provides a small performance improvement in situations where overlapped I/O operations complete straight away as the completion is handled on the thread that initiated the I/O operation rather than being marshalled to an I/O thread. You can turn off this functionality by defining the appropriate configuration option to 0 in your Config.h. Note that this option is only enabled by default for stream sockets. You can, of course, enable the option by defining the appropriate configuration option to 1 in your Config.h BUT there is a known bug with enabling this option for UDP sockets on Windows Vista and Windows 7 which may cause resource leaks and we have been unable to exercise the new code for the JetByteTools::IO::CAsyncFileWriter during testing and so have not enabled the code at this time. See here for more details.
    • JETBYTE_DEBUG_BREAK_ON_CONNECTIONS_ACTIVE_DURING_DESTRUCTION - enabled by default. If enabled a breakpoint is triggered if you are in the debugger and there are connections active when a connection manager is destroyed. The connection manager base class cannot wait for these connections to complete as the callback that it uses to notify your code that connections have completed will likely have been destroyed by the time this code is executed. You should always have waited for a shutdown to complete and not created any connections again after that point before destroying your connection manager. You CAN and SHOULD wait for shutdown in your own derived class's destructor! Enabling this option helps you spot situations where you are NOT waiting correctly in your own code.
    • JETBYTE_DUMP_SOCKET_READ_AND_WRITE_DATA_TO_DEBUG_LOG - disabled by default. When enabled the low level socket I/O code dumps complete details of all data sent and received to the default debug trace log. This can be useful when developing connection filters. Note that this should not be left enabled as the debug generation is time consuming.
    • JETBYTE_DISABLE_VS2005_DEBUG_UUIDLIB_CHECK - has been removed. It was too difficult to detect installations that needed it and you can fix the problem that it tried to work around with the hotfix that we link to from here.
    • We now create typedefs for DWORD and BYTE in Types.h. This means that we don't need to include <wtypes.h> quite so often.
Win32 Tools Library - 6.2
  • Added JetByteTools::Win32::CAtomicBool - a simple boolean class which provides an atomic way to query and toggle the value using InterlockedCompareExchange()
  • Fixed a memory leak bug in JetByteTools::Win32::CCallStack where we were deleting an array with delete rather than delete [].
  • Added JetByteTools::Win32::CConditionalSmartPointer - a smart pointer which can be told, during construction or assignment, not to act as a smart pointer. This can be useful if sometimes you own some memory and sometimes you don't and you want to hold the memory in a smart pointer when you DO own it...
  • Breaking Change All instances of InitiateShutdown() have been changed to BeginShutdown() this is to remove the issue with the Win32 InitiateShutdown() function which is conditionally defined as either InitiateShutdownA() or InitiateShutdownW() and which could then cause issues. The following classes are affected:
    • JetByteTools::Win32::CDirectoryChangeMonitor
    • JetByteTools::Win32::ICollectableThreadPool
    • JetByteTools::Win32::CIOCPWorkerThread
    • JetByteTools::Win32::CThreadedCallbackTimerQueue
    • JetByteTools::Win32::CThreadPool
    • JetByteTools::Win32::CThreadPoolCollection
  • Added JetByteTools::Win32::ICompressData an interface that represents a stateless data compressor/decompressor. At present the only implementation of this is JetByteTools::ZLib::CDataCompressor.
  • JetByteTools::Win32::ISingleWriterMultipleReaderLock::Reader now takes a const ISingleWriterMultipleReaderLock reference in its constructor as entering to read is a logically const operation for most classes that would be using the ISingleWriterMultipleReaderLock lock.
  • Added JetByteTools::Win32::IWaitable::IsSignalled() which calls Wait() with a timeout of zero to determine if the object is currently signalled. This gets around the need to cast the zero to JetByteTools::Milliseconds to avoid ambiguity.
  • Fixed JetByteTools::Win32::CNamedIndex::FindOrAdd() so that it only checks to see if the index collection is locked if it needs to add a new index.
  • Added an optional null pointer dereference check to JetByteTools::Win32::TReferenceCountedSmartPointer::operator->(). If JETBYTE_REFERENCE_COUNTED_SMART_POINTER_THROW_ON_NULL_REFERENCE is enabled then we throw an exception if the reference is null at point of use.
  • Added JetByteTools::Win32::CRegistryConfiguration an implementation of JetByteTools::Win32::IConfiguration which reads data from a specify set of registry keys.
  • Added JetByteTools::Win32::CSmartBool::operator bool().
  • Added a new overload to JetByteTools::Win32::CStringConverter::AtoBSTR().
  • Added some new conversion formats to JetByteTools::Win32::CSystemTime. You can now pass date/times in any of the following string formats to the JetByteTools::Win32::CSystemTime constructor.
    • YYYYMMDD HHMMSS
    • YYYYMMDD HHMMSSMMM
    • YYYY-MM-DD HH:MM:SS
    • YYYY-MM-DD HH:MM:SS.MMM
  • Added JetByteTools::Win32::CSystemTime::GetAsHHMMSSMMM().
  • Added JetByteTools::Win32::CSystemTime::GetAsDatabaseDateTimeStamp() which returns the date/time as YYYY-MM-DD HH:MM:SS.MMM
  • Added JetByteTools::Win32::CSystemTime::ContainsDate() which returns true if the date portion of date/time is valid.
  • Breaking Change JetByteTools::Win32::CTempDirectory::RemoveContents() now removes the contents of sub-directories as well as files within the temporary directory.
  • Breaking Change JetByteTools::Win32::CThread::WasStarted() has been removed and replaced by JetByteTools::Win32::CThread::IsRunning(). This is because instances of JetByteTools::Win32::CThread can now be restarted after they have been shutdown.
  • Fixed a rare memory leak with JetByteTools::Win32::CThreadedCallbackTimerQueue. We now use JetByteTools::Win32::CConditionalSmartPointer to hold the timer queue so that failed construction of the JetByteTools::Win32::CThreadedCallbackTimerQueue after the timer queue has been created causes the timer queue to be destroyed correctly.
  • Added conditional code for VS2002 and VS2003 for JetByteTools::Win32::GetStringLengthAsDWORD() and all other versions so that we are using the templated version for VS2005 and later and only using custom code for those compilers that don't support the template code. This is to make it easier to move away from the custom code when we drop support for the older compilers.
  • JetByteTools::Win32::CWaitableCounter::Increment() now returns true if the value was incremented from zero.
  • JetByteTools::Win32::CWaitableCounter::Decrement() now returns true if the value was decremented to zero.
  • Added JetByteTools::Win32::CWaitableCounter::Add() which allows you to add an arbitrary amount to a waitable counter and correctly toggles the various events if the counter passes through zero.
  • Added JetByteTools::Win32::CWaitableCounter::Subtract() which allows you to subtract an arbitrary amount from a waitable counter and correctly toggles the various events if the counter passes through zero.
  • Added JetByteTools::Win32::CWaitableCounter::Incrementer::LeaveIncremented().
  • Added JetByteTools::Win32::CWaitableCounter::Decrementer::LeaveDecremented().
  • JetByteTools::Win32::CException now strips trailing new lines from error messages obtained by JetByteTools::Win32::GetLastErrorMessage().
  • Added JetByteTools::Win32::CSEHException::GetCallStack() which returns the callstack that was active when the JetByteTools::Win32::CSEHException was generated.
I/O Tools Library - 6.2
  • We now check for numeric overflow when we're calculating the total size of the buffers in JetByteTools::IO::CMultiBufferHandle::GetUsed().
  • Fixed a bug in JetByteTools::IO::CAsyncFileWriter::HandleWriteRequest() which could cause a race condition between the buffer allocator's destruction and the last operation that we perform on a buffer that has been allocated from it. Basically we signal that the last write is complete after we've finished fiddling with the buffers that we've been using.
  • Added conditional code to allow FILE_SKIP_SET_EVENT_ON_HANDLE and FILE_SKIP_COMPLETION_PORT_ON_SUCCESS to be enabled on the file handle used by JetByteTools::IO::CAsyncFileWriter. See the JETBYTE_PERF_FILE_WRITER_SKIP_EVENT_ON_HANDLE and JETBYTE_PERF_FILE_WRITER_SKIP_COMPLETION_PORT_ON_SUCCESS configuration options in Config.h
  • Added conditional code to allow FILE_SKIP_SET_EVENT_ON_HANDLE and FILE_SKIP_COMPLETION_PORT_ON_SUCCESS to be enabled on the file handle used by JetByteTools::IO::CAsyncFileReader. See the JETBYTE_PERF_FILE_READER_SKIP_EVENT_ON_HANDLE and JETBYTE_PERF_FILE_READER_SKIP_COMPLETION_PORT_ON_SUCCESS configuration options in Config.h
  • Fixed JetByteTools::IO::CBufferAllocator::RequestUserDataSlot() so that it only checks to see if the index collection is locked if an exception is thrown whilst manipulating it.
  • Removed <wtypes.h> from IAsyncIOStream.h
  • Added JetByteTools::IO::CInOrderBufferList::SetInitialSequenceNumber() and allow the initial sequence number to be passed into the constructor.
  • Fixed JetByteTools::IO::CInOrderBufferList sequence number wrap issues.
  • Breaking Change All instances of InitiateShutdown() have been changed to BeginShutdown() this is to remove the issue with the Win32 InitiateShutdown() function which is conditionally defined as either InitiateShutdownA() or InitiateShutdownW() and which could then cause issues. The following classes are affected:
    • JetByteTools::IO::IOPool
Socket Tools Library - 6.2
  • Added JetByteTools::Socket::CCompressingDatagramSocketConnectionFilter - a filter which uses an instance of JetByteTools::Win32::ICompressData to compress the data stream.
  • Breaking Change Separated servers and connection managers that support filtering from those that do not. You now need to select a server or connection manager that supports filtering if you want to use connection filters with it. This removes the connection filtering code from those servers that do not use it and provides a slight performance improvement. This was a fairly invasive change as to avoid code duplication we switched to using templates for the server and connection manager base classes. The new classes that support filtering are:
    • JetByteTools::Socket::CFilteringStreamSocketConnectionManager
    • JetByteTools::Socket::CFilteringStreamSocketServer
    • JetByteTools::Socket::CFilteringStreamSocketServerEx
    • JetByteTools::Socket::ICreateFilteredStreamSocketConnections
  • Breaking Change to JetByteTools::Socket::IFilterStreamSocketConnections. Most of the function signatures have changed because we've done away with the FilterProcessing enum and now indicate that a filter has 'swallowed' a call by simply not returning the buffer that it was passed in. Filter methods now generally return a buffer which is passed on to the next filter in the chain and eventually on to the client code callback. A filter can return any buffer, not just the buffer that it was passed, but it should be sure to copy the sequence number if it replaces the input buffer with one of its own. By returning null the filter can signal that the call stops there, it may pass buffers on to later filters directly using JetByteTools::Socket::IManageStreamSocketConnectionFilters or simply swallow the request. Note that only JetByteTools::Socket::IFilterStreamSocketConnections::RequestRead() differs in that it returns a bool to indicate if processing should continue; this is due to the fact that the input buffer is optional anyway and a null buffer is no indication of anything special. This change has knock on changes to the following classes as they implement or deal with JetByteTools::Socket::IFilterStreamSocketConnections.
    • JetByteTools::Socket::CCompressingStreamSocketConnectionFilter
    • JetByteTools::Socket::CConnectionMaintainingStreamSocketConnectionFilter
    • JetByteTools::Socket::CFlowControlStreamSocketConnectionFilter
    • JetByteTools::Socket::CNullStreamSocketConnectionFilter
    • JetByteTools::Socket::CReadSequencingStreamSocketConnectionFilter
    • JetByteTools::Socket::CReadTimeoutStreamSocketConnectionFilter
    • JetByteTools::Socket::CFilteringStreamSocketConnectionManager
    • JetByteTools::Socket::CFilteringStreamSocketServer
    • JetByteTools::Socket::CFilteringStreamSocketServerEx
  • Added Filtering to datagram sockets. Use JetByteTools::Socket::CFilteringDatagramSocketServer and JetByteTools::Socket::CFilteringDatagramConnectionManager if you wish to use filters with datagram sockets. The following filters are currently available:
    • JetByteTools::Socket::CCompressingDatagramSocketConnectionFilter - a filter which uses an instance of JetByteTools::Win32::ICompressData to compress the contents of each datagram.
    • JetByteTools::Socket::CReadTimeoutDatagramSocketConnectionFilter - a filter which provides a read timeout for datagram sockets.
  • Breaking Change Renamed JetByteTools::Socket::TCPBufferSize to JetByteTools::Socket::SocketBufferSize as it is now used to set send/recv buffer sizes for datagram sockets as well as TCP (stream) sockets.
  • Added JetByteTools::Socket::IRenderAddresses::GetPort() which returns the port of a given address.
  • Fixed a bug in how socket connections were aborted during shutdown. See here for more details.
  • Added new connection closure socket callbacks; JetByteTools::Socket::IStreamSocketCallback::OnConnectionClosure(), JetByteTools::Socket::IDatagramSocketCallback::OnConnectionClosure() and JetByteTools::Socket::IDatagramServerSocketCallback::OnConnectionClosure() which provide a reason for the connection closure in the form of a value from the JetByteTools::Socket::ConnectionClosureReason enum. Note that JetByteTools::Socket::IStreamSocketCallback::OnConnectionClosed(), etc, MAY be removed in a future release of the framework.
  • Added JetByteTools::Socket::ILimitConnections::GetOverallLimit() which returns the total number of connections that a connection limiter allows.
  • Moved JetByteTools::Socket::CConnectionLimiter::NoLimit to JetByteTools::Socket::ILimitConnections::NoLimit so that it can be returned by JetByteTools::Socket::ILimitConnections::GetOverallLimit() for a connection limiter which does not have a limit.
  • Added JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnBindCompleted() which is called when a datagram socket has successfully been bound to a local address.
  • JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnSocketCreated() is no longer marked as 'no throw'.
  • JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnConnectionEstablished() is no longer passed a pointer to user data.
  • Added JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnAllConnectionsAborted(), JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnShutdownInitiated() and JetByteTools::Socket::IDatagramSocketConnectionManagerCallback::OnShutdownComplete().
  • Breaking Change rationalised how server shutdown occurs so that we guarantee that OnServerShutdownComplete() is only ever called once.
  • Breaking Change All instances of InitiateShutdown() have been changed to BeginShutdown() this is to remove the issue with the Win32 InitiateShutdown() function which is conditionally defined as either InitiateShutdownA() or InitiateShutdownW() and which could then cause issues. The following classes are affected:
    • JetByteTools::Socket::IServerControl
    • JetByteTools::Socket::CServerCollection
    • JetByteTools::Socket::CNamedServerCollection
    • JetByteTools::Socket::CDatagramSocketConnectionManager
    • JetByteTools::Socket::CDatagramSocketServer
    • JetByteTools::Socket::CStreamSocketConnectionManager
    • JetByteTools::Socket::CStreamSocketServer
    • JetByteTools::Socket::CStreamSocketServerEx
  • Added conditional code to allow FILE_SKIP_SET_EVENT_ON_HANDLE and FILE_SKIP_COMPLETION_PORT_ON_SUCCESS to be enabled on sockets. See the JETBYTE_PERF_STREAM_SOCKETS_SKIP_EVENT_ON_HANDLE, JETBYTE_PERF_DATAGRAM_SOCKETS_SKIP_EVENT_ON_HANDLE, JETBYTE_PERF_STREAM_SOCKETS_SKIP_COMPLETION_PORT_ON_SUCCESS and JETBYTE_PERF_DATAGRAM_SOCKETS_SKIP_COMPLETION_PORT_ON_SUCCESS configuration options in Config.h
  • Added JetByteTools::Socket::IAcceptDatagramSocketConnectionFilters, JetByteTools::Socket::IFilterableDatagramSocket, JetByteTools::Socket::IFilterDatagramSocketConnections and JetByteTools::Socket::IManageDatagramSocketConnectionFilters to support datagram socket filtering.
  • Fixed a memory leak in JetByteTools::Socket::CReadSequencingStreamSocketConnectionFilter::FilterSocketReleased() where we were deleting a void pointer rather than casting it to its correct class before deletion.
  • Added JetByteTools::Socket::CReadTimeoutStreamSocketConnectionFilter::SetupReadTimeout() which can be used to set the details of a read timeout without actually setting the timer.
  • Added various versions of JetByteTools::Socket::CSocketReferenceTracker::DumpReferences() to make it easy to dump a socket's references.
  • OpenSSL Tools Library - 6.2
    • Refactored the code to remove duplication. This resulted in the creation of JetByteTools::OpenSSL::CSecureConnectShim and JetByteTools::OpenSSL::CStreamSocketConnectionFilter.
    • Templatised the code in JetByteTools::OpenSSL::CStreamSocketServer into JetByteTools::OpenSSL::TStreamSocketServer to allow the creation of JetByteTools::OpenSSL::CStreamSocketServerEx and the new JetByteTools::OpenSSL::CStreamSocketServer.
    • Added JetByteTools::OpenSSL::CStreamSocketServerExCallback.
    • Breaking Change to JetByteTools::OpenSSL::CStreamSocketConnectionFilter due to function signature changes in JetByteTools::Socket::IFilterStreamSocketConnections.
    SChannel Tools Library - 6.2
    • Refactored the code to remove duplication. This resulted in the creation of JetByteTools::SSPI::SChannel::CSecureConnectShim, JetByteTools::SSPI::SChannel::CSecureConnectData, JetByteTools::SSPI::SChannel::CFilterHolder and JetByteTools::SSPI::SChannel::CStreamSocketConnectionFilter.
    • Templatised the code in JetByteTools::SSPI::SChannel::CStreamSocketServer into JetByteTools::SSPI::SChannel::TStreamSocketServer to allow the creation of JetByteTools::SSPI::SChannel::CStreamSocketServerEx and the new JetByteTools::SSPI::SChannel::CStreamSocketServer.
    • Added JetByteTools::SSPI::SChannel::CStreamSocketServerExCallback.
    • Breaking Change to JetByteTools::SSPI::SChannle::CStreamSocketConnectionFilter due to function signature changes in JetByteTools::Socket::IFilterStreamSocketConnections.
    SSPINegotiate Tools Library - 6.2
    • Refactored the code to remove duplication. This resulted in the creation of JetByteTools::SSPI::Negotiate::CSecureConnectShim, JetByteTools::SSPI::Negotiate::CClientImpersonator, JetByteTools::SSPI::Negotiate::CFilterHolder and JetByteTools::SSPI::Negotiate::CStreamSocketConnectionFilter.
    • Templatised the code in JetByteTools::SSPI::Negotiate::CStreamSocketServer into JetByteTools::SSPI::Negotiate::TStreamSocketServer to allow the creation of JetByteTools::SSPI::Negotiate::CStreamSocketServerEx and the new JetByteTools::SSPI::Negotiate::CStreamSocketServer.
    • Added JetByteTools::SSPI::Negotiate::CStreamSocketServerExCallback.
    • Breaking Change to JetByteTools::SSPI::Negotiate::CStreamSocketConnectionFilter due to function signature changes in JetByteTools::Socket::IFilterStreamSocketConnections.
    SSPI Tools Library - 6.2
    • No changes.
    PerfMon Tools Library - 6.2
    • Removed the whole broken concept of JETBYTE_PERFMON_SAFE_BUT_SLOW_64BIT_OPERATIONS which allowed 64 bit wide counters to be accessed in a non atomic manner if the various InterlockedXXX64() intrinsics weren't available if it was set to zero. Now there's no way of turning off the lock used to ensure atomic increments of 64 bit values if required. This will prevent 64 bit counter values from being completely broken if compiled with JETBYTE_PERFMON_SAFE_BUT_SLOW_64BIT_OPERATIONS set to zero on a platform that doesn't support InterlockedXXX64().
    Service Tools Library - 6.2
    • Breaking Change to JetByteTools::Service::IServiceCallbacks::InitialiseService() which now returns a bool (rather than void) where false indicates that the initialisation failed and that the service should shutdown.
    CLR Hosting Tools Library - 6.2
    • Added some initial support for the new hosting API with JetByteTools::CLRHosting::CCLRMetaHost and JetByteTools::CLRHosting::CCLRRuntimeInfo.

    Full details of the licensed version of the code are available at http://wwww.serverframework.com.

    Full details of the free version of the code are available here.

    Doxygen documentation for the latest relase is available here.

    If you're an existing client and you'd like these changes let me know. I'm currently in the final phases of testing the release. I expect it will be ready to ship by the end of next week at the latest.

    6 Comments

    great work!!
    finally it comes!

    is there any chance that you release another version of the free code?? i am really expecting that .

    I think it's unlikely that I'll ever release anything but a bug fix release for the free code and right now there are no outstanding bugs.

    The licensed code is considerably different to the free code now and it's only the license fees and the consulting that often comes from people wanting to extend the licensed code in various new directions that causes the licensed code to keep moving forwards. If I gave it all away for free there'd be less incentive for me to improve it...

    The free code works fine for many people and, if you have the time and inclination, you can build complex systems from it and extend it in the same direction that the licensed code has been taken. The licensed code is there for people who prefer to have support, consulting and the more advanced features, and, once you're building production server systems, the time saved by using the licensed code and having me on hand to support the code and your designs is easily worth the cost of the license fee.

    Why don't you have public price information?

    It's mainly historical; I never have and it hasn't seemed to do me any harm so there hasn't seemed to be a need to do so. Initially (back in 2003) the pricing was more fluid than it is now as I was trying to work out what the code was worth and that was the reason that the prices weren't published. These days the pricing hasn't changed for over 3 years and so there's less reason not to have a price list somewhere. In fact, my list of things to do includes a more formal website with pricing etc. I expect that there's actually no real reason not to have prices on the licensing information page...

    However, I like to be able to track who's interested in the licensed code as it means that I get more information about the kinds of companies that are interested and the kinds that actually buy. It also means that I can track the conversion rate between enquiries and sales which I couldn't do with a public price list as I wouldn't be able to know who was interested except for the price.

    A steady stream of enquiries with a healthy conversion rate means has tended to convince me that the current methods works OK.

    Does lack of public price information deter you from sending an email asking about licensing terms and prices?

    I now have full pricing information available here: http://www.serverframework.com/prices.html

    Leave a comment