nats.uno

本章从nats的协议出发, 来阐述如何遵循协议实现一个client

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "sync"
)

type Client struct {
    conn net.Conn
    w    *bufio.Writer
    sync.Mutex
}

func NewClient() *Client {
    return &Client{}
}

func (c *Client) Connect(uri string) error {
    conn, err := net.Dial("tcp", uri)
    if err != nil {
        return err
    }
    c.conn = conn
    c.w = bufio.NewWriter(conn) //
    return nil
}

func (c *Client) Publish(subject string, payload []byte) error {
    c.Lock()
    defer c.Unlock()
    pub := fmt.Sprintf("pub %s %d\r\n", subject, len(payload))
    _, err := c.w.WriteString(pub)
    if nil != err {
        return err
    }
    _, err = c.w.Write(payload)
    if nil != err {
        return err
    }
    _, err = c.w.WriteString("\r\n")
    if nil != err {
        return err
    }
    return c.w.Flush()
}

func (c *Client) Close() {
    c.Lock()
    defer c.Unlock()
    c.conn.Close() // along with the writer
}

func main() {
    nc := NewClient()
    err := nc.Connect(":4222")
    if err != nil {
        log.Fatalf("Error: %s", err)
    }
    defer nc.Close()

    err = nc.Publish("hello", []byte("world"))
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
    log.Println("done")
}

原文链接:
https://www.oreilly.com/ideas/building-messaging-in-go-network-clients

附上简单read/write实现.
read需要在单独的goroutine中完成.

package main

import (
    "bufio"
    "errors"
    "fmt"
    "io"
    "log"
    "net"
    "strings"
    "sync"
)

type MsgHandler func(subject string, reply string, sid int)

type ClientImpl struct{}

func (c *ClientImpl) processMsg(subj string, reply string, sid int, payload []byte) {
    panic(errors.New("you shall override this method"))
}

func (c *ClientImpl) processInfo(line string) {}
func (c *ClientImpl) processPing()            {}
func (c *ClientImpl) processPong()            {}
func (c *ClientImpl) processErr(msg string)   {}

// true, we shall continue
// false: no. stop reading routine.
func (c *ClientImpl) processOpErr(err error) bool {
    return false
}

type client interface {
    processInfo(line string)
    processMsg(subj string, reply string, sid int, payload []byte)
    processPing()
    processPong()
    processErr(msg string)
    processOpErr(err error) bool
}

func readLoop(c client, conn net.Conn, whenDone func()) {
    defer whenDone()
    r := bufio.NewReader(conn)
    for {
        line, err := r.ReadString('\n')
        if err != nil {
            if !c.processOpErr(err) {
                return
            }
        }

        line = strings.TrimSuffix(line, "\r\n") // if not ends with \r\n, it will remain unchanged.
        args := strings.SplitN(line, " ", 2)
        if len(args) < 1 {
            c.processOpErr(errors.New("Error: malformed control line"))
            return
        }
        op := strings.TrimSpace(args[0])
        switch op {
        case "MSG":
            //fmt.Printf("Msg: <%v>\n", line)
            var subject, reply string
            var sid, size int
            n := strings.Count(args[1], " ")
            switch n {
            case 2:
                // No reply is expected in this case
                // (message is just broadcast)
                // MSG foo 1 3\r\n
                // bar\r\n
                _, err := fmt.Sscanf(args[1], "%s %d %d",
                    &subject, &sid, &size)
                if err != nil {
                    c.processOpErr(err)
                    return
                }
                // ok, good to go
            case 3:
                // Reply is expected
                // MSG foo 1 bar 4\r\n
                // quux\r\n
                _, err := fmt.Sscanf(args[1], "%s %d %s %d",
                    &subject, &sid, &reply, &size)
                if err != nil {
                    c.processOpErr(err)
                    return
                }
            default:
                c.processOpErr(errors.New("nats: bad control line"))
                return
            }

            payload := make([]byte, size)
            _, err = io.ReadFull(r, payload)
            if err != nil {
                c.processOpErr(err)
                return
            }
            // In the two-argument case, the reply below is null.
            c.processMsg(subject, reply, sid, payload)
        case "INFO":
            c.processInfo(line)
        case "PING":
            c.processPing()
        case "PONG":
            c.processPong()
        case "+OK":
            // Do nothing
        case "-ERR":
            c.processOpErr(errors.New(args[1]))
        }
    }
}

/
//
/

type Client struct {
    ClientImpl // the default implementation

    conn net.Conn
    w    *bufio.Writer

    seqID int

    sync.Mutex
    cbs       map[int]MsgHandler
    waitGroup sync.WaitGroup
}

func NewClient() *Client {
    return &Client{
        seqID: 1000,
        cbs:   make(map[int]MsgHandler),
    }
}

func (c *Client) Connect(uri string) error {
    conn, err := net.Dial("tcp", uri)
    if err != nil {
        return err
    }
    c.conn = conn
    c.w = bufio.NewWriter(conn) //
    return nil
}

func (c *Client) PublishString(subject string, content string) error {
    return c.Publish(subject, []byte(content))
}

func (c *Client) Publish(subject string, payload []byte) error {
    c.Lock()
    defer c.Unlock()
    pub := fmt.Sprintf("pub %s %d\r\n", subject, len(payload))
    _, err := c.w.WriteString(pub)
    if nil != err {
        return err
    }
    _, err = c.w.Write(payload)
    if nil != err {
        return err
    }
    _, err = c.w.WriteString("\r\n")
    if nil != err {
        return err
    }
    return c.w.Flush()
}

func (c *Client) Subscribe(subject string, cb MsgHandler) error {
    c.Lock()
    defer c.Unlock()

    subjectID := c.seqID //use once
    c.seqID++
    sub := fmt.Sprintf("sub %s %d\r\n", subject, subjectID)
    _, err := c.w.WriteString(sub)
    if nil != err {
        return err
    }
    c.cbs[subjectID] = cb
    return c.w.Flush()
}

//override
func (c *Client) processMsg(subj string, reply string, sid int, payload []byte) {
    cb, ok := c.cbs[sid]
    if !ok {
        log.Printf("missing callback for sid:<%v>", sid)
    }
    cb(subj, reply, sid)
}

func (c *Client) StartRead() {
    c.waitGroup.Add(1)
    go readLoop(c, c.conn, func() {
        c.waitGroup.Done()
    })
}

func (c *Client) Join() {
    c.waitGroup.Wait()
    // log.Printf("join done")
}

func (c *Client) Close() {
    c.Lock()
    defer c.Unlock()
    c.conn.Close() // along with the writer
}

func main() {
    nc := NewClient()
    err := nc.Connect("myvnc:4222")
    if err != nil {
        log.Fatalf("Error: %s", err)
    }

    // err = nc.Publish("hello", []byte("world"))
    // if err != nil {
    //  log.Fatalf("Error: %v", err)
    // }

    const sendCount = 10
    left := sendCount

    doneCh := make(chan struct{})

    recvID := 0
    nc.Subscribe("hello", func(subject string, reply string, sid int) {
        fmt.Printf("recv %v: %v <%v>\n", recvID, subject, reply)
        recvID++
        left--
        if left <= 0 {
            close(doneCh)
        }
    })
    nc.StartRead()
    for i := sendCount; i > 0; i-- {
        if err := nc.PublishString("hello", "world"); err != nil {
            log.Fatalf("Error: %v", err)
        }
    }
    <-doneCh
    nc.Close()
    nc.Join()
    log.Println("done")
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值