introduce
This is a series
- Explore Golang cloud native game server development, 5 minutes to learn the Nano game server framework
What is Gorilla WebSocket?
Gorilla WebSocket is the Go implementation of the WebSocket protocol.
WebSocket is what? For less here not to repeat, digging friends in the nuggets on science too much 😂.
The sample warehouse
- Official example: Chat example
- For example: cloud-native game-server/2-gorilla- Websocket-chat
Why familiarize yourself with this example?
Shared memory by communication, shared memory by communication, shared memory by communication
Before we look at the Nano, let’s go over Golang’s concurrent programming again.
The sample analysis
Here I put together the official readme.md for this example
Describe the business in one sentence
- The client can connect to the server
- The client can send a message, and the server immediately broadcasts the message
Technical description Service
Essentially, it is the management and read/write of multiple WebSocket connections.
- The server sends a message to the client, which is technically the client
websocket
connectionread
å’Œwrite
Operation.
- This is abstracted
Client
It has this of its ownwebsocket
Of the connectionread
å’Œwrite
operation
- Multiple clients, which means multiple
websocket
Maintenance work.
- This is abstracted
Hub
It maintains all theClient
Broadcasting is nothing more than callingClient
The inside of thewebsocket
Of the connectionwrite
operation
Server
Server applications define two types, Client and Hub. The server creates an instance of the Client type for each WebSocket connection. A Client acts as a mediator between a WebSocket connection and a single instance of the Hub type. The Hub maintains a set of registered clients and broadcasts messages to them.
The application runs one Goroutine for the Hub and two for each Client. Multiple Goroutines communicate with each other using channels. The Hub has channels for registering clients, deregistering clients, and broadcasting messages. The Client has a cached outbound message channel. One of the client’s Goroutines reads the message from this channel and writes it to the WebSocket. Another client, Goroutine, reads the message from the WebSocket and sends it to the Hub.
Core source code explanation:
.func main(a){...// Once the application is running, the 'Hub' management is initialized
hub := newHub()
// Open a goroutine and run it in the background to listen for three channels
// Register: registers the client channel
// unregister: deregisters the client channel
// broadcast: broadcast client channel
go hub.run()
http.HandleFunc("/", serveHome)
http.HandleFunc("/ws".func(w http.ResponseWriter, r *http.Request){ serveWs(hub, w, r) }) ..... }...// Server processes "/ws" requests from each client.
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
// Update this request to the 'webSocket' protocol
conn, err := upgrader.Upgrade(w, r, nil)
iferr ! =nil {
log.Println(err)
return
}
// Initialize the current client instance and hook up with the 'hub' central manager,
client := &Client{hub: hub, conn: conn, send: make(chan []byte.256)}
client.hub.register <- client
// Allows callers to reference collections of memory by doing all the work in the new Goroutines.
// The 'I/O' operation on the current 'websocket' connection
// Write operation (send message to client) -> here 'Hub' will be processed uniformly
go client.writePump()
// Read operation (message to client) -> send the message to 'Hub' to distribute the message to all connections
go client.readPump()
}
Copy the code
Hub
The code for the Hub type is in hub.go. The application’s main function starts the hub’s run method in the form of a Goroutine. The client sends requests to the Hub using the Register, unregister, and broadcast channels.
The Hub registers clients by adding a client pointer to the clients Map as a key. Map is always true.
The logout code is a little more complicated. In addition to removing the client pointer from the Clients Map, the Hub also closes the send channel of the client and sends a signal to the client that no more messages will be sent to the client.
The Hub processes the message by looping over the registered client and sending the message to the client’s SEND channel. If the client’s SEND buffer is full, the Hub assumes that the client is dead or stuck. In this case, the Hub logs out the client and closes the Websocket.
Core source code explanation:
func (h *Hub) run(a) {
for {
select {
/ / registered channel
case client := <-h.register:
// Key-value pair operations, nothing to say
h.clients[client] = true
/ / cancellation of the channel
case client := <-h.unregister:
// Key-value pair operations, nothing to say
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
/ / radio channel
case message := <-h.broadcast:
for client := range h.clients {
select {
// Send channel directly to each connection
case client.send <- message:
// If it's stuck, kick it off
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
Copy the code
Client
The code for the Client type is in client.go.
Service functions are registered as HTTP handlers by the application’s main function. The handler upgrades the HTTP connection to the WebSocket protocol, creates a client, registers the client on the hub, and plans to logout the client using the defer statement.
Next, the HTTP handler starts the Client’s writePump method as a Goroutine. This method transmits the message from the client’s send channel to the WebSocket connection. The writer method exits when the hub closes the channel or an error is written to the WebSocket connection.
Finally, the HTTP handler calls the client’s readPump method. This method transmits inbound messages from the WebSocket to the hub.
WebSocket connections support one concurrent reader and one concurrent writer. The application ensures that these concurrency requirements are met by performing all reads to the readPump Goroutine and all writes to the writePump Goroutine.
To improve efficiency under high load, the writePump function merges the waiting chat messages in the SEND channel into a single WebSocket message. This reduces the number of system calls and the amount of data sent over the network.
Core source code explanation:
// readPump Pumps messages from the Websocket connection to the hub.
// The application runs readPump on each connected Goroutine.
// The application ensures that there is at most one reader on the connection by performing all the reads in the goroutine.
func (c *Client) readPump(a) {
defer func(a) {
c.hub.unregister <- c
c.conn.Close()
}()
// SetReadLimit Sets the maximum size of messages read from the peer. If the message exceeds the limit, the connection sends a close message to the peer and then returns the ErrReadLimit to the application.
c.conn.SetReadLimit(maxMessageSize)
// SetReadDeadline Sets the read deadline on the basic network connection. After a read timeout, the WebSocket connection state is corrupted and all subsequent reads will return an error. If the parameter value is zero, the read will not time out.
c.conn.SetReadDeadline(time.Now().Add(pongWait))
// SetPongHandler Sets the handler for the Pong messages received from the peer. The processor parameter is the PONG message application data. The default Pong handler does nothing.
// The handler function is called from the NextReader, ReadMessage, and Message Reader Read methods.
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
// Read the message
_, message, err := c.conn.ReadMessage()
iferr ! =nil {
// Error handling
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
// Clean up the message content
message = bytes.TrimSpace(bytes.Replace(message, newline, space, - 1))
/ / radio
c.hub.broadcast <- message
}
}
// writePump sends messages from hub Pump to WebSocket connection.
// Start the goroutine that runs writePump for each connection.
// By performing all the writes in the Goroutine, the application ensures that at most one writer is connected.
func (c *Client) writePump(a) {
ticker := time.NewTicker(pingPeriod)
defer func(a) {
ticker.Stop()
c.conn.Close()
}()
for {
select {
// Write a message to the current WebSocket connection
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if! ok {// Hub closes the channel
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
NextWriter returns a writer for the next message to be sent. The writer's Close method refreshes the complete message to the network.
w, err := c.conn.NextWriter(websocket.TextMessage)
iferr ! =nil {
return
}
w.Write(message)
// Add queue chat message to the current WebSocket message.
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write(<-c.send)
}
iferr := w.Close(); err ! =nil {
return
}
// Periodically check the client status
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err ! =nil {
return}}}}Copy the code
For details of the API, you can go directly to the documentation, but the idea is the most important
Frontend
The front-end code is in home.html.
When the document is loaded, the script checks the WebSocket functionality in the browser. If the Websocket functionality is available, the script opens a connection to the server and registers a callback function to process messages from the server. The callback function uses the appendLog function to append the message to the chat log.
To allow the user to manually scroll the chat log without being disturbed by new messages, the appendLog function checks where the scrolling is before adding new content. If the chat log scrolls to the bottom, this feature will scroll new content into the view after it is added. Otherwise, the scroll position will not change.
The form handler writes the user input to the WebSocket and clears the input fields.
Docker builds a development debugging environment
Building the Image:
docker build -f Dockerfile.dev -t cloud-native-game-server:dev .
Copy the code
Start the development environment (Live Reload support)
DEMO=2-gorilla-websocket-chat docker-compose up demo
#docker-compose down
Copy the code
Go to localhost:3250 to see the effect.
Start the mode environment
DEMO=2-gorilla-websocket-chat docker-compose up demo-debug
Copy the code