1 web服务器向浏览器 推送
http://www.aikaiyuan.com/7968.html
人们常常提到”Comet”, 或者”Web 服务器推”, “HTTP 长连接”, 事实上, 他们指的是同一件东西, 可以统称为 Comet 技术. 但是, Comet 技术又不是单独的一种东西, 而解决某一个问题的许多技术的统称. 要解决的问题是 Web 服务器向浏览器实时推送数据, 而解决方案有很多种.
最经典的方案是 AJAX 轮询, 这种方案和”推”技术毫无关系, 只是由于轮询的间隔比较短, 如一两秒, 便给了用户实时的错觉.
新下来是安装浏览器插件, 如 Active-X, 或者使用 Flash 插件, Java Applet 插件等, 这些方案都不通用, 兼容性不好, 也不能被称为 Comet 技术.
根据实践, 真正的 HTTP 长连接方案主要有: Script Tag Long-Polling, Forever Iframe, WebSocket. 这些方案在我的另一篇文章”各种 Comet 技术优缺点对比“有介绍.
对于开发者, 为了快速和方便的开发, 应该选择一个支持 Comet 技术的 Web 服务器和一套 JavaScript 库.iComet就是这样的一套解决方案.
iComet 开源项目:https://github.com/ideawu/icomet
iComet Demo:http://www.ideawu.com/icomet/chat/
ICOMET subscriber.cpp
/*
Copyright (c) 2012-2014 The icomet Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
*/
#include "subscriber.h"
#include "channel.h"
#include "server.h"
#include "util/log.h"
#include "server_config.h"
#include <http-internal.h>
static std::string iframe_header = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'><meta http-equiv='Cache-Control' content='no-store'><meta http-equiv='Cache-Control' content='no-cache'><meta http-equiv='Pragma' content='no-cache'><meta http-equiv=' Expires' content='Thu, 1 Jan 1970 00:00:00 GMT'><script type='text/javascript'>window.onError = null;try{document.domain = window.location.hostname.split('.').slice(-2).join('.');}catch(e){};</script></head><body>";
static std::string iframe_chunk_prefix = "<script>parent.icomet_cb(";
static std::string iframe_chunk_suffix = ");</script>";
Subscriber::Subscriber(){
req = NULL;
}
Subscriber::~Subscriber(){
}
static void on_sub_disconnect(struct evhttp_connection *evcon, void *arg){
log_debug("subscriber disconnected");
Subscriber *sub = (Subscriber *)arg;
sub->close();
}
void Subscriber::start(){
bufferevent_enable(req->evcon->bufev, EV_READ);
evhttp_connection_set_closecb(req->evcon, on_sub_disconnect, this);
evhttp_add_header(req->output_headers, "Connection", "keep-alive");
//evhttp_add_header(req->output_headers, "Cache-Control", "no-cache");
//evhttp_add_header(req->output_headers, "Expires", "0");
evhttp_add_header(req->output_headers, "Content-Type", "text/html; charset=utf-8");
evhttp_send_reply_start(req, HTTP_OK, "OK");
if(this->type == POLL){
//
}else if(this->type == IFRAME){
struct evbuffer *buf = evhttp_request_get_output_buffer(this->req);
evbuffer_add_printf(buf, "%s\n", iframe_header.c_str());
evhttp_send_reply_chunk(this->req, buf);
}
// send buffered messages
if(this->seq_next == 0){
this->seq_next = channel->seq_next;
}
if(!channel->msg_list.empty() && channel->seq_next != this->seq_next){
this->send_old_msgs();
}
}
void Subscriber::send_old_msgs(){
std::vector<std::string>::iterator it = channel->msg_list.end();
int msg_seq_min = channel->seq_next - channel->msg_list.size();
if(Channel::SEQ_GT(this->seq_next, channel->seq_next) || Channel::SEQ_LT(this->seq_next, msg_seq_min)){
this->seq_next = msg_seq_min;
}
log_debug("send old msg: [%d, %d]", this->seq_next, channel->seq_next - 1);
it -= (channel->seq_next - this->seq_next);
struct evbuffer *buf = evhttp_request_get_output_buffer(this->req);
if(this->type == POLL){
if(!this->callback.empty()){
evbuffer_add_printf(buf, "%s(", this->callback.c_str());
}
evbuffer_add_printf(buf, "[");
for(/**/; it != channel->msg_list.end(); it++, this->seq_next++){
std::string &msg = *it;
evbuffer_add_printf(buf,
"{\"type\":\"data\",\"cname\":\"%s\",\"seq\":%d,\"content\":\"%s\"}",
this->channel->name.c_str(),
this->seq_next,
msg.c_str());
if(this->seq_next != channel->seq_next - 1){
evbuffer_add(buf, ",", 1);
}
}
evbuffer_add_printf(buf, "]");
if(!this->callback.empty()){
evbuffer_add_printf(buf, ");");
}
evbuffer_add_printf(buf, "\n");
evhttp_send_reply_chunk(this->req, buf);
this->close();
}else if(this->type == IFRAME || this->type == STREAM){
for(/**/; it != channel->msg_list.end(); it++, this->seq_next++){
std::string &msg = *it;
this->send_chunk(this->seq_next, "data", msg.c_str());
}
}
}
void Subscriber::close(){
if(req->evcon){
evhttp_connection_set_closecb(req->evcon, NULL, NULL);
}
evhttp_send_reply_end(req);
channel->serv->sub_end(this);
}
void Subscriber::noop(){
this->send_chunk(this->seq_noop, "noop", "");
}
void Subscriber::send_chunk(int seq, const char *type, const char *content){
struct evbuffer *buf = evhttp_request_get_output_buffer(this->req);
if(this->type == POLL){
if(!this->callback.empty()){
evbuffer_add_printf(buf, "%s(", this->callback.c_str());
}
}else if(this->type == IFRAME){
evbuffer_add_printf(buf, "%s", iframe_chunk_prefix.c_str());
}
evbuffer_add_printf(buf,
"{\"type\":\"%s\",\"cname\":\"%s\",\"seq\":%d,\"content\":\"%s\"}",
type, this->channel->name.c_str(), seq, content);
if(this->type == POLL){
if(!this->callback.empty()){
evbuffer_add_printf(buf, ");");
}
}else if(this->type == IFRAME){
evbuffer_add_printf(buf, "%s", iframe_chunk_suffix.c_str());
}
evbuffer_add_printf(buf, "\n");
evhttp_send_reply_chunk(this->req, buf);
this->idle = 0;
if(this->type == POLL){
this->close();
}
}
void Subscriber::send_error_reply(int sub_type, struct evhttp_request *req, const char *cb, const std::string &cname, const char *type, const char *content){
struct evbuffer *buf = evhttp_request_get_output_buffer(req);
if(sub_type == POLL){
evbuffer_add_printf(buf, "%s(", cb);
}else if(sub_type == IFRAME){
evbuffer_add_printf(buf, "%s", iframe_chunk_prefix.c_str());
}
evbuffer_add_printf(buf,
"{\"type\":\"%s\",\"cname\":\"%s\",\"seq\":%d,\"content\":\"%s\"}",
type, cname.c_str(), 0, content);
if(sub_type == POLL){
evbuffer_add_printf(buf, ");");
}else if(sub_type == IFRAME){
evbuffer_add_printf(buf, "%s", iframe_chunk_suffix.c_str());
}
evbuffer_add_printf(buf, "\n");
evhttp_send_reply(req, HTTP_OK, "OK", buf);
}