This site, and my book, both focus on writing portable networking code. Of course, it's not possible to write portable code until you know the differences between platforms.
This article aims to provide a quick run-down of the major differences between network programming on Windows using Winsock and network programming on Linux or macOS using BSD sockets / Berkeley sockets.
The needed headers
The needed header files for Winsock (Windows) and Berkeley sockets (Linux and macOS) differ.
On Windows, you may want to include your headers like this:
#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 #endif #include <winsock2.h> #include <ws2tcpip.h>
To use Berkeley sockets on Linux or macOS you will probably want the following headers:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <errno.h>
Linking
On Windows, you will need to link your networked programs with the ws2_32
Winsock library. Some compliers may let you do this with a pragma:
#pragma comment(lib, "ws2_32.lib")
Other compliers will need you to pass in a command line parameter to link the library.
This isn't needed on Linux or macOS.
Winsock needs to be initialized
Before you can use Winsock on Windows, you need to initialize it. This is done
with a call to the WSAStartup()
function. An example follows:
WSADATA d; if (WSAStartup(MAKEWORD(2, 2), &d)) { fprintf(stderr, "Failed to initialize.\n"); }
The MAKEWORD()
macro is used to request the version of Winsock — 2.2
in this case. The WSADATA
returned by WSAStartup()
indicates the actual
version of Winsock returned. It's worth noting that the Winsock API has been
stable for a very long time. Winsock version 2.2 was first released in 1996,
and at the time of this writing (2020), it's still the latest version.
You will also want to call the WSACleanup()
function when your Windows
program is finished using Winsock.
On Linux and macOS, there is no need to call any initialization functions or cleanup functions when using the Berkeley socket libraries.
Creating a socket
When creating a new socket, you'll need to call the socket()
function. On
Windows, this function returns an unsigned int
, while on Linux and macOS
socket()
returns a normal (signed) int
.
The way you check if your call to socket()
succeeded or failed varies
between platforms.
I've written an article about the return value of
socket()
. You'll want to check that out to
learn how to determine if the call to socket()
succeeded or failed.
Closing a socket
Closing a socket on Windows/Winsock uses the closesocket()
function.
Closing a socket on Linux or macOS uses the close()
function. This is because
on Unix-like systems socket handles are essentially equivalent to file handles.
This means that on Linux and macOS, you can generally use all of the general
purpose file functions with socket handles (e.g. read()
, write()
). On
Windows, socket handles can only be used with special socket functions.
Configuring a socket
On Windows you can set some socket features with the ioctlsocket()
function.
On Unix-like systems, you can use the fcntl()
(or the older and not
recommended ioctl()
) function.
Both platforms implement the setsockopt()
function. However, the type of the
optval
parameter of that function on Windows is const char*
, while on other
platforms it is const void*
. This is a tiny difference, and it won't affect
most C programmers. If you're writing code to be complied as C++, you may need
an explicit cast before calling setsockopt()
.
Error codes and messages
On Linux and macOS, you can get the last socket error by simplying reading the
errno
global variable. On Windows you'll want to use the WSAGetLastError()
function.
Once you have the error code, you may also want a more descriptive error
message. On Linux and macOS, you can use the strerror()
function for this,
while on Windows you will want to use the FormatMessage()
function.
You can read about a cross-platform approach to socket error messages here.
Other issues
There are many other differences as well. For example, the select()
function
used to multiplex sockets works very similar on both Windows and Unix-like
systems, but the way this function stores socket identifiers is different
between platforms. So you can and should use select()
on both platforms, but
you have to be careful about how you interact with it. The same is true of many
other socket functions as well.
For more information on the differences between Winsock and Berkeley sockets, there is a really good write-up from Microsoft about porting socket applications to Windows.
For new programs, I suggest you take a nuanced approach and attempt to write
code that works on both platforms, whenever possible. For example, on Linux you
could use write()
to send data through a socket. However, if you use the
send()
function instead, it will work on both Linux and Windows. Instead of
working to port code between systems, it's easier to simply use portable
methods from the beginning. That's a lot of what I try to address in my book.
I hope you found this article helpful. I'd love to hear any feedback you might have. Don't hesitate to get in touch.
You can find the rest of my network programming articles here.