Websocket は、ハンドシェイク ステージとデータ送信ステージ、つまり HTTP ハンドシェイク二重による TCP 接続
ハンドシェイク ステージ ## に分かれています。
#ハンドシェイク フェーズは通常の HTTPクライアントはメッセージを送信します: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
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
データ送信
Websocket のデータ送信に使用されるプロトコルは次のとおりです:パラメータの具体的な説明:
FIN: 1 ビット、これがメッセージの最後のメッセージ フラグメントであることを示すために使用されます。もちろん、最初のメッセージ フラグメントが最後の A メッセージ フラグメントである場合もあります; RSV1、RSV2、RSV3: 各ビットは 1 ビットです。両者の間で合意されたカスタム プロトコルがない場合、これらのビットの値は0 である場合は、WebSocket 接続を切断する必要があります; オペコード: 4 桁のオペコード、ペイロード データを定義します。不明なオペコードを受信した場合、接続を切断する必要があります。定義されたオペコードは次のとおりです: * %x0 は連続メッセージ フラグメントを表します * %x1 はテキスト メッセージ フラグメントを表します * %x2 はバイナリ メッセージ フラグメントを表します * %x3 ~ 7 は将来の非制御メッセージ フラグメント用に予約されています オペレーション コード * %x8 は接続が閉じられたことを意味します * %x9 はハートビート チェックの ping を意味します * %xA はポン ### を意味しますハートビート チェックの* % xB-F は将来の制御メッセージ フラグメント用に予約されたオペコードです
Mask:1 ビットで、送信データがマスクされるかどうかを定義します。1 に設定されている場合、マスク キーは次でなければなりません。マスキング キー領域では、このビットの値は、クライアントからサーバーに送信されるすべてのメッセージに対して 1 です。
Payload length:バイト単位で表される送信データ: 7 ビット、7 16 ビット、または 7 64 ビット。
Masking-key:0 または 4 バイト、クライアントからサーバーに送信されるデータは、埋め込まれた 32 ビット値によってマスクされます。マスク キーは、マスク ビットが存在する場合にのみ存在します。は 1 に設定されます。
ペイロード データ:(x y) ビット、ペイロード データは拡張データとアプリケーション データの長さの合計です。
拡張データ:#xx ビット。クライアントとサーバーの間に特別な取り決めがない場合、拡張データの長さは常に 0 です。拡張では、拡張の長さを指定する必要があります。データや、長さの計算方法、握手の際の正しい握手の決め方など。拡張データが存在する場合、拡張データはペイロードデータの長さに含まれます。アプリケーション データ:
y ビット、拡張データの後に配置される任意のアプリケーション データ アプリケーション データの長さ = ロード データの長さ - 拡張データの長さ。例
go を使用した具体的な実装例:
Client:html:
<html> <head> <script type="text/javascript" src="./jquery.min.js"></script> </head> <body> <input type="button" id="connect" value="websocket connect" /> <input type="button" id="send" value="websocket send" /> <input type="button" id="close" value="websocket close" /> </body> <script type="text/javascript" src="./websocket.js"></script> </html>
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(); })
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 // 先暂时这么判断 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:这里省略其他的验证 guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // 计算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 101 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)) // 直接读取 } } 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 { // 这里只处理data长度<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 //第一个字节: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 }
go 言語チュートリアル
列に注目してください。以上がGo WebSocket の実装 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。