BSD Sockets Compatibility
by Warren Young
So you say you're a long-time Unix hacker who's new to Windows
programming? And you've heard of this great API called Winsock that's
compatibile with your beloved BSD sockets, but try as you might, you
just can't find the readv() call? Well bunky, this is the article
for you.
Introduction
In the beginning, there was chaos in the world of Windows TCP/IP APIs.
A program written for, say, FTP Software's TCP/IP stack wouldn't run on
JSB's stack.
Then, sometime in 1990, a bunch of people got together and decided
to make one nice, big, compatible API called Windows Sockets that would
allow a single program to run on any vendor's stack. They decided to
base this API on the popular BSD sockets model of network programming,
but for various reasons, there are still many differences between Winsock
and BSD sockets. This article points out how Winsock differs from BSD
sockets, and how to translate BSD sockets programs to use similar Winsock
functionality.
The Official Word
The Winsock API documentation has a section called Porting
Socket Applications to Winsock that covers many of the same issues
that this article does, and a few others besides.
#include Differences
Under BSD sockets, there are quite a few different header files you
need to include, depending on what sockets calls you use. A typical BSD
sockets program has a block of #includes near the top like this:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
For Winsock, you don't need any of these. Instead, you just need to
include winsock.h. (Or, if you need Winsock 2-specific functionality,
winsock2.h.)
errno vs. WSAGetLastError()
WSAGetLastError() is essentially the same thing as Unix's
errno global variable. The error constants and their values
are different; there's a table in the Winsock spec where it lists all
the error constants, one column of which shows the equivalent BSD error
constant for a given Winsock error constant. Usually the difference is
just the addition of "WSA" to the beginning of the constant name for the
Winsock versions. (E.g. WSAEINTR is the Winsock version of BSD's EINTR
error constant.)
Another thing to keep in mind is that, although the perror()
call exists in most Windows compilers' run-time libraries, it doesn't work
for Winsock calls. (This is a consequence of Winsock not returning its
error codes in the errno variable.) There is a function called
WSAGetLastErrorMessage() in the basic
Winsock examples area of the FAQ that you can use to build a
perror()-like function. It's in the ws-util.cpp module.
EAGAIN
Many Unix programs, especially those with System V roots, check
for the EAGAIN value in the global errno variable when a
non-blocking call fails. This is the same thing as BSD's EWOULDBLOCK
and Winsock's WSAEWOULDBLOCK errors. You'll have to check your system's
header files, but all Unixes I've checked on this matter #define EAGAIN
and EWOULDBLOCK to the same value, so you may want to get into the habit
of using EWOULDBLOCK instead of EAGAIN under Unix, to make transitions
to and from Winsock easier.
Equivalence of File and Socket Handles
Under Unix, the I/O system calls work with file descriptors and
socket descriptors equally well. It's common in BSD sockets programs to
use read() instead of recv() to read data from a socket,
for example. Under Windows 3.1 and Windows 95 derivatives, socket
descriptors are completely distinct from file descriptors. Although the
run-time library (RTL) supplied with most Windows compilers includes
POSIX emulation functions, these are designed to work only with files,
not sockets.
Windows NT derivatives are more like Unix, in that its native file I/O
functions also work with sockets: ReadFile() is roughly equivalent
to Winsock's recv() function, for example.
The Visual C++ RTL emulates POSIX functions, except that they're
named with a leading underscore: for example, _read() instead
of read(). The _read() function uses ReadFile()
internally, so you'd think it would work with sockets. The problem
is, the first argument is an RTL-specific handle, not an operating
system file handle. If you pass a socket handle to _read() or
_write(), the RTL will realize that it isn't an RTL handle and
the call will fail.
Fortunately, there is a bridge function in Visual C++'s RTL:
_open_osfhandle(). (If you're not using Visual C++, you'll have to
check its RTL source for a similar function.) I've not tried it, but it
appears to take an operating system file handle (including socket handles)
and return a handle you can use with the POSIX emulation functions in
the RTL. I'm told that this will work with sanely-coded non-Microsoft
Winsock stacks, but since I haven't tried it, you should if you want to
support these alternate stacks.
Again, the _open_osfhandle() workaround only works on Windows
NT derivatives, because the file I/O functions don't work with sockets
on the Windows 95 derivatives, so fooling the RTL into accepting one
doesn't help you.
If these limitations are too much for your program, you
may want to give one of the Unix emulation systems a try. I've
personally used Cygwin for this
purpose, and it works beautifully. Others include Microsoft's Services for Unix and
AT&T's UWIN,
but I've not used either of them.
All that aside, it's usually much easier to rewrite your program to
use portable functions like recv() than it is to arm-twist the
Windows port to work with Unix idioms.
Winsock's closesocket() vs. Unix's close()
Winsock defines a different function for closing sockets because
not all versions of Windows have file descriptor and socket descriptor
equivalency like Unix. See the discussion in the previous item for more
on the file/socket handle mismatch issue.
Winsock's ioctlsocket() vs. Unix's ioctl()
Unix provides the ioctl() call to allow you to set and get
various bits of info on a file descriptor, which includes socket
descriptors. Winsock replicates some common Unix ioctls in the
ioctlsocket() call, but much is missing.
If you use the SIOCGIFCONF ioctl on Unix to get information
about the system's network interfaces, Winsock 2 provides very similar
functionality with its SIO_GET_INTERFACE_LIST option for
ioctlsocket().
fcntl()
The Unix fcntl() call has no direct equivalent under
Winsock. Where necessary, similar functionality exists in Winsock's
ioctlsocket() call. For example, the equivalent of using Unix's
fcntl() to set a socket's O_NONBLOCK flag is setting the
FIONBIO flag with Winsock's ioctlsocket().
poll()
There are several wrappers for
poll() using select() out there. Here's
one. It doesn't attempt to implement any of the special
poll() features found in a true System V system, such as
STREAMS support. Also, the code is rather old, written in a K&R
C style that some newer compilers might reject. Finally, since
it is built directly on top of select(), it has the same
limitations.
Another option is to dig the implementation of poll() out of
Jarle Aasa's Win32 port of the
adns library. This implementation has three limitations: 1) It's GPL'd, which means you
can't use the code in your program unless your program is also licensed
under the GPL; 2) it's built on the Win32 event object mechanism, which
has a hard 64-object limitation;
and 3) it is reportedly not written in a way that is easy for third-party
programmers to extract and use.
readv() and writev()
Winsock 2's overlapped I/O mechanism includes scatter/gather
functionality similar to that provided by readv() and
writev().
dup()
The Unix dup() function duplicates a file handle, and
of course also works for sockets. Under Winsock 2, you can do
the same thing with WSADuplicateSocket(). It's a bit more
involved, but the WSADuplicateSocket() documentation in MSDN has a good step-by-step
example showing how to use this mechansim.
dup2()
There is partial support for this feature under Winsock, though
the mechanism is dissimilar to the dup2() feature. Under Unix,
dup2() takes a handle and duplicates it like dup() does, but
with a twist: it assigns the new filehandle a value that you specify. This
is usually used to map a socket to the C language's stdin or stdout file
descriptors so that you can use standard I/O functions like printf()
and fgets() with the socket.
Item Q190351 in the Microsoft Knowledge Base documents
a method by which you can redirect a child process's standard descriptors
to a socket. The limitations are that you cannot do this to your own
process's descriptors, you cannot redirect arbitrary descriptors
to a socket (i.e. you can only do it with stdin, stdout and stderr),
and not all processes are fully compatible with this API feature. Still,
it at least makes an inetd-like program possible under Win32.
Detecting a Dropped Connection
Under BSD Unixes, if the remote peer closes its connection and
your program is blocking on recv(), you will get a 0 back from
recv(). Winsock behaves the same way, except that it can also return
-1, with WSAGetLastError() returning WSAECONNRESET,
WSAECONNABORTED or WSAESHUTDOWN, to signal the
detectable flavors of abnormal disconnections.
Under Unix, if you're blocking on send() and your program
is ignoring the SIGPIPE signal, it will return with
a -1 when the remote peer disconnects, and errno will be
EPIPE. Otherwise, your program will be sent the SIGPIPE signal,
which will terminate your program if you don't handle it. Under Winsock,
the SIGPIPE/EPIPE functionality does not exist at all: send()
will either return 0 for a normal disconnect or -1 for an abnormal
disconnect, with WSAGetLastError() returning the same errors as
in the recv() discussion above.
UDP Behavior
According to Ilpo Ruotsalainen, "...most BSD socket implementations
do not pass delayed UDP errors (ICMP port unreachable at least,
maybe others too) to recvfrom() while Winsock 2 [under Windows
2000 but not Windows 98] does. Linux [behaves like Windows 2000] too,
but provives SO_BSDCOMPAT setsockopt() for being compatible with
the BSD style."
In other words, a portable program has to be prepared for
the possibility of error codes for non-immediate problems from
recvfrom(), but it can't depend on receiving them.
A Cry For Updates
This article can always use more work. If you can help me out,
please drop me a line! In particular, I'll
bet that there are several other BSD-only ioctl() options that we
can duplicate in other ways with Win32 facilities.
Copyright © 1998-2005 by Warren Young. All rights
reserved.
|