Ivan Borshukov

@Bo0mer

Sockets in action

This post series aims to clarify the different operations you could do with a network socket and the verbs to use when talking about them.

So what is a socket and why should I care about things I could do with it? From Wikipedia:

A network socket is an endpoint of a connection in a computer network. It is a handle (abstract reference) that a program can pass to the networking application programming interface (API) to use the connection for receiving and sending data. Sockets are often represented internally as integers.

So, if your application is using sockets in some way, and it most probably is, you should be familiar with the operations on sockets. Let’s start by creating a socket.

Creating a socket

Creating a socket is done via the socket system call. To create a socket we need to specify 3 things — this is the signature of the socket method:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

So, what are domain, type and protocol?

Domain specifies the protocol family which will be used for communication. A protocol family is a group of logical properties within an network interface configuration. Protocol families include all the protocols that make up a protocol suite. The most popular ones are IPv4, IPv6, UNIX.

The type describes the semantics of the protocols that are part of the protocol family specified by the previous argument — e.g. I want to have a 2-way connection oriented byte streams with guarantee of delivery, or I just want to send datagrams (connectionless, unreliable messages of a fixed maximum length).

Ok, so we have specified the protocol family, and what we want from it. The last argument specifies a concrete protocol implementation from the family. In general, there is only one protocol that satisfies the (family, type) pair, so this argument is omitted (0 is passed), but if more than one implementations exist, this argument must be set accordingly.

After we have created the socket, we could either connect to a remote socket, or listen for incoming connections. Let’s start with the later.

Binding address to socket

In order to listen for incoming connections, or just to receive datagrams, we first need to specify an address for the socket (think of it as assigning a name to the socket). This operations is called binding — we bind an address (name) to the socket.

int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

This call takes the socket descriptor, the address which should be used and the size in bytes of the address type. The actual structure passed for the addr argument will depend on the address family (we talked about protocol families earlier, remember?). The only purpose of the struct sockaddr structure is to cast the structure pointer passed in addr in order to avoid compiler warnings. Let’s see an example for binding the 127.0.0.1:8000 IPv4/TCP address to a socket. (Error handling is omitted for simplicity)

int sockfd;
struct sockaddr_in serv_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_LOOPBACK;
serv_addr.sin_port = htons(8000);
bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

Great! We now have address associated with the socket. If we had specified a connectionless protocol, e.g. UDP, we could start reading directly from the socket. But since we chose a connection-oriented protocol, TCP, we should announce willingness to accept incoming connections and then read data from those connections. Let’s see how this happens.

Listening on a socket

Listening is done via the listen system call:

int listen(int sockfd, int backlog);

The first arguments is the socket we want to listen on, and the second one specifies a so-called backlog. A queue for pending incoming connections is created (by the OS) for each socket. The backlog represents the size of the queue. If a connection request arrives when the queue is full, the client may receive an error or the request may be ignored (depending on the underlying protocol). Note that the queue size might be affected by some protocol specifics or OS configurations, thus it might not take the value specified via listen.

Accepting incoming connections

After we announced that we want to accept connections, the last part is to actually accept such. This is done via (surprise!) the accept syscall.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd is the socket we earlier listened on. The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket (client’s address), as known to the communications layer. The exact format of the address returned is determined by the socket’s address family. If we do not care about the peer address, we can just pass NULL.

The addrlen argument is a value-result argument: the caller must initialize it to contain the size (in bytes) of the structure pointed to by addr. On return it will contain the actual size of the peer address.

Example

Let’s put it all together. Note that the below example will exit only if there’s error when trying to accept an incoming connection.

After building and running, we could verify that the behavior is correct. First, let’s see that the program is listening on the desired address:

$ ss -tnl src :8000
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:8000 *:*

You can see that we have a TCP socket in LISTEN state. The local address of the socket is *:8000, which corresponds to INADDR_ANY:8000 that we’ve configured. The Send-Q size is 128, as specified by the backlog arg of the listen syscall.

If we establish a connection to our program using netcat, we see the “Hello, socket!” message.

$ nc localhost 8000
Hello, socket!

And the output of our program prints the peer’s address (127.0.0.1 if we’re connection from the same machine):

$ ./main
received connection from peer: 127.0.0.1

Summary

In this post we explained the purpose of the socket, bind, listen and accept actions on sockets. Let’s reiterate.

socket — create a socket endpoint for communication
bind — bind a name (address) to a socket
listen — listen for connections on a socket
accept — accept a connection on a socket

In the next part of the series, we’ll look from a client’s perspective by initiating a connection on a socket, sending and receiving data and cleaning after ourselves. Stay tuned.

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising &sponsorship opportunities.
To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

More by Ivan Borshukov

Topics of interest

More Related Stories