169 lines
4.6 KiB
C
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");
|
|
}
|