go 实现简单websocket帧解析, 接发文本消息

7个月前 0 1 243

Go

这一篇继续上一篇文章的之后, 进行帧包的解析,当然是简洁的实现文本消息, 因为帧包里面的信息很多,就不一一弄了, 有兴趣的可以试试

//定义一个64位的长度整形
var datalength int64
//定义个结构体
type webSocket struct {
    Mask []byte
    Conn net.Conn
}
func WebSocket(conn net.Conn) *webSocket {
    return &webSocket{Conn:conn}
}

Websocket帧格式

0                    1                   2                    3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+------+-+-------------+--------------------------------+
|F|R|R|R|opcode|M| Payload Len |     extended payload length    |
|I|S|S|S|  (4) |A|    (7)      |        (16/63)                 |
|N|V|V|V|      |S|             |     (if payload len = 126/127) |
| |1|2|3|      |K|             |                                |
+-+-+-+-+------+-+-------------+--------------------------------+
|    Extended payload length continued, if payload len == 127   |
+------------------------------+--------------------------------+
|                              | Masking-key, if Mask set To 1  |
+------------------------------+--------------------------------+
|    Masking-key (continued)   |    Payload Data                |
+------------------------------+- - -  - - - - -  - - -  - - - -+
|                    Payload Data continued                     |
+- - - - -  - - - - - - - - - - - - - - -  - - - - - - - - - - -+
|                    Payload Data continued                     |
+----------------------------------------------------------------

下面一一解释包的说明
FIN 长度1位,表示是否是最后一帧,为1则表示最后一帧,为0则表示还有后续帧
RSV 长度三位 默认都是0 如果服务端与客户端没有协商,那么非0则认为是一个错误的帧
opcode 表示帧格式,占4位,格式如下

0x00,表示继续帧
0x01,表示文本帧
0x02,表示二进制帧
0x03-0x07,保留的未定义非控制帧
0x08,连接关闭帧
0x09,表示ping
0xA,表示pong
0xB-0xF,用于保留的控制帧

MASK,1位,定义负载数据是否使用掩码,1为使用掩码,0为不使用掩码

Payload Length,7位,7+16位,7+64位,定义负载数据的长度,以字节为单位。这部分如果为0-125,则负载长度则就是这段定义的长度,如果为126,之后的 Extend payload Length 16位将作为负载长度,如果为127,那么之后的Extend payload Length 64位将作为负载长度。

Masking-key 0 或者 32位,mask位设为0,则该字段缺失(不过协议要求,所有的帧都需要使用mask)

Payload data 负载数据=扩展数据+应用数据

包解析

//读取
func (this *webSocket)readFrame() string {
     //解析第一个字节位
    first := make([]byte, 1)
    this.Conn.Read(first)
    //获取FIN值,0代表数据未结束 1 代表数据结束
    FIN := first[0] >> 7
    RSV1 := first[0] >> 6 & 1
    RSV2 := first[0] >> 5 & 1
    RSV3 := first[0] >> 4 & 1
    log.Println(FIN, RSV1, RSV2, RSV3)
    OPCODE := first[0] & 0xF
    log.Println(OPCODE)
    //解析第二个字节位
    second := make([]byte, 1)
    this.Conn.Read(second)
    //获取MASK值
    MASK := second[0] >> 7
    log.Println(MASK)
    payLength := second[0] & 0x7F
    fmt.Println(int(payLength))
    datalength = int64(payLength)

    //如果payload 长度为126则读取后面的两个字节 数据长度
    if payLength == 126 {
        extendByte := make([]byte, 2)
        this.Conn.Read(extendByte)
        datalength = uint16(binary.BigEndian.Uint16(extendByte))
    }
    //如果payload 长度为127则读取后面的两个字节 数据长度
    if payLength == 127 {
        extendByte := make([]byte, 8)
        this.Conn.Read(extendByte)
        datalength = unit64(binary.BigEndian.Uint64(extendByte))
    }

    // 读取Masking-key
    maskSec := make([]byte, 4)
    if MASK == 1 {
        this.Conn.Read(maskSec)
    }
    //读取数据
    data := make([]byte, datalength)
    this.Conn.Read(data)
    if MASK == 1 {
        var i int64
        for i = 0; i < datalength; i++ {
            data[i] ^= maskSec[i % 4]
        }
    }
    fmt.Println(FIN)
    // 如果FIN 为 1 表示是最后一个数据帧
    if FIN == 1 {
        return string(data)
    }

    getNextData := this.readFrame()

    data = append(data, getNextData...)

    return string(data)
}

//发送包
/**
给客户端发送数据, 也要以帧格式发送, 这里以不做掩码处理, 如果需要就自行改变帧的二进制格式
 */
func (this *webSocket) writeFrame(data []byte) {

    length := len(data)
    buf := make([]byte, 10+length)

    // 数据开始和结束的位置
    payloadStart := 2

    // 数据帧的第一个字节, 不支持分片,且值能发送文本类型数据 二进制格式为 1000 0001
    buf[0] = 0x81
    // 数据帧第二个字节,服务器发送的数据不需要进行掩码处理
    if length < 125 {
        buf[1] = byte(0x00) | byte(length)
    } else if (length > 125 && length < 65536) {
        buf[1] = byte(0x00) | 126
        binary.BigEndian.PutUint16(buf[payloadStart:], uint16(length))
        payloadStart += 2
    } else {
        buf[1] = byte(0x00) | 127
        binary.BigEndian.PutUint64(buf[payloadStart:], uint64(length))
        payloadStart += 8
    }
    // 复制
    copy(buf[payloadStart:], data)
    this.Conn.Write(buf)
}

完整代码

package main

import (
    "net"
    "fmt"
    "log"
    "encoding/binary"
    "strings"
    "crypto/sha1"
    "io"
    "encoding/base64"
)

const (
    WEBSOCKET_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    // 文本数据帧类型
)
var datalength int64

type webSocket struct {
    Mask []byte
    Conn net.Conn
}

func WebSocket(conn net.Conn) *webSocket {
    return &webSocket{Conn:conn}
}
func main() {
    connect()
}


func connect() {
    ln, err := net.Listen("tcp", ":8000")

    if err != nil {
        fmt.Println(err)
    }

    for {
        conn, err := ln.Accept()
        if err != nil {
            fmt.Println(err)
        }

        for {
            handConnect(conn)
        }
    }
}

func handConnect(conn net.Conn) {
    content := make([]byte, 1024)
    n, err := conn.Read(content)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(fmt.Sprintf("读取%d个字节", n))

    header := parseHeaders(string(content))
    fmt.Println(header["Sec-WebSocket-Key"])

    secret := getSecret(header["Sec-WebSocket-Key"])

    response := "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
    response += "Upgrade: websocket\r\n"
    response +="Connection: Upgrade\r\n"
    response +="Sec-WebSocket-Accept: " + secret +"\r\n"
    response += "\r\n"

    conn.Write([]byte(response))

    for {
        ws := WebSocket(conn)
        data := ws.readFrame()
        fmt.Println("data is :", data)

        ws.writeFrame([]byte("i recive a message as you konw"))
    }

}

func (this *webSocket)readFrame() string {
     //解析第一个字节位
    first := make([]byte, 1)
    this.Conn.Read(first)
    //获取FIN值,0代表数据未结束 1 代表数据结束
    FIN := first[0] >> 7
    RSV1 := first[0] >> 6 & 1
    RSV2 := first[0] >> 5 & 1
    RSV3 := first[0] >> 4 & 1
    log.Println(FIN, RSV1, RSV2, RSV3)
    OPCODE := first[0] & 0xF
    log.Println(OPCODE)
    //解析第二个字节位
    second := make([]byte, 1)
    this.Conn.Read(second)
    //获取MASK值
    MASK := second[0] >> 7
    log.Println(MASK)
    payLength := second[0] & 0x7F
    fmt.Println(int(payLength))
    datalength = int64(payLength)

    //如果payload 长度为126则读取后面的两个字节 数据长度
    if payLength == 126 {
        extendByte := make([]byte, 2)
        this.Conn.Read(extendByte)
        datalength = int64(binary.BigEndian.Uint16(extendByte))
    }
    //如果payload 长度为127则读取后面的两个字节 数据长度
    if payLength == 127 {
        extendByte := make([]byte, 8)
        this.Conn.Read(extendByte)
        datalength = int64(binary.BigEndian.Uint16(extendByte))
    }

    // 读取Masking-key
    maskSec := make([]byte, 4)
    if MASK == 1 {
        this.Conn.Read(maskSec)
    }
    //读取数据
    data := make([]byte, datalength)
    this.Conn.Read(data)
    if MASK == 1 {
        var i int64
        for i = 0; i < datalength; i++ {
            data[i] ^= maskSec[i % 4]
        }
    }
    fmt.Println(FIN)
    // 如果FIN 为 1 表示是最后一个数据帧
    if FIN == 1 {
        return string(data)
    }

    getNextData := this.readFrame()

    data = append(data, getNextData...)

    return string(data)
}

/**
给客户端发送数据, 也要以帧格式发送, 这里以不做掩码处理, 如果需要就自行改变帧的二进制格式
 */
func (this *webSocket) writeFrame(data []byte) {

    length := len(data)
    buf := make([]byte, 10+length)

    // 数据开始和结束的位置
    payloadStart := 2

    // 数据帧的第一个字节, 不支持分片,且值能发送文本类型数据 二进制格式为 1000 0001
    buf[0] = 0x81
    // 数据帧第二个字节,服务器发送的数据不需要进行掩码处理
    if length < 125 {
        buf[1] = byte(0x00) | byte(length)
    } else if (length > 125 && length < 65536) {
        buf[1] = byte(0x00) | 126
        binary.BigEndian.PutUint16(buf[payloadStart:], uint16(length))
        payloadStart += 2
    } else {
        buf[1] = byte(0x00) | 127
        binary.BigEndian.PutUint64(buf[payloadStart:], uint64(length))
        payloadStart += 8
    }
    // 复制
    copy(buf[payloadStart:], data)
    this.Conn.Write(buf)
}

func parseHeaders(content string) map[string]string {
    h := strings.Split(content, "\r\n")
    header := make(map[string]string, 0)
    for _, value  := range h {
        v := strings.Split(value, ":")

        if len(v) >= 2 {
            header[strings.Trim(v[0], " ")] = strings.Trim(v[1], " ")
        }
    }

    return  header
}

func getSecret(key string) string{
    key += WEBSOCKET_KEY

    res := sha1.New()
    io.WriteString(res, key)

    return base64.StdEncoding.EncodeToString(res.Sum(nil))
}

试一试吧,前台

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

评论 (1)
  • Cinque Terre

    yanwenwu

    #1 6个月前

    file

njphper@copyright From 2014 to 2019-02-17