Skip to content

Instantly share code, notes, and snippets.

@willeccles
Last active May 9, 2024 06:43
Show Gist options
  • Save willeccles/3ba0741143b573b74b1c0a7aea2bdb40 to your computer and use it in GitHub Desktop.
Save willeccles/3ba0741143b573b74b1c0a7aea2bdb40 to your computer and use it in GitHub Desktop.
Cross-platform socket header for both POSIX sockets and Winsock
/*
* File: sockets.h
* Author: Will Eccles
* Date: 2020-03-12
*
* 1. Description
*
* This header attempts to make it easy to use sockets across platforms,
* for both Windows and POSIX systems. While Winsock is often somewhat
* compatible with Berkeley sockets, it is not strictly compatible,
* and therefore must be treated differently.
* For more information on incompatibilities, see:
* https://docs.microsoft.com/en-us/windows/win32/winsock/porting-socket-applications-to-winsock
*
* This header should be compatible with both C and C++ code using sockets,
* though if the C++ networking TS ever gets implemented, this will be
* unnecessary for C++ code. We all know how C++ standards go, however.
*
* 2. Conventions
*
* Functions will return int when any sort of error handling is required.
* When possible, 0 will be considered a success. The documentation for each
* individual function should explain its return value, but if one does not,
* you can safely assume 0 is a success value.
*
* This header tries to make it as painless as possible to adapt code using
* Berkeley sockets to Winsock. Thus, the usage of this API is as identical
* as possible. You should be able to follow any Berkeley sockets guide and
* simply add one or two more function calls here and there (i.e. for errors
* [see Caveats], init, cleanup, etc.).
*
* All functions contained in this header will be prefixed with sock_.
*
* When referring to a socket, instead of using either int or SOCKET, you
* should use socket_t. This deals with each system's socket types for you.
*
* 3. Flow
*
* The flow of sockets using this header is nearly the same as a Berkeley
* sockets implementation without it. However, you should call sock_init()
* before doing anything else (this is for Winsock), and before exiting,
* you should call sock_cleanup() (this is also for Winsock). Note that
* because of the signature of sock_cleanup(), it should be compatible
* with atexit().
*
* 4. Caveat(s)
*
* While this header does all it can to make life easy for socket
* programmers, there are certain concessions which must be made in order to
* support Winsock.
*
* 4.1. Error Handling
*
* Winsock contains some functions which return int, and some functions
* which allow you to use WSAGetLastError(). The sock_perror() function only
* works for those functions which allow for WSAGetLastError(). Some
* functions (such as getaddrinfo()) do not support this. For those
* functions, you should *first* call sock_seterror(). This will do nothing
* on a POSIX system, but with Winsock, it will call WSASetLastError().
* Then, you can use sock_perror().
*/
#ifndef SOCKETS_H
#define SOCKETS_H
/* Includes */
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif /* WIN32_LEAN_AND_MEAN */
# ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
# define _WINSOCK_DEPRECATED_NO_WARNINGS
# endif /* _WINSOCK_DEPRECATED_NO_WARNINGS */
# include <winsock2.h>
# include <ws2tcpip.h>
# include <iphlpapi.h>
# include <windows.h>
# pragma comment(lib, "Ws2_32.lib")
# pragma comment(lib, "Mswsock.lib")
# pragma comment(lib, "AdvApi32.lib")
#else
# include <arpa/inet.h>
# include <sys/socket.h>
# include <sys/types.h>
# include <sys/time.h>
# include <netdb.h>
# include <unistd.h>
#endif /* _WIN32 */
#include <stdio.h>
#include <stdint.h>
/* Types */
#ifdef _WIN32
typedef SOCKET socket_t;
typedef int socklen_t;
/* POSIX ssize_t is not a thing on Windows */
typedef signed long long int ssize_t;
/* Allow the use of close() (which is POSIX) for Winsock */
# define close(x) closesocket(x)
#else
typedef int socket_t;
// winsock has INVALID_SOCKET which is returned by socket(),
// this is the POSIX replacement
# define INVALID_SOCKET -1
#endif /* _WIN32 */
/* Functions */
/*
* This function works like perror(), simply provide
* a simple statement about what failed.
*/
inline void sock_perror(const char* msg) {
#ifdef _WIN32
// TODO use Win32's awful message formatting functions
printf("%s: %ld\n", msg, WSAGetLastError());
#else
perror(msg);
#endif /* _WIN32 */
}
/*
* Sets the last error on Windows so that sock_perror may
* be used. This does not do anything on POSIX systems.
*/
inline void sock_seterror(int err) {
#ifdef _WIN32
WSASetLastError(err);
#else
(void)err;
return;
#endif /* _WIN32 */
}
/*
* This must be called before any subsequent socket function
* calls. Only necessary for Windows; on unix systems, nothing
* will be done. Returns 0 on success.
*/
inline int sock_init() {
#ifdef _WIN32
WSADATA wsaData;
return WSAStartup(MAKEWORD(2,2), &wsaData);
#else
return 0;
#endif /* _WIN32 */
}
/*
* Must be called on Windows before the program ends.
* No-op on unix.
*/
inline void sock_cleanup() {
#ifdef _WIN32
WSACleanup();
#else
return;
#endif /* _WIN32 */
}
/*
* Set the timeout for socket input (in milliseconds).
* This has to be wrapped as Windows has a weird arg type.
* Return value is the same as setsockopt().
* You should probably be using poll() or select(), but
* this can be useful in certain cases where those are not
* practical.
*/
inline int sock_setrecvtimeout(socket_t sock, int32_t ms) {
#ifdef _WIN32
return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<char*>(&ms), sizeof(ms));
#else
struct timeval tv;
tv.tv_usec = 1000L * ((long)ms - (long)ms / 1000L * 1000L);
tv.tv_sec = (long)ms / 1000L;
return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<void*>(&tv), sizeof(tv));
#endif /* _WIN32 */
}
/*
* Set the timeout for socket output (in milliseconds).
* This has to be wrapped as Windows has a weird arg type.
* Return value is the same as setsockopt().
*/
inline int sock_setsendtimeout(socket_t sock, int32_t ms) {
#ifdef _WIN32
return setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<char*>(&ms), sizeof(ms));
#else
struct timeval tv;
tv.tv_usec = 1000L * ((long)ms - (long)ms / 1000L * 1000L);
tv.tv_sec = (long)ms / 1000L;
return setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<void*>(&tv), sizeof(tv));
#endif /* _WIN32 */
}
#endif
@zaddach
Copy link

zaddach commented Sep 9, 2021

Hi, this header is awesome! Could you please tell me what license it is covered under? Is this public domain? If so, would you mind adding a note in the header?

@willeccles
Copy link
Author

@zaddach public domain!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment