Files
ground/libs/socket/socket.c
2026-05-19 20:34:05 +10:00

169 lines
4.6 KiB
C

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <groundvm.h>
#include <groundext.h>
#define BACKLOG 16
#define CHUNK_SIZE 4096
typedef struct {
int fd;
struct sockaddr_in addr;
GroundFunction* function;
} client_t;
typedef struct {
char* data;
size_t len;
size_t cap;
} buf_t;
static void buf_init(buf_t *b) {
b->data = malloc(CHUNK_SIZE);
b->len = 0;
b->cap = CHUNK_SIZE;
}
static int buf_append(buf_t *b, const char *src, size_t n) {
if (b->len + n > b->cap) {
size_t new_cap = b->cap;
while (new_cap < b->len + n) new_cap *= 2;
char *p = realloc(b->data, new_cap);
if (!p) return -1;
b->data = p;
b->cap = new_cap;
}
memcpy(b->data + b->len, src, n);
b->len += n;
return 0;
}
static void buf_free(buf_t *b) {
free(b->data);
b->data = NULL;
b->len = b->cap = 0;
}
static void handle(int fd, const char *data, size_t len, GroundFunction* function) {
GroundValue value = groundRunFunction(function, 1, groundCreateValue(STRING, data));
if (value.type == ERROR) {
printf(" error type: %s\n", value.data.errorVal.type);
printf(" error what: %s\n", value.data.errorVal.what);
send(fd, "Server Error", 12, 0);
return;
}
if (value.type != STRING) {
printf(" wrong type: %d\n", value.type);
send(fd, "Server Error", 12, 0);
return;
}
size_t resplen = strlen(value.data.stringVal);
send(fd, value.data.stringVal, resplen, 0);
}
static void *client_thread(void *arg) {
client_t *client = arg;
int fd = client->fd;
GroundFunction* function = client->function;
// ...
free(client);
buf_t buf;
buf_init(&buf);
char chunk[CHUNK_SIZE];
while (1) {
ssize_t n = recv(fd, chunk, sizeof(chunk), 0);
if (n < 0) { perror("recv"); break; }
if (n == 0) break; // client disconnected
if (buf_append(&buf, chunk, n) < 0) {
fprintf(stderr, "[fd=%d] out of memory\n", fd);
break;
}
// handle every complete line we've accumulated
while (1) {
char* newline = memchr(buf.data, '\n', buf.len);
if (!newline) break;
size_t msg_len = newline - buf.data;
// null terminate and handle
char saved = buf.data[msg_len];
buf.data[msg_len] = '\0';
handle(fd, buf.data, msg_len, function);
buf.data[msg_len] = saved;
// shift the buffer left past the consumed message
size_t consumed = msg_len + 1;
memmove(buf.data, buf.data + consumed, buf.len - consumed);
buf.len -= consumed;
}
}
buf_free(&buf);
close(fd);
printf("[fd=%d] disconnected\n", fd);
return NULL;
}
GroundValue socket_Listen(GroundScope* scope, List args) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) { perror("socket"); ERROR("Failed to open socket (see above error for detail", "SocketFail"); }
int64_t port = args.values[1].data.intVal;
GroundFunction* function = args.values[0].data.fnVal;
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = { .s_addr = INADDR_ANY },
};
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); ERROR("Failed to bind to socket (see above error for detail", "SocketFail");
}
if (listen(listen_fd, BACKLOG) < 0) {
perror("listen"); ERROR("Failed to listen on socket (see above error for detail)", "SocketFail");
}
while (1) {
client_t *client = malloc(sizeof(client_t));
socklen_t addrlen = sizeof(client->addr);
client->fd = accept(listen_fd, (struct sockaddr *)&client->addr, &addrlen);
client->function = function;
if (client->fd < 0) {
perror("accept");
free(client);
continue;
}
pthread_t tid;
if (pthread_create(&tid, NULL, client_thread, client) != 0) {
perror("pthread_create");
close(client->fd);
free(client);
continue;
}
pthread_detach(tid);
}
close(listen_fd);
return groundCreateValue(INT, 0);
}
void ground_init(GroundScope* scope) {
groundAddNativeFunction(scope, "socket_Listen", socket_Listen, ANY, 2, FUNCTION, "handler", INT, "port");
}