pusher websocket client,基于golang

来源:互联网 发布:广州三箭汽枪淘宝网. 编辑:程序博客网 时间:2024/06/06 03:05

虚拟货币国内叫停,之前公司使用的是okcoin提供的交易数据,现在okcoin不更新了,需要更换数据源,bitstamp是一家国外的虚拟货币数据提供商,他们提供数据的方式是基于websocket的pusher推送。我作为client,自然要以pusher client的身份去订阅。

这个pusher和普通的websocket虽然本质一样,但是请求的方式不一样,正常的websocket请求是直接有一个ip和port,然后去dial建立连接。pusher的话是拿着一个key去连接pusher服务器,相当于是pusher是一个中间服务,为双方提供数据交互。

pusher官方提供的client没有go的实现,所以这块只能自己实现了,实现方式如下:

main.go

package mainimport (    "pusher")func main() {    global.PusherCli= &pusher.PusherClient{        Key: "de504dc5763aeef9ff52",     //数据提供商给你的key        PushUrl:"ws://ws.pusherapp.com:80/app/%s?protocol=7", //pusher服务的url    }    errpusher := global.PusherCli.New()    if errpusher != nil {        panic(errpusher)    }  ch:=make(chan int,1)  <-ch}



connection.go

package pusherimport (    "fmt"    "time"    "golang.org/x/net/websocket")type PusherClient struct{    Key string    PushUrl string    conn     *websocket.Conn    channels []*Channel}var PongMessgage = make(chan interface{} , 1)var IfHasMessgage = make(chan interface{} , 1)var goroutineStop bool= falsevar countReconnect intvar countGoroutine intfunc (p *PusherClient) New() (  er error) {Reconnect_Loop:    log.Println("connecting")    ws, err := websocket.Dial(fmt.Sprintf(p.PushUrl, p.Key), "","http://localhost/") //websocket拨号连接    if err != nil {         log.Println(err)        time.Sleep(time.Second * 5)        countReconnect+=1        if countReconnect>1000{            log.Println("Reconnect too much") //重连次数过多        }        goto Reconnect_Loop    }    log.Println("connected")    countReconnect=0    countGoroutine+=1    if countGoroutine>1000{  //协程数过多        log.Println("Goroutine too much")    }    p.conn=ws    p.channels=[]*Channel{}    goroutineStop=false    go p.poll_pong()    go p.ping()    var SubChannel []*Channel    btcusd := p.Channel("live_trades")         //订阅频道     ethusd := p.Channel("live_trades_ethusd")  //订阅频道    SubChannel=append(SubChannel,btcusd)    SubChannel=append(SubChannel,ethusd)    go Handler(SubChannel)     // handler协程处理接受到数据后的业务逻辑    return nil}func (p *PusherClient) ping() {      //心跳机制    ping := NewPingMessage()    for {        select {        case <- IfHasMessgage:        case <- time.After(120*time.Second): //120秒没有接受到消息则发送ping包            {err:=websocket.JSON.Send(p.conn, ping)            log.Println(ping)            //fmt.Println(ping)            if err!=nil{                log.Println(err)                return            }                select {                case <- PongMessgage:                case <- time.After(120*time.Second): //发送ping包后120秒没有收到pong包,则重连                    {go p.reconnect()                        return}                }            }        }    }}func (p *PusherClient) reconnect(){  //重连    goroutineStop=true    err:=p.Disconnect()    if err!=nil{        log.Println(err)    }    time.Sleep(60*time.Second)    p.New()}func (p *PusherClient) poll_pong() {    for {        var msg Message        err := websocket.JSON.Receive(p.conn, &msg)//阻塞        if err != nil {            log.Println(err)            return        }        if msg.Event == "pusher:pong" {            //fmt.Println(msg)            log.Println(msg)            PongMessgage<-msg        }else if  msg.Event == "pusher:ping"{ //如果接受到server的ping包,则回应pong包            err:=websocket.JSON.Send(p.conn, NewPongMessage())            if err!=nil{                log.Println(err)                return            }        }else{            IfHasMessgage<-msg            p.processMessage(&msg) //处理消息        }    }}func (p *PusherClient) processMessage(msg *Message) {    for _, channel := range p.channels {        if channel.Name == msg.Channel {            channel.processMessage(msg)   //如果接收到的消息的频道和我订阅的频道相等,则继续处理        }    }}func (p *PusherClient) Disconnect() error {    return p.conn.Close()}func (p *PusherClient) Channel(name string) *Channel {    for _, channel := range p.channels {        if channel.Name == name {            return channel        }    }    channel := NewChannel(name)    p.channels = append(p.channels, channel)    websocket.JSON.Send(p.conn, NewSubscribeMessage(name)) //把订阅的频道发送给pusher    return channel}



message.go

package pushertype Message struct {    Event   string      `json:"event"`    Channel string      `json:"channel"`    Data    interface{} `json:"data"`}func NewSubscribeMessage(channel string) *Message {    return &Message{"pusher:subscribe", "", map[string]string{"channel": channel}}}func NewPongMessage() *Message {    return &Message{"pusher:pong", "", nil}}func NewPingMessage() *Message {    return &Message{"pusher:ping", "", nil}}



channel.go

package pushertype Channel struct {    Name      string    dataChans map[string][]chan interface{}}func NewChannel(name string) *Channel {    return &Channel{name, make(map[string][]chan interface{})}}func (c *Channel) Bind(event string) chan interface{} {   //将事件和频道进行绑定    dataChan := make(chan interface{},10)     //处理队列,缓冲10个消息    c.dataChans[event] = append(c.dataChans[event],dataChan)    return dataChan}func (c *Channel) processMessage(msg *Message) {     value,ok:=c.dataChans[msg.Event]     if ok{         dataChan:=value[0]         dataChan <- msg.Data   //将消息压入管道中    }}



handler.go

package pusherfunc Handler(SubChannels []*Channel){    subChan1:= SubChannels[0].Bind("trade") //绑定事件    subchan2:= SubChannels[1].Bind("trade")    for {        if goroutineStop==true{            return        }        select {        case msg,ok:=<-subChan1:  //有数据则处理            if ok{                // do something            }        case msg,ok:=<-subchan2:  //有数据则处理            if ok{                // do something            }        case <-time.After(5*time.Second):            continue        }    }}