Hi all,
An intro to me, my name is Trevor Herselman in real life ;)
I currently work in South Africa where I'm lead developer on database validation software for our IBML scanners. The scanner is the fastest in the world and comes from Alabamma in the USA. It scans @ 200 A4 pages per minute in full color (there is a 400 page per minute model), and does hardware OCR and barcode recognition. What I design mainly is the software to make sure the scanner has writen the correct values to the SQL database when it does the hardware OCR and barcode recognition. The scanner is used in most airlines worldwide to scan airline tickets doing ticket barcode recognition. Other uses include scanning of cheques for banks or anywhere a paperless office is a fantasy! The scanner saves files in mainly TIFF and JPEG format. I use C/C++ mainly at work but have the rare opportunity to use assembler whenever I want to (or whenever I can finish the project in time using it!) I've been programming for half my life already, 13 years, I'm 26 years old for those that can't do math (like me) and I've been a "fly on the wall" of this board for over 2 years. During this time I've frequently read the new updates, on average about 3 times per week but I haven't contributed much. It's taken me about a week to put these 2 projects in a format that is presentable to the community. By adding LOTS of comments and reformatting my code for readability. This leads me to today's post, cause today I hope to give a small gem back to the community that taught me so much!
Basic Description:
The zip file consists of 2 small RadASM projects (.rap thanx to Kitelo!) with multiple asm and inc files. Both have been written in MASM syntax with lots of high level syntax (.if .while & invoke etc) with loads of macro's (mostly made by me). The main reason I use HL syntax and macros is for readability. Macro's are used extensively for error reporting and to reduce code clutter. Maybe you find it more readable NOT to use HL syntax, good for you, you machine!
The main project is a WinSock enabled DLL, that controls all WinSock related events and activity including listening, connecting, sending and receiving. Using a semi-dynamic array to store the sockets, socket state, IP address and port. It currently uses WSAAsyncSelect for WinSock event notification and callbacks to provide the client with socket event notification.
The other project is the one I use to test and debug my DLL! It demonstrates most of the DLL's features and is basically just a form with several edit boxes and buttons. Sorry in advance for the very basic look and feel of this test app!
Design Principles:
Stability, functionality (being feature rich), ease of use and readability are high on the agenda, in that general order! As stability and functionality increase, I wish to move speed as a requirement higher on the agenda, particularly by implementing a more advanced overall design with IO Completion Ports. I also wish to implement a more graceful shutdown procedure, optional inline data compression and sofisticated send and receive buffers. I'd also like to provide a way to change the WinSock internal send and receive buffer sizes and other TCP/IP options like disabling the Nagle algorithm.
About the semi-dynamic array:
When designing a WinSock enabled app for example, you'll have to know what sockets are connected, what their socket number is and what state they're in etc. This DLL takes care of that using a semi-dynamic array I call Sox (short for sockets). You will notice that most functions require or return a Sox value, this is essentially just the array index number. I call it a semi-dynamic array because when a socket is removed, that slot will be used in the future but the array will not compact/reshuffle the sockets, it will only reduce the array count (shrink) when there are empty slots at the end of the array. The memory for all sockets (max sockets estimated at 8000, about 125K of RAM, or 16 bytes per socket) gets pre-allocated on DLL startup. Microsoft claims that Windows supports up to 25000 sockets, but I've only ever managed to add 7000 something sockets. The main reason it fails to reach this count is that it seems to reach a handle limit! Maybe someone can point out the design principles involved in reaching this 25000 limit! I've tested Win98, WinNT, Win2000 & WinXP with various WinSock projects (including VB's WinSock OCX and the C++ MFC WinSock objects) and never reached 25000 connections!
About WSAEventSelect and IOCP (IO Completion Ports) design:
Well, I researched these 2 long ago, so I can't remember all the Pros & Cons. But basically, WSAEventSelect is generally faster than WSAAsyncSelect, BUT, most people forget to mention that a call to WSAEventSelect can only monitor 64 sockets at any one time. So this might be best for multiplayer games but poses a major design obstacle for very active servers. There are several workarounds, you could add more threads and call WSAEventSelect on each new thread to monitor 64 sockets per thread, or you could loop through all your sockets, 64 at a time, but this is even more CPU intensive! Imagine you have 8000 sockets, monitoring them with 125 threads or looping through them. Maybe I'm overlooking something here or just don't understand WSAEventSelect. Oh, the other way was to create a custom event and link 64 sockets to that event, then monitor up to 64 of these custom events. That still only accounts for half the sockets and the other problem with this method is when an event happens on one socket, you'll have to re-check the whole group of sockets to find the one socket that fired the event. IO Completion Ports are only slightly more complicated to implement. Generally it only really helps to reduce CPU usage on large or active send and receive servers, but one of the other 2 methods still needs to be used. However, I mainly chose WSAAsyncSelect (with it's own headaches) for it's design and implementation simplicity!
About the callbacks:
I've read numerous articles on the pro's and con's of Callbacks vs. other event notification techniques. Basically, callbacks are fairly easy to implement but very fast because you simply call another function as if it were part of your app, obviously the function is written by the client! I liked the speed advantages of callbacks. Other techniques include using custom (sometimes registered) events (similar to WSAEventSelect), standard 'equate' type messages (similar to WSAAsyncSelect) usually sent to a wndproc, the standard COM event is often used especially in OCX controls or (my personal favourite) customized IO completion port messages. Problems with callbacks include: The client app will 'freeze' your DLL thread while processing the message and if the callback has error's, you could potentially crash the thread or DLL. Both these problems are sometimes resolved by creating a new thread to call the callback, but the problem with doing this is some client apps are not thread safe, so executing code on a new thread might cause instability for the client!
What I really like about using IO completion ports as an event notification system is that the client app will have to be thread safe, also the number of threads ready to receive and process your messages is controlled by the client! So if they have a multiprocessor machine, they can assign more threads to the IOCP making everything faster. I also like this because posting events to the client doesn't 'freeze' your app like callbacks! Well, callbacks will have to suffice for now!
NB: Using IOCP as a 'customised' event notification system is different in the above context to using it with WinSock's WSARecv and WSASend methods.
Some history:
I started on WinSock programming about 3 years ago by creating a small VB 6 chat app. I then had to create a small server app but I only had VB WinSock OCX experience and many people told me the VB WinSock con