Sockets

A socket is a mean for two programs to communicate with each other via file descriptors. Each socket represents a communication end point, each socket is associated (or bound) to a network address, and programs communicate with each other by opening a local socket, which it then connects to a remote socket that is listening for connections, after which data is sent and received via this two-way connection.

A program that listens for incoming connections on a socket and responds to those connection requests is called a server. A program that makes a connection to a server is called a client. A socket server works as follows:

  1. Create a socket

  2. Assign (or bind) a network address and port to the socket

  3. Listen on the socket for incoming connections

  4. If a client makes a connection request, then accept the connection

This forms a channel between the server and the client, from which the server can read and write data.

A client works as follows:

  1. Create a socket

  2. Connect the socket to a remote address and port

Once the remote server accepts the connection a channel between the server and client is formed, and the client can similarly read and write data on this channel.

Socket creation

A socket is created via the API function osl_createSocket() . A socket consists of a family, type and _protocol. _The OSL supports the IP and IPX/SPX families (though to be frank, IPX/SPX is obsolete). The type of socket can be stream (a connection-oriented, sequenced and unique flow of data), datagram (a connection-less point for data packets with well defined boundaries), raw (socket users aren't aware of encapsulating headers, so can process them directly), RDM and sequenced packet. Each family has one or more protocols - currently OSL sockets support the IPv4 protocol.

Sockets are very similar between Unix and Windows, however sockets were introduced late in the Windows world, whilst sockets were invented on Unix. The Unix socket creation function is as follows:

oslSocket SAL_CALL osl_createSocket(
    oslAddrFamily Family,
    oslSocketType Type,
    oslProtocol Protocol)
{
    oslSocket pSocket;

    /* alloc memory */
    pSocket= createSocketImpl(OSL_INVALID_SOCKET);

    /* create socket */
    pSocket->m_Socket= socket(FAMILY_TO_NATIVE(Family),
                                TYPE_TO_NATIVE(Type),
                                PROTOCOL_TO_NATIVE(Protocol));

    /* creation failed => free memory */
    if(pSocket->m_Socket == OSL_INVALID_SOCKET)
    {
        int nErrno = errno;
        SAL_WARN( "sal.osl", "socket creation failed: (" << nErrno << ") " << strerror(nErrno) );

        destroySocketImpl(pSocket);
        pSocket= nullptr;
    }
    else
    {
        sal_Int32 nFlags=0;
        /* set close-on-exec flag */
        if ((nFlags = fcntl(pSocket->m_Socket, F_GETFD, 0)) != -1)
        {
            nFlags |= FD_CLOEXEC;
            if (fcntl(pSocket->m_Socket, F_SETFD, nFlags) == -1)
            {
                pSocket->m_nLastError=errno;
                int nErrno = errno;
                SAL_WARN( "sal.osl", "failed changing socket flags: (" << nErrno << ") " << strerror(nErrno) );
            }
        }
        else
        {
            pSocket->m_nLastError=errno;
        }
    }

    return pSocket;
}

createSocketImpl() is actually just an initialization of the internal oslSocket structure. The creation of the actual socket is done via calling the socket() function, which returns the socket file descriptor. In the Unix version of creating a socket, we also set the close-on-exec flag via fcntl(). What this means is that if any forked children call on an exec function, then the socket file descriptor will be closed automatically, which prevents FD leaks from occurring.

The Windows version of osl_createSocket() is as follows:

The main difference is in the way that it reports an error - Win32 doesn't really like strerr() and has it's own function for extracting the error message. Also, the whole concept of file descriptors doesn't really exist in Windows (not to mention Windows handles children process differently to Unix) so there is no need to set a close-on-exec option, as in the Unix version.

Socket addressing

A server must bind an address to its socket. The following functions deal with host addressing:

Binding, listening and connecting

Once a socket address has been setup, it is then associated - or bound - to the socket. The OSL function that does this is osl_bindAddrToSocket()which just wraps around a the bind function call. The Unix version is virtually the same as the Win32 version, which is implemented as so:

A server listens on its socket for incoming connections. The function that handles this is osl_listenOnSocket() (also virtually the same between Unix and Win32):

Listen is nothing without accept() however - listen() basically is a passive socket that waits for incoming connections, and accept() makes the listening socket accept the next connection and returns a socket file descriptor for this connection. The function that accepts connections is osl_acceptConnectionOnSocket()the Unix version is:

A few notes about this function: the way it works is the same as the accept() function - you pass the listening socket to the function and the new connecting address is populated into the ppAddr output parameter if the address is not null. The Unix version also sets close-on-exec on the socket. However, a special consideration needs to be made for Linux - as the man page states:

On Linux, even in the absence of signal handlers, certain blocking interfaces can fail with the error EINTR after the process is stopped by one of the stop signals and then resumed via SIGCONT. This behavior is not sanctioned by POSIX.1, and doesn't occur on other systems.

Thus, the function loops while accept() errors out (returns -1) and errno is set to EINTR.

From the client side, you connect to the server's listening socket. The OSL API function that does this is osl_connectSocketTo(). The Unix version is:

The Windows version of the function is similar:

The OSL connect functions handle connect() in both blocking and non-blocking mode.

Example

The following example is from my private branch in the LibreOffice git repository.

This example basically runs on two threads: the main thread which runs the client, and a second background threat that runs the server that the client connects to. Essentially, the program first opens a socket, binds this to the localhost address 127.0.0.1 on port 30,000 (a high port), and then listens for connections. The client is run after this server thread which opens its own socket, then connects to the remote server and sends a single character to it, which the server receives and echos to the screen. A condition variable is used to ensure that the client doesn't try to connect to the server before it has started listening.

.../sal/workben/osl/socket/socket.cxx

Last updated

Was this helpful?