| @@ -1,209 +1,65 @@ | |||
| #include "core.h" | |||
| #define DJSTD_BASIC_ENTRY | |||
| #include "core.c" | |||
| #include "signal.h" | |||
| Server *openServer = NULL; | |||
| SocketList *openSockets = NULL; | |||
| void handleSigint(int dummy) { | |||
| if (openServer) { | |||
| println(""); | |||
| println("Closing server socket."); | |||
| serverClose(openServer); | |||
| println("Success."); | |||
| } | |||
| if (openSockets && openSockets->length) { | |||
| println(""); | |||
| println("Closing open sockets."); | |||
| for (EachEl(*openSockets, Socket, socket)) { | |||
| socketClose(socket); | |||
| } | |||
| println("Success."); | |||
| } | |||
| signal(SIGINT, SIG_DFL); | |||
| raise(SIGINT); | |||
| } | |||
| typedef struct ChatClient ChatClient; | |||
| struct ChatClient { | |||
| Socket *socket; | |||
| string nickname; | |||
| }; | |||
| DefineList(ChatClient, ChatClient); | |||
| int djstd_entry(Arena *arena, StringList args) { | |||
| Socket sock = socketConnect(arena, (SocketConnectInfo){ .address="::1", .port=8080, .blocking=true }); | |||
| void startServer(Arena *arena, int32 port) { | |||
| println("Starting server..."); | |||
| Server server = serverInit((ServerInitInfo){ | |||
| .concurrentClients=2, | |||
| .port=port, | |||
| .memory=Megabytes(64), | |||
| .maxEvents=64, | |||
| }); | |||
| openServer = &server; | |||
| string newLine = s("\r\n"); | |||
| string lines[] = { | |||
| s("GET / HTTP/1.1"), | |||
| s("Host: localhost"), | |||
| }; | |||
| serverListen(&server); | |||
| if (server.listening) { | |||
| println("Listening on port %d", port); | |||
| for (EachInArray(lines, i)) { | |||
| socketWriteStr(&sock, lines[i]); | |||
| socketWriteStr(&sock, newLine); | |||
| } | |||
| socketWriteStr(&sock, newLine); | |||
| Arena *serverLoopArena = arenaAlloc(Megabytes(64)); | |||
| ChatClientList chatClients = PushListZero(arena, ChatClientList, 256); | |||
| CharList body = EmptyList(); | |||
| bool streamingBody = false; | |||
| Forever { | |||
| ServerEvent *nextEvent; | |||
| do { | |||
| nextEvent = serverGetNextEvent(&server); | |||
| switch (nextEvent->type) { | |||
| case ServerEventType_AcceptClient: { | |||
| Socket *client = serverAccept(&server); | |||
| if (client != NULL) { | |||
| println("New client connected from %d", client->address); | |||
| } | |||
| break; | |||
| }; | |||
| case ServerEventType_ClientMessage: { | |||
| StringResult clientMsg = socketReadStr(serverLoopArena, nextEvent->tClientMessage.client); | |||
| ChatClient *chatClient = NULL; | |||
| if (clientMsg.valid) { | |||
| if (strStartsWith(clientMsg.result, s("hello-"))) { | |||
| StringList nickSplit = strSplit(serverLoopArena, s("-"), clientMsg.result); | |||
| if (nickSplit.length == 2 && nickSplit.data[1].length > 0) { | |||
| string newNick = PushString(arena, nickSplit.data[1].length); | |||
| newNick.length = nickSplit.data[1].length; | |||
| memcpy(newNick.str, nickSplit.data[1].str, nickSplit.data[1].length); | |||
| ChatClient newChatClient = (ChatClient){ | |||
| .socket=nextEvent->tClientMessage.client, | |||
| .nickname=newNick, | |||
| }; | |||
| AppendList(&chatClients, newChatClient); | |||
| println("Client from %d calls themselves \"%S\"", newChatClient.socket->address, newChatClient.nickname); | |||
| } | |||
| } else { | |||
| for (EachEl(chatClients, ChatClient, maybeChatClient)) { | |||
| if (maybeChatClient->socket->handle == nextEvent->tClientMessage.client->handle) { | |||
| chatClient = maybeChatClient; | |||
| } | |||
| } | |||
| if (chatClient != NULL) { | |||
| if (strStartsWith(clientMsg.result, s("say-"))) { | |||
| StringList saySplit = strSplit(arena, s("-"), clientMsg.result); | |||
| if (saySplit.length == 2 && saySplit.data[1].length > 0) { | |||
| string broadcast = strPrintf(serverLoopArena, "%S says:\n%S", chatClient->nickname, saySplit.data[1]); | |||
| for (EachEl(server.clients, Socket, client)) { | |||
| socketWriteStr(client, broadcast); | |||
| } | |||
| } | |||
| } else { | |||
| // Invalid client message | |||
| } | |||
| StringResult response = socketReadStr(arena, &sock); | |||
| if (response.valid) { | |||
| if (streamingBody) { | |||
| if (body.capacity - body.length >= response.result.length) { | |||
| memcpy(body.data + body.length, response.result.str, response.result.length); | |||
| body.length += response.result.length; | |||
| } | |||
| } else { | |||
| StringList lines = strSplit(arena, s("\r\n"), response.result); | |||
| for (EachEl(lines, string, line)) { | |||
| if (body.capacity > 0 && strEql(*line, s(""))) { | |||
| streamingBody = true; | |||
| } else if (streamingBody && (body.capacity - body.length) >= line->length) { | |||
| // TODO(dledda): append list to list, string to string, whatever | |||
| // TODO(dledda): `joinStringList` | |||
| memcpy(body.data + body.length, line->str, line->length); | |||
| body.length += line->length; | |||
| } else { | |||
| // TODO(dledda): `strContains` | |||
| // TODO(dledda): `stringContains` -> `strContainsChar` | |||
| StringList split = strSplit(arena, s("Content-Length: "), *line); | |||
| if (split.length > 1) { | |||
| Int32Result lengthResult = parsePositiveInt(split.data[1]); | |||
| if (lengthResult.valid) { | |||
| body = PushList(arena, CharList, lengthResult.result); | |||
| } | |||
| } | |||
| } | |||
| break; | |||
| }; | |||
| case ServerEventType_None: { | |||
| break; | |||
| }; | |||
| default: | |||
| break; | |||
| } | |||
| } while (nextEvent != NULL); | |||
| arenaFreeFrom(serverLoopArena, 0); | |||
| } | |||
| println("Shutting down chat."); | |||
| serverClose(&server); | |||
| } | |||
| void clearStdInLn() { | |||
| print("\r"); | |||
| print(ANSI_INSTRUCTION(J)); | |||
| } | |||
| void clearStdInLnAfterInput() { | |||
| print("\r"); | |||
| print(ANSI_INSTRUCTION(A)); | |||
| print(ANSI_INSTRUCTION(J)); | |||
| } | |||
| void startClient(Arena *arena, string addr, int32 port, string nickname) { | |||
| fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); | |||
| println("Connecting to server at [%S]:%d with nickname \"%S\"", addr, port, nickname); | |||
| Socket server = socketConnect(arena, (SocketConnectInfo){ .address=addr, .port=port }); | |||
| if (server.closed) { | |||
| println("Connection error. Closing."); | |||
| } else { | |||
| println("Connected successfully"); | |||
| string message = strPrintf(arena, "hello-%S", nickname); | |||
| CharList inputBuf = PushList(arena, CharList, 512); | |||
| socketWriteStr(&server, message); | |||
| print("(you)> "); | |||
| Forever { | |||
| Scratch scratch = scratchStart(&arena, 1); | |||
| int32 numRead = read(0, inputBuf.data + inputBuf.length, inputBuf.capacity - inputBuf.length); | |||
| if (numRead >= 0) { | |||
| inputBuf.length += numRead; | |||
| if (inputBuf.data[inputBuf.length - 1] == '\n') { | |||
| clearStdInLnAfterInput(); | |||
| socketWriteStr(&server, strPrintf(scratch.arena, "say-%S", (string){.str=inputBuf.data,.length=inputBuf.length})); | |||
| inputBuf.length = 0; | |||
| } | |||
| } | |||
| StringResult serverMsg = socketReadStr(scratch.arena, &server); | |||
| if (serverMsg.valid && serverMsg.result.length > 0) { | |||
| clearStdInLn(); | |||
| println("%S", serverMsg.result); | |||
| print("(you)> %S", (string){.str=inputBuf.data, inputBuf.length}); | |||
| } | |||
| scratchEnd(scratch); | |||
| } | |||
| } | |||
| socketClose(&server); | |||
| } | |||
| int djstd_entry(Arena *arena, StringList args) { | |||
| signal(SIGINT, &handleSigint); | |||
| bool argumentErr = true; | |||
| bool isServer = strEql(args.data[0], s("server")); | |||
| bool isClient = strEql(args.data[0], s("client")); | |||
| if (isServer) { | |||
| Int32Result portParsed = parsePositiveInt(args.data[1]); | |||
| if (portParsed.valid) { | |||
| startServer(arena, portParsed.result); | |||
| argumentErr = false; | |||
| } | |||
| } else if (isClient) { | |||
| if (args.length == 3) { | |||
| StringList split = strSplit(arena, s("]:"), args.data[1]); | |||
| if (split.length == 2) { | |||
| Int32Result portParsed = parsePositiveInt(split.data[1]); | |||
| string addr = strSlice(split.data[0], 1, split.data[0].length); | |||
| string nickname = args.data[2]; | |||
| if (portParsed.valid && addr.length > 0 && nickname.length > 0) { | |||
| startClient(arena, addr, portParsed.result, nickname); | |||
| argumentErr = false; | |||
| } | |||
| } | |||
| if (streamingBody == true && body.length == body.capacity) { | |||
| break; | |||
| } | |||
| } | |||
| if (argumentErr) { | |||
| println("Usage:"); | |||
| println("server [PORT]"); | |||
| println("OR"); | |||
| println("client [REMOTE_ADDRESS:PORT] [NICKNAME]"); | |||
| } | |||
| // TODO(dledda): string from CharList/ByteList | |||
| // TODO(dledda): `printStr` | |||
| print("%S", (string){.str=body.data, .length=body.length}); | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,205 @@ | |||
| #include "core.h" | |||
| #define DJSTD_BASIC_ENTRY | |||
| #include "core.c" | |||
| #include "signal.h" | |||
| Server *openServer = NULL; | |||
| SocketList *openSockets = NULL; | |||
| void handleSigint(int dummy) { | |||
| if (openServer) { | |||
| println(""); | |||
| println("Closing server socket."); | |||
| serverClose(openServer); | |||
| println("Success."); | |||
| } | |||
| if (openSockets && openSockets->length) { | |||
| println(""); | |||
| println("Closing open sockets."); | |||
| for (EachEl(*openSockets, Socket, socket)) { | |||
| socketClose(socket); | |||
| } | |||
| println("Success."); | |||
| } | |||
| signal(SIGINT, SIG_DFL); | |||
| raise(SIGINT); | |||
| } | |||
| typedef struct ChatClient ChatClient; | |||
| struct ChatClient { | |||
| Socket *socket; | |||
| string nickname; | |||
| }; | |||
| DefineList(ChatClient, ChatClient); | |||
| void startServer(Arena *arena, int32 port) { | |||
| println("Starting server..."); | |||
| Server server = serverInit((ServerInitInfo){ | |||
| .concurrentClients=2, | |||
| .port=port, | |||
| .memory=Megabytes(64), | |||
| .maxEvents=64, | |||
| }); | |||
| openServer = &server; | |||
| serverListen(&server); | |||
| if (server.listening) { | |||
| println("Listening on port %d", port); | |||
| } | |||
| Arena *serverLoopArena = arenaAlloc(Megabytes(64)); | |||
| ChatClientList chatClients = PushListZero(arena, ChatClientList, 256); | |||
| ServerEvent *nextEvent; | |||
| Forever { | |||
| nextEvent = serverGetNextEvent(&server); | |||
| switch (nextEvent->type) { | |||
| case ServerEventType_AcceptClient: { | |||
| Socket *client = serverAccept(&server); | |||
| if (client != NULL) { | |||
| println("New client connected from %d", client->address); | |||
| } | |||
| break; | |||
| }; | |||
| case ServerEventType_ClientMessage: { | |||
| StringResult clientMsg = socketReadStr(serverLoopArena, nextEvent->tClientMessage.client); | |||
| ChatClient *chatClient = NULL; | |||
| if (clientMsg.valid) { | |||
| if (strStartsWith(clientMsg.result, s("hello-"))) { | |||
| StringList nickSplit = strSplit(serverLoopArena, s("-"), clientMsg.result); | |||
| if (nickSplit.length == 2 && nickSplit.data[1].length > 0) { | |||
| string newNick = PushString(arena, nickSplit.data[1].length); | |||
| newNick.length = nickSplit.data[1].length; | |||
| memcpy(newNick.str, nickSplit.data[1].str, nickSplit.data[1].length); | |||
| ChatClient newChatClient = (ChatClient){ | |||
| .socket=nextEvent->tClientMessage.client, | |||
| .nickname=newNick, | |||
| }; | |||
| AppendList(&chatClients, newChatClient); | |||
| println("Client from %d calls themselves \"%S\"", newChatClient.socket->address, newChatClient.nickname); | |||
| } | |||
| } else { | |||
| for (EachEl(chatClients, ChatClient, maybeChatClient)) { | |||
| if (maybeChatClient->socket->handle == nextEvent->tClientMessage.client->handle) { | |||
| chatClient = maybeChatClient; | |||
| } | |||
| } | |||
| if (chatClient != NULL) { | |||
| if (strStartsWith(clientMsg.result, s("say-"))) { | |||
| StringList saySplit = strSplit(arena, s("-"), clientMsg.result); | |||
| if (saySplit.length == 2 && saySplit.data[1].length > 0) { | |||
| string broadcast = strPrintf(serverLoopArena, "%S says:\n%S", chatClient->nickname, saySplit.data[1]); | |||
| for (EachEl(server.clients, Socket, client)) { | |||
| socketWriteStr(client, broadcast); | |||
| } | |||
| } | |||
| } else { | |||
| // Invalid client message | |||
| } | |||
| } | |||
| } | |||
| } | |||
| break; | |||
| }; | |||
| case ServerEventType_None: | |||
| default: | |||
| break; | |||
| } | |||
| arenaFreeFrom(serverLoopArena, 0); | |||
| } | |||
| println("Shutting down chat."); | |||
| serverClose(&server); | |||
| } | |||
| void clearStdInLn() { | |||
| print("\r"); | |||
| print(ANSI_INSTRUCTION(J)); | |||
| } | |||
| void clearStdInLnAfterInput() { | |||
| print("\r"); | |||
| print(ANSI_INSTRUCTION(A)); | |||
| print(ANSI_INSTRUCTION(J)); | |||
| } | |||
| void startClient(Arena *arena, string addr, int32 port, string nickname) { | |||
| fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); | |||
| println("Connecting to server at [%S]:%d with nickname \"%S\"", addr, port, nickname); | |||
| Socket server = socketConnect(arena, (SocketConnectInfo){ .address=addr, .port=port }); | |||
| if (server.closed) { | |||
| println("Connection error. Closing."); | |||
| } else { | |||
| println("Connected successfully"); | |||
| string message = strPrintf(arena, "hello-%S", nickname); | |||
| CharList inputBuf = PushList(arena, CharList, 512); | |||
| socketWriteStr(&server, message); | |||
| print("(you)> "); | |||
| Forever { | |||
| Scratch scratch = scratchStart(&arena, 1); | |||
| int32 numRead = read(0, inputBuf.data + inputBuf.length, inputBuf.capacity - inputBuf.length); | |||
| if (numRead >= 0) { | |||
| inputBuf.length += numRead; | |||
| if (inputBuf.data[inputBuf.length - 1] == '\n') { | |||
| clearStdInLnAfterInput(); | |||
| socketWriteStr(&server, strPrintf(scratch.arena, "say-%S", (string){.str=inputBuf.data,.length=inputBuf.length})); | |||
| inputBuf.length = 0; | |||
| } | |||
| } | |||
| StringResult serverMsg = socketReadStr(scratch.arena, &server); | |||
| if (serverMsg.valid && serverMsg.result.length > 0) { | |||
| clearStdInLn(); | |||
| println("%S", serverMsg.result); | |||
| print("(you)> %S", (string){.str=inputBuf.data, inputBuf.length}); | |||
| } | |||
| scratchEnd(scratch); | |||
| } | |||
| } | |||
| socketClose(&server); | |||
| } | |||
| int djstd_entry(Arena *arena, StringList args) { | |||
| signal(SIGINT, &handleSigint); | |||
| bool argumentErr = true; | |||
| bool isServer = strEql(args.data[0], s("server")); | |||
| bool isClient = strEql(args.data[0], s("client")); | |||
| if (isServer) { | |||
| Int32Result portParsed = parsePositiveInt(args.data[1]); | |||
| if (portParsed.valid) { | |||
| startServer(arena, portParsed.result); | |||
| argumentErr = false; | |||
| } | |||
| } else if (isClient) { | |||
| if (args.length == 3) { | |||
| StringList split = strSplit(arena, s("]:"), args.data[1]); | |||
| if (split.length == 2) { | |||
| Int32Result portParsed = parsePositiveInt(split.data[1]); | |||
| string addr = strSlice(split.data[0], 1, split.data[0].length); | |||
| string nickname = args.data[2]; | |||
| if (portParsed.valid && addr.length > 0 && nickname.length > 0) { | |||
| startClient(arena, addr, portParsed.result, nickname); | |||
| argumentErr = false; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if (argumentErr) { | |||
| println("Usage:"); | |||
| println("server [PORT]"); | |||
| println("OR"); | |||
| println("client [REMOTE_ADDRESS:PORT] [NICKNAME]"); | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -67,6 +67,7 @@ typedef struct SocketConnectInfo SocketConnectInfo; | |||
| struct SocketConnectInfo { | |||
| string address; | |||
| uint16 port; | |||
| bool blocking; | |||
| }; | |||
| @@ -5,15 +5,15 @@ | |||
| #include "sys/mman.h" | |||
| #include "sys/stat.h" | |||
| #include "unistd.h" // POSIX Standard | |||
| #include "stdio.h" | |||
| #include "string.h" // memcpy TODO(dledda): replace memcpy with custom impl? | |||
| #include "unistd.h" // POSIX Standard, read, write, close, open, etc. | |||
| #include "pthread.h" | |||
| #include "fcntl.h" | |||
| #include "sys/epoll.h" | |||
| #include "sys/socket.h" | |||
| #include "arpa/inet.h" | |||
| #include "string.h" // memcpy | |||
| void *os_alloc(uint64 capacity) { | |||
| @@ -34,17 +34,21 @@ void os_free(void *ptr, uint64 size) { | |||
| string os_readEntireFile(Arena *arena, string filename) { | |||
| Scratch temp = scratchStart(&arena, 1); | |||
| FILE *input = fopen(cstring(temp.arena, filename), "r"); | |||
| int input = open(cstring(temp.arena, filename), O_RDONLY); | |||
| string readBuffer; | |||
| if (input) { | |||
| struct stat st; | |||
| stat((char *)filename.str, &st); | |||
| uint64 fsize = st.st_size; | |||
| readBuffer = PushString(arena, fsize); | |||
| fread(readBuffer.str, sizeof(byte), readBuffer.length, input); | |||
| fclose(input); | |||
| int64 bytesRead = read(input, readBuffer.str, readBuffer.length); | |||
| close(input); | |||
| if (bytesRead == -1) { | |||
| arenaPopTo(arena, readBuffer.str); | |||
| return s(""); | |||
| } | |||
| } else { | |||
| readBuffer = PushString(arena, 0); | |||
| readBuffer = s(""); | |||
| } | |||
| scratchEnd(temp); | |||
| @@ -55,11 +59,13 @@ bool os_writeEntireFile(Arena *arena, string filename, const byte *contents, uin | |||
| Scratch temp = scratchStart(&arena, 1); | |||
| bool result = false; | |||
| FILE *output = fopen(cstring(temp.arena, filename), "w"); | |||
| int output = open(cstring(temp.arena, filename), O_WRONLY); | |||
| if (output) { | |||
| fwrite(contents, sizeof(byte), contentsLength, output); | |||
| fclose(output); | |||
| result = true; | |||
| int64 bytesWritten = write(output, contents, contentsLength); | |||
| if (bytesWritten != -1) { | |||
| result = true; | |||
| } | |||
| close(output); | |||
| } | |||
| scratchEnd(temp); | |||
| @@ -70,11 +76,13 @@ bool os_fileAppend(Arena *arena, string filename, const byte *contents, uint64 c | |||
| Scratch temp = scratchStart(&arena, 1); | |||
| bool result = false; | |||
| FILE *output = fopen(cstring(temp.arena, filename), "a"); | |||
| int output = open(cstring(temp.arena, filename), O_APPEND); | |||
| if (output) { | |||
| fwrite(contents, sizeof(byte), contentsLength, output); | |||
| fclose(output); | |||
| result = true; | |||
| int bytesWritten = write(output, contents, contentsLength); | |||
| if (bytesWritten != -1) { | |||
| result = true; | |||
| } | |||
| close(output); | |||
| } | |||
| scratchEnd(temp); | |||
| @@ -98,12 +106,10 @@ void os_println(StdStream target, const char *fmt, va_list argList) { | |||
| write(0, (const void *)result.str, result.length); | |||
| break; | |||
| case StdStream_stderr: | |||
| fflush(stderr); | |||
| write(2, (const void *)result.str, result.length); | |||
| break; | |||
| case StdStream_stdout: | |||
| default: | |||
| fflush(stdout); | |||
| write(1, (const void *)result.str, result.length); | |||
| break; | |||
| } | |||
| @@ -121,12 +127,10 @@ void os_print(StdStream target, const char *fmt, va_list argList) { | |||
| write(0, (const void *)result.str, result.length); | |||
| break; | |||
| case StdStream_stderr: | |||
| fflush(stderr); | |||
| write(2, (const void *)result.str, result.length); | |||
| break; | |||
| case StdStream_stdout: | |||
| default: | |||
| fflush(stdout); | |||
| write(1, (const void *)result.str, result.length); | |||
| break; | |||
| } | |||
| @@ -211,8 +215,6 @@ Socket *serverAccept(Server *s) { | |||
| uint64 clientSockHandle = accept((int)(uint64)s->handle, (struct sockaddr *)clientAddr, &clientAddrLen); | |||
| if (clientSockHandle == -1) { | |||
| clientSockHandle = (uint64)NULL; | |||
| println("ERR server accept"); | |||
| perror("accept"); | |||
| } else { | |||
| fcntl((uint64)clientSockHandle, F_SETFL, fcntl((uint64)clientSockHandle, F_GETFL, 0) | O_NONBLOCK); | |||
| } | |||
| @@ -339,7 +341,9 @@ void socketClose(Socket *s) { | |||
| Socket socketConnect(Arena *arena, SocketConnectInfo info) { | |||
| int socketFd = socket(AF_INET6, SOCK_STREAM, 0 /* IPPROTO_TCP */); | |||
| fcntl(socketFd, F_SETFL, fcntl(socketFd, F_GETFL, 0) | O_NONBLOCK); | |||
| if (!info.blocking) { | |||
| fcntl(socketFd, F_SETFL, fcntl(socketFd, F_GETFL, 0) | O_NONBLOCK); | |||
| } | |||
| struct sockaddr_in6 *remoteAddr = PushStructZero(arena, struct sockaddr_in6); | |||
| remoteAddr->sin6_family = AF_INET6; | |||
| @@ -352,6 +356,7 @@ Socket socketConnect(Arena *arena, SocketConnectInfo info) { | |||
| .address=(Address *)remoteAddr, | |||
| .closed=false, | |||
| //.closed=connectErr == -1, | |||
| // TODO(dledda): investigate error behaviour | |||
| }; | |||
| return result; | |||