WebSockets provide a great way to implement real-time communication in multiplayer games, allowing the server and players to communicate back and forth with minimal latency. This article will guide you through setting up a basic multiplayer game server using WebSockets in Go.
What Are WebSockets?
WebSockets are a protocol that provides full-duplex communication channels over a single TCP connection. They are especially useful for applications like games, where rapid two-way communication is essential.
Setting Up Your Environment
Before diving into code, you'll need to install Go from the official Go website, and you might want to set up your environment to work with Go using an IDE like VSCode or GoLand.
Installing Gorilla WebSocket
In Go, one of the most popular libraries for WebSockets is Gorilla WebSocket. It’s robust, well-documented, and widely used in production-quality applications.
Use Go modules to install Gorilla WebSocket:
go get -u github.com/gorilla/websocketCreating a Simple Game Server
Let’s start building a simple WebSocket server using Gorilla WebSocket.
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
// Define an upgrader that will upgrade our HTTP server connection to a websocket connection
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Allow any origin for simplicity
},
}
// Function that will handle websocket requests
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer ws.Close()
for {
// Read in a new message as JSON and map it to a struct
var msg map[string]interface{}
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
break
}
log.Printf("Received message: %v", msg)
// Write the message back to the client
err = ws.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
break
}
}
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("./public"))
http.Handle("/", fs)
// Set up a websocket route
http.HandleFunc("/ws", handleConnections)
// Start the server on localhost:8080
log.Println("[INFO] Server started on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}Handling WebSocket Connections
The code above establishes a basic WebSocket server. Each connected client communicates through a persistent open WebSocket. The `handleConnections` function reads incoming messages and simply echoes them back to the client.
Building a Client
For this demonstration, we’ll build a simple HTML/JavaScript client to connect to our WebSocket server. Create an `index.html` file in your `public` directory:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Game Client</title>
</head>
<body>
<h1>WebSocket Game Client</h1>
<div>Connection Status: <span id="status">Disconnected</span></div>
<input type="text" id="message" placeholder="Enter a message"/>
<button onclick="sendMessage()">Send Message</button>
<script>
let ws;
function connect() {
ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = () => {
document.getElementById('status').textContent = 'Connected';
console.log('Connected');
};
ws.onmessage = (message) => {
console.log('Received:', message.data);
};
ws.onclose = () => {
document.getElementById('status').textContent = 'Disconnected';
console.log('Disconnected');
// Attempt to reconnect in 2 seconds
setTimeout(connect, 2000);
};
}
function sendMessage() {
const message = document.getElementById('message').value;
ws.send(message);
console.log('Sent:', message);
}
connect();
</script>
</body>
</html>Running Your Server and Client
To see everything in action, first run your Go server:
go run main.goOpen your browser and navigate to http://localhost:8080. Here, you can send messages using your client interface, and you’ll see the messages being echoed back through the WebSocket connection.
Adding Game Logic
To utilize WebSockets effectively in a multiplayer game, you’ll eventually want to replace the echo message logic with real game state updates, player actions, and broadcasts to all connected players. This involves implementing separate goroutines for each connection and using channels for concurrency and synchronization within Go.
For instance, if implementing a chat feature, you might maintain a list of all connected players and broadcast any player message to the entire list. This will ensure each player receives real-time updates from others.
Here’s a simple example of a broadcast mechanism:
// Assume a global "clients" map and a global "broadcast" channel
var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan map[string]interface{})
func handleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer ws.Close()
clients[ws] = true
for {
var msg map[string]interface{}
err := ws.ReadJSON(&msg)
if err != nil {
delete(clients, ws)
break
}
broadcast <- msg
}
}
func handleMessages() {
for {
msg := <-broadcast
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
client.Close()
delete(clients, client)
}
}
}
}With these fundamentals, you can build a robust multiplayer game using WebSockets in Go, taking advantage of the language’s concurrency model to efficiently handle multiple connections and messages with low latency.