views:

2164

answers:

10

What is the normal way people writing network code in Delphi use Windows-style overlapped asynchronous socket I/O?

Here's my prior research into this question:

The Indy components seem entirely synchronous. On the other hand, while ScktComp unit does use WSAAsyncSelect, it basically only asynchronizes a BSD-style multiplexed socket app. You get dumped in a single event callback, as if you had just returned from select() in a loop, and have to do all the state machine navigation yourself.

The .NET situation is considerably nicer, with Socket.BeginRead / Socket.EndRead, where the continuation is passed directly to Socket.BeginRead, and that's where you pick back up. A continuation coded as a closure obviously has all the context you need, and more.

+2  A: 

I have found that Indy, while a simpler concept in the beginning, is awkward to manage due to the need to kill sockets to free threads at application termination. In addition, I had the Indy library stop working after an OS patch upgrade. ScktComp works well for my application.

Mike
If you have to kill sockets manually, then you are not using it correctly. Indy servers handle that stuff for you automatically. I shoulf know - I'm on the Indy development team. As for the OS upgrade, what exactly stopped working?
Remy Lebeau - TeamB
To clarify, this is Indy 9, not Indy 10 which could explain the need to manually close sockets on exit. The OS security patch caused calls to Write() to block and not return (between XP SP2 and 3). The app had worked for 2 years and failed after a security patch. Thinking it was hardware, the deployment team moved the app to a second server with the same result. Since it was a production server, we tried substituting the ScktComp interface and everything has worked. I did not have time to use a kernel debugger to find out what caused the call to block.
Mike
A: 

What is the normal way people writing network code in Delphi use Windows-style overlapped asynchronous socket I/O?

Well, Indy has been the 'standard' library for socket I/O for a long while now - and it's based on blocking sockets. This means if you want asynchronous behaviour, you use additional thread(s) to connect/read/write data. To my mind this is actually a major advantage, as there's no need to manage any kind of state machine navigation, or worry about callback procs or similar stuff. I find the logic of my 'reading' thread is less cluttered and much more portable than non-blocking sockets would allow.

Indy 9 has been mostly bombproof, fast and reliable for us. However the move to Indy 10 for Tiburon is causing me a little concern.

@Mike: "...the need to kill sockets to free threads...".

This made go "huh?" until I remembered our threading library uses an exception-based technique to kill 'waiting' threads safely. We call QueueUserAPC to queue a function which raises a C++ exception (NOT derived from class Exception) which should only be caught by our thread wrapper procedure. All destructors get called so the threads all terminate cleanly and tidy up on the way out.

Roddy
It was during the Indy9 to Indy10 API instability phase that I began my "get the heck away from Indy" phase. I use only ICS now and I am very happy with it. It was not because of the sync/async thing, but all the glitches (especially hangs at application shutdown) and the whole IdAntifreeze thing that I got sick of Indy.
Warren P
+2  A: 

@Roddy - Synchronous sockets are not what I'm after. Burning a whole thread for the sake of a possibly long-lived connection means you limit the amount of concurrent connections to the number of threads that your process can contain. Since threads use a lot of resources - reserved stack address space, committed stack memory, and kernel transitions for context switches - they do not scale when you need to support hundreds of connections, much less thousands or more.

Barry Kelly
A: 

"Synchronous sockets are not what I'm after."

Understood - but I think in that case the answer to your original question is that there just isn't a Delphi idiom for async socket IO because it's actually a highly specialized and uncommon requirement.

As a side issue, you might find these links interesting. They're both a little old, and more *nxy than Windows. The second one implies that - in the right environment - threads might not be as bad as you think.

The C10K problem

Why Events Are A Bad Idea (for High-concurrency Servers)

Roddy
A: 

Indy uses synchronous sockets because it's a simpler way of programming. The asynchronous socket blocking was something added to the winsock stack back in the Windows 3.x days. Windows 3.x did not support threads and there you couldn't do socket I/O without threads. For some additional information about why Indy uses the blocking model, please see this article.

The .NET Socket.BeginRead/EndRead calls are using threads, it's just managed by the Framework instead of by you.

@Roddy, Indy 10 has been bundled with Delphi since at Delphi 2006. I found that migrating from Indy 9 to Indy 10 to be a straight forward task.

Chris Miller
A: 

@Chris Miller - What you've stated in your answer is factually inaccurate.

Windows message-style async, as available through WSAAsyncSelect, is indeed largely a workaround for lack of a proper threading model in Win 3.x days.

.NET Begin/End, however, is not using extra threads. Instead, it is using overlapped I/O, using the extra argument on WSASend / WSARecv, specifically the overlapped completion routine, to specify the continuation.

This means that the .NET style harnesses the Windows OS's async I/O support to avoid burning a thread by blocking on a socket.

Since threads are generally speaking expensive (unless you specify a very small stack size to CreateThread), having threads blocking on sockets will stop you from scaling to 10,000s of concurrent connections.

This is why it's important that async I/O be used if you want to scale, and also why .NET is not, I repeat, is not, simply "using threads, [...] just managed by the Framework".

Barry Kelly
And given that INDY is a component set that I mistrust, and ICS is a third-party component set that is async without IOCP support, I think Embarcadero should step up to the plate and build a proper async+iocp network layer. The fact that INDY is the layer underneath the current VCL enterprise database network layer stuff is just awful, and is the reason I won't touch the teetering pile of stuff with a ten foot pole.
Warren P
+1  A: 

@Roddy - I've already read the links you point to, they are both referenced from Paul Tyma's presentation "Thousands of Threads and Blocking I/O - The old way to write Java Servers is New again".

Some of the things that don't necessarily jump out from Paul's presentation, however, are that he specified -Xss:48k to the JVM on startup, and that he's assuming that the JVM's NIO implementation is efficient in order for it to be a valid comparison.

Indy does not specify a similarly shrunken and tightly constrained stack size. There are no calls to BeginThread (the Delphi RTL thread creation routine, which you should use for such situations) or CreateThread (the raw WinAPI call) in the Indy codebase.

The default stack size is stored in the PE, and for the Delphi compiler it defaults to 1MB of reserved address space (space is committed page by page by the OS in 4K chunks; in fact, the compiler needs to generate code to touch pages if there are >4K of locals in a function, because the extension is controlled by page faults, but only for the lowest (guard) page in the stack). That means you're going to run out of address space after max 2,000 concurrent threads handling connections.

Now, you can change the default stack size in the PE using the {$M minStackSize [,maxStackSize]} directive, but that will affect all threads, including the main thread. I hope you don't do much recursion, because 48K or (similar) isn't a lot of space.

Now, whether Paul is right about non-performance of async I/O for Windows in particular, I'm not 100% sure - I'd have to measure it to be certain. What I do know, however, is that arguments about threaded programming being easier than async event-based programming, are presenting a false dichotomy.

Async code doesn't need to be event-based; it can be continuation-based, like it is in .NET, and if you specify a closure as your continuation, you get state maintained for you for free. Moreover, conversion from linear thread-style code to continuation-passing-style async code can be made mechanical by a compiler (CPS transform is mechanical), so there need be no cost in code clarity either.

Barry Kelly
I guess you would still have to worry about data access sync'ing while using closures or is all the communication serialized? How does this combine with f.x. asynch. database responses providing data to the large number of incoming requests? Use work thread pools? You should blog this.
Lars Fosdal
Indy's threads are based on the VCL's TThread class, which does not allow you to specify a stack size.
Remy Lebeau - TeamB
A: 

For async stuff try ICS

http://www.overbyte.be/frame_index.html?redirTo=/products/ics.html

ddowns
ICS doesn't use iocp
Stephan Eggermont
A: 

With the ScktComp classes, you need to use a ThreadBlocking server rather than an a NonBlocking server type. Use the OnGetThread event to hand off the ClientSocket param to a new thread of your devising. Once you've instantiated an inherited instance of TServerClientThread you'll create a instance of TWinSocketStream (inside the thread) which you can use to read and write to the socket. This method gets you away from trying to process data in the event handler. These threads could exist for just the short period need to read or write, or hang on for the duration for the purpose of being reused.

The subject of writing a socket server is fairly vast. There are many techniques and practices you could choose to implement. The method of reading and writing to the same socket with in the TServerClientThread is straight forward and fine for simple applications. If you need a model for high availability and high concurrency then you need to look into patterns like the Proactor pattern.

Good luck!

Jody Dawkins
A: 

There is a free IOCP (completion ports) socket components : http://www.torry.net/authorsmore.php?id=7131 (source code included)

"By Naberegnyh Sergey N.. High performance socket server based on Windows Completion Port and with using Windows Socket Extensions. IPv6 supported. "

i've found it while looking better components/library to rearchitecture my little instant messaging server. I haven't tried it yet but it looks good coded as a first impression.

AhmetC