问:您对这个设计和实现有什么看法?这是一个简单的网络框架,用于IPv4、IPv6 TCP客户端和和MS。它使用了围绕系统调用select()的单线程解决方案,即IO多路复用。连接和网络读取是异步的,网络写入是同步的。liomux.h文件实现Linux特定版本,wiomux.h实现MS特定版本,chat.c实现聊天应用程序作为示例。
在http://www.andreadrian.de/non-blocking_连接/index.html上有很多关于这个源代码的旧版本的信息。
/* 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 // 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;
}该框架提供了一个"Timer类“和一个"Connection类”。将连接类子类转换为“服务器类”和“客户端类”。因为C不是面向对象的,所以这些只是设计思想。
/* 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
#include
#include
#include
#include
#include
// Linux
#include
#include
#include
#include
#include
#include
#include
#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_在异步连接方面,MS实现是不同的.Linux ()使用写fd_set来表示“连接成功/失败”事件,而()对此事件使用异常fd_set。还有一些不同之处。
/* 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
#include
#include
#include
#include
// MS-Windows
#include // _kbhit()
#include
#include // 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_此源代码的行计数(wc -l)为: chat.c为109,liomux.h为443,wiomux.h为409。我认为,这真的是一个简单的网络框架。常量CONNMAX定义应用程序中"TCP服务器“和"TCP客户端”的最大数量。TIMERMAX定义最大定时器数,FDMAX定义打开文件描述符的最大数量。
在C++中有一个简单的网络框架,请参阅C++中的简单网络框架
发布于 2022-06-25 14:11:45
。
应用程序不必编写代码在liomux.h和wiomux.h之间进行选择,而是创建一个名为iomux.h的头文件,在内部执行此操作。这简化了应用程序,并且还允许您将来在框架中添加对其他平台的支持,而不必修改应用程序代码本身。
我在您的代码中看到了很多不一致的地方。在你所做的事情中尽量保持一致。下面列出了一些不一致的事情:
typedef struct {...} TIMER;对struct CONN_ {...}; typedef struct CONN_ CONN;。我建议您像这样声明结构:typedef struct FOO {...} FOO;。请注意,struct和它的typedef可以有完全相同的名称。assert()、perror()、exit(EXIT_FAILURE),有时甚至忘记处理错误。下面会有更多的介绍。write_fds (带有下划线分隔词)与remoteaddr vs isConnecting。确保检查可能返回错误的所有函数的返回值。这包括inet_ntop()、close()、clock_gettime()等。是的,这可能是非常不可能的,但对于一个您希望能够盲目依赖的框架来实际处理它可能遇到的所有可能的问题来说,这是很重要的。
只使用assert()检查编程错误。例如,assert(interval >= 0)在after()中是很好的;如果使用负间隔调用它,那就是调用代码中的一个bug。但是,永远不要将它用于不是编程错误的东西。例如,assert(rv != NULL) in poll_keyboard()是错误的;如果stdin有问题,fgets()肯定会返回NULL (例如,程序运行的终端关闭了)。当使用assert()标志编译时,-DNDEBUG意味着成为一个非op,在生成版本时,它会自动设置在Meson和CMake这样的构建系统中。
当您确实遇到错误时,请确保使用fprintf()或perror()将错误消息打印到D39或D40。但是,也要确保您然后以最好的方式处理错误,千万不要忽视它们。不要调用exit();这会阻止应用程序以自己的方式处理错误,而是返回有意义的错误代码。在server_handle()中,您只需忽略返回错误的accept()。也许在某些情况下它是好的,比如如果是errno == EAGAIN,但是如果errno == EBADF,这意味着侦听套接字有问题,忽略它只会导致程序进入一个无限循环,认为有一个新的套接字可以接受,但是它会一次又一次地出现相同的错误。
在命名函数、变量等时,可以改进以下几点:
obj。这是不是意味着它是个物体?但是什么样的对象呢?最好把它命名为connection。conn_factory()重命名为类似于new_connection()的东西。conn[],而是编写connections[]。关于调试打印语句的
有几次将调试信息打印到stdout,比如在接受连接时打印消息,包括连接到的地址。虽然这在开发框架时可能很好,但对于链接到您的框架的应用程序来说,这将是非常烦人的。它还会给代码添加相当多的噪音。现在,我建议您从代码中删除所有这些printf("II...")语句,以及将地址转换为字符串的所有代码。了解如何使用适当的调试器(如GDB ),如果您想调试代码,可以避免在任何地方添加print()语句。
getnameinfo()将地址转换为字符串与其使用inet_ntop(),不如使用getnameinfo()将地址转换为字符串。这样,您就不必处理IPv4和IPv6地址之间的差异。
不带参数的
(void)我看到你写像void timer_walk() {...}这样的函数。应该避免使用这种语法,而是编写void timer_walk(void) {...}。有关解释,请参见这个职位。
https://codereview.stackexchange.com/questions/277603
复制相似问题