Finally, the penny drops about AcceptEx

| 0 Comments

So there I was, cleaning my teeth, about to get into bed and suddenly I saw a reason for using AcceptEx... I've known about AcceptEx for ages, and even written an article about how to use it but I always thought that it just wasn't something I needed to use in our servers. I'd latched on to the officially advertised reasons for using it and ignored the more subtle advantages...

In my article on AcceptEx from 2002 I started by saying:

When a server has to deal with lots of short lived client connections it's advisable to use the Microsoft extension function for WinSock, AcceptEx(), to accept connections. Creating a socket is a relatively "expensive" operation and by using AcceptEx() you can create the socket before the connection occurs rather than as it occurs, thus speeding the establishment of the connection. What's more, AcceptEx() can perform an initial data read at the same time as doing the connection establishment which means you can accept a connection and retrieve data with a single call.

Which is all quite true. The thing is, it's easy to look at that and say to yourself that your server can easilly handle the connection establishment load that you're expecting it to see (and testing for) and the design of your protocol means that the server sends data first therefore AcceptEx isn't for you. Wrong. I think. Perhaps...

My problem was that I didn't really see the need for in The Server Framework for AcceptEx at the time I wrote the article. I thought it might be useful to understand, started writing some code, wrote it up for CodeProject and then never really used it in anger because of the reasoning above. Unfortunately because I never used it I never really tested the design that I'd used. The design works, but isn't ideal. It doesn't fully exploit the advantages of what you can do with AcceptEx.

Amusingly, I focused on the complexity that I then decided I would never need; the ability to receive data at the same time as performing the accept on the new connection. This speeds things up because there's less transitions from user mode to kernel mode, apparently, but leaves you open to a denial of service attack if malicious clients connect and don't send data. Extra plumbing is needed to thwart the denial of service attack and things started getting complicated quickly. When I originally read the documentation at the time I decided that if you didn't want to receive data in the AcceptEx call then all you were really gaining was the ability to create your accepted sockets up front and speed up the connection acceptance process slightly - however, given the fact that once a connection is established you will need to queue another AcceptEx call to "replace" the one you've used it seemed to me that after the first few accepts happened you were creating new sockets to queue for accepting just after you were accepting a connection so the work just moved around a bit...

I think I was missing the main advantage; you don't need a listen/accept thread. My half-arsed attempt at AcceptEx for the article I wrote was based on the design of the normal listen/accept server. As such there was a thread that issued AcceptEx calls and woke up every now and then to post more so that the server always had some queued. This thread dealt with the potential of the denial of service attack and lots of other complexity. Last night I came to the conclusion, whilst brushing my teeth, that the listen/accept thread was totally redundant and this is important for the scalability of the server. Bear in mind that I don't have any code to demonstrate this yet...

Using The Server Framework, if you have a server that listens on a single port, and you take the normal default options, you have an IO pool of 2 threads x number of processors and a listen/accept thread (lets say 3). If you need to listen on another port you can share the IO pool with the new server; you still get a new listen/accept thread for the new port (4). So we have a server that needs to listen on a lot of ports, for whatever reason, and we get a new thread per port that we listen on... This hasn't proved to be much of a problem for us so far as our servers don't tend to do that, but even if you don't need to listen on a large number of ports you still pay for that extra thread. Every time a new connection occurs the listen/accept thread needs to run. If you are accepting connections on different ports then each of these ports causes a different thread to run. This increases the context switching that's going on.

I think we can remove the listen/accept thread altogether by using AcceptEx. This would mean that the server can listen on as many ports as you want and you'd only have your IO pool threads. It would mean that you'd maintain the smooth thread handling and minimal context switches that you get from allowing an IOCP to manage your threads for you. It should mean a more scalable and simpler server.

This whole thought process occurred because I've plugged my OBEX code into the Bluetooth server that I've built using the framework. That works well and provides a basic OBEX file transfer server. Now I want to add more Bluetooth services to the server and it just didn't seem right that each service I added would consume a thread...

Hopefully I'll get a chance to try this out later in the week. Watch this space...

Leave a comment