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=Sec-WebSocket-Accept의 계산 방법은 다음과 같습니다.
base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))Sec-WebSocket-Accept가 잘못 계산되면 브라우저에 다음 메시지가 표시됩니다. #🎜 🎜 #
Sec-WebSocket-Accept 불일치
반환이 성공하면 Websocket은 onopen 이벤트를 다시 호출합니다
Data 전송#🎜🎜 #웹소켓의 데이터 전송에 사용되는 프로토콜은 다음과 같습니다. #매개변수에 대한 자세한 설명:
FIN: 1비트, 메시지의 마지막 메시지 조각임을 나타내는 데 사용됩니다. 물론 첫 번째 메시지 조각이 마지막 메시지일 수도 있습니다. 조각;
RSV1, RSV2, RSV3: 두 당사자 간에 맞춤 계약이 없는 경우 이 비트의 값은 0이어야 하며, 그렇지 않으면 WebSocket 연결이 끊어져야 합니다. 🎜🎜##🎜 🎜#Opcode: 4자리 opcode, 페이로드 데이터를 정의합니다. 알 수 없는 opcode가 수신되면 연결을 끊어야 합니다. 정의된 opcode는 다음과 같습니다.
* %x0 연속 메시지 조각* %x1은 텍스트 메시지 조각을 나타냅니다.
* %x2는 바이너리 메시지 조각을 나타냅니다. * %x3-7은 다음과 같습니다. 메시지 조각용으로 예약된 향후 비제어 작업 코드 * %x8은 연결이 닫혔음을 의미합니다. * %x9는 하트비트 확인 핑을 의미합니다 * % xA는 하트비트 확인을 의미합니다. pong * %xB-F 향후 제어 메시지 조각을 위한 예약된 opcodeMask:
1 비트, 전송된 데이터를 정의 마스크가 있습니까? 1로 설정되면 클라이언트가 서버로 보내는 모든 메시지에 대해 마스크 키가 1이 됩니다. #🎜🎜 #페이로드 길이:전송된 데이터의 길이는 바이트로 표시됩니다: 7비트, 7+16비트 또는 7+64비트.
Masking-key:0 또는 4바이트, 클라이언트가 서버로 보낸 데이터는 내장된 32비트 값으로 마스크됩니다. 마스크 키는 다음 경우에만 존재합니다. 마스크 비트는 1로 설정됩니다.
페이로드 데이터:(x+y)비트, 페이로드 데이터는 확장 데이터와 애플리케이션 데이터의 길이를 합한 것입니다.
확장 데이터:x비트, 클라이언트와 서버 사이에 특별한 합의가 없는 경우 확장 데이터의 길이는 항상 0이며 모든 확장은 다음을 지정해야 합니다. 확장자 데이터의 길이 또는 길이를 계산하는 방법, 핸드셰이크 시 올바른 핸드셰이크를 결정하는 방법. 확장 데이터가 있는 경우 페이로드 데이터의 길이에 확장 데이터가 포함됩니다.
응용 프로그램 데이터:y 비트, 확장 데이터 뒤에 배치되는 모든 응용 프로그램 데이터, 응용 프로그램 데이터의 길이 = 로드 데이터의 길이 - 로드 데이터의 길이 확장된 데이터.
Instance
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>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(); })Server:
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 웹소켓 구현(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!