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

returnnil

}

}

// In order to select between websocket messages and subscription events, we

// need to stuff websocket events into a channel.

newMessages := make(chanstring)

gofunc() {

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.

returnnil

}

case msg, ok := <-newMessages:

// If the channel is closed, they disconnected.

if !ok {

returnnil

}

// Otherwise, say something.

chatroom.Say(user, msg)

}

}

returnnil

}

参看资料:

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