
In many ways, compatible at the source level, the socket models from Berkeley and Microsoft, in practice, are not that cross-platform.
Consider some tricky differences in their implementation, which were revealed when writing a cross-platform RPC to redirect network calls of a process in one OS to another OS.
Socket type- BSD: int
- Win: void * // macro SOCKET
As long as the processor has 32 bits, there are no problems with interconnection. On 64 bits in Windows, the SOCKET type turns out to be 2 times larger.
')
The BSD socket descriptor is no different from a file descriptor, which means that some system calls accept descriptors of both sockets and files at the same time (for example, such rarely used calls like close (), fcntl () and ioctl ()).
There is also a side effect that manifests itself in rather dastardly situations, namely, in systems with Berkeley model support, the numeric value of the socket descriptor is usually a small number (less than 100), and the descriptors that are created in a row differ by 1. In the Microsoft model, such a descriptor is immediately more than 200 (approximately), and successively created descriptors differ in sizeof (SOCKET).
Error processing- BSD: Calls return -1, global errno is set.
- Win: Calls return -1 (macro SOCKET_ERROR), the status is obtained using WSAGetLastError ().
The errno constants and Windows error codes have completely different meanings.
Creating sockets:socket (int af, int type, int protocol);
The constants for the first argument have completely different values on BSD and Windows. For the second, they are the same.
Socket configuration- BSD:
getsockopt (int sockfd, int level, int option_name, void * option_value, socklen_t * option_len);
setsockopt (int sockfd, int level, int option_name, void const * option_value, socklen_t option_len)
- Win:
getsockopt (SOCKET sock, int level, int option_name, void * option_value, socklen_t * option_len);
setsockopt (SOCKET sock, int level, int option_name, void const * option_value, socklen_t option_len)
The flag constants for the second and third arguments have completely different values on BSD and Windows.
Socket Setup 2- BSD: fcntl (int fd, int cmd, ...);
- Win: ioctlsocket (SOCKET sock, long cmd, long unsigned * arg);
The only fully correct mapping: fcnlt (descriptor, F_SETFL, O_NONBLOCK) -> ioctlsocket (descriptor, FIONBIO, the address of the variable with the value O_NONBLOCK). Numeric values of flags should be perceived relative to the target system (they are different on BSD and Windows).
In this case, the query type fcnlt (descriptor, F_GETFL), you can return 0 or O_RDWR.
Setting Sockets 3- BSD: ioctl (int fd, int cmd, ...);
- Win: ioctlsocket (SOCKET sock, long cmd, long unsigned * arg);
No cases of actual use of ioctl () with a socket as a first argument have yet been identified.
Work with DNSgetaddrinfo (char const * node, char const * service, struct addrinfo const * hints, struct addrinfo ** res)
- BSD:
struct addrinfo
{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr * ai_addr;
char * ai_canonname;
struct addrinfo * ai_next;
};
- Win:
typedef struct addrinfo
{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char * ai_canonname;
struct sockaddr_ * ai_addr;
struct addrinfo_ * ai_next;
} ADDRINFOA, * PADDRINFOA;
Look carefully at the invariants of these structures. ai_addr and ai_canonname have different offsets relative to the beginning of the structure. The developers simply reversed them (mixed up?).
Data transfer- BSD:
recv (int sockfd, void * buffer, size_t length, int flags);
recvfrom (int sockfd, void * buffer, size_t length, int flags, struct sockaddr * from, socklen_t * fromlen);
send (int sockfd, void const * buffer, size_t length, int flags);
sendto (int sockfd, void const * buffer, size_t length, int flags, struct sockaddr const * to, socklen_t tolen);
- Win:
recv (SOCKET sock, void * buffer, size_t length, int flags);
recvfrom (SOCKET sock, void * buffer, size_t length, int flags, struct sockaddr * from, socklen_t * fromlen);
send (SOCKET sock, void const * buffer, size_t length, int flags);
sendto (SOCKET sock, void const * buffer, size_t length, int flags, struct sockaddr const * to, socklen_t tolen);
The flags for the fourth argument have completely different meanings on BSD and Windows.
Waiting for operations- BSD: poll (struct pollfd * fds, nfds_t nfds, int timeout);
struct pollfd
{
int fd;
short events;
short revents;
};
- Win: WSAPoll (struct pollfd * fds, nfds_t nfds, int timeout);
typedef struct pollfd
{
SOCKET sock;
WORD events;
WORD revents;
} WSAPOLLFD, * PWSAPOLLFD;
Flag constants for the second and third pollfd structure invariants have completely different values for BSD and Windows. WSAPoll () is available only in Windows 6th version (Vista) and later.
Pending operations 2- BSD: select (int nfds, fd_set * readfds, fd_set * writefds, fd_set * errorfds, struct timeval * timeout);
typedef struct
{
long fds_bits [FD_SETSIZE / 8 * sizeof (long)];
} fd_set;
- Win: select (int nfds, FDSET * readfds, FDSET * writefds, FDSET * errorfds, struct timeval * timeout);
typedef struct fd_set
{
unsigned fd_count;
SOCKET fd_array [FD_SETSIZE];
} FDSET, * PFDSET;
The problem in select occurs when the fd_set structure is mapped. Recall how select () works. This call accepts three sets of sockets: for checking read, write, and errors for some time. You can add your socket for checking into one of these sets by using the macro FD_SET (socket, set), check for installed - FD_ISSET (socket, set), remove one socket from the set - FD_CLR (socket, set), delete all - FD_ZERO (set) . After the call, select () leaves in the corresponding sets only those sockets which, during the timeout specified by the last argument, acquired the expected state.
For BSD, placing a socket in a certain set consists in cocking in the last such bit, the sequence number of which is numerically equal to the socket descriptor. FD_SETSIZE is usually 1024. The first select () argument is the maximum numeric value of the socket descriptor included in any of the three sets plus one. Considering that setting the bit in the fds_bits array is done exclusively without checking the range, it becomes clear that when the socket descriptor is> = FD_SETSIZE, the program's behavior is undefined. Such a somewhat unreliable implementation of select is a relic of computers stingy with memory. By the way, this is where the indirect conversion int -> SOCKET and back is important.
For Windows, placing a socket in a set consists of inserting it into the fd_array array at the fd_count index and further increasing the latter. FD_SETSIZE is usually 64. In this case, the first select () argument is ignored altogether.