聊天室
聊天室
聊天室应用程序示例如下:
使用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}
一个订阅有两个属性:
聊天日志 一个订阅者能在上面监听并获得新信息的channelPublish
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
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息