103 lines
3.1 KiB
Markdown
103 lines
3.1 KiB
Markdown
|
---
|
||
|
title: "Example post"
|
||
|
date: 2022-01-28T03:00:00+03:00
|
||
|
---
|
||
|
|
||
|
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.
|
||
|
|
||
|
```C
|
||
|
/* 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`.
|
||
|
```C
|
||
|
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](https://gitlab.com/kernal/kchat/-/blob/master/src/kchat.c).
|
||
|
|
||
|
Leave us a message on
|
||
|
```
|
||
|
$ nc 2a02:c207:2043:4492:: 1337 # or
|
||
|
$ nc chat.kernal.eu 1337
|
||
|
```
|
||
|
|