This article describes the WebSocket implementation principle and usage of go. Share to everyone for your reference, specific as follows:
WebSocket is divided into handshake and data transfer phase, that is, the HTTP handshake + Duplex TCP connection
RFC protocol document in: http://tools.ietf.org/html/rfc6455
Handshake phase
The handshake phase is normal HTTP
The client sends a message:
Get/chat http/1.1
Host:server.example.com
upgrade:websocket
connection:upgrade
sec-websocket-key:dghlihnhbxbszsbub25jzq==
origin:http://example.com
sec-websocket-version:13
Server-side return message:
http/1.1 switching Protocols
upgrade:websocket
connection:upgrade
sec-websocket-accept: s3pplmbitxaq9kygzzhzrbk+xoo=
The sec-websocket-accept here is calculated by:
Base64 (HSA1 (Sec-websocket-key + 258eafa5-e914-47da-95ca-c5ab0dc85b11))
If this sec-websocket-accept calculation error browser will prompt:
Sec-websocket-accept Dismatch
If the return succeeds, WebSocket will recall the OnOpen event
Data transmission
The protocol used for WebSocket data transmission is:
The specific description of the parameter is in this:
Fin:1 bit, used to indicate that this is the last message fragment of a message, and of course the first message fragment may also be the last piece of a message;
RSV1, RSV2, RSV3:1, respectively, if there is no agreement between the two sides of the custom protocol, then these values must be 0, otherwise must be broken websocket connection;
opcode:4 bit opcode, define payload data, if you receive an unknown opcode, the connection must also be broken, the following is the definition of the opcode:
*%x0 represents a continuous message fragment
*%X1 represents a text message fragment
*%X2 table not binary message fragment
*%x3-7 the opcode reserved for future uncontrolled message fragments
*%X8 indicates connection shutdown
*%x9 the ping that represents the heartbeat check
*%XA indicates the pong of the heartbeat check
*%xb-f reserve opcode for future control message fragments
Mask:1 bit, defines whether the transmitted data has a mask, if set to 1, the mask key must be placed in the Masking-key area, the client sent to the server all messages, this bit value is 1;
Payload Length: The lengths of the transmitted data, expressed in bytes: 7 bits, 7+16 bits, or 7+64 bits. If the value is 0-125 in bytes, the value represents the length of the transmitted data, and if this value is 126, then the two bytes followed by a 16 unsigned number representing the length of the transmitted data; If this value is 127, Then the 8-byte representation of a 64-bit non conformance number is used to represent the length of the transmitted data. The number of multibyte lengths is expressed in the order of network bytes. The length of the load data is the sum of the extended data and the applied data, and the length of the extended data may be 0, so the length of the load data is the length of the application data at this time.
masking-key:0 or 4 bytes, the data that the client sends to the server is masked by an embedded 32-bit value; The Mask key exists only when the mask bit is set to 1.
Payload data: (x+y) bit, the sum of the load is extended data and the length of the applied data.
Extension data:x bit, if there is no special convention between the client and the server, the extended data length is always 0, and any extension must specify the length of the extended data, or how the length is calculated, and how to determine the correct handshake when shaking the handshake. If there is extended data, the extended data is included within the length of the load data.
Application Data:y Bits, arbitrary application data, after the extended data, the length of the applied data = the length of the load data-the length of the extended data.
Instance
Use the Go implementation example specifically:
Client:
Html:
Js:
var socket;
$ ("#connect"). Click (Function (event) {
socket = new WebSocket ("ws://127.0.0.1:8000");
Socket.onopen = function () {
alert ("Socket has been opened");
Socket.onmessage = function (msg) {
alert (msg.data);
}
Socket.onclose = function () {
alert ("Socket has been closed");
}
);
$ ("#send"). Click (Function (event) {
socket.send ("Send from Client");
$ ("#close"). Click (Function (event) {
socket.close ();
})
Service side:
Copy Code code as follows:
Package Main
Import
"NET"
"Log"
"Strings"
"CRYPTO/SHA1"
"IO"
"Encoding/base64"
"Errors"
)
Func Main () {
ln, ERR: = Net. Listen ("TCP", ": 8000")
If Err!= nil {
Log. Panic (ERR)
}
for {
Conn, err: = ln. Accept ()
If Err!= nil {
Log. Println ("Accept err:", err)
}
for {
Handleconnection (conn)
}
}
}
Func handleconnection (Conn net. Conn) {
Content: = Make ([]byte, 1024)
_, ERR: = conn. Read (content)
Log. Println (string (content))
If Err!= nil {
Log. PRINTLN (ERR)
}
Ishttp: = False
Let's judge for a moment.
If string (content[0:3]) = = "Get" {
Ishttp = true;
}
Log. Println ("Ishttp:", ishttp)
If Ishttp {
Headers: = Parsehandshake (string (content))
Log. Println ("Headers", headers)
Secwebsocketkey: = headers["Sec-websocket-key"]
Note: This omits the other validation
GUID: = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
Calculate sec-websocket-accept
H: = SHA1. New ()
Log. PRINTLN ("Accept raw:", Secwebsocketkey + GUID)
Io. WriteString (H, Secwebsocketkey + GUID)
Accept: = Make ([]byte, 28)
Base64. Stdencoding.encode (Accept, h.sum (nil))
Log. Println (String (Accept))
Response: = "http/1.1 switching protocols\r\n"
Response = response + "Sec-websocket-accept:" + string (Accept) + "\ r \ n"
Response = response + "connection:upgrade\r\n"
Response = response + "upgrade:websocket\r\n\r\n"
Log. PRINTLN ("Response:", response)
If lenth, err: = conn. Write ([]byte (response)); Err!= Nil {
Log. PRINTLN (ERR)
} else {
Log. Println ("Send len:", Lenth)
}
Wssocket: = Newwssocket (conn)
for {
Data, err: = Wssocket. Readiframe ()
If Err!= nil {
Log. Println ("Readiframe err:", err)
}
Log. PRINTLN ("Read data:", string (data))
Err = Wssocket. Sendiframe ([]byte ("good"))
If Err!= nil {
Log. Println ("Sendiframe err:", err)
}
Log. PRINTLN ("Send Data")
}
} else {
Log. Println (string (content))
Direct Read
}
}
Type Wssocket struct {
Maskingkey []byte
Conn Net. Conn
}
Func Newwssocket (Conn net. Conn) *wssocket {
Return &wssocket{conn:conn}
}
Func (this *wssocket) sendiframe (data []byte) error {
This only deals with data-length <125.
If Len (data) >= 125 {
return errors. New ("Send IFRAME data Error")
}
Lenth: = Len (data)
Maskeddata: = Make ([]byte, Lenth)
For I: = 0; i < lenth; i++ {
If this. Maskingkey!= Nil {
Maskeddata[i] = Data[i] ^ this. Maskingkey[i% 4]
} else {
Maskeddata[i] = Data[i]
}
}
This. Conn.write ([]byte{0x81})
var paylenbyte byte
If this. Maskingkey!= Nil && len (this. Maskingkey)!= 4 {
Paylenbyte = Byte (0x80) | Byte (lenth)
This. Conn.write ([]byte{paylenbyte})
This. Conn.write (this. Maskingkey)
} else {
Paylenbyte = Byte (0x00) | Byte (lenth)
This. Conn.write ([]byte{paylenbyte})
}
This. Conn.write (data)
return Nil
}
Func (this *wssocket) Readiframe () (data []byte, err Error) {
Err = Nil
First byte: FIN + rsv1-3 + OPCODE
Opcodebyte: = Make ([]byte, 1)
This. Conn.read (Opcodebyte)
FIN: = opcodebyte[0] >> 7
RSV1: = opcodebyte[0] >> 6 & 1
RSV2: = opcodebyte[0] >> 5 & 1
RSV3: = opcodebyte[0] >> 4 & 1
OPCODE: = opcodebyte[0] & 15
Log. Println (Rsv1,rsv2,rsv3,opcode)
Payloadlenbyte: = Make ([]byte, 1)
This. Conn.read (Payloadlenbyte)
Payloadlen: = Int (payloadlenbyte[0] & 0x7F)
Mask: = payloadlenbyte[0] >> 7
If Payloadlen = 127 {
Extendedbyte: = Make ([]byte, 8)
This. Conn.read (Extendedbyte)
}
Maskingbyte: = Make ([]byte, 4)
If mask = 1 {
This. Conn.read (Maskingbyte)
This. Maskingkey = Maskingbyte
}
Payloaddatabyte: = Make ([]byte, Payloadlen)
This. Conn.read (Payloaddatabyte)
Log. PRINTLN ("Data:", Payloaddatabyte)
Databyte: = Make ([]byte, Payloadlen)
For I: = 0; i < Payloadlen; i++ {
If mask = 1 {
Databyte[i] = payloaddatabyte[i] ^ maskingbyte[i% 4]
} else {
Databyte[i] = Payloaddatabyte[i]
}
}
if FIN = = 1 {
data = Databyte
Return
}
Nextdata, err: = this. Readiframe ()
If Err!= nil {
Return
}
data = append (data, nextdata ...)
Return
}
Func parsehandshake (content string) map[string]string {
Headers: = Make (map[string]string, 10)
Lines: = Strings. Split (content, "\ r \ n")
For _,line: = Range Lines {
If Len (line) >= 0 {
Words: = strings. Split (Line, ":")
If Len (words) = = 2 {
Headers[strings. Trim (Words[0], "")] = strings. Trim (Words[1], "")
}
}
}
return headers
}
Something
PS: Later found that the official also has achieved websocket, but it is not under the PKG, but under the net branch
It is highly recommended to use the official websocket, do not write yourself
https://code.google.com/p/go.net/
Of course, if you implement the agreement, it will be clearer to see the official bag.
I hope this article will help you with your go language program.