From ffae473590ec6949a414257fc6ea9f8805cf8d89 Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Thu, 27 May 2021 21:45:15 -0400 Subject: [PATCH] use just one socket --- src/netplayClient.ts | 312 +++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 189 deletions(-) diff --git a/src/netplayClient.ts b/src/netplayClient.ts index f148318..9aed646 100644 --- a/src/netplayClient.ts +++ b/src/netplayClient.ts @@ -53,16 +53,12 @@ const PEER_KEEPALIVE_INTERVAL = 1000; const DISCONNECT_TIMEOUT_INTERVAL = 10000; let TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT: NodeJS.Timeout; +let PEER_TRAVERSAL_TIMEOUT: NodeJS.Timeout; let PEER_ESTABLISHMENT_TIMEOUT: NodeJS.Timeout; let PEER_KEEPALIVE_TIMEOUT: NodeJS.Timeout; let DISCONNECT_TIMEOUT: NodeJS.Timeout; -let TRAVERSAL_SERVER_SOCKET: Socket; -let PEER_PUBLIC_SOCKET: Socket; -let PEER_PRIVATE_SOCKET: Socket; - -let SOCKET: Socket | null; -let CONNECTED = false; +let SOCKET: Socket; function handleMessage(messageData: MessageData) { const { @@ -111,11 +107,20 @@ function handleMessage(messageData: MessageData) { } } -function initializeConnection(socket: Socket) { - socket.on('message', (msg) => { - clearTimeout(DISCONNECT_TIMEOUT); +function initializeConnection() { + SOCKET.on('error', () => { + store.dispatch(disconnectedFromPeer()); + }); + + SOCKET.on('message', (msg, rinfo) => { + console.log(`Message from ${rinfo.address} port ${rinfo.port}: ${msg}`); + store.dispatch(connectedToPeer()); - CONNECTED = true; + + clearTimeout(DISCONNECT_TIMEOUT); + DISCONNECT_TIMEOUT = setTimeout(() => { + store.dispatch(disconnectedFromPeer()); + }, DISCONNECT_TIMEOUT_INTERVAL); const message = String(msg); @@ -128,21 +133,13 @@ function initializeConnection(socket: Socket) { } if (message === 'established') { - try { - socket.send('acknowledged'); - } catch { - // socket has been closed - } + SOCKET.send('acknowledged'); // if hosting, send game settings and color const { hosting, isBlack } = selectNetplayState(store.getState()); if (hosting) { const { settings } = selectGameState(store.getState()); const settingsMessage = { settings, isBlack: !isBlack }; - try { - socket.send(JSON.stringify(settingsMessage)); - } catch { - // socket has been closed - } + SOCKET.send(JSON.stringify(settingsMessage)); } return; } @@ -158,7 +155,7 @@ function initializeConnection(socket: Socket) { clearInterval(PEER_ESTABLISHMENT_TIMEOUT); PEER_ESTABLISHMENT_TIMEOUT = setInterval(() => { try { - socket.send('established'); + SOCKET.send('established'); } catch { // socket has been closed clearInterval(PEER_ESTABLISHMENT_TIMEOUT); @@ -169,7 +166,7 @@ function initializeConnection(socket: Socket) { clearInterval(PEER_KEEPALIVE_TIMEOUT); PEER_KEEPALIVE_TIMEOUT = setInterval(() => { try { - socket.send('keepalive'); + SOCKET.send('keepalive'); } catch { // socket has been closed clearInterval(PEER_KEEPALIVE_TIMEOUT); @@ -178,218 +175,155 @@ function initializeConnection(socket: Socket) { } function attemptTraversal( - socket: Socket, - port: number, - address: string, - altPort: number, - altAddress: string + peerPublicPort: number, + peerPublicAddress: string, + peerPrivatePort: number, + peerPrivateAddress: string ) { - socket.on('message', (msg, rinfo) => { - console.log(`Message from ${rinfo.address} port ${rinfo.port}: ${msg}`); + // start sending packets to both public and private addresses + PEER_TRAVERSAL_TIMEOUT = setInterval(() => { + SOCKET.send('traversal', peerPublicPort, peerPublicAddress); + SOCKET.send('traversal', peerPrivatePort, peerPrivateAddress); + }, TRAVERSAL_PACKET_INTERVAL); + + // connect to the first address that responds + SOCKET.on('message', (_msg, rinfo) => { if ( - !SOCKET && - ((rinfo.address === address && rinfo.port === port) || - (rinfo.address === altAddress && rinfo.port === altPort)) + (rinfo.address === peerPublicAddress && rinfo.port === peerPublicPort) || + (rinfo.address === peerPrivateAddress && rinfo.port === peerPrivatePort) ) { - SOCKET = socket; - socket.connect(rinfo.port, rinfo.address, () => { - initializeConnection(socket); - store.dispatch(connectedToPeer()); - history.push('/game'); - }); + clearInterval(PEER_TRAVERSAL_TIMEOUT); clearInterval(TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT); - try { - TRAVERSAL_SERVER_SOCKET.close(); - } catch { - // socket has been closed already - } + const { address, port } = SOCKET.address(); + SOCKET.close(); + SOCKET = createSocket({ type: 'udp4' }); + SOCKET.bind(port, address, () => { + SOCKET.connect(rinfo.port, rinfo.address, () => { + initializeConnection(); + store.dispatch(connectedToPeer()); + history.push('/game'); + }); + }); } }); - // TODO check if this executes unnecessarily when address and ports align - const timer = setInterval(() => { - if (SOCKET) { - clearTimeout(timer); - if (SOCKET !== socket) { - try { - socket.close(); - } catch { - // socket has been closed already - } - } - return; - } - try { - socket.send('traversal', port, address); - } catch { - // socket might not have been bound yet - } - }, TRAVERSAL_PACKET_INTERVAL); -} - -function closeAllSockets() { - if (TRAVERSAL_SERVER_SOCKET) { - try { - TRAVERSAL_SERVER_SOCKET.close(); - } catch { - // socket might have already been closed - } - } - if (PEER_PUBLIC_SOCKET) { - try { - PEER_PUBLIC_SOCKET.close(); - } catch { - // socket might have already been closed - } - } - if (PEER_PRIVATE_SOCKET) { - try { - PEER_PRIVATE_SOCKET.close(); - } catch { - // socket might have already been closed - } - } } export function stopNetplay() { clearInterval(TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT); + clearInterval(PEER_TRAVERSAL_TIMEOUT); + clearInterval(PEER_ESTABLISHMENT_TIMEOUT); clearInterval(PEER_KEEPALIVE_TIMEOUT); clearInterval(DISCONNECT_TIMEOUT); - closeAllSockets(); - SOCKET = null; - CONNECTED = false; + try { + SOCKET.close(); + } catch { + // socket hasn't been created or has already been closed + } } export function startNetplay(hostCode?: string) { stopNetplay(); - TRAVERSAL_SERVER_SOCKET = createSocket({ type: 'udp4', reuseAddr: true }); - PEER_PUBLIC_SOCKET = createSocket({ type: 'udp4', reuseAddr: true }); - PEER_PRIVATE_SOCKET = createSocket({ type: 'udp4', reuseAddr: true }); - - TRAVERSAL_SERVER_SOCKET.on('error', (err) => { - console.log(err); - }); - - const handleError = () => { - if (CONNECTED) { - DISCONNECT_TIMEOUT = setTimeout(() => { - store.dispatch(disconnectedFromPeer()); - }, DISCONNECT_TIMEOUT_INTERVAL); - CONNECTED = false; - } - }; - PEER_PUBLIC_SOCKET.on('error', handleError); - PEER_PRIVATE_SOCKET.on('error', handleError); - - TRAVERSAL_SERVER_SOCKET.on('message', (msg, rinfo) => { - console.log(`Message from ${rinfo.address} port ${rinfo.port}: ${msg}`); + SOCKET = createSocket({ type: 'udp4' }); - let parsedMessage; - try { - parsedMessage = JSON.parse(String(msg)); - } catch { - // ignore badly formed messages - return; - } + SOCKET.connect(TRAVERSAL_SERVER_PORT, TRAVERSAL_SERVER_ADDRESS, () => { + const { address: privateAddress, port: privatePort } = SOCKET.address(); + SOCKET.close(); + SOCKET = createSocket({ type: 'udp4' }); - const { - hostCode: receivedHostCode, - peerPublicAddress, - peerPublicPort, - peerPrivateAddress, - peerPrivatePort, - } = parsedMessage; + SOCKET.on('message', (msg, rinfo) => { + console.log(`Message from ${rinfo.address} port ${rinfo.port}: ${msg}`); - if (receivedHostCode) { - store.dispatch(hostCodeReceived(receivedHostCode)); - } + let parsedMessage; + try { + parsedMessage = JSON.parse(String(msg)); + } catch { + // ignore badly formed messages + return; + } - if (peerPublicAddress && peerPublicPort) { - attemptTraversal( - PEER_PUBLIC_SOCKET, - peerPublicPort, + const { + hostCode: receivedHostCode, peerPublicAddress, + peerPublicPort, + peerPrivateAddress, peerPrivatePort, - peerPrivateAddress - ); - } + } = parsedMessage; - if (peerPrivateAddress && peerPrivatePort) { - attemptTraversal( - PEER_PRIVATE_SOCKET, - peerPrivatePort, - peerPrivateAddress, - peerPublicPort, - peerPublicAddress - ); - } - }); + if (receivedHostCode) { + store.dispatch(hostCodeReceived(receivedHostCode)); + } - TRAVERSAL_SERVER_SOCKET.on('connect', () => { - const { - address: privateAddress, - port: privatePort, - } = TRAVERSAL_SERVER_SOCKET.address(); - PEER_PUBLIC_SOCKET.bind(privatePort, () => { - PEER_PUBLIC_SOCKET.setMulticastInterface(privateAddress); + if ( + peerPublicAddress && + peerPublicPort && + peerPrivateAddress && + peerPrivatePort + ) { + attemptTraversal( + peerPublicPort, + peerPublicAddress, + peerPrivatePort, + peerPrivateAddress + ); + } }); - PEER_PRIVATE_SOCKET.bind(privatePort, () => { - PEER_PRIVATE_SOCKET.setMulticastInterface(privateAddress); + + SOCKET.bind(privatePort, privateAddress, () => { + const message = { privateAddress, privatePort, hostCode }; + SOCKET.send( + JSON.stringify(message), + TRAVERSAL_SERVER_PORT, + TRAVERSAL_SERVER_ADDRESS + ); + clearInterval(TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT); + TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT = setInterval(() => { + SOCKET.send( + 'keepalive', + TRAVERSAL_SERVER_PORT, + TRAVERSAL_SERVER_ADDRESS + ); + }, TRAVERSAL_SERVER_KEEPALIVE_INTERVAL); }); - const message = { privateAddress, privatePort, hostCode }; - TRAVERSAL_SERVER_SOCKET.send(JSON.stringify(message)); - clearInterval(TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT); - TRAVERSAL_SERVER_KEEPALIVE_TIMEOUT = setInterval(() => { - TRAVERSAL_SERVER_SOCKET.send('keepalive'); - }, TRAVERSAL_SERVER_KEEPALIVE_INTERVAL); }); +} - TRAVERSAL_SERVER_SOCKET.connect( - TRAVERSAL_SERVER_PORT, - TRAVERSAL_SERVER_ADDRESS - ); +function sendMessage(message: string) { + try { + SOCKET.send(message); + } catch { + // socket has been closed + } } export function sendSwap(swap: boolean) { - if (SOCKET) { - const message = { swap }; - SOCKET.send(JSON.stringify(message)); - } + const message = { swap }; + sendMessage(JSON.stringify(message)); } export function sendMove(move: Array) { - if (SOCKET) { - const message = { move }; - SOCKET.send(JSON.stringify(message)); - } + const message = { move }; + sendMessage(JSON.stringify(message)); } export function sendRequestUndo() { - if (SOCKET) { - const message = { requestUndo: true }; - SOCKET.send(JSON.stringify(message)); - } + const message = { requestUndo: true }; + sendMessage(JSON.stringify(message)); } export function sendAcceptUndo() { - if (SOCKET) { - const message = { acceptUndo: true }; - SOCKET.send(JSON.stringify(message)); - } + const message = { acceptUndo: true }; + sendMessage(JSON.stringify(message)); } export function sendSettings() { - if (SOCKET) { - const { isBlack } = selectNetplayState(store.getState()); - const { settings } = selectGameState(store.getState()); - const settingsMessage = { settings, isBlack: !isBlack }; - SOCKET.send(JSON.stringify(settingsMessage)); - } + const { isBlack } = selectNetplayState(store.getState()); + const { settings } = selectGameState(store.getState()); + const message = { settings, isBlack: !isBlack }; + sendMessage(JSON.stringify(message)); } export function sendResign() { - if (SOCKET) { - const message = { resign: true }; - SOCKET.send(JSON.stringify(message)); - } + const message = { resign: true }; + sendMessage(JSON.stringify(message)); }