Golang-扩展websocket客户端以实现与不同服务器的多个连接

时间:2019-01-31 21:19:08

标签: sockets go scaling

我有一个websocket客户端。实际上,它比下面显示的基本代码复杂得多。 我现在需要扩展此客户端代码,以打开与多个服务器的连接。最终,从服务器收到消息时需要执行的任务是相同的。 处理此问题的最佳方法是什么? 正如我上面所说的,接收消息时执行的实际代码比示例中显示的复杂得多。

package main

import (
        "flag"
        "log"
        "net/url"
        "os"
        "os/signal"
        "time"

        "github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:1234", "http service address")

func main() {
        flag.Parse()
        log.SetFlags(0)

        interrupt := make(chan os.Signal, 1)
        signal.Notify(interrupt, os.Interrupt)

        // u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
        u := url.URL{Scheme: "ws", Host: *addr, Path: "/"}
        log.Printf("connecting to %s", u.String())

        c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
        if err != nil {
                log.Fatal("dial:", err)
        }
        defer c.Close()

        done := make(chan struct{})

        go func() {
                defer close(done)
                for {
                        _, message, err := c.ReadMessage()
                        if err != nil {
                                log.Println("read:", err)
                                return
                        }
                        log.Printf("recv: %s", message)
                }
        }()

        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()

        for {
                select {
                case <-done:
                        return
                case t := <-ticker.C:
                        err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
                        if err != nil {
                                log.Println("write:", err)
                                return
                        }
                case <-interrupt:
                        log.Println("interrupt")

                        // Cleanly close the connection by sending a close message and then
                        // waiting (with timeout) for the server to close the connection.
                        err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
                        if err != nil {
                                log.Println("write close:", err)
                                return
                        }
                        select {
                        case <-done:
                        case <-time.After(time.Second):
                        }
                        return
                }
        }
}

2 个答案:

答案 0 :(得分:0)

与每个不同服务器的通信是否完全独立于其他服务器?如果是的话,我会像这样:

  • main 中创建一个具有取消功能的context
  • 在主目录中创建一个waitgroup来跟踪启动的goroutine
  • 对于每台服务器,将其添加到等待组,并通过传递上下文和等待组引用的主函数启动一个新的goroutine
  • main 进入for / select循环,侦听信号,如果到达,则调用cancelfunc并等待waitgroup。
  • main 还可以侦听goroutine中的结果chan,并且可能在goroutine不应该直接执行结果的情况下打印结果本身。
  • 正如我们所说,
  • 每个 goroutine 都有对wg,上下文以及可能返回结果的chan的引用。现在,如果goroutine仅必须执行一项和一项操作,或者需要执行一系列操作,则该方法就产生了分歧。第一种方法
  • 如果仅要做一件事,我们将按照描述为here的方法进行操作(请注意,如果不同步,他会启动一个新的goroutine来执行DoSomething()步骤,该步骤将返回频道上的结果) 这使它能够随时接受取消信号。由您确定要成为非阻塞对象的程度以及要对提示信号做出响应的提示的时间。将上下文关联传递给goroutine的好处是您可以调用启用的上下文大多数库函数的版本。例如,如果您希望您的拨号盘具有1分钟的超时时间,则可以通过传递的超时值创建一个新的上下文,然后使用该值创建DialContext。这样一来,拨号既可以从超时停止,也可以停止调用父级(您在主目录中创建的一个)上下文的cancelfunc。
  • 如果需要做更多的事情,我通常更喜欢用goroutine做一件事,让它调用新的goroutine并执行下一步(将所有引用向下传递到管道中)并退出。

这种方法可以很好地扩展取消功能,并且可以在任何步骤停止管道,并且可以轻松地为需要花费很长时间的步骤提供脱碱支持上下文。

答案 1 :(得分:0)

修改中断处理以在中断时关闭通道。这样,多个goroutine可以通过等待通道关闭来等待事件。

shutdown := make(chan struct{})
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
go func() {
    <-interrupt
    log.Println("interrupt")
    close(shutdown)
}()

将每个连接的代码移至函数。该代码是对问题的复制和粘贴,但有两个更改:中断通道替换为关闭通道;函数完成后,该函数会通知sync.WaitGroup。

func connect(u string, shutdown chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    log.Printf("connecting to %s", u)
    c, _, err := websocket.DefaultDialer.Dial(u, nil)
    if err != nil {
        log.Fatal("dial:", err)
    }
    defer c.Close()

    done := make(chan struct{})

    go func() {
        defer close(done)
        for {
            _, message, err := c.ReadMessage()
            if err != nil {
                log.Println("read:", err)
                return
            }
            log.Printf("recv: %s", message)
        }
    }()

    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
            if err != nil {
                log.Println("write:", err)
                return
            }
        case <-shutdown:
            // Cleanly close the connection by sending a close message and then
            // waiting (with timeout) for the server to close the connection.
            err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
            if err != nil {
                log.Println("write close:", err)
                return
            }
            select {
            case <-done:
            case <-time.After(time.Second):
            }
            return
        }
    }
}

main()中声明一个sync.WaitGroup。对于您要连接的每个websocket端点,增加WaitGroup并启动goroutine来连接该端点。启动goroutine后,请在WaitGroup中等待goroutine完成。

var wg sync.WaitGroup
for _, u := range endpoints { // endpoints is []string 
                              // where elements are URLs 
                              // of endpoints to connect to.
    wg.Add(1)
    go connect(u, shutdown, &wg)
}
wg.Wait()

上面的代码进行了编辑,以使其能够与Gorilla的echo示例服务器一起运行,是posted on the playground