WebSocket
WebSocket is a mechanism for low-cost, full-duplex communication on Web, which protocol was standardized as RFC 6455.
The following diagram, quoted by Wikipedia, describe a communication using WebSocket between client and server.
"Handshake" is explained in the following two articles.
- Deep dive into WebSocket opening handshake protocol with Go
- Learn WebSocket handshake protocol with gorilla/websocket server
- How decided a value set in Sec-WebSocket-Key/Accept header
This post will focus on "Bidirectional messages"
WebSocket server
Let's learn the WebSocket bidirectional messaging protocol with a specific Go implementation, which is available on a GitHub repository.
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
func main() {
p := 12345
log.Printf("Starting websocket echo server on port %d", p)
http.HandleFunc("/", echo)
if err := http.ListenAndServe(fmt.Sprintf(":%d", p), nil); err != nil {
log.Panicf("Error while starting to listen: %#v", err)
}
}
func echo(w http.ResponseWriter, r *http.Request) {
// Start handshaking
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Upgrading error: %#v\n", err)
return
}
defer c.Close()
// End
log.Println("Success to handshake with client")
// Start bidirectional messages
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Printf("Reading error: %#v\n", err)
break
}
log.Printf("recv: message %q", message)
if err := c.WriteMessage(mt, message); err != nil {
log.Printf("Writing error: %#v\n", err)
break
}
}
// End bidirectional messages
}
Open handshake
The first half of the code is the server implementation for "Handshake".
// 1. Initializes parameters for upgrading an HTTP connection to a WebSocket connection.
var upgrader = websocket.Upgrader{}
// 2. Starts WebSocket server which waits client HTTP requests on port 12345
func main() {
p := 12345
log.Printf("Starting websocket echo server on port %d", p)
http.HandleFunc("/", echo)
if err := http.ListenAndServe(fmt.Sprintf(":%d", p), nil); err != nil {
log.Panicf("Error while starting to listen: %#v", err)
}
}
func echo(w http.ResponseWriter, r *http.Request) {
// 3. Start handshaking
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Upgrading error: %#v\n", err)
return
}
defer c.Close()
// End
log.Println("Success to handshake with client")
// ...(Bidirectional messages part)
As noted in the comments in the code, to start up a WebSocket server, do the following:
- Initializes parameters for upgrading an HTTP connection to a WebSocket connection.
- Starts WebSocket server which waits client HTTP requests on port 12345
- Start handshaking
When you run this program, it will start an HTTP server on port 12345.
$ go run server/main.go
2021/12/13 11:54:22 Starting websocket echo server on port 12345
You can check if the handshake is actually working by making a curl request (but it's not perfect).
$ curl -X GET http://localhost:12345 \
-H "Connection: upgrade" \
-H "Upgrade: websocket" \
-H "Sec-Websocket-version: 13" \
-H "Sec-Websocket-Key: 08kp54j1E3z4IfuM1m75tQ==" \
-H "Host: localhost:12345" \
-H "Origin: http://localhost:12345"
When you send it, you will see that the request was accepted in stdout.
$ go run main.go
(omit)...
2021/12/13 12:07:19 Success to handshake with client
See Learn WebSocket handshake protocol with gorilla/websocket server for more information.
WebSocket connection
The key to understanding the message exchanging is the return value of this line.
c, err := upgrader.Upgrade(w, r, nil)
The variable c
is a pointer of websocket.Conn struct. The Conn type represents a WebSocket connection.
When the handshake is completed, you can start exchanging messages over the established WebSocket connection.
You can receive and send messages by calling the Conn's ReadMessage and WriteMessage.
// Start bidirectional messages
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Printf("Reading error: %#v\n", err)
break
}
log.Printf("recv: message %q", message)
// (omit...writing message part)
}
// End bidirectional messages
Use for
loop so that the server keeps waiting for messages from the client.
Data Framing
In the WebSocket protocol, data is transmitted using a sequence of frames. This wire format for the data transfer part is described by the ABNF (Augmented BNF for Syntax Specification). A overview of the framing is given in the following figure.
Server and client exchange data in accordance with this method.
Read messages from the peer
ReadMessage is a method to read messages from the peer which interprets data in base framing protocol.
mt, message, err := c.ReadMessage()
The implementation of ReadMessage is as follow:
func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
var r io.Reader
messageType, r, err = c.NextReader()
if err != nil {
return messageType, nil, err
}
p, err = ioutil.ReadAll(r)
return messageType, p, err
}
The Conn.NextReader function reads bytes received from the peer.
messageType, r, err = c.NextReader()
Opcode
First return value is messageType
which represents WebSocket opcode numbers defined in RFC 6455 - 11.8. WebSocket Opcode Registry.
Opcode | Meaning |
---|---|
0 | Continuation Frame |
1 | Text Frame |
2 | Binary Frame |
8 | Connection Close Frame |
9 | Ping Frame |
10 | Pong Frame |
There are two types of opcode: message for exchanging data and controlling WebSocket connection.
- For exchanging data
- Text Frame
- Binary Frame
- For controlling WebSocket connection
- Connection Close Frame
- Ping Frame
- Pong Frame
- Contination Frame
The messages for exchanging data distinguishes between text and binary data messages. And, the WebSocket protocol defines four types of control messages: close, ping, pong, and continuation.
Conclusion
This article explains WebSocket bidirectional message using gorilla/websocket server implementation.
Related articles are here.
Top comments (0)