diff --git a/src/dusk/client/CMakeLists.txt b/backup/client/CMakeLists.txt
similarity index 100%
rename from src/dusk/client/CMakeLists.txt
rename to backup/client/CMakeLists.txt
diff --git a/src/dusk/client/client.c b/backup/client/client.c
similarity index 100%
rename from src/dusk/client/client.c
rename to backup/client/client.c
diff --git a/src/dusk/client/client.h b/backup/client/client.h
similarity index 100%
rename from src/dusk/client/client.h
rename to backup/client/client.h
diff --git a/src/dusk/client/clientremote.c b/backup/client/clientremote.c
similarity index 56%
rename from src/dusk/client/clientremote.c
rename to backup/client/clientremote.c
index e736ad9..9f35fa8 100644
--- a/src/dusk/client/clientremote.c
+++ b/backup/client/clientremote.c
@@ -8,6 +8,8 @@
 #include "client.h"
 #include "assert/assert.h"
 #include "console/console.h"
+#include <fcntl.h>
+#include <unistd.h>
 
 void* clientRemoteThreadFunc(void* arg) {
   client_t *client = (client_t*)arg;
@@ -15,10 +17,24 @@ void* clientRemoteThreadFunc(void* arg) {
 
   client->state = CLIENT_STATE_CONNECTED;
 
-  // Send some data
-  write(client->remote.clientSockDesc, "Hello, World!", 13);
-  
-  printf("Thread func\n");
+  // Set socket to non-blocking
+  int flags = fcntl(client->remote.clientSockDesc, F_GETFL, 0);
+  fcntl(client->remote.clientSockDesc, F_SETFL, flags | O_NONBLOCK);
+
+  char buffer[1024];
+  while (client->state == CLIENT_STATE_CONNECTED) {
+    ssize_t bytesRead = read(client->remote.clientSockDesc, buffer, sizeof(buffer));
+    if (bytesRead > 0) {
+      // Process the received data
+      printf("Received data: %.*s\n", (int)bytesRead, buffer);
+    } else if (bytesRead == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
+      // An error occurred
+      perror("Read error");
+      break;
+    }
+    // Sleep for a short duration to prevent busy-waiting
+    usleep(10000); // 10ms
+  }
 
   return NULL;
 }
diff --git a/src/dusk/client/clientremote.h b/backup/client/clientremote.h
similarity index 100%
rename from src/dusk/client/clientremote.h
rename to backup/client/clientremote.h
diff --git a/src/dusk/server/CMakeLists.txt b/backup/server/CMakeLists.txt
similarity index 100%
rename from src/dusk/server/CMakeLists.txt
rename to backup/server/CMakeLists.txt
diff --git a/src/dusk/server/server.c b/backup/server/server.c
similarity index 100%
rename from src/dusk/server/server.c
rename to backup/server/server.c
diff --git a/src/dusk/server/server.h b/backup/server/server.h
similarity index 100%
rename from src/dusk/server/server.h
rename to backup/server/server.h
diff --git a/src/dusk/server/serverlocal.c b/backup/server/serverlocal.c
similarity index 100%
rename from src/dusk/server/serverlocal.c
rename to backup/server/serverlocal.c
index dab079b..0c15f20 100644
--- a/src/dusk/server/serverlocal.c
+++ b/backup/server/serverlocal.c
@@ -17,10 +17,6 @@ void serverLocalStart(const serverlocalstart_t start) {
     assertUnreachable("Failed to create socket");
   }
 
-  // Set the socket to non-blocking mode
-  int flags = fcntl(SERVER.local.sockDesc, F_GETFL, 0);
-  fcntl(SERVER.local.sockDesc, F_SETFL, flags | O_NONBLOCK);
-
   //Bind the socket
   SERVER.local.sockAddr.sin_family = AF_INET;
   SERVER.local.sockAddr.sin_addr.s_addr = INADDR_ANY;
@@ -78,6 +74,10 @@ void * serverLocalThread(void *arg) {
   timeout.tv_sec = 0;
   timeout.tv_usec = SERVER_LOCAL_THREAD_ACCEPT_SLEEP_TIME;
 
+  // Set the socket to non-blocking mode
+  int flags = fcntl(SERVER.local.sockDesc, F_GETFL, 0);
+  fcntl(SERVER.local.sockDesc, F_SETFL, flags | O_NONBLOCK);
+
   while(SERVER.local.threadState == SERVER_LOCAL_THREAD_STATE_RUNNING) {
     FD_ZERO(&readfds);
     FD_SET(SERVER.local.sockDesc, &readfds);
diff --git a/src/dusk/server/serverlocal.h b/backup/server/serverlocal.h
similarity index 100%
rename from src/dusk/server/serverlocal.h
rename to backup/server/serverlocal.h
diff --git a/src/dusk/server/serversingleplayer.c b/backup/server/serversingleplayer.c
similarity index 100%
rename from src/dusk/server/serversingleplayer.c
rename to backup/server/serversingleplayer.c
diff --git a/src/dusk/server/serversingleplayer.h b/backup/server/serversingleplayer.h
similarity index 100%
rename from src/dusk/server/serversingleplayer.h
rename to backup/server/serversingleplayer.h
diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt
index 3529a5e..178610a 100644
--- a/src/dusk/CMakeLists.txt
+++ b/src/dusk/CMakeLists.txt
@@ -26,7 +26,6 @@ target_sources(${DUSK_TARGET_NAME}
 # Subdirs
 add_subdirectory(assert)
 add_subdirectory(console)
-add_subdirectory(client)
-add_subdirectory(server)
+add_subdirectory(net)
 add_subdirectory(entity)
 add_subdirectory(display)
\ No newline at end of file
diff --git a/src/dusk/dusk.h b/src/dusk/dusk.h
index 47c7111..51d88a1 100644
--- a/src/dusk/dusk.h
+++ b/src/dusk/dusk.h
@@ -22,4 +22,6 @@
 typedef float float_t;
 typedef double double_t;
 typedef bool bool_t;
-typedef char char_t;
\ No newline at end of file
+typedef char char_t;
+
+#define DUSK_VERSION "1.0.0"
\ No newline at end of file
diff --git a/src/dusk/game.c b/src/dusk/game.c
index 2978cc4..0ea17dd 100644
--- a/src/dusk/game.c
+++ b/src/dusk/game.c
@@ -9,7 +9,7 @@
 #include "gametime.h"
 #include "input.h"
 #include "console/console.h"
-#include "server/server.h"
+#include "net/server/server.h"
 
 #include "entity/entity.h"
 
@@ -18,11 +18,10 @@ void gameInit() {
   gameTimeInit();
   inputInit();
 
-  serverInit();
-  serverStart((serverstart_t){
-    .type = SERVER_TYPE_LOCAL,
-    .local = {
-      .port = SERVER_LOCAL_PORT_DEFAULT
+  serverInit((serverinit_t){
+    .type = SERVER_TYPE_ONLINE,
+    .online = {
+      .port = SERVER_PORT_DEFAULT
     }
   });
 
@@ -32,7 +31,6 @@ void gameInit() {
 void gameUpdate(const float delta) {
   gameTimeUpdate(delta);
   inputUpdate();
-  serverUpdate();
 }
 
 void gameDispose() {
diff --git a/src/dusk/net/CMakeLists.txt b/src/dusk/net/CMakeLists.txt
new file mode 100644
index 0000000..22ebcf1
--- /dev/null
+++ b/src/dusk/net/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https:#opensource.org/licenses/MIT
+
+# Sources
+target_sources(${DUSK_TARGET_NAME}
+  PRIVATE
+)
+
+# Subdirs
+add_subdirectory(server)
\ No newline at end of file
diff --git a/src/dusk/net/server/CMakeLists.txt b/src/dusk/net/server/CMakeLists.txt
new file mode 100644
index 0000000..aaeacf1
--- /dev/null
+++ b/src/dusk/net/server/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https:#opensource.org/licenses/MIT
+
+# Sources
+target_sources(${DUSK_TARGET_NAME}
+  PRIVATE
+    server.c
+    serverclient.c
+)
\ No newline at end of file
diff --git a/src/dusk/net/server/server.c b/src/dusk/net/server/server.c
new file mode 100644
index 0000000..e9d946f
--- /dev/null
+++ b/src/dusk/net/server/server.c
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2025 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "server.h"
+#include "assert/assert.h"
+#include "console/console.h"
+
+server_t SERVER;
+
+void serverInit(const serverinit_t init) {
+  memset(&SERVER, 0, sizeof(server_t));
+  SERVER.state = SERVER_STATE_STARTING;
+  SERVER.type = init.type;
+
+  // Create the server
+  switch(init.type) {
+    case SERVER_TYPE_ONLINE:
+      SERVER.online.socket = socket(AF_INET, SOCK_STREAM, 0);
+      SERVER.online.address.sin_family = AF_INET;
+      SERVER.online.address.sin_addr.s_addr = INADDR_ANY;
+      SERVER.online.address.sin_port = htons(init.online.port);
+
+      // Bind
+      if(bind(
+        SERVER.online.socket,
+        (struct sockaddr *)&SERVER.online.address,
+        sizeof(SERVER.online.address)
+      ) < 0) {
+        assertUnreachable("Failed to bind server.");
+      }
+
+      // Begin listening
+      if(listen(SERVER.online.socket, SERVER_CLIENT_COUNT_MAX) < 0) {
+        assertUnreachable("Failed to listen on server.");
+      }
+
+      // Set the socket to non-blocking mode
+      int flags = fcntl(SERVER.online.socket, F_GETFL, 0);
+      fcntl(SERVER.online.socket, F_SETFL, flags | O_NONBLOCK);
+
+      break;
+    
+    default:
+      assertUnreachable("Invalid server type.");
+      break;
+  }
+
+  // Start thread
+  pthread_create(&SERVER.thread, NULL, serverThread, NULL);
+
+  // Wait for server to start
+  while(SERVER.state == SERVER_STATE_STARTING) usleep(1000);
+}
+
+void * serverThread(void *arg) {
+  assertNull(arg, "Server thread does not accept arguments.");
+  
+  // Server now running
+  SERVER.state = SERVER_STATE_RUNNING;
+
+  fd_set readfds;
+  int32_t activity;
+  int32_t error;
+  struct timeval timeout;
+  timeout.tv_sec = 0;
+  timeout.tv_usec = 1000;
+
+  // Start server
+  while(SERVER.state == SERVER_STATE_RUNNING) {
+    FD_ZERO(&readfds);
+    FD_SET(SERVER.online.socket, &readfds);
+
+    activity = select(
+      SERVER.online.socket + 1,
+      &readfds,
+      NULL,
+      NULL,
+      &timeout
+    );
+
+    if(activity < 0) assertUnreachable("Select error");
+
+    if(!FD_ISSET(SERVER.online.socket, &readfds)) {
+      continue;
+    }
+
+    // Accept incoming connections
+    int32_t clientSockDesc = accept(SERVER.online.socket, NULL, NULL);
+    if(clientSockDesc < 0) {
+      printf("Failed to accept connection\n");
+      continue;
+    }
+
+    // Now, is the server full?
+    if(SERVER.clientCount >= SERVER_CLIENT_COUNT_MAX) {
+      consolePrint("Server is full, rejecting connection.");
+      // TODO: Write a reason to client.
+      close(clientSockDesc);
+      continue;
+    }
+
+    // Create server client for handling
+    serverclient_t *client = &SERVER.clients[SERVER.clientCount];
+    serverClientInit(client, (serverclientinit_t){
+      .socket = clientSockDesc
+    });
+    SERVER.clientCount++;
+  }
+
+  SERVER.state = SERVER_STATE_STOPPED;
+  return NULL;
+}
+
+void serverDispose() {
+  // Disconnect clients
+  for(uint8_t i = 0; i < SERVER.clientCount; i++) {
+    serverClientDispose(&SERVER.clients[i]);
+  }
+  SERVER.clientCount = 0;
+
+  // Stop server thread.
+  if(SERVER.state == SERVER_STATE_RUNNING) {
+    // Request thread to stop
+    SERVER.state = SERVER_STATE_STOPPING;
+
+    // Wait for server to stop
+    while(SERVER.state == SERVER_STATE_STOPPING) usleep(1000);
+    pthread_join(SERVER.thread, NULL);
+
+    // Close the socket
+    switch(SERVER.type) {
+      case SERVER_TYPE_ONLINE:
+        close(SERVER.online.socket);
+        break;
+      
+      default:
+        assertUnreachable("Invalid server type.");
+        break;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/dusk/net/server/server.h b/src/dusk/net/server/server.h
new file mode 100644
index 0000000..967fe86
--- /dev/null
+++ b/src/dusk/net/server/server.h
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2025 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "serverclient.h"
+
+#define SERVER_CLIENT_COUNT_MAX 8
+#define SERVER_PORT_DEFAULT 24555
+
+typedef enum {
+  SERVER_STATE_STOPPED,
+  SERVER_STATE_STARTING,
+  SERVER_STATE_RUNNING,
+  SERVER_STATE_STOPPING
+} serverstate_t;
+
+typedef enum {
+  SERVER_TYPE_OFFLINE,
+  SERVER_TYPE_ONLINE
+} servertype_t;
+
+typedef struct {
+  serverstate_t state;
+  servertype_t type;
+  serverclient_t clients[SERVER_CLIENT_COUNT_MAX];
+  uint8_t clientCount;
+  pthread_t thread;
+
+  union {
+    struct {
+      int32_t socket;
+      struct sockaddr_in address;
+    } online;
+  };
+} server_t;
+
+typedef struct {
+  servertype_t type;
+
+  union {
+    struct {
+      int32_t port;
+    } online;
+  };
+} serverinit_t;
+
+extern server_t SERVER;
+
+void serverInit(const serverinit_t init);
+void * serverThread(void* arg);
+void serverDispose();
\ No newline at end of file
diff --git a/src/dusk/net/server/serverclient.c b/src/dusk/net/server/serverclient.c
new file mode 100644
index 0000000..2755a0a
--- /dev/null
+++ b/src/dusk/net/server/serverclient.c
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2025 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "serverclient.h"
+#include "assert/assert.h"
+#include "console/console.h"
+
+void serverClientInit(serverclient_t *client, const serverclientinit_t init) {
+  assertNotNull(client, "Client cannot be NULL.");
+  client->socket = init.socket;
+  consolePrint("Accepted client %i", client->socket);
+
+  client->state = SERVER_CLIENT_STATE_CONNECTING;
+
+  // Set socket to non-blocking
+  int flags = fcntl(client->socket, F_GETFL, 0);
+  fcntl(client->socket, F_SETFL, flags | O_NONBLOCK);
+
+  // Start thread
+  pthread_create(&client->thread, NULL, serverClientThread, client);
+
+  // Wait for client to connect
+  while(client->state == SERVER_CLIENT_STATE_CONNECTING) usleep(1000);
+}
+
+void * serverClientThread(void *arg) {
+  serverclient_t *client = (serverclient_t*)arg;
+  assertNotNull(client, "Client cannot be NULL.");
+
+  // Set the client state to connected
+  client->state = SERVER_CLIENT_STATE_CONNECTED;
+
+  // Send hello to client, as well as the version number.
+  {
+    uint8_t hello[16];
+    memset(hello, 0, sizeof(hello));
+    memcpy(hello, "Dusk", 4);
+    strcpy((char*)&hello[4], DUSK_VERSION);
+    
+    _serverClientSend(client, hello, sizeof(hello));
+  }
+
+  // Client should respond "OK"
+  {
+    uint8_t response[2];
+    _serverClientReceive(client, response, sizeof(response));
+    assertTrue(
+      response[0] == 'O' &&
+      response[1] == 'K',
+      "Client did not respond with OK."
+    );
+  }
+  
+  while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
+    // Do nothing for now.
+    usleep(1000);
+  }
+
+  // Disconnect
+  client->state = SERVER_CLIENT_STATE_DISCONNECTED;
+  return NULL;
+}
+
+void _serverClientSend(
+  serverclient_t *client,
+  const uint8_t *data,
+  const size_t size
+) {
+  assertNotNull(client, "Client cannot be NULL.");
+  assertNotNull(data, "Data cannot be NULL.");
+  assertTrue(size > 0, "Size cannot be zero.");
+}
+
+void _serverClientReceive(
+  serverclient_t *client,
+  uint8_t *data,
+  const size_t size
+) {
+  assertNotNull(client, "Client cannot be NULL.");
+  assertNotNull(data, "Data cannot be NULL.");
+  assertTrue(size > 0, "Size cannot be zero.");
+  
+  recv(client->socket, data, size, 0);
+}
+
+void serverClientDispose(serverclient_t *client) {
+  assertNotNull(client, "Client cannot be NULL.");
+
+  if(client->state == SERVER_CLIENT_STATE_CONNECTED) {
+    // Let the thread know we are disconnecting
+    client->state = SERVER_CLIENT_STATE_DISCONNECTING;
+
+    // Wait for the thread to finish
+    while(client->state == SERVER_CLIENT_STATE_DISCONNECTING) usleep(1000);
+    pthread_join(client->thread, NULL);
+  }
+}
\ No newline at end of file
diff --git a/src/dusk/net/server/serverclient.h b/src/dusk/net/server/serverclient.h
new file mode 100644
index 0000000..e5b5c5f
--- /dev/null
+++ b/src/dusk/net/server/serverclient.h
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2025 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "dusk.h"
+#include <netinet/in.h>
+#include <fcntl.h>
+
+typedef enum {
+  SERVER_CLIENT_STATE_DISCONNECTED,
+  SERVER_CLIENT_STATE_CONNECTING,
+  SERVER_CLIENT_STATE_CONNECTED,
+  SERVER_CLIENT_STATE_DISCONNECTING
+} serverclientstate_t;
+
+typedef struct {
+  serverclientstate_t state;
+  pthread_t thread;
+  int32_t socket;
+} serverclient_t;
+
+typedef struct {
+  int32_t socket;
+} serverclientinit_t;
+
+void serverClientInit(serverclient_t *client, const serverclientinit_t init);
+void * serverClientThread(void *arg);
+
+void _serverClientSend(
+  serverclient_t *client,
+  const uint8_t *data,
+  const size_t size
+);
+
+void _serverClientReceive(
+  serverclient_t *client,
+  uint8_t *buffer,
+  const size_t size
+);
+
+void serverClientDispose(serverclient_t *client);
\ No newline at end of file