Hands-On Network Programming with C

Written By Lewis Van Winkle

Differences between socket programming on Windows, Linux, and macOS

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.

If you found this information helpful, you might be interested in my book.

Purchase on Amazon Purchase on Packt