What Kind of Socket Should I Use?

by Warren Young

There are several different conventions for communicating with Winsock, and each method has distinct advantages. The question of the hour is, what are these advantages, and how does someone choose the convention that makes the most sense for their application? The choices are:

Further confusing the issue are threads, because each of the above mechanisms changes in nature when used with threads.

In trying it find an answer to the "which socket type" question, it becomes apparent that there are only a few major kinds of programs, and the successful ones follow the same patterns. From those patterns and practical experience -- some personal and some borrowed -- I have derived the following set of heuristics. None of these heuristics are absolute laws, no one isolated heuristic is sufficient, and the heuristics sometimes conflict. When two heuristics conflict, you need to decide which is more important to your application and ignore the other. However, beware of ignoring a heuristic simply because violating it does not create noticeable consequences for your program. If you get into the habit of ignoring a certain heuristic, it becomes useless.

The heuristics are ordered in terms of compatibility, then speed, and finally functionality. Compatibility is first, because if a given socket type won't work on the platforms you need to support, it doesn't matter how fast or functional it is. Speed is next because performance requirements are easy to determine, and often important. Functionality is last, because once you decide the compatibility and speed issues, your choices become much more subjective.

Heuristic 1: Narrow your choices by deciding your compatibility requirements.

There are several kinds of socket types mainly because of the large number of platforms involved. Winsock was created as a subset of BSD sockets, and then as new varieties of Windows arrived, Winsock was extended to take advantage of their features. The positive side is that, if you know your compatibility requirements, you can quickly narrow your choices.

  Windows 3.x Windows 95/98 Windows NT 3.x Windows NT 4.0+ BSD UNIX
Blocking Sockets yes yes yes yes yes
Non-blocking Sockets yes yes yes yes yes
Asynchronous Sockets yes yes yes yes no
Overlapped I/O no some1 no yes no2
Threads no yes yes yes no3
  1. Windows 95 and 98 only support a subset of the overlapped I/O mechanisms supported by Windows NT 4.0, so unless your application absolutely requires it, you should avoid overlapped I/O on all platforms except Windows NT 4.0 and higher.

  2. BSD UNIX does support the readv() and writev() calls which are similar to Win32's overlapped I/O, but the mechanisms are not completely equivalent.

  3. Although threads are becoming common in the UNIX world, there is not yet complete standardization, and again, the mechanisms are not completely equivalent to the ones in Win32.

Heuristic 2: Avoid non-blocking sockets.

Non-blocking sockets are almost never necessary, and a good thing, too: their [lack of] performance makes them a poor architecture choice.

When a socket is set as non-blocking, every Winsock call on that socket will return immediately, whether it was able to do anything or not. This is useful because it lets your program do other things while the network is busy. The problem is, sometimes the program doesn't have anything to do. This leads to a commonly-seen construct that might be called "spin until the network becomes ready," usually expressed something like this:

    while (recv(sd, buf, sizeof(buf), 0) == WSAEWOULDBLOCK) {
        // Do something useful here or just spin...
    }

Obviously, this is very inefficient, so Winsock provides the select() call. Among other things, select() will let you block on a socket until it becomes ready for reading or writing, or some error occurs. The problem with select() is that it is still fairly inefficient because four of its parameters are structures that you must set up each time you call the function.

About the only time you should use non-blocking sockets and/or select() is when you are porting BSD sockets code to Winsock or when your code must also work under BSD sockets. In all other cases, there are better alternatives.

Heuristic 3: Avoid asynchronous sockets in programs that must deal with high volumes of data.

This heuristic is very simple: window messages impose a significant overhead on a program. The actual numbers vary, but if you anticipate the need to deal with thousands of window messages per second, you might find it worthwhile to consider another architecture.

Heuristic 4: Do not block inside your user interface thread.

This heuristic sounds more like a straightforward rule of Windows programming, and it is, on one level. Under Win32, if you block in your user interface thread, your user interface will stop responding: button clicks will have no effect, menus won't pull down, and scroll bars will freeze. If you block in Win16, the whole system actually stops responding, because Windows uses the message loop mechanism to manage its cooperative multitasking. Since a call on a blocking socket can take anywhere from a small fraction of a second to forever, blocking sockets are clearly bad things to use in your user interface thread. Further, all Win16 programs are single-threaded, and most Win32 programs are, too, so for these programs, blocking calls are always bad.

Compatibility with BSD sockets was a major design goal for Winsock. Since BSD's blocking calls were so obviously problematic under Win16, Winsock's designers created a workaround. When a Winsock call blocks under Win16, Winsock "yields" control of the processor, which lets other programs run. The problem is, this lets the blocked program continue handling window messages; if another message comes along like the one that caused the program to become blocked in the first place, reentrancy problems often result.

Say for example a program has a button that, when clicked, causes the program to send a network command. Also assume that the network is fairly busy, so each command takes several seconds to complete. Because of the network delay, the user is free to click that button several times before the first command actually completes. It depends on the program what problems this causes, but they range from inefficient use of the network to crashes and data corruption. Of course you know your users will do this sort of thing, so you have two choices: make your program reentrancy-proof, or don't use blocking sockets under Win16. I recommend the latter.

Heuristic 5: For servers, prefer overlapped I/O or threads.

Of all the various socket types, most Winsock servers use overlapped I/O or threads. The reason has to do with the characteristics of a typical server: they usually have to handle multiple simultaneous connections, performance is important if not critical, and they usually have no user interface.

The performance issue eliminates non-blocking sockets and asynchronous sockets, as covered by Heuristics 2 and 3. Another problem with asynchronous sockets has to do with the lack of user interface. Such a server would have to support a basic GUI for no other purpose than to support its asynchronous sockets.

As hinted at in Heuristic 1, Windows NT's networking code is much more robust than Windows 95's. For this reason alone, it is best to deploy highly critical servers on Windows NT only. If that is feasible, and performance is critical, you should take the next step and deploy on Windows NT 4.0 or higher so you can use the high-performance overlapped I/O mechanisms. (Note that the overlapped I/O mechanisms are not simple to program, so make sure that you really need the performance benefits before you decide to use them.)

What if you must deploy on Windows 95, or performance is not critical, but merely important? In that case, you should use threads with blocking sockets. Blocking sockets have several advantages. They are fast, because when a program blocks, the operating system immediately lets other threads run. They also have low overhead, and code that uses them is often more straightforward than equivalent code for other socket types. Adding threads means that blocking doesn't cause the problems discussed in Heuristic 4, and since the network streams in a typical server have nothing to do with each other, the threading problems discussed in Heuristic 6 also don't concern us.

If by chance you must implement a server under Win16, or if performance is of absolutely no concern, you are probably best off with asynchronous sockets.

Heuristic 6: Threads are only rarely necessary in client programs.

Many new Win32 programmers, when they first learn about threads, are eager to try them out in their own programs. They see that they have several advantages, but they don't yet see the drawbacks. Unfortunately for the soon-to-be-educated newbie, these drawbacks can have very significant consequences.

The first benefit of threads is speed. The alternatives to threads available under Windows 95/98 -- non-blocking and asynchronous sockets -- are relatively slow. Although threads coupled with blocking sockets can indeed be faster than the alternatives, this speed is not always possible to achieve, as we shall soon see.

Another perceived benefit of threads is a kind of encapsulation: a programmer can split a program up into a number of threads, each of which has a single well-defined task. These separate threads are usually not completely independent, however. The most common way to let threads communicate is through a shared data structure. The problem is, true encapsulation depends on hidden data structures, so the encapsulation afforded by threads is illusory.

In the end, the biggest problem with threads is also related to shared data structures: synchronization. This issue is covered better elsewhere, so I won't spend many words on it here. In short, poorly-synchronized threads are subject to serialization delays, context switching overhead, deadlocks, race conditions and corrupted data. These are hard problems, and for most programs the benefits are not large enough to make them worth overcoming.

In general, an asynchronous program will be clearer and less buggy than an equivalent threaded program, because the asynchronous program does not have to deal with concurrency. Thus, the only time threads are necessary in a client program is when the threaded version is clearer than the asynchronous version would be. This is not the only condition you need to meet, however. First, you need to know how to use threads responsibly. This may seem obvious, but many programmers naïvely add threads to their program without understanding their proper use, usually causing great havoc in the process. The second condition is that you must take the time to carefully design your program, particularly with regard to how the threads affect shared data. A poorly-designed program is bad enough, but a poorly-designed threaded program can be a nightmare. Only when you can satisfy these conditions should you overrule this heuristic.

Heuristic 7: Use threads only when their effect on the rest of the program is easily contained.

Heuristic 6 cautions that threads are often very hard to program correctly, but the truth is that they are sometimes very useful. If your program has a networking task that has very little effect on the rest of the program, these concurrency issues become manageable. Examples where threads are viable are:

  1. An FTP server. One way to write an FTP server is to let the main thread accept the incoming network connections, and send each one to a separate thread. Then, each thread can process the incoming FTP commands, send any required replies, and terminate when the session closes. Because each thread never has to interact with any other, and they all act alike, this is an ideal application of threads.

  2. A web browser. When you download a file with a modern web browser, the file comes down in the background, so that you can continue browsing. That download stream is most likely handled by a dedicated thread.

  3. An email program. In an email program, the primary focus is usually on reading and writing email. However, when an email message needs to be sent, it is best not to interrupt the user's work. You can send that message with a separate network thread, since the process affects the rest of the program only minimally.

  4. A stock ticker. A stock ticker is a typical sort of program: display a small amount of continuous real-time data in a pleasing and useful format. When the amount of network data involved is low, the thread synchronization overhead becomes negligible. Plus, this kind of application only has a single data structure that needs protection; the really big synchronization problems appear when multiple data structures need to be protected.

Heuristic 8: Use asynchronous sockets if automatic synchronization is important.

Asynchronous sockets have one very important but often overlooked feature: automatic synchronization. Winsock sends window messages any time something "interesting" happens on an asynchronous socket. The cool thing is, a single-threaded program can only handle one window message at a time, and no message handler can interrupt another. This powerful fact means that it is much easier to guarantee that a network stream will not interfere with the rest of the program.

This feature can benefit any program that has more than one task to manage. All but the simplest network clients have a user interface, and that code must often interact closely with the network code. By contrast, Heuristic 5 and Heuristic 7 (item 1) give examples where this benefit of asynchronous sockets is either overridden by other forces or unimportant.

Heuristic 9: Use asynchronous methods in programs that have to handle several diverse network streams.

The canonical example of a program that handles diverse network streams is a web browser: at any one time, its incoming HTTP streams can contain pure text, HTML, music, images, video and more. Newer browsers must also handle things like Java, "push streams," and FTP file transfers. Further, most of these streams affect the browser's main window, and each in different ways.

The various types of Winsock conventions fall into one of two categories: synchronous and asynchronous. The synchronous socket conventions are overlapped I/O and blocking and non-blocking sockets. From a synchronous program's perspective, network data only comes in when the program asks for it. (This is a simplification. For example, the overlapped I/O mechanisms include asynchronous completion routines.)

The asynchronous methods are asynchronous sockets and threads, because with them, network data can come in at any point in the program's execution. The nature of asynchronous sockets is no mystery. Threads, however, are asynchronous as a matter of perspective: they usually use synchronous I/O internally, but from the perspective of the rest of the program, they appear asynchronous.

The problem with handling diverse content with synchronous methods is the "monster case statement." You know the kind: each time data comes in, the program has to send it through at least one large, and sometimes nested, case or "switch" statement to figure out how to handle it. Maintaining these constructs is hard, error-prone and just plain mentally taxing.

By contrast, asynchronous methods let you logically separate the incoming data stream from the rest of the program. With asynchronous sockets, you can create a separate, hidden window for each incoming connection. If you use an object-oriented windowing framework like MFC, VCL or OWL, you can create a different window subclass for each kind of incoming network connection, so that the object itself knows how to handle the data stream. The case for threads is similar: just start the thread with a co-routine that knows how to handle the data in the stream. Use Heuristics 3 and 5 through 8 to help you to decide between these two alternatives.

Conclusion

It is my hope that you find these heuristics helpful. Although you may not agree with each of them, I think that they will at least make you think about your own choices. Design is a highly subjective enterprise, and this list is based mainly on my own thoughts and preferences. Special thanks go, however, to Philippe Jounin for his comments on an early version of this paper, and on several emails in which we rounded these heuristics out.

Copyright © 1998 by Warren Young. All rights reserved.

Back to the Winsock Programmer's FAQ...



Go to my home page Go to my Important RFC Lists page Go to the main Programming Resources page

Please send updates and corrections to <tangent@cyberport.com>.