diff --git a/client.go b/client.go index be7be92..2d6ebde 100644 --- a/client.go +++ b/client.go @@ -31,7 +31,11 @@ func Dial(addr string, op ...client.Option) (*Client, error) { w: wait.New(time.Second * 2), } - err = cli.Connect() + err = cli.connect() + if err != nil { + c.Close() + return nil, err + } return cli, err } @@ -55,7 +59,7 @@ type Client struct { msgID uint32 } -func (this *Client) SendFrame(f protocol.Frame) (any, error) { +func (this *Client) SendFrame(f *protocol.Frame) (any, error) { this.msgID++ f.MsgID = this.msgID if _, err := this.c.Write(f.Bytes()); err != nil { @@ -79,8 +83,8 @@ func (this *Client) Close() error { return this.c.Close() } -func (this *Client) Connect() error { - f := protocol.NewConnect() +func (this *Client) connect() error { + f := protocol.MConnect.Frame() _, err := this.Write(f.Bytes()) return err } @@ -91,7 +95,7 @@ func (this *Client) GetSecurityList() (*protocol.SecurityListResp, error) { f := protocol.Frame{ Control: 0x01, - Type: protocol.Connect, + Type: protocol.TypeConnect, Data: nil, } _ = f @@ -105,3 +109,16 @@ func (this *Client) GetSecurityList() (*protocol.SecurityListResp, error) { return nil, err } + +// GetSecurityQuotes 获取盘口五档报价 +func (this *Client) GetSecurityQuotes(m map[protocol.Exchange]string) (*protocol.SecurityQuotesResp, error) { + f, err := protocol.MSecurityQuote.Frame(m) + if err != nil { + return nil, err + } + result, err := this.SendFrame(f) + if err != nil { + return nil, err + } + return result.(*protocol.SecurityQuotesResp), nil +} diff --git a/protocol/const.go b/protocol/const.go index e778f8d..22be980 100644 --- a/protocol/const.go +++ b/protocol/const.go @@ -7,6 +7,7 @@ func (this Exchange) Uint8() uint8 { return uint8(this) } const ( ExchangeSH Exchange = iota //上海交易所 ExchangeSZ //深圳交易所 + ExchangeBJ //北京交易所 ) const ( @@ -14,9 +15,9 @@ const ( ) const ( - Connect = 0x000d //建立连接 - Handshake = 0xdb0f //握手 - SecurityQuote = 0x053e // 行情信息 + TypeConnect = 0x000d //建立连接 + TypeHandshake = 0xdb0f //握手 + TypeSecurityQuote = 0x053e // 行情信息 ) const ( diff --git a/protocol/frame.go b/protocol/frame.go index 443340e..e536354 100644 --- a/protocol/frame.go +++ b/protocol/frame.go @@ -54,9 +54,9 @@ func (this *Frame) Bytes() g.Bytes { type Response struct { Prefix uint32 //未知,猜测是帧头 - Control uint8 //未知,猜测是响应的控制码 + I2 uint8 //未知,猜测是响应的控制码 MsgID uint32 //消息ID - I3 uint8 //未知,猜测是响应的控制码 + Control uint8 //未知,猜测是响应的控制码 Type uint16 //响应类型,对应请求类型,如建立连接,请求分时数据等 ZipLength uint16 //数据长度 Length uint16 //未压缩长度 @@ -73,9 +73,9 @@ func Decode(bs []byte) (*Response, error) { } resp := &Response{ Prefix: Uint32(bs[:4]), - Control: bs[4], + I2: bs[4], MsgID: Uint32(bs[5:9]), - I3: bs[9], + Control: bs[9], Type: Uint16(bs[10:12]), ZipLength: Uint16(bs[12:14]), Length: Uint16(bs[14:16]), @@ -86,15 +86,17 @@ func Decode(bs []byte) (*Response, error) { return nil, fmt.Errorf("压缩数据长度不匹配,预期%d,得到%d", resp.ZipLength+16, len(bs)) } - r, err := zlib.NewReader(bytes.NewReader(resp.Data)) - if err != nil { - return nil, err - } - defer r.Close() - - resp.Data, err = io.ReadAll(r) - if err != nil { - return nil, err + //进行数据解压 + if resp.ZipLength != resp.Length { + r, err := zlib.NewReader(bytes.NewReader(resp.Data)) + if err != nil { + return nil, err + } + defer r.Close() + resp.Data, err = io.ReadAll(r) + if err != nil { + return nil, err + } } if int(resp.Length) != len(resp.Data) { diff --git a/protocol/frame_test.go b/protocol/frame_test.go index e6fd6fe..8ab3379 100644 --- a/protocol/frame_test.go +++ b/protocol/frame_test.go @@ -9,7 +9,7 @@ func TestFrame_Bytes(t *testing.T) { f := Frame{ MsgID: 1, Control: 1, - Type: Connect, + Type: TypeConnect, Data: []byte{0x01}, } hex := f.Bytes().HEX() @@ -47,7 +47,7 @@ func TestDecode(t *testing.T) { t.Log(string(UTF8ToGBK(resp.Data[68:]))) - t.Log(DecodeConnect(resp.Data)) + t.Log(MConnect.Decode(resp.Data)) //result, err := DecodeSecurityList(resp.Data) //if err != nil { diff --git a/protocol/model.go b/protocol/model.go index 5b4ee6f..b4b78c4 100644 --- a/protocol/model.go +++ b/protocol/model.go @@ -1,12 +1,31 @@ package protocol -import "errors" +import ( + "errors" + bytes2 "github.com/injoyai/base/bytes" +) + +var ( + MConnect = connect{} + MSecurityQuote = securityQuote{} + SecurityList = securityList{} +) type ConnectResp struct { Info string } -func DecodeConnect(bs []byte) (*ConnectResp, error) { +type connect struct{} + +func (connect) Frame() *Frame { + return &Frame{ + Control: Control, + Type: TypeConnect, + Data: []byte{0x01}, + } +} + +func (connect) Decode(bs []byte) (*ConnectResp, error) { if len(bs) < 68 { return nil, errors.New("数据长度不足") } @@ -14,6 +33,12 @@ func DecodeConnect(bs []byte) (*ConnectResp, error) { return &ConnectResp{Info: string(UTF8ToGBK(bs[68:]))}, nil } +/* + + + + */ + type SecurityListResp struct { Count uint16 List []*Security @@ -27,7 +52,17 @@ type Security struct { PreClose float64 } -func DecodeSecurityList(bs []byte) (*SecurityListResp, error) { +type securityList struct{} + +func (securityList) Frame() *Frame { + return &Frame{ + Control: 0x01, + Type: TypeConnect, + Data: nil, + } +} + +func (securityList) Decode(bs []byte) (*SecurityListResp, error) { if len(bs) < 2 { return nil, errors.New("数据长度不足") @@ -41,18 +76,75 @@ func DecodeSecurityList(bs []byte) (*SecurityListResp, error) { } -func NewConnect() *Frame { - return &Frame{ - Control: Control, - Type: Connect, - Data: []byte{0x01}, - } +/* + + + + */ + +type SecurityQuotesResp []*SecurityQuote + +type PriceLevel struct { + Price float64 + Vol int } -func NewSecurityQuotes(m map[Exchange]string) (*Frame, error) { +type SecurityQuote struct { + Market uint8 // 市场 + Code string // 代码 + Active1 uint16 // 活跃度 + Price float64 // 现价 + LastClose float64 // 昨收 + Open float64 // 开盘 + High float64 // 最高 + Low float64 // 最低 + ServerTime string // 时间 + ReversedBytes0 int // 保留(时间 ServerTime) + ReversedBytes1 int // 保留 + Vol int // 总量 + CurVol int // 现量 + Amount float64 // 总金额 + SVol int // 内盘 + BVol int // 外盘 + ReversedBytes2 int // 保留 + ReversedBytes3 int // 保留 + BidLevels []PriceLevel + AskLevels []PriceLevel + Bid1 float64 + Ask1 float64 + BidVol1 int + AskVol1 int + Bid2 float64 + Ask2 float64 + BidVol2 int + AskVol2 int + Bid3 float64 + Ask3 float64 + BidVol3 int + AskVol3 int + Bid4 float64 + Ask4 float64 + BidVol4 int + AskVol4 int + Bid5 float64 + Ask5 float64 + BidVol5 int + AskVol5 int + ReversedBytes4 uint16 // 保留 + ReversedBytes5 int // 保留 + ReversedBytes6 int // 保留 + ReversedBytes7 int // 保留 + ReversedBytes8 int // 保留 + Rate float64 // 涨速 + Active2 uint16 // 活跃度 +} + +type securityQuote struct{} + +func (this securityQuote) Frame(m map[Exchange]string) (*Frame, error) { f := &Frame{ Control: Control, - Type: SecurityQuote, + Type: TypeSecurityQuote, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, } @@ -68,3 +160,28 @@ func NewSecurityQuotes(m map[Exchange]string) (*Frame, error) { return f, nil } + +/* +Decode +b1cb74000c02000000003e05af00af000136020000303030303031320bb2124c56105987e6d10cf212b78fa801ae01293dc54e8bd740acb8670086ca1e0001af36ba0c4102b467b6054203a68a0184094304891992114405862685108d0100000000e8ff320b0136303030303859098005464502468defd10cc005bed2668e05be15804d8ba12cb3b13a0083c3034100badc029d014201bc990384f70443029da503b7af074403a6e501b9db044504a6e2028dd5048d050000000000005909 +*/ +func (this securityQuote) Decode(bs []byte) (*SecurityQuotesResp, error) { + + //前2字节是什么? + bs = bs[2:] + + number := Uint16(bs[:2]) + bs = bs[2:] + + for i := uint16(0); i < number; i++ { + sec := SecurityQuote{ + Market: bs[0], + Code: string(UTF8ToGBK(bytes2.Reverse(bs[1:7]))), + Active1: Uint16(bs[7:9]), + } + _ = sec + + } + + return nil, nil +} diff --git a/protocol/model_test.go b/protocol/model_test.go index 31a4306..1a9474f 100644 --- a/protocol/model_test.go +++ b/protocol/model_test.go @@ -1,6 +1,7 @@ package protocol import ( + "encoding/hex" "testing" ) @@ -9,7 +10,7 @@ import ( 0c02000000011a001a003e05050000000000000002000030303030303101363030303038 */ func TestNewSecurityQuotes(t *testing.T) { - f, err := NewSecurityQuotes(map[Exchange]string{ + f, err := MSecurityQuote.Frame(map[Exchange]string{ ExchangeSH: "000001", ExchangeSZ: "600008", }) @@ -19,3 +20,20 @@ func TestNewSecurityQuotes(t *testing.T) { } t.Log(f.Bytes().HEX()) } + +func Test_securityQuote_Decode(t *testing.T) { + s := "b1cb74000c02000000003e05af00af000136020000303030303031320bb2124c56105987e6d10cf212b78fa801ae01293dc54e8bd740acb8670086ca1e0001af36ba0c4102b467b6054203a68a0184094304891992114405862685108d0100000000e8ff320b0136303030303859098005464502468defd10cc005bed2668e05be15804d8ba12cb3b13a0083c3034100badc029d014201bc990384f70443029da503b7af074403a6e501b9db044504a6e2028dd5048d050000000000005909" + bs, err := hex.DecodeString(s) + if err != nil { + t.Error(err) + return + } + f, err := Decode(bs) + if err != nil { + t.Error(err) + return + } + t.Log(hex.EncodeToString(f.Data)) + //SecurityQuote.Decode(f.Data) + +} diff --git a/protocol/unit.go b/protocol/unit.go index f22b473..b894718 100644 --- a/protocol/unit.go +++ b/protocol/unit.go @@ -9,6 +9,10 @@ import ( "io" ) +func String(bs []byte) string { + return string(bytes2.Reverse(bs)) +} + func Bytes(n any) []byte { return bytes2.Reverse(conv.Bytes(n)) } @@ -27,3 +31,42 @@ func UTF8ToGBK(text []byte) []byte { content, _ := io.ReadAll(decoder) return bytes.ReplaceAll(content, []byte{0x00}, []byte{}) } + +func getprice(b []byte, pos *int) int { + /* + 0x7f与常量做与运算实质是保留常量(转换为二进制形式)的后7位数,既取值区间为[0,127] + 0x3f与常量做与运算实质是保留常量(转换为二进制形式)的后6位数,既取值区间为[0,63] + + 0x80 1000 0000 + 0x7f 0111 1111 + 0x40 100 0000 + 0x3f 011 1111 + */ + posByte := 6 + bData := b[*pos] + data := int(bData & 0x3f) + bSign := false + if (bData & 0x40) > 0 { + bSign = true + } + + if (bData & 0x80) > 0 { + for { + *pos += 1 + bData = b[*pos] + data += (int(bData&0x7f) << posByte) + + posByte += 7 + + if (bData & 0x80) <= 0 { + break + } + } + } + *pos++ + + if bSign { + data = -data + } + return data +}