Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

droplet is a cloud variable server in C #148

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "droplet/libwebsockets"]
path = droplet/libwebsockets
url = https://github.com/warmcat/libwebsockets
[submodule "droplet/jsmn"]
path = droplet/jsmn
url = https://github.com/zserge/jsmn
12 changes: 12 additions & 0 deletions droplet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# macOS
.DS_Store

# cmake
build

# various C
*.out
*.o
*.so
*.a
callgrind.out.*
39 changes: 39 additions & 0 deletions droplet/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.13)

project(droplet LANGUAGES C)

set(LWS_WITH_SSL OFF CACHE BOOL "")
set(LWS_WITH_MINIMAL_EXAMPLES OFF CACHE BOOL "")
set(LWS_WITH_SHARED OFF CACHE BOOL "")
add_subdirectory(libwebsockets)

# we want -fhardened but we are using too old GCC
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -pedantic -fsanitize=address -fPIE -pie -Wl,-z,relro,-z,now -fstack-protector-strong -fstack-clash-protection ${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "-g -Wall -pedantic -O3 -march=native -fPIE -pie -Wl,-z,relro,-z,now -fstack-protector-strong -fstack-clash-protection ${CMAKE_C_FLAGS_RELEASE}")

add_executable(
droplet
src/droplet.c
src/protocol_cloud.c
src/resizable_buffer.c
src/username.c
)
target_include_directories(
droplet PRIVATE
jsmn
${PROJECT_BINARY_DIR}/libwebsockets
${PROJECT_BINARY_DIR}/libwebsockets/include
)
target_link_libraries(droplet websockets)

add_executable(
resizable_buffer_test
src/resizable_buffer_test.c
src/resizable_buffer.c
)

add_executable(
username_test
src/username_test.c
src/username.c
)
15 changes: 15 additions & 0 deletions droplet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# droplet

A cloud variable server in C.

Development:

```bash
rm -rf build && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=DEBUG .. && make -j4
```

Production:

```bash
rm -rf build && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=RELEASE .. && make -j4
```
1 change: 1 addition & 0 deletions droplet/jsmn
Submodule jsmn added at 25647e
1 change: 1 addition & 0 deletions droplet/libwebsockets
Submodule libwebsockets added at 8674bf
53 changes: 53 additions & 0 deletions droplet/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<style>
textarea, input {
width: 90%;
}
textarea {
height: 400px;
}
</style>
</head>

<body>
<textarea id="out" readonly></textarea><br>
<input type="text" id="input" value='{"method":"handshake","user":"player1234","project_id":"123"}'>
<button id="button">Send</button>

<script>
const out = document.getElementById("out");
const input = document.getElementById("input");
const button = document.getElementById("button");

const ws = new WebSocket(location.href.replace(/^http/, "ws"));

function log(m) {
out.value += m + '\n';
out.scrollTop = out.scrollHeight;
}

ws.onopen = function() {
log('opened');
};
ws.onmessage = function(msg) {
log(msg.data);
};
ws.onclose = function(e) {
log(`closed with code ${e.code} reason ${e.reason}`);
};

function send() {
ws.send(input.value);
input.value = JSON.stringify({
method: "set",
name: "a b c",
value: Math.random()
});
}
button.addEventListener("click", send);
</script>
</body>
</html>
178 changes: 178 additions & 0 deletions droplet/playground/stress.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
</head>

<body>
<div>
<select id="ws">
<option value="ws://localhost:9082">droplet</option>
<option value="ws://localhost:9080">cloud-server</option>
<option value="ws://localhost:7681">libwebsockets</option>
</select>
</div>

<div>
<button onclick="start()">Start</button>
<button onclick="stop()">Stop</button>
</div>

<textarea id="metrics" readonly style="width: 90vw; height: 80vh;" autocomplete="off"></textarea>

<script>
const sockets = [];
const errors = [];
let updateQueued = false;

let startTime = 0;
let stopped = false;

function updateMetrics() {
if (updateQueued) return;
requestAnimationFrame(() => {
updateQueued = false;

let text = "";

if (errors.length) {
text += "--- ERRORS ---\n";
text += errors.join("\n");
text += "\n--- ERRORS ---\n";
}

text += "\topen\ttxMess\ttxBytes\trxMess\trxBytes\n";

let txMessages = 0;
let txBytes = 0;
let rxMessages = 0;
let rxBytes = 0;

for (let i = 0; i < sockets.length; i++) {
const s = sockets[i];

txMessages += s.txMessages;
txBytes += s.txBytes;
rxMessages += s.rxMessages;
rxBytes += s.rxBytes;

text += `ws${i}\t${s.open}\t${s.txMessages}\t${s.txBytes}\t${s.rxMessages}\t${s.rxBytes}\n`;
}

text += `total\t\t${txMessages}\t${txBytes}\t${rxMessages}\t${rxBytes}\n`;

const time = (Date.now() - startTime) / 1000;
const r = Math.round.bind(Math);
text += `avg\t\t${r(txMessages / time)}\t${r(txBytes / time)}\t${r(rxMessages / time)}\t${r(rxBytes / time)}\n`;

document.getElementById("metrics").value = text;
}, 0);
}

function reportError(m) {
errors.push(m);
updateMetrics();
}

function start() {
startTime = Date.now();
sockets.length = 0;

const server = document.getElementById("ws").value;

const randInt = (lower, upper) => Math.floor(Math.random() * (upper - lower) + lower);
const randStr = (size) => {
const buffer = new Uint8Array(Math.random() * 10000);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = 48 + randInt(0, 9);
}
return new TextDecoder().decode(buffer);
};
const projects = [
"60917032",
"@p4-345908734509834598ye4r35oidgtherfdiogher"
];

for (let i = 0; i < 50; i++) {
const s = {
ws: new WebSocket(server),
open: false,
txMessages: 0,
rxMessages: 0,
txBytes: 0,
rxBytes: 0
};

s.ws.onopen = () => {
s.ws.send(JSON.stringify({
method: "handshake",
user: "player1234",
project_id: projects[randInt(0, projects.length)]
}));
s.open = true;
updateMetrics();

const strings = [
randStr(),
randStr(),
randStr(),
randStr(),
randStr(),
randStr(),
randStr(),
];

s.interval = setInterval(() => {
for (let j = 0; j < 5; j++) {
const message = JSON.stringify({
method: "set",
name: "variable" + randInt(0, 100),
value: strings[randInt(0, strings.length)]
});
s.ws.send(message);
s.txMessages += 1;
s.txBytes += message.length;
updateMetrics();
}
}, 0);
};

s.ws.onmessage = (e) => {
s.rxMessages += e.data.split("\n").length;
s.rxBytes += e.data.length;
updateMetrics();
};

s.ws.onerror = (e) => {
s.open = false;
console.error(e);
clearInterval(s.interval);
reportError(`ws${i} onerror ${e}`);
};

s.ws.onclose = (e) => {
clearInterval(s.interval);
s.open = false;
if (!stopped) {
console.warn(e);
reportError(`ws${i} onclose ${e}`);
}
};

sockets.push(s);
}

updateMetrics();
}

function stop() {
stopped = true;
for (let i = 0; i < sockets.length; i++) {
sockets[i].ws.close();
clearInterval(sockets[i].interval);
}
updateMetrics();
}
</script>
</body>
</html>
Loading