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); }