• 聊天室


    聊天室


    聊天室应用程序示例如下:

    • 使用channel来实现一个聊天室(pub-sub模式),俗称的发布-订阅模式
    • 使用Comet和Websockets

    应用程序的文件结构如下:

    复制代码
    chat/app/
        chatroom           # Chat room routines
            chatroom.go
    
        controllers
            app.go         # The welcome screen, allowing user to pick a technology
            refresh.go     # Handlers for the "Active Refresh" chat demo
            longpolling.go # Handlers for the "Long polling" ("Comet") chat demo
            websocket.go   # Handlers for the "Websocket" chat demo
    
        views
            ...            # HTML and Javascript
    复制代码

    Browse the code on Github

    首先我们来看一下这个聊天室是怎么实现的,chatroom.go.

    聊天室作为一个独立的go-routine运行, 如下所示:

    func init() {
        go chatroom()
    }

    chatroom() 函数简单的在3个channel中选择并执行响应的action

    复制代码
    var (
        // Send a channel here to get room events back.  It will send the entire
        // archive initially, and then new messages as they come in.
        subscribe = make(chan (chan<- Subscription), 10)
        // Send a channel here to unsubscribe.
        unsubscribe = make(chan (<-chan Event), 10)
        // Send events here to publish them.
        publish = make(chan Event, 10)
    )
    
    func chatroom() {
        archive := list.New()
        subscribers := list.New()
    
        for {
            select {
            case ch := <-subscribe:
                // Add subscriber to list and send back subscriber channel + chat log.
            case event := <-publish:
                // Send event to all subscribers and add to chat log.
            case unsub := <-unsubscribe:
                // Remove subscriber from subscriber list.
            }
        }
    }
    复制代码

    我们来分别看一下每一个都是怎么实现的。

    Subscribe

    复制代码
    case ch := <-subscribe:
            var events []Event
            for e := archive.Front(); e != nil; e = e.Next() {
                events = append(events, e.Value.(Event))
            }
            subscriber := make(chan Event, 10)
            subscribers.PushBack(subscriber)
            ch <- Subscription{events, subscriber}
    复制代码

    一个订阅有两个属性:

    • 聊天日志
    • 一个订阅者能在上面监听并获得新信息的channel

    Publish

    复制代码
        case event := <-publish:
            for ch := subscribers.Front(); ch != nil; ch = ch.Next() {
                ch.Value.(chan Event) <- event
            }
            if archive.Len() >= archiveSize {
                archive.Remove(archive.Front())
            }
            archive.PushBack(event)
    复制代码

    发布的event一个一个发送给订阅者的channel,然后event被添加到archive,archive里面的数量大于10,前面的会被移出。

    Unsubscribe

    case unsub := <-unsubscribe:
            for ch := subscribers.Front(); ch != nil; ch = ch.Next() {
                if ch.Value.(chan Event) == unsub {
                    subscribers.Remove(ch)
                }
            }

    订阅者channel在list中被移除。

    Handlers

    现在你知道了聊天室是怎么运行的,我们可以看一看handler是怎么使用不同的技术的。

    主动刷新

    主动刷新聊天室通过javascript每隔5秒刷新页面来从服务器获取新信息:

    复制代码
    // Scroll the messages panel to the end
      var scrollDown = function() {
        $('#thread').scrollTo('max')
      }
    
      // Reload the whole messages panel
      var refresh = function() {
        $('#thread').load('/refresh/room?user= #thread .message', function() {
          scrollDown()
        })
      }
    
      // Call refresh every 5 seconds
      setInterval(refresh, 5000)
    复制代码

    Refresh/Room.html

    以下是请求的action:

    复制代码
    func (c Refresh) Room(user string) rev.Result {
        subscription := chatroom.Subscribe()
        defer subscription.Cancel()
        events := subscription.Archive
        for i, _ := range events {
            if events[i].User == user {
                events[i].User = "you"
            }
        }
        return c.Render(user, events)
    }
    复制代码

    refresh.go

    它订阅chatroom并传递archive到template来做页面渲染。这里没有什么值得看的。

    长轮询(Comet)

     长轮询javascript聊天室使用一个ajax请求server并保持这个连接一直打开知道有一个新消息到来。javascript提供了一个lastReceived时间戳来告诉server,客户端知道的最新消息是哪个。

    复制代码
    var lastReceived = 0
      var waitMessages = '/longpolling/room/messages?lastReceived='
      var say = '/longpolling/room/messages?user='
    
      $('#send').click(function(e) {
        var message = $('#message').val()
        $('#message').val('')
        $.post(say, {message: message})
      });
    
      // Retrieve new messages
      var getMessages = function() {
        $.ajax({
          url: waitMessages + lastReceived,
          success: function(events) {
            $(events).each(function() {
              display(this)
              lastReceived = this.Timestamp
            })
            getMessages()
          },
          dataType: 'json'
        });
      }
      getMessages();
    复制代码

    LongPolling/Room.html

    对应的handler

    复制代码
    func (c LongPolling) WaitMessages(lastReceived int) rev.Result {
        subscription := chatroom.Subscribe()
        defer subscription.Cancel()
    
        // See if anything is new in the archive.
        var events []chatroom.Event
        for _, event := range subscription.Archive {
            if event.Timestamp > lastReceived {
                events = append(events, event)
            }
        }
    
        // If we found one, grand.
        if len(events) > 0 {
            return c.RenderJson(events)
        }
    
        // Else, wait for something new.
        event := <-subscription.New
        return c.RenderJson([]chatroom.Event{event})
    }
    复制代码

    longpolling.go

    在这种实现里面,它能简单的阻塞在订阅channel上(假设它已经发回了所有信息到archive)。

    Websocket

    Websocket聊天室,当用户加载了聊天室页面后,javascript打开了一个websocket连接。

    复制代码
     // Create a socket
      var socket = new WebSocket('ws://127.0.0.1:9000/websocket/room/socket?user=')
    
      // Message received on the socket
      socket.onmessage = function(event) {
        display(JSON.parse(event.data))
      }
    
      $('#send').click(function(e) {
        var message = $('#message').val()
        $('#message').val('')
        socket.send(message)
      });
    复制代码

    WebSocket/Room.html

    第一件事是订阅新的events并加入房间和发出archive,如下所示:

    复制代码
    func (c WebSocket) RoomSocket(user string, ws *websocket.Conn) rev.Result {
        // Join the room.
        subscription := chatroom.Subscribe()
        defer subscription.Cancel()
    
        chatroom.Join(user)
        defer chatroom.Leave(user)
    
        // Send down the archive.
        for _, event := range subscription.Archive {
            if websocket.JSON.Send(ws, &event) != nil {
                // They disconnected
                return nil
            }
        }
    复制代码

    websocket.go

    下面我们必须从订阅监听新的event, 无论如何websocket库只提供一个阻塞call来获得一个新frame,为了在它们之间选择,我们必须包装它们。

    复制代码
        // In order to select between websocket messages and subscription events, we
        // need to stuff websocket events into a channel.
        newMessages := make(chan string)
        go func() {
            var msg string
            for {
                err := websocket.Message.Receive(ws, &msg)
                if err != nil {
                    close(newMessages)
                    return
                }
                newMessages <- msg
            }
        }()
    复制代码

    websocket.go

    现在我们能在newMessages channel上选择新的websocket消息。

    最后一点就是这样做的 - 它从websocket等待一个新消息(如果用户说了什么的话)或从订阅并传播消息到其他用户。

    复制代码
    // Now listen for new events from either the websocket or the chatroom.
        for {
            select {
            case event := <-subscription.New:
                if websocket.JSON.Send(ws, &event) != nil {
                    // They disconnected.
                    return nil
                }
            case msg, ok := <-newMessages:
                // If the channel is closed, they disconnected.
                if !ok {
                    return nil
                }
    
                // Otherwise, say something.
                chatroom.Say(user, msg)
            }
        }
        return nil
    }
    复制代码

    websocket.go

    如果我们发现websocket channel已经关闭,然后我们返回nil。

    至此结束。 ----- 已同步到 一步一步学习Revel Web开源框架

     
     
    分类: Golang
  • 相关阅读:
    Python进阶06 循环对象
    Python进阶05 循环设计
    Python进阶 函数的参数对应
    Python进阶01 词典
    Python基础 反过头来看看
    Python基础08 面向对象的基本概念
    利用zepto.js实现移动页面图片全屏滑动
    数组弃重方法
    fcc筆記
    文字颜色渐变效果
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2874366.html
Copyright © 2020-2023  润新知