complete rewrite

This commit is contained in:
Nishi 2026-01-07 04:24:22 +09:00
commit 8a717a2b2e
Signed by: nishi
GPG key ID: 27EF69B208EB9343
41 changed files with 2625 additions and 1255 deletions

93
engine/net/src/client.c Normal file
View file

@ -0,0 +1,93 @@
#include <fishsoup.h>
fishsoup_client_t* fishsoup_client(const char* host, int port) {
fishsoup_client_t* client = malloc(sizeof(*client));
struct addrinfo hints;
char p[32];
struct addrinfo* result;
struct addrinfo* rp;
if(port == 0) port = FISHSOUP_PORT;
sprintf(p, "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if(getaddrinfo(host, p, &hints, &result) != 0) {
free(client);
return NULL;
}
for(rp = result; rp != NULL; rp = rp->ai_next) {
int val = 1;
if((client->fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0) {
continue;
}
val = 1;
setsockopt(client->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
if(connect(client->fd, rp->ai_addr, rp->ai_addrlen) >= 0) {
break;
}
close(client->fd);
}
if(rp == NULL) {
free(client);
return NULL;
}
client->on_packet = NULL;
client->on_disconnect = NULL;
return client;
}
void fishsoup_client_on_packet(fishsoup_client_t* client, void (*on_packet)(fishsoup_client_t* client, fishsoup_packet_t* packet)) {
client->on_packet = on_packet;
}
void fishsoup_client_on_disconnect(fishsoup_client_t* client, void (*on_disconnect)(fishsoup_client_t* client)) {
client->on_disconnect = on_disconnect;
}
int fishsoup_client_poll(fishsoup_client_t* client) {
struct pollfd pfd;
fishsoup_packet_t pkt;
int ret;
pfd.fd = client->fd;
pfd.events = POLLIN | POLLPRI;
ret = poll(&pfd, 1, 0);
if(ret <= 0) return ret;
ret = fishsoup_packet_recv(client->fd, &pkt);
if(ret == 0) return -1; /* connection broke, probably */
if(pkt.header.type == FISHSOUP_PACKET_PING) {
fishsoup_packet_t ret;
client->last_ping = time(NULL);
ret.header.type = FISHSOUP_PACKET_PONG;
ret.header.length = 0;
ret.data = NULL;
if(fishsoup_packet_send(client->fd, &ret) == 0) {
if(pkt.data != NULL) free(pkt.data);
return -1;
}
}
if(client->on_packet != NULL) client->on_packet(client, &pkt);
if(pkt.data != NULL) free(pkt.data);
return 1;
}
void fishsoup_client_destroy(fishsoup_client_t* client) {
if(client->on_disconnect != NULL) client->on_disconnect(client);
close(client->fd);
free(client);
}

199
engine/net/src/server.c Normal file
View file

@ -0,0 +1,199 @@
#include <fishsoup.h>
#include "stb_ds.h"
fishsoup_server_t* fishsoup_server(int port) {
fishsoup_server_t* server = malloc(sizeof(*server));
int val;
if(port == 0) port = FISHSOUP_PORT;
if((server->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
free(server);
return NULL;
}
val = 1;
setsockopt(server->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
server->address.sin_family = AF_INET;
server->address.sin_addr.s_addr = INADDR_ANY;
server->address.sin_port = htons(port);
if(bind(server->fd, (struct sockaddr*)&server->address, sizeof(server->address)) < 0) {
close(server->fd);
free(server);
return NULL;
}
if(listen(server->fd, 128) < 0) {
close(server->fd);
free(server);
return NULL;
}
server->clients = NULL;
server->on_client = NULL;
server->on_tick = NULL;
return server;
}
int fishsoup_server_clients(fishsoup_server_t* server) {
return arrlen(server->clients);
}
void fishsoup_server_loop(fishsoup_server_t* server) {
struct pollfd* pfd;
while(1) {
int i;
int ret;
pfd = malloc(sizeof(*pfd) * (1 + arrlen(server->clients)));
pfd[0].fd = server->fd;
pfd[0].events = POLLIN | POLLPRI;
for(i = 0; i < arrlen(server->clients); i++) {
pfd[1 + i].fd = server->clients[i].fd;
pfd[1 + i].events = POLLIN | POLLPRI;
}
ret = poll(pfd, 1 + arrlen(server->clients), 50);
if(ret < 0) {
free(pfd);
break;
}
if(server->on_tick != NULL) server->on_tick(server);
if(pfd[0].revents & POLLIN) {
fishsoup_client_t client;
struct sockaddr_in address;
int len = sizeof(address);
client.fd = accept(server->fd, (struct sockaddr*)&address, &len);
client.last_ping = time(NULL);
client.server.cleanup = 0;
client.server.sent_ping = 0;
client.on_packet = NULL;
arrput(server->clients, client);
if(server->on_client != NULL) server->on_client(server, &server->clients[arrlen(server->clients) - 1]);
#ifdef DEBUG
fprintf(stderr, "Client %d accepted\n", server->clients[i].fd);
#endif
}
for(i = 0; i < arrlen(server->clients); i++) {
time_t diff;
if(server->clients[i].server.cleanup) continue;
if(pfd[1 + i].revents & POLLIN) {
fishsoup_packet_t pkt;
if(fishsoup_packet_recv(server->clients[i].fd, &pkt) == 0) {
server->clients[i].server.cleanup = 1;
} else {
if(pkt.header.type == FISHSOUP_PACKET_PONG && server->clients[i].server.sent_ping) {
server->clients[i].last_ping = time(NULL);
server->clients[i].server.sent_ping = 0;
#ifdef DEBUG
fprintf(stderr, "Pong from client %d\n", server->clients[i].fd);
#endif
} else if(pkt.header.type == FISHSOUP_PACKET_PING) {
fishsoup_packet_t ret;
ret.header.type = FISHSOUP_PACKET_PONG;
ret.header.length = 0;
ret.data = NULL;
if(fishsoup_packet_send(server->clients[i].fd, &ret) == 0) {
server->clients[i].server.cleanup = 1;
} else {
#ifdef DEBUG
fprintf(stderr, "Ping from client %d, sending back pong\n", server->clients[i].fd);
#endif
}
}
if(!server->clients[i].server.cleanup && server->clients[i].on_packet != NULL) server->clients[i].on_packet(&server->clients[i], &pkt);
if(pkt.data != NULL) free(pkt.data);
}
}
if(server->clients[i].server.cleanup) continue;
diff = time(NULL) - server->clients[i].last_ping;
if(diff == FISHSOUP_PING && !server->clients[i].server.sent_ping) {
fishsoup_packet_t pkt;
pkt.header.type = FISHSOUP_PACKET_PING;
pkt.header.length = 0;
pkt.data = NULL;
if(fishsoup_packet_send(server->clients[i].fd, &pkt) == 0) {
server->clients[i].server.cleanup = 1;
#ifdef DEBUG
fprintf(stderr, "Failed to ping client %d\n", server->clients[i].fd);
#endif
} else {
server->clients[i].server.sent_ping = 1;
#ifdef DEBUG
fprintf(stderr, "Pinging client %d\n", server->clients[i].fd);
#endif
}
} else if(diff == FISHSOUP_PING * 2 && server->clients[i].server.sent_ping) {
server->clients[i].server.cleanup = 1;
#ifdef DEBUG
fprintf(stderr, "Ping timeout (client %d)\n", server->clients[i].fd);
#endif
}
}
for(i = 0; i < arrlen(server->clients); i++) {
if(server->clients[i].server.cleanup) {
#ifdef DEBUG
fprintf(stderr, "Client %d cleanup\n", server->clients[i].fd);
#endif
if(server->clients[i].on_disconnect != NULL) server->clients[i].on_disconnect(&server->clients[i]);
close(server->clients[i].fd);
arrdel(server->clients, i);
i--;
}
}
free(pfd);
}
}
void fishsoup_server_on_client(fishsoup_server_t* server, void (*on_client)(fishsoup_server_t* server, fishsoup_client_t* client)) {
server->on_client = on_client;
}
void fishsoup_server_on_tick(fishsoup_server_t* server, void (*on_tick)(fishsoup_server_t* server)) {
server->on_tick = on_tick;
}
void fishsoup_server_destroy(fishsoup_server_t* server) {
int i;
for(i = 0; i < arrlen(server->clients); i++) {
if(server->clients[i].on_disconnect != NULL) server->clients[i].on_disconnect(&server->clients[i]);
close(server->clients[i].fd);
}
arrfree(server->clients);
close(server->fd);
free(server);
}
void fishsoup_server_broadcast(fishsoup_server_t* server, fishsoup_packet_t* packet) {
int i;
for(i = 0; i < arrlen(server->clients); i++) {
if(fishsoup_packet_send(server->clients[i].fd, packet) == 0) {
server->clients[i].server.cleanup = 1;
}
}
}

200
engine/net/src/socket.c Normal file
View file

@ -0,0 +1,200 @@
#include <fishsoup.h>
#include <zlib.h>
#define CHUNK 512
int fishsoup_packet_send(int fd, fishsoup_packet_t* packet) {
int r;
fishsoup_packet_header_t h;
int len = 0;
unsigned char* data = NULL;
if(packet->header.length > 0) {
z_stream strm;
int in = packet->header.length;
int seek = 0;
int flush = 0;
unsigned char buf[CHUNK];
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if(deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) return 0;
do {
strm.avail_in = in <= CHUNK ? in : CHUNK;
strm.next_in = ((unsigned char*)packet->data) + seek;
flush = in <= CHUNK ? Z_FINISH : Z_NO_FLUSH;
do {
int have;
strm.avail_out = CHUNK;
strm.next_out = buf;
deflate(&strm, flush);
have = CHUNK - strm.avail_out;
if(data == NULL) {
data = malloc(have);
memcpy(data, buf, have);
} else {
unsigned char* old = data;
data = malloc(len + have);
memcpy(data, old, len);
memcpy(data + len, buf, have);
free(data);
}
len += have;
} while(strm.avail_out == 0);
seek += CHUNK;
} while(flush != Z_FINISH);
deflateEnd(&strm);
}
memcpy(&h, &packet->header, sizeof(h));
h.length = htons(len);
r = fishsoup_fd_send(fd, &h, sizeof(h));
if(r != sizeof(h)) return 0;
if(data != NULL) {
r = fishsoup_fd_send(fd, data, len);
if(r != sizeof(h)) {
free(data);
return 0;
}
free(data);
}
return sizeof(h) + len;
}
int fishsoup_packet_recv(int fd, fishsoup_packet_t* packet) {
fishsoup_packet_header_t h;
if(fishsoup_fd_recv(fd, &h, sizeof(h), 500) != sizeof(h)) {
packet->data = NULL;
return 0;
}
memcpy(&packet->header, &h, sizeof(h));
packet->header.length = ntohs(packet->header.length);
packet->data = NULL;
if(packet->header.length > 0) {
unsigned char* data = malloc(packet->header.length);
z_stream strm;
int in = packet->header.length;
int ret;
int seek = 0;
int len = 0;
unsigned char buf[CHUNK];
if(fishsoup_fd_recv(fd, data, packet->header.length, 50) != packet->header.length) {
free(data);
return 0;
}
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if(inflateInit(&strm) != Z_OK) {
free(data);
return 0;
}
do {
strm.avail_in = in <= CHUNK ? in : CHUNK;
strm.next_in = data + seek;
do {
int have;
strm.avail_out = CHUNK;
strm.next_out = buf;
ret = inflate(&strm, Z_NO_FLUSH);
if(ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
inflateEnd(&strm);
if(packet->data != NULL) {
free(packet->data);
packet->data = NULL;
}
free(data);
return 0;
}
have = CHUNK - strm.avail_out;
if(packet->data == NULL) {
packet->data = malloc(have);
memcpy(packet->data, buf, have);
} else {
unsigned char* old = packet->data;
packet->data = malloc(len + have);
memcpy(packet->data, old, len);
memcpy(packet->data + len, buf, have);
free(packet->data);
}
len += have;
} while(strm.avail_out == 0);
} while(ret != Z_STREAM_END);
inflateEnd(&strm);
free(data);
packet->header.length = len;
}
return sizeof(h) + packet->header.length;
}
int fishsoup_fd_send(int fd, const void* buf, int length) {
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLOUT | POLLPRI;
poll(&pfd, 1, 0);
if(!(pfd.revents & POLLOUT)) return 0;
return send(fd, buf, length, 0);
}
int fishsoup_fd_recv(int fd, void* buf, int length, int to) {
struct pollfd pfd;
int ret;
unsigned char* b = buf;
int i = 0;
time_t t = time(NULL);
pfd.fd = fd;
pfd.events = POLLIN | POLLPRI;
while(i < length) {
/* i think 500 is reasonable timeout. no? */
ret = poll(&pfd, 1, to);
if((time(NULL) - t) >= 5) return i; /* we waited enough! kill the connection */
if(ret <= 0) return i;
ret = recv(fd, &b[i], 1, 0);
if(ret < 1) return i;
i++;
}
return i;
}

2
engine/net/src/stb_ds.c Normal file
View file

@ -0,0 +1,2 @@
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"

1895
engine/net/src/stb_ds.h Normal file

File diff suppressed because it is too large Load diff