Skip to content

WebSocket मल्टीप्लेयर की बुनियादी बातें

WebSockets के साथ एक मल्टीप्लेयर game बनाना: सर्वर आर्किटेक्चर, game rooms और state sync

WebSockets मल्टीप्लेयर games के लिए रियल-टाइम कम्युनिकेशन को संभव बनाते हैं। यह tutorial क्लाइंट-साइड की बुनियादी बातें कवर करता है।

1) सर्वर से कनेक्ट करना

js
const ws = new WebSocket('wss://game-server.example.com')

ws.onopen = () => {
  console.log('Connected')
  ws.send(JSON.stringify({ type: 'join', name: 'Player1' }))
}

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data)
  handleMessage(msg)
}

ws.onclose = () => {
  console.log('Disconnected')
}

ws.onerror = (err) => {
  console.error('WebSocket error:', err)
}

2) मैसेज प्रोटोकॉल डिज़ाइन

एक सरल प्रोटोकॉल तय करें:

js
// Client -> Server
{ type: 'join', name: 'Player1' }
{ type: 'input', keys: { left: true, jump: true } }
{ type: 'chat', message: 'Hello!' }

// Server -> Client
{ type: 'state', players: [...], entities: [...] }
{ type: 'playerJoined', id: 'abc', name: 'Player2' }
{ type: 'playerLeft', id: 'abc' }

3) कनेक्शन मैनेजर क्लास

js
class GameConnection {
  constructor(url) {
    this.url = url
    this.ws = null
    this.handlers = new Map()
    this.reconnectDelay = 1000
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = () => {
      this.reconnectDelay = 1000
      this.emit('connected')
    }
    
    this.ws.onmessage = (e) => {
      const msg = JSON.parse(e.data)
      this.emit(msg.type, msg)
    }
    
    this.ws.onclose = () => {
      this.emit('disconnected')
      this.scheduleReconnect()
    }
    
    this.ws.onerror = () => {
      this.ws.close()
    }
  }
  
  scheduleReconnect() {
    setTimeout(() => {
      this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000)
      this.connect()
    }, this.reconnectDelay)
  }
  
  send(type, data = {}) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type, ...data }))
    }
  }
  
  on(type, handler) {
    if (!this.handlers.has(type)) {
      this.handlers.set(type, [])
    }
    this.handlers.get(type).push(handler)
  }
  
  emit(type, data) {
    const handlers = this.handlers.get(type) || []
    handlers.forEach(h => h(data))
  }
  
  disconnect() {
    this.ws?.close()
  }
}

4) प्लेयर input भेजना

positions नहीं, inputs भेजें। सर्वर को authoritative रहने दें:

js
const connection = new GameConnection('wss://server.example.com')

function sendInput(input) {
  connection.send('input', {
    left: input.left,
    right: input.right,
    jump: input.jump,
    shoot: input.shoot,
    seq: inputSequence++,
  })
}

// Send input at a fixed rate (e.g., 20 times/sec)
setInterval(() => {
  sendInput(currentInput)
}, 50)

5) game state प्राप्त करना

js
let gameState = { players: [], entities: [] }

connection.on('state', (msg) => {
  gameState = msg
})

connection.on('playerJoined', (msg) => {
  gameState.players.push({ id: msg.id, name: msg.name })
})

connection.on('playerLeft', (msg) => {
  gameState.players = gameState.players.filter(p => p.id !== msg.id)
})

6) स्मूद मूवमेंट के लिए इंटरपोलेशन

सर्वर snapshots भेजता है। उनके बीच इंटरपोलेट करें:

js
class EntityInterpolator {
  constructor() {
    this.buffer = [] // { timestamp, state }
    this.renderDelay = 100 // ms behind server
  }
  
  addSnapshot(timestamp, state) {
    this.buffer.push({ timestamp, state })
    
    // Keep only recent snapshots
    while (this.buffer.length > 10) {
      this.buffer.shift()
    }
  }
  
  getState(currentTime) {
    const renderTime = currentTime - this.renderDelay
    
    // Find surrounding snapshots
    let before = null
    let after = null
    
    for (let i = 0; i < this.buffer.length - 1; i++) {
      if (this.buffer[i].timestamp <= renderTime && 
          this.buffer[i + 1].timestamp >= renderTime) {
        before = this.buffer[i]
        after = this.buffer[i + 1]
        break
      }
    }
    
    if (!before || !after) {
      return this.buffer[this.buffer.length - 1]?.state
    }
    
    // Interpolate
    const t = (renderTime - before.timestamp) / (after.timestamp - before.timestamp)
    return this.lerp(before.state, after.state, t)
  }
  
  lerp(a, b, t) {
    return {
      x: a.x + (b.x - a.x) * t,
      y: a.y + (b.y - a.y) * t,
    }
  }
}

7) क्लाइंट-साइड प्रेडिक्शन

रिस्पॉन्सिव कंट्रोल्स के लिए, लोकल रूप से प्रेडिक्ट करें और सर्वर के साथ reconcile करें:

js
class PredictedPlayer {
  constructor() {
    this.position = { x: 0, y: 0 }
    this.pendingInputs = []
  }
  
  applyInput(input) {
    // Apply input locally
    if (input.left) this.position.x -= 5
    if (input.right) this.position.x += 5
    
    // Store for reconciliation
    this.pendingInputs.push({ seq: input.seq, input })
  }
  
  reconcile(serverState, lastProcessedSeq) {
    // Server confirmed position
    this.position = { ...serverState.position }
    
    // Remove confirmed inputs
    this.pendingInputs = this.pendingInputs.filter(i => i.seq > lastProcessedSeq)
    
    // Re-apply unconfirmed inputs
    for (const pending of this.pendingInputs) {
      if (pending.input.left) this.position.x -= 5
      if (pending.input.right) this.position.x += 5
    }
  }
}

8) लेटेंसी डिस्प्ले संभालना

js
class LatencyMonitor {
  constructor(connection) {
    this.connection = connection
    this.pingStart = 0
    this.latency = 0
    
    connection.on('pong', () => {
      this.latency = Date.now() - this.pingStart
    })
    
    setInterval(() => this.ping(), 1000)
  }
  
  ping() {
    this.pingStart = Date.now()
    this.connection.send('ping')
  }
  
  getLatency() {
    return this.latency
  }
}

9) दक्षता के लिए बाइनरी मैसेज

हाई-फ्रीक्वेंसी अपडेट्स के लिए, बाइनरी का इस्तेमाल करें:

js
// Sending binary
const buffer = new ArrayBuffer(12)
const view = new DataView(buffer)
view.setFloat32(0, player.x)
view.setFloat32(4, player.y)
view.setUint32(8, inputFlags)
ws.send(buffer)

// Receiving binary
ws.binaryType = 'arraybuffer'
ws.onmessage = (e) => {
  if (e.data instanceof ArrayBuffer) {
    const view = new DataView(e.data)
    const x = view.getFloat32(0)
    const y = view.getFloat32(4)
    // ...
  }
}

10) बेसिक सर्वर उदाहरण (Node.js)

js
import { WebSocketServer } from 'ws'

const wss = new WebSocketServer({ port: 8080 })
const players = new Map()

wss.on('connection', (ws) => {
  const id = crypto.randomUUID()
  players.set(id, { id, x: 0, y: 0, ws })
  
  ws.on('message', (data) => {
    const msg = JSON.parse(data)
    
    if (msg.type === 'input') {
      const player = players.get(id)
      if (msg.left) player.x -= 5
      if (msg.right) player.x += 5
    }
  })
  
  ws.on('close', () => {
    players.delete(id)
    broadcast({ type: 'playerLeft', id })
  })
})

// Broadcast state 20 times/sec
setInterval(() => {
  const state = {
    type: 'state',
    players: Array.from(players.values()).map(p => ({
      id: p.id, x: p.x, y: p.y
    }))
  }
  broadcast(state)
}, 50)

function broadcast(msg) {
  const data = JSON.stringify(msg)
  for (const player of players.values()) {
    player.ws.send(data)
  }
}

संबंधित

बाहरी संसाधन

  • MDN: WebSocket API — browser WebSocket रेफरेंस
  • MDN: WebRTC API — कम लेटेंसी के लिए peer-to-peer कनेक्शन
  • MDN: WebTransport API — एक नया HTTP/3 ट्रांसपोर्ट जो मार्च 2026 में Safari 26.4 के सपोर्ट शिप करने के बाद क्रॉस-browser Baseline तक पहुंच गया (यह पहले से ही Chrome 97+, Edge और Firefox 114+ में था)। एक अकेले TCP-आधारित WebSocket के विपरीत, WebTransport QUIC पर चलता है और unreliable datagrams के साथ-साथ कई स्वतंत्र streams देता है, इसलिए एक dropped packet बाकी सब कुछ को head-of-line-block नहीं करता। एक आम पैटर्न है हाई-फ्रीक्वेंसी position अपडेट्स को datagrams पर भेजना और chat या scoreboards को एक reliable stream पर रखना। WebSockets अभी भी सबसे ज्यादा compatible विकल्प हैं और ऊपर बताई गई हर बात अब भी लागू होती है, लेकिन लेटेंसी-सेंसिटिव games के लिए WebTransport पर विचार करना अब फायदेमंद है।
  • Socket.IO documentation — लोकप्रिय WebSocket wrapper लाइब्रेरी
  • Valve Source Multiplayer Networking — प्रोडक्शन games में इस्तेमाल होने वाले authoritative networking पैटर्न