Berkeley and Microsoft socket models that are mostly compatible on the source code level are not so cross-platform in practice.
Let’s examine some subtle differences in their implementation. These differences were found when writing a cross-platform RPC for redirection of network calls of some process from one OS to another.
void * // macros SOCKET
While the processor capacity is 32 bits, there are no problems in mutual displaying. On Windows 64 bits, the SOCKET type is twice larger in size.
The socket descriptor on BSD does not differ from the file descriptor. It means that some system calls accept descriptors of sockets and files simultaneously (for example, such commonly used calls as
There is one more side effect that appears in some cases. The matter is that systems, which support Berkeley model, have a small numerical value of the socket descriptor (less than 100) and the descriptors that are created in succession differ on 1. In the Microsoft model, such descriptors have values that are approximately more than 200 at once, and the descriptors created in succession differ on sizeof(SOCKET).
- BSD: Calls return -1, global variable
- Win: Calls return -1 (
SOCKET_ERRORmacro), we receive the status with
errno constants and Windows error codes have absolutely different values.
Constants for the first argument have absolutely different values on BSD and Windows. Constants for the second argument coincide so far.
Flag constants for the second and third arguments have absolutely different values on BSD and Windows.
Socket Setting 2
fcntl(int fd, int cmd, ...);
ioctlsocket(SOCKET sock, long cmd, long unsigned *arg);
The only completely correct correspondence is as follows:
fcnlt(descriptor, F_SETFL, O_NONBLOCK) -> ioctlsocket(descriptor, FIONBIO, address of the variable with the O_NONBLOCK value).
Flag numerical values should be considered in regard to the target system (they are different on BSD and Windows systems).
At the same time, we can return 0 or
O_RDWR for the call of the
fcnlt(descriptor, F_GETFL) type.
Socket Setting 3
ioctl(int fd, int cmd, ...);
ioctlsocket(SOCKET sock, long cmd, long unsigned *arg);
The cases of real usage of
ioctl() with the socket as the first argument have not been discovered so far.
Work with DNS
Pay attention to the invariants of these structures.
ai_canonname have different offsets from the beginning of the structure. Developers just rearranged them (or mixed up?).
Flags for the fourth argument have absolutely different values on BSD and Windows.
Waiting for Operations
Flag constants for the second and third invariants of the
pollfd structure have absolutely different values on BSD and Windows.
WSAPoll() is present only in Windows of the 6th version (Vista) and higher.
Waiting for Operations 2
The problem in the
select procedure appears while mutual reflection of the
fd_set structure. Let’s recollect how
select() works. This call accepts three sets of sockets: for checking reading, writing, and errors during some period of time. You can add your own socket for checking to one of these sets via the
FD_SET(socket, set) macro. To check the socket on being installed, use the
FD_ISSET(socket, set) macro; to delete one socket from the set, use the
FD_CLR(socket, set) macro; to delete all sockets, use the
FD_ZERO(set) macro. After the call,
select() leaves only those sockets in the corresponding sets, which got the expected state during the time out defined by the last argument.
For BSD, adding of some socket to some set consists in setting its bit which number is equal to the socket descriptor.
FD_SETSIZE is usually equal to 1024. The first
select() argument is one bigger than the maximum numerical value of the socket descriptor that is a part of any of three sets. Taking into account that setting of a bit in the
fds_bits array is performed without the check of range, it becomes clear that the program behavior is undefined with the socket descriptor value equal to or greater than
FD_SETSIZE. Such rather unreliable implementation of
select is a remnant of computers with little memory. Besides, in such case, an indirect conversion of
int -> SOCKET and vice versa is important.
For Windows, adding of some socket to some set consists in its insertion to the
fd_array array by the
fd_count index and the further increase of the latter one.
FD_SETSIZE is usually equal to 64. At the same time, the first
select() argument is skipped at all.
Here is a certain useful code that I used in my project.
First, it is supposed that we somehow managed to redirect standard network calls to the GLibC library to our implementations (for example, see http://apriorit.com/dev-blog/181-elf-hook). Besides, we have some mechanism of a synchronous RPC that performs the serialization of parameters and the transfer of calls from Linux to Windows. Also, there are declarations of all required Windows constants so that they do not cross with Linux analogs.
As socket types are different on the systems, the following class for converting of descriptors during the call transfer will prove useful:
Its implementation can look as follows:
For the created socket, the first pseudodescriptor will have the 768 value. It is rather a lot for real descriptors whose values begin from about 6 but less than
FD_SETSIZE to work out
select() correctly filling its
Also, we need functions of constants converting for certain calls:
Examples of implementation of certain redirected functions are the following:
The complete example of the code with all definitions is attached to the article.
Continue reading with our Linux driver tutorial
Take a look at another dev blog article: Win debugger via USB.
Get details about Apriorit proprietary technologies: Sound recording SDK