Listening on multiple ports using select() in C

What you need to do is:

  1. Create the sockets.
  2. Add the sockets to the set (FD_SET).
  3. Find the socket with the highest file descriptor for calls to select().

Above also works with different type sockets (e.g. AF_INET, AF_INET6). It even works if you have different type sockets (e.g. one IPv4 and one IPv6) listening on the same port number. Dual stack sockets are a thing but this approach is more flexible.

Create sockets

For this example I will be creating two sockets. One of them will have an IPv4 address and the other IPv6. Nothing out of the ordinary here.

	/* Create IPv4 socket */
	struct sockaddr_in serv_addr4, cli_addr4;
	int addrlen4 = sizeof(cli_addr4);

	if ((sockfd_v4 = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		return 1;
	}

	setsockopt(sockfd_v4, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(int));

	serv_addr4.sin_family = AF_INET;
	serv_addr4.sin_addr.s_addr = inet_addr(IPV4_ADDR);
	serv_addr4.sin_port = htons(IPV4_PORT);

	if (bind(sockfd_v4, (struct sockaddr *)&serv_addr4, sizeof(serv_addr4)) < 0) {
		perror("bind");
		return 1;
	}

	if (listen(sockfd_v4, BACKLOG) < 0) {
		perror("listen");
		return 1;
	}
	
	/* Create IPv6 socket */
	struct sockaddr_in6 serv_addr6, cli_addr6;
	int addrlen6 = sizeof(cli_addr6);

	if ((sockfd_v6 = socket(AF_INET6, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		return 1;
	}

	setsockopt(sockfd_v6, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(int));
	serv_addr6.sin6_family = AF_INET6;
	inet_pton(AF_INET6, IPV6_ADDR, &serv_addr6.sin6_addr);
	serv_addr6.sin6_port = htons(IPV6_PORT);

	if (bind(sockfd_v6, (struct sockaddr *)&serv_addr6, sizeof(serv_addr6)) < 0) {
		perror("bind");
		return 1;
	}

	if (listen(sockfd_v6, BACKLOG) < 0) {
		perror("listen");
		return 1;
	}

Adding sockets to the set and calling select()

FD_SET() is called on each socket. Later on we compare the two sockets to see which one has the highest file descriptor (stored in maxfd). Additionally, there’s an array of structures that also contain sockets (clients), their values are also checked to find the highest descriptor. Finally we can call select() with maxfd + 1.

	for (;;) {
		FD_ZERO(&descriptors);
		FD_SET(sockfd_v4, &descriptors);
		FD_SET(sockfd_v6, &descriptors);
		if (sockfd_v6 > sockfd_v4)
			maxfd = sockfd_v6;
		else
			maxfd = sockfd_v4;
		/* Add all socket descriptors to the read list. */
		for (id = 0; id < maxclients; id++) {
			if (clients[id] != NULL) {
				FD_SET(clients[id]->connfd, &descriptors);
				/* Find highest file descriptor, needed for the select function. */
				if (clients[id]->connfd > maxfd)
					maxfd = clients[id]->connfd;
			}
		}

		if (select(maxfd + 1 ,&descriptors, NULL, NULL, NULL) == -1)
			perror("select");
...

Full server example

The snippets in this post are taken from kernal-chat and simplified. You can find the full source code here.

Leave us a message on

$ nc 2a02:c207:2043:4492:: 1337 # or
$ nc chat.kernal.eu 1337