Chuyển đến nội dung chính

第 11 課:TCP、UDP 和 WebSocket

net 模組(TCP 伺服器/客戶端)、dgram (UDP)。帶有 ws 庫的 WebSocket 伺服器。連線管理,二進位協定。協議設計,定制線路格式。

💻 程式設計 — 第 11 課 第 11 課:TCP、UDP 和 WebSocket

Node.js 核心:從基礎到高級

第 3 部分:並發與網絡

亞洲開發網

1. TCP 伺服器和客戶端

import net from 'node:net'

// TCP Server
const server = net.createServer((socket) => {
  console.log(`Client connected: ${socket.remoteAddress}:${socket.remotePort}`)

  socket.on('data', (data) => {
    const message = data.toString().trim()
    console.log(`Received: ${message}`)
    socket.write(`Echo: ${message}\n`)
  })

  socket.on('end', () => console.log('Client disconnected'))
  socket.on('error', (err) => console.error('Socket error:', err))
})

server.listen(8080, () => console.log('TCP server on :8080'))

// TCP Client
const client = net.createConnection({ port: 8080 }, () => {
  client.write('Hello Server!')
})

client.on('data', (data) => {
  console.log(`Server: ${data.toString()}`)
  client.end()
})

2.UDP(dgram)

import dgram from 'node:dgram'

// UDP Server
const server = dgram.createSocket('udp4')

server.on('message', (msg, rinfo) => {
  console.log(`${rinfo.address}:${rinfo.port} → ${msg}`)
  server.send(`ACK: ${msg}`, rinfo.port, rinfo.address)
})

server.bind(41234)

// UDP Client
const client = dgram.createSocket('udp4')
client.send('Hello UDP', 41234, 'localhost', (err) => {
  if (err) console.error(err)
})
client.on('message', (msg) => {
  console.log(`Response: ${msg}`)
  client.close()
})

3.WebSocket伺服器(ws)

import { WebSocketServer, WebSocket } from 'ws'
import { createServer } from 'node:http'

const httpServer = createServer()
const wss = new WebSocketServer({ server: httpServer })

const clients = new Set<WebSocket>()

wss.on('connection', (ws, req) => {
  clients.add(ws)
  console.log(`Connected: ${req.socket.remoteAddress} (${clients.size} clients)`)

  ws.on('message', (data, isBinary) => {
    const message = data.toString()
    // Broadcast cho tất cả clients
    for (const client of clients) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message)
      }
    }
  })

  ws.on('close', () => {
    clients.delete(ws)
  })

  ws.on('error', (err) => console.error('WS error:', err))

  // Heartbeat
  ws.on('pong', () => { (ws as any).isAlive = true })
})

// Ping/Pong heartbeat
setInterval(() => {
  for (const ws of clients) {
    if ((ws as any).isAlive === false) return ws.terminate()
    ;(ws as any).isAlive = false
    ws.ping()
  }
}, 30000)

httpServer.listen(3000)

4. 基於房間的模式

const rooms = new Map<string, Set<WebSocket>>()

function joinRoom(ws: WebSocket, room: string) {
  if (!rooms.has(room)) rooms.set(room, new Set())
  rooms.get(room)!.add(ws)
}

function broadcastToRoom(room: string, message: string, exclude?: WebSocket) {
  const members = rooms.get(room)
  if (!members) return
  for (const ws of members) {
    if (ws !== exclude && ws.readyState === WebSocket.OPEN) {
      ws.send(message)
    }
  }
}

5. 二進位協議

// Custom binary protocol: [type:1][length:4][payload:N]
function encodeMessage(type: number, payload: Buffer): Buffer {
  const header = Buffer.alloc(5)
  header.writeUInt8(type, 0)
  header.writeUInt32BE(payload.length, 1)
  return Buffer.concat([header, payload])
}

function decodeMessage(data: Buffer) {
  const type = data.readUInt8(0)
  const length = data.readUInt32BE(1)
  const payload = data.subarray(5, 5 + length)
  return { type, payload }
}

下一篇: 事件、定時器和診斷 — EventEmitter、AbortController、async_hooks。