好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

聊天室

聊天室

聊天室

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

使用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

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于聊天室的详细内容...

  阅读:39次