Go语言Revel框架 聊天室三种通讯方式分析

三种机制的切换

首页相关的网页请求路由如下:

# Login

GET     /                                       Application.Index

GET     /demo                                   Application.EnterDemo

首页显示输入昵称和三种聊天技术选择入口,选择后form提交到Application.EnterDemo页面。跳转到三种具体的聊天技术页面是通过Get参数增加user的方式进行的。

func (c Application) EnterDemo(user, demo string) revel.Result {

    c.Validation.Required(user)

    c.Validation.Required(demo)

 

    if c.Validation.HasErrors() {

        c.Flash.Error("Please choose a nick name and the demonstration type.")

        return c.Redirect(Application.Index)

    }

 

    switch demo {

    case "refresh":

        return c.Redirect("/refresh?user=%s", user)

    case"longpolling":

        return c.Redirect("/longpolling/room?user=%s", user)

    case"websocket":

        return c.Redirect("/websocket/room?user=%s", user)

    }

    returnnil

}

定时刷新机制

从功能角度,定时刷新页面永远会带用户名。http://localhost:9000/refresh?user=ghj1976

在routes表中我们可以看到定时刷新涉及到下面几个页面请求:

# Refresh demo

GET     /refresh                                Refresh.Index

GET     /refresh/room                           Refresh.Room

POST    /refresh/room                           Refresh.Say

GET     /refresh/room/leave                     Refresh.Leave

进入页面,或者整理浏览器刷新页面,都会触发用户进入chatroom。

func (c Refresh) Index(user string) revel.Result {

    chatroom.Join(user)

    return c.Room(user)

}

每隔5秒钟,重新请求一下Refresh.Room页面。

<script type="text/javascript"charset="utf-8">

 

  // 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={{.user}} #thread .message', function() {

      scrollDown()

    })

  }

 

  // Call refresh every 5 seconds

  setInterval(refresh, 5000)

 

  scrollDown()

 

</script>

Refresh.Room 的处理逻辑:

使用一个订阅者把目前聊天室的所有信息都显示出来。离开这个处理过程就把订阅者注销。

func (c Refresh) Room(user string) revel.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/room 发出Post请求是,则记录发出的消息,同时刷新页面。

func (c Refresh) Say(user, message string) revel.Result {

    chatroom.Say(user, message)

    return c.Room(user)

}

离开聊天室时,标示离开,同时跳转页面。

func (c Refresh) Leave(user string) revel.Result {

    chatroom.Leave(user)

    return c.Redirect(Application.Index)

}

长连接 comet

长连接的路由请求如下:

# Long polling demo

GET     /longpolling/room                       LongPolling.Room

GET     /longpolling/room/messages              LongPolling.WaitMessages

POST    /longpolling/room/messages              LongPolling.Say

GET     /longpolling/room/leave                 LongPolling.Leave

当请求长连接的页面时,http://localhost:9000/longpolling/room?user=ghj1976  把用户加入聊天室。

func (c LongPolling) Room(user string) revel.Result {

    chatroom.Join(user)

    return c.Render(user)

}

长连接的浏览器端核心逻辑如下,删除了一些跟核心逻辑无关的代码:
向服务器请求有没有新的消息,如果没有新的消息,则会一直等待服务器。如果有则请求完成消息,然后再次发出一个请求getMessages();
 

<script type="text/javascript">

 

  var lastReceived = 0

  var waitMessages ='/longpolling/room/messages?lastReceived='

 

  // 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();

 

  // Display a message

  var display = function(event) {

    $('#thread').append(tmpl('message_tmpl', {event: event}));

    $('#thread').scrollTo('max')

  }

 

</script>

服务器端处理逻辑则是借用了channel无法读出新的内容时,会一直等下去的技巧,代码如下:

func (c LongPolling) WaitMessages(lastReceived int) revel.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})

}

离开和发送消息的逻辑跟定时刷新的机制基本类似,就不再表述。

WebSocket机制

请求路由信息:

# WebSocket demo

GET     /websocket/room                         WebSocket.Room

WS      /websocket/room/socket                  WebSocket.RoomSocket

WebSocket.Room 处理请求这套机制的进入页面。

WebSocket是Chrome支持的一套通讯机制,javascript中只需要简单的 socket.onmessage 、socket.send 两个方法就可以完成相关工作。

<script type="text/javascript">

 

  // Create a socket

  var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

 

  // Display a message

  var display = function(event) {

    $('#thread').append(tmpl('message_tmpl', {event: event}));

    $('#thread').scrollTo('max')

  }

 

  // 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)

  });

 

  $('#message').keypress(function(e) {

    if(e.charCode == 13 || e.keyCode == 13) {

      $('#send').click()

      e.preventDefault()

    }

  })

 

</script>

服务器端WebSocket 请求使用了 code.google.com/p/go.net/websocket 封装包。 

代码实现分了三大块,先把用户加入聊天室,订阅聊天室,websocket.JSON.Send 发送之前已有的聊天信息,

然后使用 websocket.Message.Receive 等待接受来自浏览器的消息内容。

同时用 channel select 方式接受自己发出的和别人发出的消息。

func (c WebSocket) RoomSocket(user string, ws *websocket.Conn) revel.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

        }

    }

 

    // 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

        }

    }()

 

    // 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

}

参看资料:

http://www.cnblogs.com/ztiandan/archive/2013/01/23/2864872.html

http://robfig.github.com/revel/samples/chat.html

HTTP长连接
http://www.blogjava.net/xjacker/articles/334709.html 

Comet:基于 HTTP 长连接的“服务器推”技术
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/

android 客户端怎么实现长连接和短连接?
http://www.eoeandroid.com/thread-30241-3-1.html

Android/iOS Notification feature
http://blog.csdn.net/totogogo/article/details/7329542

原文地址:https://www.cnblogs.com/ghj1976/p/3002644.html