Question: What do you think about this design and the implementation? This is a simple networking framework for IPv4, IPv6 TCP client and TCP server for Linux and MS-Windows in C. It uses a single-thread solution around the system call select(), that is IO-multiplexing. Connect and network read are asynchronous, network write is synchronous. The liomux.h file implements the Linux specific version, the wiomux.h implements the MS-Windows specific version and chat.c implements a chat application as example.
On http://www.andreadrian.de/non-blocking_connect/index.html is a lot of information about an older version of this source code.
/* chat.c
* chat application, example for simple networking framework in C
*
* Copyright 2022 Andre Adrian
* License for this source code: 3-Clause BSD License
*
* 25jun2022 adr: 2nd published version
*/
#include <libgen.h> // basename()
#ifdef _WIN32
#include "wiomux.h"
#else
#include "liomux.h"
#endif
/* ******************************************************** */
// Business logic
enum {
BUFMAX = 1460, // Ethernet packet size minus IPv4 TCP header size
};
// callback client read available
int cb_chat_client_read(Conn* obj, SOCKET fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd >= 0 && fd < FDMAX);
char buf[BUFMAX];
int readbytes = recv(fd, buf, sizeof buf, 0);
if (readbytes > 0 && readbytes != SOCKET_ERROR) {
// Write all data out
int writebytes = fwrite(buf, 1, readbytes, stdout);
assert(writebytes == readbytes && "fwrite");
}
return readbytes;
}
// callback server read available
int cb_chat_server_read(Conn* obj, SOCKET fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd >= 0 && fd < FDMAX);
char buf[BUFMAX]; // buffer for client data
int readbytes = recv(fd, buf, sizeof buf, 0);
if (readbytes > 0 && readbytes != SOCKET_ERROR) {
// we got some data from a client
for (SOCKET i = 0; i < FDMAX; ++i) {
// send to everyone!
if (FD_ISSET(i, &obj->fds)) {
// except the listener and ourselves
if (i != obj->sockfd && i != fd) {
int writebytes = send(i, buf, readbytes, 0);
if (writebytes != readbytes) {
perror("WW send");
}
}
}
}
}
return readbytes;
}
void keyboard_poll(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
if (_kbhit()) { // very MS-DOS
char buf[BUFMAX];
char* rv = fgets(buf, sizeof buf, stdin);
assert(rv != NULL);
send(obj->sockfd, buf, strlen(buf), 0);
}
after(TIMEOUT, (cb_timer_t)keyboard_poll, obj);
}
int main(int argc, char* argv[]) {
iomux_begin();
if (argc < 4) {
char* name = basename(argv[0]);
fprintf(stderr,"usage server: %s s hostname port\n", name);
fprintf(stderr,"usage client: %s c hostname port\n", name);
fprintf(stderr,"example server IPv4: %s s 127.0.0.1 60000\n", name);
fprintf(stderr,"example client IPv4: %s c 127.0.0.1 60000\n", name);
fprintf(stderr,"example server IPv6: %s s ::1 60000\n", name);
fprintf(stderr,"example client IPv6: %s c ::1 60000\n", name);
exit(EXIT_FAILURE);
}
switch(argv[1][0]) {
case 'c': {
Conn* obj = conn_make();
client_open(obj, argv[2], argv[3]);
conn_add_cb(obj, cb_chat_client_read);
after(TIMEOUT, (cb_timer_t)keyboard_poll, obj);
}
break;
case 's': {
Conn* obj = conn_make();
server_open(obj, argv[2], argv[3]);
conn_add_cb(obj, cb_chat_server_read);
}
break;
default:
fprintf(stderr,"EE %s: unexpected argument %s\n", FUNCTION, argv[1]);
exit(EXIT_FAILURE);
}
conn_event_loop(); // start inversion of control
iomux_end();
return 0;
}
The framework offers a "Timer class" and a "Connection class". The Connection class sub-classes into a "server class" and into a "client class". Because C is not object oriented, these are only design ideas.
/* liomux.h
* Simple networking framework in C
* Linux version
* I/O Multiplexing (select) IPv4, IPv6, TCP Server, TCP client
*
* Copyright 2022 Andre Adrian
* License for this source code: 3-Clause BSD License
*
* 25jun2022 adr: 2nd published version
*/
#ifndef LIOMUX_H_
#define LIOMUX_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <limits.h>
// Linux
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <arpa/inet.h>
#define FUNCTION __func__
enum {
CONNMAX = 10, // maximum number of Connection objects
FDMAX = 64, // maximum number of open file descriptors
CONN_SERVER = 1, // TCP server connection object label
CONN_CLIENT = 2, // TCP client connection object label
TIMEOUT = 40, // select() timeout in milli seconds
STRMAX = 80, // maximum length of C-String
TIMERMAX = 10, // maximum number of Timer objects
SOCKET_ERROR = INT_MAX, // for MS-Windows compability
INVALID_SOCKET = INT_MAX-1, // for MS-Windows compability
};
typedef int SOCKET; // for MS-Windows compability
// get sockaddr, IPv4 or IPv6:
void* get_in_addr(struct sockaddr* sa) {
assert(sa != NULL);
if (AF_INET == sa->sa_family) {
return &(((struct sockaddr_in*)sa)->sin_addr);
} else {
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
}
// Copies a string with security enhancements
void strcpy_s(char* dest, size_t n, const char* src) {
strncpy(dest, src, n-1);
dest[n - 1] = '\0';
}
/** @brief Checks the console for keyboard input
* @retval >0 keyboard input
* @retval =0 no keyboard input
*/
int _kbhit(void) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
struct timeval tv = {0, 0};
return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
}
/* ******************************************************** */
// Timer object
typedef void (*cb_timer_t)(void* obj);
typedef struct {
cb_timer_t cb_timer; // cb_timer and obj are a closure
void* obj;
struct timespec ts; // expire time
} Timer;
static Timer timers[TIMERMAX]; // Timer objects array
int after(int interval, cb_timer_t cb_timer, void* obj) {
assert(interval >= 0);
assert(cb_timer != NULL);
// no assert obj
int id;
for (id = 0; id < TIMERMAX; ++id) {
if (NULL == timers[id].cb_timer) {
break; // found a free entry
}
}
assert (id < TIMERMAX && "timer array full");
// convert interval in milliseconds to timespec
struct timespec dts;
dts.tv_nsec = (interval % 1000) * 1000000;
dts.tv_sec = interval / 1000;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
timers[id].cb_timer = cb_timer;
timers[id].obj = obj;
timers[id].ts.tv_nsec = (now.tv_nsec + dts.tv_nsec) % 1000000000;
timers[id].ts.tv_sec = (now.tv_nsec + dts.tv_nsec) / 1000000000;
timers[id].ts.tv_sec += (now.tv_sec + dts.tv_sec);
/*
printf("II %s now=%ld,%ld dt=%ld,%ld ts=%ld,%ld\n", FUNCTION,
now.tv_sec, now.tv_nsec, dts.tv_sec, dts.tv_nsec,
timers[i].ts.tv_sec, timers[i].ts.tv_nsec);
*/
return id;
}
void timer_walk(void) {
// looking for expired timers
for (int i = 0; i < TIMERMAX; ++i) {
if (timers[i].cb_timer != NULL) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
if ((ts.tv_sec > timers[i].ts.tv_sec) || (ts.tv_sec == timers[i].ts.tv_sec
&& ts.tv_nsec >= timers[i].ts.tv_nsec)) {
Timer tmp = timers[i];
// erase array entry because called function can overwrite this entry
memset(&timers[i], 0, sizeof timers[i]);
assert(tmp.cb_timer != NULL);
(*tmp.cb_timer)(tmp.obj);
}
}
}
}
/* ******************************************************** */
// Connection object
typedef struct Conn {
fd_set fds; // read file descriptor set
int sockfd; // network port for client, listen port for server
int isConnecting; // non-blocking connect started, but no finished
int typ; // tells client or server object
char hostname[STRMAX];
char port[STRMAX];
int (*cb_read)(struct Conn* obj, int fd); // Callback Pointer
} Conn;
typedef int (*cb_read_t)(Conn* obj, int fd); // Callback Pointer type
static Conn conns[CONNMAX]; // Connection objects array
Conn* conn_make(void) {
for (int i = 0; i < CONNMAX; ++i) {
if (0 == conns[i].typ) {
return &conns[i]; // found a free entry
}
}
assert(0 && "conn array full");
return NULL;
}
void client_open(Conn* obj, const char* hostname, const char* port) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(port != NULL);
assert(hostname != NULL);
printf("II %s: port=%s hostname=%s\n", FUNCTION, port, hostname);
FD_ZERO(&obj->fds);
obj->typ = CONN_CLIENT;
strcpy_s(obj->port, sizeof obj->port, port);
strcpy_s(obj->hostname, sizeof obj->hostname, hostname);
void client_open1(Conn* obj);
client_open1(obj);
}
void client_open1(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* res;
int rv = getaddrinfo(obj->hostname, obj->port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "EE %s getaddrinfo: %s\n", FUNCTION, gai_strerror(rv));
exit(EXIT_FAILURE);
}
// loop through all the results and connect to the first we can
struct addrinfo* p;
for (p = res; p != NULL; p = p->ai_next) {
obj->sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (-1 == obj->sockfd) {
perror("WW socket");
continue;
}
// Unix Networking Programming v2 ch. 15.4, 16.2 non-blocking connect
int val = 1;
rv = ioctl(obj->sockfd, FIONBIO, &val);
if (rv != 0) {
perror("WW ioctl FIONBIO ON");
close(obj->sockfd);
continue;
}
rv = connect(obj->sockfd, p->ai_addr, p->ai_addrlen);
if (rv != 0) {
if (EINPROGRESS == errno) {
obj->isConnecting = 1;
} else {
perror("WW connect");
close(obj->sockfd);
continue;
}
}
break; // exit loop after socket and connect were successful
}
assert(p != NULL && "connect try");
void* src = get_in_addr((struct sockaddr*)p->ai_addr);
char dst[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family, src, dst, sizeof dst);
freeaddrinfo(res);
// don't add sockfd to the fd_set, client_connect() will do
printf("II %s: connect try to %s (%s) port %s socket %d\n", FUNCTION,
obj->hostname, dst, obj->port, obj->sockfd);
}
void client_reopen(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
close(obj->sockfd);
FD_CLR(obj->sockfd, &obj->fds); // remove network fd
obj->sockfd = -1;
after(5000, (cb_timer_t)client_open1, obj);
// ugly bug with after(0, ...
}
void client_connect(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
obj->isConnecting = 0;
int optval = 0;
socklen_t optlen = sizeof optval;
int rv = getsockopt(obj->sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
assert(0 == rv && "getsockopt SOL_SOCKET SO_ERROR");
if (0 == optval) {
FD_SET(obj->sockfd, &obj->fds);
printf("II %s: connect success to %s port %s socket %d\n", FUNCTION,
obj->hostname, obj->port, obj->sockfd);
} else {
fprintf(stderr, "WW %s: connect fail to %s port %s socket %d: %s\n", FUNCTION,
obj->hostname, obj->port, obj->sockfd, strerror(optval));
client_reopen(obj);
}
}
void client_handle(Conn* obj, int fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd >= 0 && fd < FDMAX);
assert(obj->cb_read != NULL);
int rv = (*obj->cb_read)(obj, fd);
if (rv < 1) {
int optval = 0;
socklen_t optlen = sizeof optval;
int rv = getsockopt(obj->sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
assert(0 == rv && "getsockopt SOL_SOCKET SO_ERROR");
fprintf(stderr, "WW %s: connect fail to %s port %s socket %d rv %d: %s\n",
FUNCTION, obj->hostname, obj->port, obj->sockfd, rv, strerror(optval));
client_reopen(obj);
}
}
void conn_add_cb(Conn* obj, cb_read_t cb_read) {
assert(cb_read != NULL);
obj->cb_read = cb_read;
}
void server_open(Conn* obj, const char* hostname, const char* port) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(port != NULL);
// no assert hostname
printf("II %s: port=%s hostname=%s\n", FUNCTION, port, hostname);
FD_ZERO(&obj->fds);
obj->typ = CONN_SERVER;
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
struct addrinfo* res;
int rv = getaddrinfo(hostname, port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "EE %s getaddrinfo: %s\n", FUNCTION, gai_strerror(rv));
exit(EXIT_FAILURE);
}
struct addrinfo* p;
for(p = res; p != NULL; p = p->ai_next) {
obj->sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (-1 == obj->sockfd) {
perror("WW socket");
continue;
}
int yes = 1;
rv = setsockopt(obj->sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if (rv != 0) {
perror("WW setsockopt SO_REUSEADDR");
close(obj->sockfd);
continue;
}
rv = bind(obj->sockfd, p->ai_addr, p->ai_addrlen);
if (rv != 0) {
perror("WW bind");
close(obj->sockfd);
continue;
}
break; // exit loop after socket and bind were successful
}
freeaddrinfo(res);
assert(p != NULL && "bind");
rv = listen(obj->sockfd, 10);
assert(0 == rv && "listen");
// add the listener to the master set
FD_SET(obj->sockfd, &obj->fds);
printf("II %s: listen on socket %d\n", FUNCTION, obj->sockfd);
}
void server_handle(Conn* obj, int fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd >= 0 && fd < FDMAX);
if (fd == obj->sockfd) {
// handle new connections
struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen = sizeof remoteaddr;
// newly accept()ed socket descriptor
int newfd = accept(obj->sockfd, (struct sockaddr*)&remoteaddr, &addrlen);
if (-1 == newfd) {
perror("WW accept");
} else {
FD_SET(newfd, &obj->fds);
void* src = get_in_addr((struct sockaddr*)&remoteaddr);
char dst[INET6_ADDRSTRLEN];
inet_ntop(remoteaddr.ss_family, src, dst, sizeof dst);
printf("II %s: new connection %s on socket %d\n", FUNCTION, dst, newfd);
}
} else {
assert(obj->cb_read != NULL);
int rv = (*obj->cb_read)(obj, fd);
if (rv < 1) {
printf("II %s: connection closed on socket %d\n", FUNCTION, fd);
close(fd);
FD_CLR(fd, &obj->fds); // remove from fd_set
}
}
}
void conn_event_loop(void) {
for(;;) {
// virtualization pattern: join all read fds into one
fd_set read_fds = conns[0].fds;
for (int i = 1; i < CONNMAX; ++i) {
for (int fd = 0; fd < FDMAX; ++fd) {
if (FD_ISSET(fd, &conns[i].fds)) {
FD_SET(fd, &read_fds);
}
}
}
// virtualization pattern: join all connect pending into one
fd_set write_fds;
FD_ZERO(&write_fds);
for (int i = 0; i < CONNMAX; ++i) {
if (conns[i].isConnecting) {
FD_SET(conns[i].sockfd, &write_fds);
}
}
struct timeval tv = {0, TIMEOUT * 1000};
int rv = select(FDMAX, &read_fds, &write_fds, NULL, &tv);
if (-1 == rv && EINTR != errno) {
perror("EE select");
exit(EXIT_FAILURE);
}
if (rv > 0) {
// looking for data to read available
for (int fd = 0; fd < FDMAX; ++fd) {
if (FD_ISSET(fd, &read_fds)) {
for (int i = 0; i < CONNMAX; ++i) {
if (FD_ISSET(fd, &conns[i].fds)) {
switch (conns[i].typ) {
case CONN_CLIENT:
client_handle(&conns[i], fd);
break;
case CONN_SERVER:
server_handle(&conns[i], fd);
break;
}
}
}
}
}
// looking for connect pending success or fail
for (int i = 0; i < CONNMAX; ++i) {
if (FD_ISSET(conns[i].sockfd, &write_fds)) {
client_connect(&conns[i]);
}
}
}
timer_walk();
}
}
void iomux_begin(void) {
// do nothing
}
void iomux_end(void) {
// do nothing
}
#endif // LIOMUX_H_
The MS-Windows implemenation is differnt in the area of asynchrounous connect. The Linux select() uses write fd_set to signal the "connecting success/failed" event, but MS-Windows select() uses exception fd_set for this event. There are some more differences.
/* wiomux.h
* Simple networking framework in C
* MS-Windows version
* I/O Multiplexing (select) IPv4, IPv6, TCP Server, TCP client
*
* Copyright 2022 Andre Adrian
* License for this source code: 3-Clause BSD License
*
* 25jun2022 adr: 2nd published version
*/
#ifndef WIOMUX_H_
#define WIOMUX_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
// MS-Windows
#include <conio.h> // _kbhit()
#include <winsock2.h>
#include <ws2tcpip.h> // getaddrinfo()
#define FUNCTION __func__
enum {
CONNMAX = 10, // maximum number of Connection objects
FDMAX = 256, // maximum number of open file descriptors
CONN_SERVER = 1, // TCP server connection object label
CONN_CLIENT = 2, // TCP client connection object label
TIMEOUT = 40, // select() timeout in milli seconds
STRMAX = 80, // maximum length of C-String
TIMERMAX = 10, // maximum number of Timer objects
};
// get sockaddr, IPv4 or IPv6:
void* get_in_addr(struct sockaddr* sa) {
assert(sa != NULL);
if (AF_INET == sa->sa_family) {
return &(((struct sockaddr_in*)sa)->sin_addr);
} else {
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
}
/* ******************************************************** */
// Timer object
typedef void (*cb_timer_t)(void* obj);
typedef struct {
cb_timer_t cb_timer; // cb_timer and obj are a closure
void* obj;
struct timespec ts; // expire time
} Timer;
static Timer timers[TIMERMAX]; // Timer objects array
int after(int interval, cb_timer_t cb_timer, void* obj) {
assert(interval >= 0);
assert(cb_timer != NULL);
// no assert obj
int id;
for (id = 0; id < TIMERMAX; ++id) {
if (NULL == timers[id].cb_timer) {
break; // found a free entry
}
}
assert (id < TIMERMAX && "timer array full");
// convert interval in milliseconds to timespec
struct timespec dts;
dts.tv_nsec = (interval % 1000) * 1000000;
dts.tv_sec = interval / 1000;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
timers[id].cb_timer = cb_timer;
timers[id].obj = obj;
timers[id].ts.tv_nsec = (now.tv_nsec + dts.tv_nsec) % 1000000000;
timers[id].ts.tv_sec = (now.tv_nsec + dts.tv_nsec) / 1000000000;
timers[id].ts.tv_sec += (now.tv_sec + dts.tv_sec);
/*
printf("II %s now=%ld,%ld dt=%ld,%ld ts=%ld,%ld\n", FUNCTION,
now.tv_sec, now.tv_nsec, dts.tv_sec, dts.tv_nsec,
timers[i].ts.tv_sec, timers[i].ts.tv_nsec);
*/
return id;
}
void timer_walk(void) {
// looking for expired timers
for (int i = 0; i < TIMERMAX; ++i) {
if (timers[i].cb_timer != NULL) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
if ((ts.tv_sec > timers[i].ts.tv_sec) || (ts.tv_sec == timers[i].ts.tv_sec
&& ts.tv_nsec >= timers[i].ts.tv_nsec)) {
Timer tmp = timers[i];
// erase array entry because called function can overwrite this entry
memset(&timers[i], 0, sizeof timers[i]);
assert(tmp.cb_timer != NULL);
(*tmp.cb_timer)(tmp.obj);
}
}
}
}
/* ******************************************************** */
// Connection object
typedef struct Conn {
fd_set fds; // read file descriptor set
SOCKET sockfd; // network port for client, listen port for server
int isConnecting; // non-blocking connect started, but no finished
int typ; // tells client or server object
char hostname[STRMAX];
char port[STRMAX];
int (*cb_read)(struct Conn* obj, SOCKET fd); // Callback Pointer
} Conn;
typedef int (*cb_read_t)(Conn* obj, SOCKET fd); // Callback Pointer type
static Conn conns[CONNMAX]; // Connection objects array
Conn* conn_make(void) {
for (int i = 0; i < CONNMAX; ++i) {
if (0 == conns[i].typ) {
return &conns[i]; // found a free entry
}
}
assert(0 && "conn array full");
return NULL;
}
void client_open(Conn* obj, const char* hostname, const char* port) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(port != NULL);
assert(hostname != NULL);
printf("II %s: port=%s hostname=%s\n", FUNCTION, port, hostname);
FD_ZERO(&obj->fds);
obj->typ = CONN_CLIENT;
strcpy_s(obj->port, sizeof obj->port, port);
strcpy_s(obj->hostname, sizeof obj->hostname, hostname);
void client_open1(Conn* obj);
client_open1(obj);
}
void client_open1(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* res;
int rv = getaddrinfo(obj->hostname, obj->port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "EE %s getaddrinfo: %d\n", FUNCTION, rv);
exit(EXIT_FAILURE);
}
// loop through all the results and connect to the first we can
struct addrinfo* p;
for (p = res; p != NULL; p = p->ai_next) {
obj->sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (INVALID_SOCKET == obj->sockfd) {
perror("WW socket");
continue;
}
// Unix Networking Programming v2 ch. 15.4, 16.2 non-blocking connect
unsigned long val = 1;
rv = ioctlsocket(obj->sockfd, FIONBIO, &val);
if (rv != 0) {
perror("WW ioctlsocket FIONBIO ON");
closesocket(obj->sockfd);
continue;
}
rv = connect(obj->sockfd, p->ai_addr, p->ai_addrlen);
if (rv != 0) {
if (WSAEWOULDBLOCK == WSAGetLastError()) {
obj->isConnecting = 1;
} else {
perror("WW connect");
closesocket(obj->sockfd);
continue;
}
}
break; // exit loop after socket and connect were successful
}
assert(p != NULL && "connect try");
void* src = get_in_addr((struct sockaddr*)p->ai_addr);
char dst[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family, src, dst, sizeof dst);
freeaddrinfo(res);
FD_SET(obj->sockfd, &obj->fds);
printf("II %s: connect try to %s (%s) port %s socket %llu\n", FUNCTION,
obj->hostname, dst, obj->port, obj->sockfd);
}
void client_reopen(Conn* obj) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
closesocket(obj->sockfd);
FD_CLR(obj->sockfd, &obj->fds); // remove network fd
obj->sockfd = -1;
client_open1(obj);
}
void client_handle(Conn* obj, SOCKET fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd < FDMAX);
assert(obj->cb_read != NULL);
int rv = (*obj->cb_read)(obj, fd);
if (rv < 1) {
// documentation conflict between
// https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockopt
// https://docs.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options
unsigned long optval;
int optlen = sizeof optval;
int rv = getsockopt(obj->sockfd, SOL_SOCKET, SO_ERROR, (char*)&optval, &optlen);
assert(0 == rv && "getsockopt SOL_SOCKET SO_ERROR");
fprintf(stderr, "WW %s: connect fail to %s port %s socket %llu rv %d: %s\n",
FUNCTION, obj->hostname, obj->port, obj->sockfd, rv, strerror(optval));
client_reopen(obj);
} else {
obj->isConnecting = 0; // hack: connect successful after first good read()
}
}
void conn_add_cb(Conn* obj, cb_read_t cb_read) {
assert(cb_read != NULL);
obj->cb_read = cb_read;
}
void server_open(Conn* obj, const char* hostname, const char* port) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(port != NULL);
assert(hostname != NULL);
printf("II %s: port=%s hostname=%s\n", FUNCTION, port, hostname);
FD_ZERO(&obj->fds);
obj->typ = CONN_SERVER;
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
struct addrinfo* res;
int rv = getaddrinfo(hostname, port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "EE %s getaddrinfo: %d\n", FUNCTION, rv);
exit(EXIT_FAILURE);
}
struct addrinfo* p;
for(p = res; p != NULL; p = p->ai_next) {
obj->sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (INVALID_SOCKET == obj->sockfd) {
perror("WW socket");
continue;
}
// documentation conflict between
// https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt
// https://docs.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options
const unsigned long yes = 1;
rv = setsockopt(obj->sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes,
sizeof(yes));
if (rv != 0) {
perror("WW setsockopt SO_REUSEADDR");
closesocket(obj->sockfd);
continue;
}
rv = bind(obj->sockfd, p->ai_addr, p->ai_addrlen);
if (rv != 0) {
perror("WW bind");
closesocket(obj->sockfd);
continue;
}
break; // exit loop after socket and bind were successful
}
freeaddrinfo(res);
assert(p != NULL && "bind");
rv = listen(obj->sockfd, 10);
assert(0 == rv && "listen");
// add the listener to the master set
FD_SET(obj->sockfd, &obj->fds);
printf("II %s: listen on socket %llu\n", FUNCTION, obj->sockfd);
}
void server_handle(Conn* obj, SOCKET fd) {
assert(obj >= &conns[0] && obj < &conns[CONNMAX]);
assert(fd < FDMAX);
if (fd == obj->sockfd) {
// handle new connections
struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen = sizeof remoteaddr;
// newly accept()ed socket descriptor
SOCKET newfd = accept(obj->sockfd, (struct sockaddr*)&remoteaddr, &addrlen);
if (INVALID_SOCKET == newfd) {
perror("WW accept");
} else {
FD_SET(newfd, &obj->fds); // add to master set
void* src = get_in_addr((struct sockaddr*)&remoteaddr);
char dst[INET6_ADDRSTRLEN];
inet_ntop(remoteaddr.ss_family, src, dst, sizeof dst);
printf("II %s: new connection %s on socket %llu\n", FUNCTION, dst, newfd);
}
} else {
assert(obj->cb_read != NULL);
int rv = (*obj->cb_read)(obj, fd);
if (rv < 1 || SOCKET_ERROR == rv) {
printf("II %s: connection closed on socket %llu\n", FUNCTION, fd);
closesocket(fd);
FD_CLR(fd, &obj->fds); // remove from fd_set
}
}
}
void conn_event_loop(void) {
for(;;) {
// virtualization pattern: join all read fds into one
fd_set read_fds = conns[0].fds;
for (int i = 1; i < CONNMAX; ++i) {
for (SOCKET fd = 0; fd < FDMAX; ++fd) {
if (FD_ISSET(fd, &conns[i].fds)) {
FD_SET(fd, &read_fds);
}
}
}
// virtualization pattern: join all connect pending into one
fd_set except_fds;
FD_ZERO(&except_fds);
for (int i = 0; i < CONNMAX; ++i) {
if (conns[i].isConnecting) {
FD_SET(conns[i].sockfd, &except_fds);
}
}
struct timeval tv = {0, TIMEOUT * 1000};
int rv = select(FDMAX, &read_fds, NULL, &except_fds, &tv);
if (SOCKET_ERROR == rv && WSAGetLastError() != WSAEINTR) {
perror("EE select");
exit(EXIT_FAILURE);
}
if (rv > 0) {
// looking for data to read available
for (SOCKET fd = 0; fd < FDMAX; ++fd) {
if (FD_ISSET(fd, &read_fds)) {
for (int i = 0; i < CONNMAX; ++i) {
if (FD_ISSET(fd, &conns[i].fds)) {
switch (conns[i].typ) {
case CONN_CLIENT:
client_handle(&conns[i], fd);
break;
case CONN_SERVER:
server_handle(&conns[i], fd);
break;
}
}
}
}
}
// looking for connect pending fail
for (int i = 0; i < CONNMAX; ++i) {
if (FD_ISSET(conns[i].sockfd, &except_fds)) {
client_reopen(&conns[i]);
}
}
}
timer_walk();
}
}
void iomux_begin(void) {
static WSADATA wsaData;
int rv = WSAStartup(MAKEWORD(2, 2), &wsaData);
assert(0 == rv && "WSAStartup");
}
void iomux_end(void) {
WSACleanup();
}
#endif // WIOMUX_H_
The line count (wc -l) of this source code is: 109 for chat.c, 443 for liomux.h and 409 for wiomux.h. I think, this is really a simple networking framework. The constant CONNMAX defines the maximum number of "TCP servers" and "TCP clients" in the application. TIMERMAX defines the maximum number of Timers and FDMAX defines the maximum number of open file descriptors.
There is a simple networking framework in C++ from me, see Simple networking framework in C++
iomux_begin()
andiomux_end()
functions are empty in the Linux variant. why? \$\endgroup\$