Go WebSocket Implementation principle and usage detailed _golang

Source: Internet
Author: User
Tags base64 sha1

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.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.