CodingBison

Sockets are versatile in terms of options they support. We can use these socket options as knobs to customize the socket behavior as per the needs of a network application. For example, if an application sends data in occasional large bursts, we can use a socket option to increase the size of the socket send buffer, so that it can such bursts.

Sockets support several options. Socket layer provides two functions that help us set/get these options:

 int setsockopt(int fd, int level, int name, const void *value, socklen_t len);
 int getsockopt(int fd, int level, int name, const void *value, socklen_t *len);

The first argument for both calls is the file descriptor of the socket for which we wish to set or get the option.

The second and third arguments for both calls specify the level and the option name for the option. Each socket option is identified by the combination of an option name and an option level; the level identifies the layer in the stack for which the behavior is being modified. For example, if we wish to modify socket layer behavior, then we can set the level to reflect the socket layer.

The last two arguments for the setsockopt() call are the value and the length of the option. The value is a pointer and the storage represented by the pointer is indicated by the option_len parameter. When we call setsockopt(), the application sets the option_value along with its length and passes it to the underlying layer.

Once an option is set for a socket, we can use getsockopt() call to retrieve its value. With getsockopt(), we pass an empty buffer (in the form of value parameter) along with the length of the buffer. This time, the underlying socket layer sets the option value in the buffer. Once getsockopt() returns, the application can read the value of the option from the buffer.

Sockets support a host of useful options. We describe some of the commonly used options for the setsockopt call. We begin by providing them in the table below. For a more detailed description, we can do "man setsockopt" for the man-pages or google "man setsockopt".


Table: Some of the setsockopt() options
Option NameOption LevelDescription
SO_LINGERSOL_SOCKETSend pending data before closing the socket
SO_REUSEADDRSOL_SOCKETAllow reuse of a local port that is already bound
SO_KEEPALIVESOL_SOCKETSend periodic keepalives to keep the connection alive
SO_SNDBUFSOL_SOCKETAdjust socket send buffer
SO_RECVBUFSOL_SOCKETAdjust socket receiver buffer
SO_NONBLOCKSOL_SOCKETMakes the socket non-blocking

Now, let us describe these options.

SO_LINGER: This option requests the local socket to send all the pending data present in the send buffer even if the application issues a close. Typically, this is used for TCP where a reliable mode of data transfer is required. The SO_LINGER option usually takes a timeout value which means that after the timeout happens, the close would proceed in a normal manner and if all the present data is not sent within the timeout period, then the remaining would be discarded.

SO_REUSEADDR (level SOL_SOCKET): This option allows a local socket to reuse a local address that may be already in use. In other words, even if there is a socket that is already bound to that local address, a new socket can specify this option and still use the same local address. Binding multiple sockets to the same port and address is often used when we have multiple multicast receivers sitting on the same machine. On certain operating systems, this option might be available as SO_REUSEPORT; this option allows reuse of both the local address and the local port. However, the behavior of these options can be platform specific and should be considered before using it. Further, on some of the platforms (including Linux), both the sockets must set the SO_REUSEADDR, else the second socket not be able to bind.

SO_KEEPALIVE (level SOL_SOCKET): This option allows an application to close a connected socket if the remote end closes without informing the local socket. As the name suggests, when this option is set and if the connection becomes idle (that is the no data to received and sent), then the socket begins to send keepalive messages. If the remote socket does not respond to a pre-specified number of keepalive retries, then the local end closes the connection.

SO_SNDBUF and SO_RECVBUF (level SOL_SOCKET): These options allow an application to control the local socket buffer for send-side and receive-side. If not set, the default value provided by the kernel is used. This buffer optimization can be helpful if the application expects either a high volume of traffic or a low volume of traffic. With high volume traffic, the current default limit might pose a problem and the throughput can decrease due to local buffer limitation. With low volume traffic, the memory consumed by the default limit might go unused; by specifying a lower value, the unused memory can be used somewhere else. Thus, the application can use this option to increase/decrease the local buffers, as per the expected traffic.

SO_NONBLOCK (level SOL_SOCKET): Setting SO_NONBLOCK makes the current socket non-blocking. This would modify the behavior of the usual blocking calls, like recv(), accept(), and connect(). Normally, these calls block if the resource is not available. For example, if there is no received data, then the recv() call would block. But with SO_NONBOCK options, instead of blocking, these calls would return with a value of -1 and errno set to EAGAIN/EWOULDBLOCK. In that case, the application would need to retry later.

All of the options in the above table have an option level of SOL_SOCKET. This means that these options apply in the socket layer of the stack. However, there are other options levels as well: SOL_TCP, SOL_IP, and SOL_IPV6. SOL_TCP is used for options that modify the socket behavior the TCP layer. SOL_IP and SOL_IPV6 are levels that modify the socket behavior for IP and IPv6 layers, respectively.

Next, we present two examples to help us understand the behavior of these two calls.

Our first example sets some of the above options on a socket. After that, we retrieve the earlier set option using the getsockopt() call. For this example, we do not verify the actual behavior corresponding to the options in this example -- that would be beyond the scope of the current text. Also, the SOL_LINGER option accepts the input in the form of a data structure" struct linger. This structure has two fields: (a) int l_onoff and (b) int l_linger. If l_onoff is nonzero, then this option is applied. The l_linger value specifies the timeout period in seconds.

 #include <stdio.h>
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>

 int main () {
     struct sockaddr_in saddr;
     int fd, ret_val, opt_len, opt_val = 1, sockbuf_val = 4096;
     struct linger set_linger, get_linger;

     /* First step is to open a socket  */
     fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (fd == -1) {
         fprintf(stderr, "socket call failed [%s]\n", strerror(errno));
         return -1;
     }

     /* Next, let us use setsockopt() to set some options */
     printf("Let us set some socket options\n");
     ret_val = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt_val, sizeof(int));
     if (ret_val != 0) {
         fprintf(stderr, "setsockopt [SO_KEEPALIVE] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("setsockopt [SO_KEEPALIVE] succeeded for socket fd: %d\n", fd);
     }

     set_linger.l_onoff = 1;
     set_linger.l_linger = 10;
     ret_val = setsockopt(fd, SOL_SOCKET, SO_LINGER, &set_linger, 
             sizeof(struct linger));
     if (ret_val != 0) {
         fprintf(stderr, "setsockopt [SO_LINGER] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("setsockopt [SO_LINGER] succeeded for socket fd: %d\n", fd);
     }

     ret_val = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &sockbuf_val, sizeof(int));
     if (ret_val != 0) {
         fprintf(stderr, "setsockopt [SO_RCVBUF] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("setsockopt [SO_RCVBUF] succeeded for socket fd: %d\n", fd);
     }

     /* After that, let us use getsockopt() to retrieve values of options */
     printf("\nLet us retrieve those options:\n");
     opt_len = sizeof(int);
     ret_val = getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt_val, &opt_len);
     if (ret_val != 0) {
         fprintf(stderr, "getsockopt [SO_KEEPALIVE] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("getsockopt [SO_KEEPALIVE]: value: %d len: %d\n",
             opt_val, opt_len);
     }

     opt_len = sizeof(struct linger);
     ret_val = getsockopt(fd, SOL_SOCKET, SO_LINGER, &get_linger, &opt_len);
     if (ret_val != 0) {
         fprintf(stderr, "getsockopt [SO_LINGER] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("getsockopt [SO_LINGER]: l_onoff: %d l_linger: %d len: %d\n",
             get_linger.l_onoff, get_linger.l_linger, opt_len);
     }

     opt_len = sizeof(int);
     ret_val = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt_val, &opt_len);
     if (ret_val != 0) {
         fprintf(stderr, "getsockopt [SO_RCVBUF] failed [%s]\n", 
                 strerror(errno));
     } else {
         printf("getsockopt [SO_RCVBUF]: value: %d len: %d\n",
             opt_val, opt_len);
     }

     /* Last step is to close the sockets  */
     close(fd);
     return 0;
 }
 $ gcc setsockopt-socket1.c -o setsockopt1
 $ 
 $ ./setsockopt1
 Let us set some socket options:
 setsockopt [SO_KEEPALIVE] succeeded for socket fd: 3
 setsockopt [SO_LINGER] succeeded for socket fd: 3
 setsockopt [SO_RCVBUF] succeeded for socket fd: 3

 Let us retrieve those options:
 getsockopt [SO_KEEPALIVE]: value: 1 len: 4
 getsockopt [SO_LINGER]: l_onoff: 1 l_linger: 10 len: 8
 getsockopt [SO_RCVBUF]: value: 8192 len: 4

The reason why Linux returns double the size of SO_RCVBUF is because Linux actually allocates double the size of what is requested by the user; it does so for storing extra packet buffer needed for each packets.

Our second example sets the SO_REUSEADDR option and also demonstrates its behavior. For that, we set this option on a socket and bind it to an address and a port. After that, we open another socket and try to reuse the same address and port number. Since we have SO_REUSEADDR, we are able to do so! Note that both sockets must set this option, otherwise the second bind() call would fail.

 #include <stdio.h>
 #include <sys/socket.h>
 #include <errno.h>
 #include <netinet/in.h>

 int main () {
     struct sockaddr_in saddr;
     int fd1, fd2;
     int ret_val, opt_val = 1, opt_len = sizeof(int);

     /* First step is to open a socket  */
     fd1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if (fd1 == -1) {
         fprintf(stderr, "socket call failed [%s]\n", strerror(errno));
         return -1;
     }
     printf("Created a socket with fd: %d\n", fd1);

     /* Next, let us use setsockopt() to set the SO_REUSEADDR option */
     ret_val = setsockopt(fd1, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val));
     if (ret_val != 0) {
         fprintf(stderr, "setsockopt call failed [%s]\n", strerror(errno));
     } else {
         printf("setsockopt [SO_REUSEADDR] succeeded for socket fd: %d\n", fd1);
     }

     /* After that, let us use getsockopt() to retrieve values of options */
     ret_val = getsockopt(fd1, SOL_SOCKET, SO_REUSEADDR, &opt_val, &opt_len);
     if (ret_val != 0) {
         fprintf(stderr, "getsockopt call failed [%s]\n", strerror(errno));
     } else {
         printf("getsockopt [SO_REUSEADDR]: value: %d len: %d\n",
             opt_val, opt_len);
     }

     printf("\nLet us use SO_REUSEADDR to bind two sockets to the same port\n");
     saddr.sin_family = AF_INET;
     saddr.sin_port = htons(7000);
     saddr.sin_addr.s_addr = INADDR_ANY;

     ret_val = bind(fd1, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
     if (ret_val != 0) {
         fprintf(stderr, "bind call failed [%s]\n", strerror(errno));
     } else {
         printf("bind succeeded for socket fd: %d\n", fd1);
     }

     /* Now, let us create another socket and verify if we can reuse the address/port */
     fd2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (fd2 == -1) {
         fprintf(stderr, "socket call failed [%s]\n", strerror(errno));
     }
     printf("Created a socket with fd: %d\n", fd2);

     ret_val = setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val));
     if (ret_val != 0) {
         fprintf(stderr, "setsockopt call failed [%s]\n", strerror(errno));
     } else {
         printf("setsockopt [SO_REUSEADDR] succeeded for socket fd: %d\n", fd2);
     }

     ret_val = bind(fd2, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
     if (ret_val != 0) {
         fprintf(stderr, "bind call failed [%s]\n", strerror(errno));
     } else {
         printf("bind succeeded for socket fd: %d\n", fd2);
     }

     /* Last step is to close the sockets  */
     close(fd1);
     close(fd2);
     return 0;
 }

Here is the output:

 $ gcc setsockopt-socket.c -o setsockopt2
 $ 
 $ ./setsockopt2
 Created a socket with fd: 3
 setsockopt [SO_REUSEADDR] succeeded for socket fd: 3
 getsockopt [SO_REUSEADDR]: value: 1 len: 4

 Let us use SO_REUSEADDR to bind two sockets to the same port
 bind succeeded for socket fd: 3
 Created a socket with fd: 4
 setsockopt [SO_REUSEADDR] succeeded for socket fd: 4




comments powered by Disqus