Socket.io的实时竞拍系统实现

某天张同学来到了一个拍卖网站,看中了一件心仪的拍品,于是他愉快的参与了竞价,并处于了领先的地位,随后张同学死死盯住拍品倒计时和领先者,在倒计时结束后发现领先者还是他自己,正当他欣喜若狂准备付款时,页面刷新发现拍品已被别人抢走。后来当张同学在次来到网站,便长了记性,开始无止境的F5~

当然我们不能这样折腾我们的用户,那么如何实现拍品信息实时更新? 要解决这个需求,实际上需要服务端主动“推” 信息到客户端。而客户端想要得到服务端推送的信息,实际上需要和服务端建立一个长连接,这样服务端才能通过这个连接把信息传递到客户端,也就是所谓“推”的概念,下面有篇文章详细介绍了WEB推送的系统的客户端实现方式:

Comet:基于 HTTP 长连接的“服务器推”技术

Nginx也有相应的模块进行服务端的支持:

HTTP Push Stream

这里我们主要来说说通过Node平台下的Socket.io如何实现,首先来看下具体的业务场景是什么,一个最基本的实时竞拍系统应该包含以下三个场景:

  1. 一个或者多个用户关注着某一个拍品
  2. 当其中某个用户出价后,更新数据库中该拍品的信息
  3. 将最新的拍品信息反应到关注该拍品用户的客户端上

那么根据这三个场景,能够确认推送的一个大概思路,也就是 “当拍品出价成功后,取得最新的拍品信息,推送给关注该拍品的所有用户”,基于这个思路可以确定一个大概的推送流程图:

推送流程图.png

这个流程图中分为4个部分:

  • 客户端(接受推送消息,已经出价)
  • 竞价接口(处理竞价逻辑,加入拍品到推送队列)
  • 队列处理(取出需要推送的拍品调用推送API)
  • 推送服务(提供socket.io的服务端供客户端进行
    连接,并提供一组推送的内网API供应用程序调用)

根据以上流程,竞价接口以及队列处理我们可以采用任意的语言去实现,这里不在延伸。这两部分中队列处理,根据具体业务可以省去,这里引入队列处理的目的主要是考虑到,推送服务本身与竞价流程解耦,以及当推送服务故障时,失败队列的维护,当然如果使用队列,也需要考虑队列处理的及时性,避免推送信息的不及时。

下面来看看推送服务如何构建:

(以下均是伪代码,只为说明具体思路)


var http = require("http");
http.globalAgent.maxSockets = Infinity;
var koa = require('koa');
var app = koa();
var bodyParser = require('koa-bodyparser');
var route = require('koa-route');
var io = require('socket.io');
var ioRedis = require('socket.io-redis');
var ioEmitter = require('socket.io-emitter')({ host: '127.0.0.1', port: '6379' });
var server = http.createServer(app.callback());

io = io(server);

io.adapter(ioRedis({ host :'127.0.0.1', prot :'6379'}));

/**************  推送API ******************/
app.use(bodyParser());
app.use(route.post('/pub', function *(next){

    var data = this.request.body;

    if(!data || typeof data != 'object'){
        this.throw('data error', 400);
    }

    var room = data.itemId;
    var channel = data.channel;
    var message = data.message;

    ioEmitter.to(room).emit(channel,message);   

    this.body = 'ok';
    
}));
app.use(function *(){
  this.response.status = 404;
})
/*****************************************/

/*************Socket.io Server ***********/
io.use(function(socket,next){
    var itemId = socket.handshake.query.itemId;
    socket.room = itemId;
    return next();
});
io.on('connection',function(socket){
    socket.join(socket.room);
});
/*****************************************/




server.listen(3000,function(){});

推送服务实际上起到的是一个中间层的作用,下面看下客户端与服务端如何和它配合:

  • 客户端代码
<script>
  var socket = io('http://推送服务地址:3000?itemId=100');
  socket.on('auction', function (data) {
        //调用 Dom 更新拍品信息
  });
</script>

客户端在连接是指明了itemId(假设它是拍品ID),这样在推送服务能够对其进行房间的划分,也就是在 socket.join(socket.room) 的时候。

  • 服务端代码(PHP)
        
    private function http($data){
               /** 
                    $data = array(
                          'itemId'=>100,            //指明推送拍品
                          'channel'=>'auction',  //指明推送到的渠道
                          'message'=>array( )  //最新的拍品信息
                    );
               */

        $server = $this->getServer();  // http://127.0.0.1:3000/pub
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $server);
        curl_setopt($ch, CURLOPT_TIMEOUT, 3);
        curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($data));
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            throw new \Exception('curl_error  '.curl_error($ch));
        }else{
            if(strtolower($response) == 'ok'){    
                curl_close($ch);
                return true;
            }else{
                throw new \Exception('response_error  '.$response);
            }
        }
        curl_close($ch);
        return false;
    }

服务端在推送时,调用 "http://127.0.0.1:3000/pub" ,也就是有 koa 搭建的http推送API ,并传入想应格式的数据,指明推送的拍品,渠道,信息。这样在 koa 接受到请求后,调用 ioEmitter.to(room).emit(channel,message); 将信息推送到客户端,这样就走完了一个流程。

总结:

  • 实时推送实际上就是消息的发布/订阅,客户端是消息订阅方,服务端是消息的发布方
  • 推送服务也就是消息发布/订阅的服务
  • 推送与竞价可以是两套不同业务,应该考虑解耦
  • 使用Socket.io做Web的实时推送,代码量小,可控性高
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容