hugo-rottenwheel/content/posts/post-1.md

104 lines
3.1 KiB
Markdown
Raw Normal View History

2024-07-14 06:37:57 +00:00
---
title: "Example post"
date: 2022-01-28T03:00:00+03:00
image: "images/duckpond-small.png"
2024-07-14 06:37:57 +00: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
```