前面提到了有关个人网站的实时在线人数问题,本文要讨论的是如何自己来实现一个这样的统计服务。因为网站也同时部署在Github上,海外用户访问Github镜像网站的访问日志Pi是拿不到的,这怎么办?
Google Channel Service
Google Channel Service允许应用和GAE (Google App Engine) 保持一个长连接,允许应用实时发送消息给JavaScript客户端,而不用让客户端用效率很低的定时轮询获取新消息。这个服务是允许有多个发布者和多个订阅者,也能创建多个主题来关联发布者和订阅者。
使用这个服务分两步:
客户端请求服务器端(部署在GAE上)获取一个Channel的Token:
客户端根据Channel Token和服务器建立长连接,并开始接收消息,这时其它的客户端(或服务器端)可以想这个通道发送消息
在线人数统计实现
在页面上部署beacon
通常的网站流量统计是依赖部署在页面上的beacon(Javascript或图片标签)来实现的,这样做的好处是可以直接过滤掉一些机器流量,并且可以将日志集中存储在日志收集服务器上,和网站分离开。
于是可以利用GAE实现一个简单的Beacon服务,这里采用Go语言来实现,用Java或Python也是可以的。
package counter
import (
"encoding/base64"
"fmt"
"time"
"net/http"
"appengine"
"appengine/channel"
)
var GIF []byte
const (
TOPIC = "counter"
)
func init() {
GIF, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
http.HandleFunc("/beacon.gif", handler)
http.HandleFunc("/new_token", handler_new_token)
}
func handler(w http.ResponseWriter, r *http.Request) {
context := appengine.NewContext(r)
now := time.Now()
expire := now.AddDate(30, 0, 0)
zcookie, _ := r.Cookie("z")
if zcookie == nil {
zcookie = &http.Cookie{}
zcookie.Name = "z"
zcookie.Value = make_hash("<your_salt>", r.RemoteAddr, now.UnixNano())
zcookie.Expires = expire
zcookie.Path = "/"
http.SetCookie(w, zcookie)
}
w.Header().Set("Content-type", "image/gif")
w.Header().Set("Cache-control", "no-cache, must-revalidate")
w.Header().Set("Expires", "Sat, 26 Jul 1997 05:00:00 GMT")
fmt.Fprintf(w, "%s", GIF)
channel.Send(context, TOPIC, zcookie.Value+"\n"+r.RemoteAddr+"\n"+r.Referer()+"\n"+r.UserAgent())
}
func handler_new_token(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
tok, err := channel.Create(c, TOPIC)
callback := r.FormValue("callback")
if err != nil {
http.Error(w, "Couldn't create Channel", http.StatusInternalServerError)
c.Errorf("channel.Create: %v", err)
return
}
if callback == "" {
w.Header().Set("Content-type", "text/javascript")
fmt.Fprintf(w, "%s", tok)
} else {
fmt.Fprintf(w, callback+"('%s')", tok)
}
}
代码最后一行是将访问日志实时通过Channel发送出去,该通道有一个指定的主题,这样订阅该主题的客户端都可以收到相应的消息。
[Read More]