Skip to content
Snippets Groups Projects
Unverified Commit f1fc6e78 authored by Ben Eltschig's avatar Ben Eltschig
Browse files

weiter an Kommunikation über WebSockets gearbeitet

parent 4f55d180
Branches
1 merge request!5Custom server
/**
* A connection to the signaling server used to start and join games.
*/
export default class SignalingServerConnection {
/**
* Creates a new WebSocket connection to port 443 of the current host, or the given url if one is given.
* @param {string} url
*/
constructor(url=`ws://${document.location.hostname}:443`){
this._socket = new CustomSocket(url);
(async()=>{
const socket = this._socket;
await socket.waitUntilReady();
console.log("Established signaling server connection!");
socket.send("test");
socket.send("test2");
socket.send("test3");
while(true){
console.log(await this._socket.next());
}
})();
}
/**
* Starts a new game, returning a promise with its ID. After that, calls the given callback with a new RTCPeerConnection whenever someone joins the game.
* @param {(connection:RTCPeerConnection)=>void} onConnect
*/
async host(onConnect){
await this._socket.waitUntilReady();
let response = this._socket.next(message=>{
console.log("possible response:",message);
return message.startsWith("skribbl started ");
});
this._socket.send("skribbl start");
let id = (await response).split(" ")[2];
(async()=>{
this._socket.onMessage(message=>{
if (message.startsWith(`skribbl join ${id} `)){
let data = message.split(" ",4)[3];
console.log(`Someone attempted to join game ${id}: `,data);
}
});
})();
return id;
}
/**
* Joins a new game, returning an RTCPeerConnection to its host.
* @param {string} id
*/
async join(id){
await this._socket.waitUntilReady();
this._socket.send(`skribbl join ${id}`);
}
/**
* Closes the connection.
*/
close(){
this._socket.close();
}
}
/**
* Custom socket class to pass strings to and from a server.
*
* Originally, the idea was to let this pass json data instead, but since json is difficult to work with in java I've stuck with normal strings for now.
*/
export class CustomSocket {
/**
* @callback MessageCallback
* @param {string} message
* @param {()=>void} remove removes this callback again
* @returns {void}
*/
/**
* @callback ErrorCallback
* @param {Event} error
* @param {()=>void} remove removes this callback again
* @returns {void}
*/
/**
* Creates a new custom socket.
* @param {string} url
*/
constructor(url){
/** @type {MessageCallback[]} */
this._onMessageCallbacks = [];
/** @type {ErrorCallback[]} */
this._onErrorCallbacks = [];
this._webSocket = new WebSocket(url);
/** @type {Promise<void>} */
this._readyPromise = new Promise((resolve,reject)=>{
this._webSocket.onopen = ()=>resolve();
this._webSocket.addEventListener("error",reject,{once:true});
});
this._webSocket.onmessage = (e)=>{
console.log(`onmessage event! e.data: "${e.data}", registered callbacks: ${this._onMessageCallbacks.length}`);
this._onMessageCallbacks.slice().forEach(callback=>callback(e.data+"",()=>this.removeMessageCallback(callback)));
};
this._webSocket.onerror = (e)=>{
this._onErrorCallbacks.slice().forEach(callback=>callback(e,()=>this.removeErrorCallback(callback)));
};
}
async waitUntilReady(){
return this._readyPromise;
}
/**
* Sends the given object as json to the server.
* @param {string} data
*/
send(data){
this._webSocket.send(data);
}
/**
* Registers a callback to be called whenever a message is received.
* @param {MessageCallback} callback
*/
onMessage(callback){
this._onMessageCallbacks.push(callback);
}
/**
* Removes a callback registered using `onMessage(callback)`.
* @param {MessageCallback} callback
*/
removeMessageCallback(callback){
let index = this._onMessageCallbacks.indexOf(callback);
if (index!==-1){
this._onMessageCallbacks.splice(index,1);
}
}
/**
* Returns a Promise that resolves to the next message received that passen the given filter, or `null` if no such message is received in the specified timeframe.
* If an error occurs before, the promise will instead be rejected with that.
* @param {(message:string)=>boolean} filter
* @param {number} time time to wait before just returning `null`, in seconds. Defaults to 30. If the given number is smaller than or equal to 0, it gets treated as `Infinity`.
* @return {Promise<string>}
*/
async next(filter=null,time=30){
return new Promise((resolve,reject)=>{
this.onMessage((message,remove)=>{
if (!filter||filter(message)){
resolve(message);
remove();
}
});
this.onError((error,remove)=>{
reject(error);
remove();
});
if (time>0){
setTimeout(()=>resolve(null),time*1000);
}
});
}
/**
* Registers a callback to be called whenever there's an error.
* @param {ErrorCallback} callback
*/
onError(callback){
this._onErrorCallbacks.push(callback);
}
/**
* Removes a callback registered using `onError(callback)`.
* @param {ErrorCallback} callback
*/
removeErrorCallback(callback){
let index = this._onErrorCallbacks.indexOf(callback);
if (index!==-1){
this._onErrorCallbacks.splice(index,1);
}
}
/**
* Closes the connection.
*/
close(){
this._webSocket.close();
}
}
\ No newline at end of file
...@@ -3,8 +3,34 @@ ...@@ -3,8 +3,34 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>signaling server test</title> <title>signaling server test</title>
<script src="script.js"></script> <script type="module" src="script.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#button {
padding: 10px 30px;
font-size: 30px;
background: orange;
color: white;
border: none;
border-radius: 4px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
cursor: pointer;
}
#button:disabled {
cursor: default;
background: #dfa580;
}
</style>
</head> </head>
<body> <body>
<button id="button">Host game</button>
</body> </body>
</html> </html>
\ No newline at end of file
{
"compilerOptions": {
"checkJs": true,
"target": "es2017",
"module": "es2020"
}
}
\ No newline at end of file
document.addEventListener("DOMContentLoaded",()=>{ import SignalingServerConnection from "./SignalingServerConnection.js";
let host = "ws://localhost:443/";
let socket = new WebSocket(host); document.addEventListener("DOMContentLoaded",async()=>{
socket.onmessage = (e)=>{ const signaler = new SignalingServerConnection();
console.log("Received message from server:",e); if (document.location.hash){
document.writeln(e.data); document.body.innerHTML = "";
}; await signaler.join(document.location.hash.substring(1));
}else{
/** @type {HTMLButtonElement} *///@ts-ignore
const button = document.getElementById("button");
console.log(signaler);
button.addEventListener("click",async e=>{
button.disabled = true;
let id = await signaler.host(()=>{});
alert(document.location.host+document.location.pathname+"#"+id);
});
}
}); });
\ No newline at end of file
import org.java_websocket.WebSocket;
public class Game {
public final String id;
public final WebSocket host;
public Game(String id, WebSocket host) {
this.id = id;
this.host = host;
}
static String genID() {
String id = "";
for (int i=0;i<8;i++) {
char c = (char)(Math.random()*62);
id += (char)(c+(c<10?'0':(c<36?'a'-10:'A'-36)));
}
return id;
}
}
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer; import org.java_websocket.server.WebSocketServer;
public class SignalingServer extends WebSocketServer { public class SignalingServer extends WebSocketServer {
private Map<String,Game> games = new HashMap<String,Game>();
public static void main(String[] args) { public static void main(String[] args) {
InetSocketAddress address = new InetSocketAddress("localhost",443); InetSocketAddress address = new InetSocketAddress("localhost",443);
SignalingServer server = new SignalingServer(address); SignalingServer server = new SignalingServer(address);
...@@ -22,20 +28,39 @@ public class SignalingServer extends WebSocketServer { ...@@ -22,20 +28,39 @@ public class SignalingServer extends WebSocketServer {
} }
public void onOpen(WebSocket conn, ClientHandshake handshake) { public void onOpen(WebSocket conn, ClientHandshake handshake) {
conn.send("hello there OwO"); //This method sends a message to the new client
broadcast( "new connection: "+handshake.getResourceDescriptor()+" :3"); //This method sends a message to all clients connected
System.out.println("new connection to "+conn.getRemoteSocketAddress()); System.out.println("new connection to "+conn.getRemoteSocketAddress());
broadcast("test1");
broadcast("test2");
broadcast("test3");
} }
public void onClose(WebSocket conn, int code, String reason, boolean remote) { public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
List<String> hostedGameIDs = new ArrayList<String>();
for (String id:games.keySet()) {
Game game = games.get(id);
if (game.host==conn) {
hostedGameIDs.add(id);
}
}
for (String id:hostedGameIDs) {
games.remove(id);
System.out.println("closed game "+id);
}
} }
public void onMessage(WebSocket conn, String message) { public void onMessage(WebSocket socket, String message) {
System.out.println("received message from " + conn.getRemoteSocketAddress() + ": " + message); System.out.println("received message from " + socket.getRemoteSocketAddress() + ": " + message);
if (message.equals("skribbl start")) {
Game game = new Game(genUnusedID(),socket);
games.put(game.id,game);
socket.send("skribbl started "+game.id);
System.out.println("started game "+game.id);
}else if(message.startsWith("skribbl join ")) {
String id = message.split(" ",3)[2];
if (games.containsKey(id)) {
System.out.println("attempt to join game "+id+" by "+socket.getRemoteSocketAddress());
}else {
System.out.println("attempt to join nonexistent game by "+socket.getRemoteSocketAddress());
}
}
} }
public void onMessage(WebSocket conn, ByteBuffer message) { public void onMessage(WebSocket conn, ByteBuffer message) {
...@@ -50,4 +75,12 @@ public class SignalingServer extends WebSocketServer { ...@@ -50,4 +75,12 @@ public class SignalingServer extends WebSocketServer {
} }
} }
private String genUnusedID(){
String id = Game.genID();
while(games.containsKey(id)) {
id = Game.genID();
}
return id;
}
} }
{
"folders": []
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment