Socket Code Examples

Socket Code Examples

For the Raft project you will need to open TCP sockets. This requires some very specific code, especially on the server side. This page contains simple examples of code for both the client and server sides; the examples are based on IPv4. This code has not been tested (or even compiled!), so it may not be perfect. If you find bugs, please report them. This code shows places where error handling code is needed, but it doesn't include actual code to handle errors.

Note: the code below is intended only to illustrate how to use the socket APIs. The APIs of the various functions are not particularly well designed; I don't recommend using APIs like these in your projects.

More detailed information about the system calls and functions is available on the Web. For example, to get full details on the getaddrinfo function, Google "man getaddrinfo". You can also invoke man getaddrinfo program directly on a Linux machine, such as the myth cluster. In some cases it may help to specify the man section number: 2 for system calls ("man 2 socket") and 3 for library functions ("man 3 getaddrinfo").

Client side

Here is an example of a method that connects to a TCP server that is listening on a given port on a given machine.

int openSocket(char *server, int port)
{
    // First, must find an IP address for the given server name.
    // getaddrinfo is now the official way to do this.

    struct addrinfo hints;
    struct addrinfo *matchingAddrs;
    struct sockaddr dest;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    int status = getaddrinfo(host, NULL, &hints, &matchingAddrs);
    if (status != 0) {
        // Handle error
    }
    memcpy(&dest, matchingAddrs->ai_addr, matchingAddrs->ai_addrlen);
    freeaddrinfo(matchingAddrs);

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        // Handle error
    }
    if (connect(fd, &dest, sizeof(dest)) == -1) {
        // Handle error
    }

    // This code disables the "Nagle Algorithm", which really shouldn't
    // be enabled by default (it makes things slower).
    int flag = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
    if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
        // Handle error
    }
    
    return fd;
}

Server side

On the server side, there are two pieces of code involved. The first piece opens the socket and sets it up to accept new incoming connections on a given port:

int openListener(int port)
{

    int listenFd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenFd == -1) {
        // Handle error
    }

    // This piece of code allows the listening socket to be reopened
    // immediately if your server crashes (or is killed) and is then
    // restarted. Without this code, the socket will not be reuable
    // for 30 seconds after the last use ended.
    int optionValue = 1;
    if (setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &optionValue,
            sizeof(optionValue)) != 0) {
        // Handle error
    }

    // This code configures the socket so that accept will return
    // immediately if there are no incoming connections to accept. You'll
    // need this code if you are using sockets in an asynchronous way
    // (e.g. with epoll or select). If the server is using the listening
    // socket synchronously (a thread blocks waiting for connections),
    // then you shouldn't use this code.
    if (fcntl(listenFd, F_SETFL, O_NONBLOCK) != 0) {
        // Handle error
    }

    // This code associates the socket with a particular port number;
    // clients will specify this port number when they connect.
    sockaddr_in addr;
    addr.in4.sin_family = AF_INET;
    addr.in4.sin_port = htons(port);
    addr.in4.sin_addr.s_addr = INADDR_ANY;
    if (bind(listenFd, (struct sockaddr *) addr, sizeof(addr)) == -1) {
        // Handle error.
    }

    // This call tells the system that this socket will be used to
    // accept new incoming connections.
    if (listen(listenFd, 1000) == -1) {
        // Handle error.
    }

The second piece of code is invoked when the listening socket becomes "readable": it accepts the connection, producing another socket that can be used to communicate with a specific client. If your server is synchronous (a thread will block waiting for incoming connections, then this code can be invoked without waiting for the listening socket to become readable. Note: you can use accept4 instead of accept if you'd like to specify options such as O_NONBLOCK.

int acceptConnection(int listenFd, struct sockaddr *addr)
{
    int fd = accept(listenFd, &addr, sizeof(addr));
    if (fd < 0) {
        // Handle error
    }

    // Disable the Nagle algoritm (as for the client side).
    int flag = 1;
    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) <!= 0) {
        // Handle error
    }
    return fd;
}