socket.io简单说明及在线抽奖demo
socket.io 简介
Socket.IO可以实现实时双向的基于事件的通信。
它适用于各种平台,浏览器或设备,也同样注重可靠性和速度。
socket.io的API比较简单,可以很轻松的上手,完成一个实时分析图表或者聊天室之类的程序。
socket.io在浏览器中主要是通过WebSocket来实现实时通信的,WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。Socket.IO实现的Polling通信机制包括Adobe Flash Socket、AJAX长轮询、AJAX multipart streaming、持久Iframe、JSONP轮询等。Socket.IO能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
这里,我们通过一个web抽奖的小程序,来说明下socket.io的使用。
需求说明
有时间,我们想整一个抽奖活动,但是又不确定参加的人数,或者部分参与人员不在现场。这时,可能我们需要一个抽奖小程序,控制台上显示出一个二维码,参与人员用手机扫描二维码或者浏览器直接访问抽签地址可参与抽奖。有人参与抽奖后,控制台和各个参与者实时显示当前所有参与者。控制台点击抽奖按钮后,抽出中奖者,各个参与者客户端实时显示是否中奖。
需求很简单,在没有socket.io的情况下,通过setinterval也可以实现需求,但是在有了socket.io(服务端使用Node.js)的情况下,实现起来会更加简单明了。
socket.on API 说明
- io.sockets.on('connection', function (socket) {}); //监听事件
- socket.emit('message', "data"); //发送消息至当前连接的客户端
- socket.broadcast.emit('message', "data"); //广播消息至所有连接的客户端
- io.sockets.in('room').emit('message', 'data'); //发送消息至某个频道的所有客户端
- 等等
开始开发
-
增加package,json
-
{ "name": "socketio-lottery", "version": "0.1.1", "description": "socketio-lottery", "main": "index.js", "author": "lazio10000", "private": true, "license": "BSD", "dependencies": { "express": "4.10.2", "socket.io":"1.4.5", "mongodb":"~2.0" } }
安装express,方面快速建站。安装mongodb,用于记录每次中奖人。
-
安装依赖
npm install
-
新增index.js
var express = require('express'); var app = express(); var http = require('http').Server(app); var io = require('socket.io')(http); var port = process.env.PORT || 3000; app.use(express.static(__dirname + '/public')); http.listen(port, function(){ console.log('listening on *:3000'); }); io.on('connection', function(socket){ console.log('a user connected'); });
这里socketio也监听3000端口,当有客户端连接时,服务器端会打印日志:a user connected
-
增加客户端:public文件夹下添加client.html
<script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script>
这里引用了js版本的socket.io客户端。运行node index后,浏览器访问,会在服务端打印日志。
-
服务端增加抽奖等事件:修改index.js
-
// Setup basic express server var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); var port = process.env.PORT || 3000; // Setup mongodb client var mongoClient = require('mongodb').MongoClient; var mongoHost = process.env.MONGODB_PORT_27017_TCP_ADDR || 'localhost'; var mongoPort = process.env.MONGODB_PORT_27017_TCP_PORT || 27017; var mongoDatabase = process.env.MONGODB_INSTANCE_NAME || 'test'; var mongoUsername = process.env.MONGODB_USERNAME; var mongoPassword = process.env.MONGODB_PASSWORD; var mongoUrl = "mongodb://" + mongoUsername + ":" + mongoPassword + "@" + mongoHost + ":" + mongoPort.toString() + "/" + mongoDatabase; console.log(mongoUrl); server.listen(port, function () { console.log('Server listening at port %d', port); }); // Routing app.use(express.static(__dirname + '/public')); //参与抽奖人 var usernames = {}; var numUsers = 0; //保存抽奖数据 var insertData = function (winnerList) { var lotteryDate = new Date(); var data = []; winnerList.forEach(function (winner) { data.push({ "LotteryPeriod": lotteryDate, "Winner": winner }); }); //插入数据 mongoClient.connect(mongoUrl, function (err, db) { var collection = db.collection('WinnerList'); collection.insert(data, function (err, result) { if (err) { console.log('Error:' + err); return; } db.close(); }); }); } io.on('connection', function (socket) { //每个客户端连接都是不同的socket实例 var addedUser = false; //控制台重置事件,用于手动刷新奖池 socket.on('reset', function () { usernames = {}; numUsers = 0; }); //重新获取奖池 socket.on('reload', function (fn) { fn(usernames); }); //抽奖 socket.on('lottery', function (data) { //保存中奖数据 insertData(data); //清空奖池 usernames = {}; numUsers = 0; //广播中奖人 socket.broadcast.emit('lottery', { winnerList: data }); }); //登录 socket.on('add user', function (username) { socket.username = username; if (!usernames[username]) { usernames[username] = username; ++numUsers; addedUser = true; socket.emit('login', { numUsers: numUsers }); socket.broadcast.emit('user joined', { username: socket.username, numUsers: numUsers }); } else { socket.emit('loginError', {}); } }); //客户端断开 socket.on('disconnect', function () { if (addedUser) { delete usernames[socket.username]; --numUsers; socket.broadcast.emit('user left', { username: socket.username, numUsers: numUsers }); } }); });
-
增加控制台:public文件夹下添加index.html
<!doctype html> <!-- html、websocket抽奖小程序 --> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>抽-抽-抽</title> <link href="bootstrap.min.css" rel="stylesheet"> <link href="bootstrap-theme.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="jumbotron" style="margin-top:30px"> <div class="col-md-8"> <h1>抽-抽-抽</h1> <p>请扫描二维码参加抽奖</p> <p> <button type="button" id="btnLottery" class="btn btn-primary btn-lg btnLottery" data-loading-text="抽ing..." autocomplete="off" data-length="1">找抽</button> <button type="button" id="btnLottery2" class="btn btn-primary btn-lg btnLottery" data-loading-text="抽ing..." autocomplete="off" data-length="2">抽两个</button> <button type="button" id="btnReset" class="btn btn-default btn-lg" autocomplete="off">重置</span></button> </p> </div> <div class="col-md-4"><img src="2139274403.png" /></div> <p>需要访问互联网哦</p> </div> <div> <h1 id="members" style="line-height:80px;"></h1> </div> </div> <script src="jquery-1.10.2.min.js"></script> <script src="bootstrap.min.js"></script> <script src="socket.io-1.0.6.js"></script> <script src="main.js"></script> </body> </html>
对应的main.js
$(function () {
var $members = $('#members');
var users = new Array();
var socket = io();
var lotteryState = false;
$('#btnReset').click(function(){
socket.emit('reload',function(data){
console.log(data);
users = new Array();
$members.empty();
for(var item in data){
users.push(data[item]);
$members.append('<span class="label label-default" data-index="' + (users.length - 1) + '">' + item + '</span> ');
}
});
});
$('.btnLottery').click(function () {
if (lotteryState) {
return false;
}
if (users.length === 0 || users.length <= 2) {
alert("没人抽毛");
}
else {
var lotteryCount = parseInt($(this).attr("data-length"));
if (lotteryCount) {
$(this).button('loading');
setTimeout(function () {
lotteryState = true;
var lotteryData = new Array();
function lotteryUser(name, idx, value) {
this.name = name;
this.idx = idx;
this.random = value;
}
$.each(users, function (i, user) {
lotteryData.push(new lotteryUser(user, i, parseInt(10000 * Math.random())));
});
lotteryData.sort(function (a, b) {
return a.random < b.random ? 1:-1
});
var lotteryUsername = [];
for (var i = 0; i < lotteryCount; i++) {
$('span[data-index="' + lotteryData[i].idx + '"]').removeClass('label-default').addClass('label-danger');
lotteryUsername.push(lotteryData[i].name);
}
socket.emit('lottery', lotteryUsername);
lotteryState = false;
$('#btnLottery').button('reset');
}, 3000);
}
}
return false;
});
socket.on('user joined', function (data) {
if (!lotteryState) {
users.push(data.username);
$members.append('<span class="label label-default" data-index="' + (users.length - 1) + '">' + data.username + '</span> ');
}
});
socket.on('user left', function (data) {
if (!lotteryState) {
var userid = $.inArray(data.username, users);
if (userid > -1) {
$('span[data-index="' + userid + '"]').remove();
users.splice(userid, 1);
}
}
});
});
-
修改客户端client.html
<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>抽-抽-抽</title> <link href="bootstrap.min.css" rel="stylesheet"> <link href="bootstrap-theme.min.css" rel="stylesheet"> <style type="text/css"> body { padding-top: 50px; background-color: #eee; } .starter-template { padding: 40px 15px; text-align: center; } </style> </head> <body> <div class="container"> <div class="starter-template"> <h1>抽-抽-抽</h1> <div id="logon"> <form class="form-inline" role="form" onsubmit="return false"> <div class="form-group"> <input type="text" class="form-control" id="logonName" placeholder="怎么称呼,兄台" maxlength="10"> </div> <button type="button" id="btnSubmit" class="btn btn-primary">确定</button> </form> </div> <div id="lottery"> <h1 id="stateMessage"></h1> <p id="messages"></p> </div> </div> </div> <!-- /container --> <script src="jquery-1.10.2.min.js"></script> <script src="jquery.cookie.js"></script> <script src="socket.io-1.0.6.js"></script> <script src="client.js"></script> </body> </html>
-
新增client.js
-
//客户端 $(function () { var $loginPage = $("#logon"); var $lotteryPage = $("#lottery"); var username; var connected = false; var socket = io(); var getQueryStringValue = function (keyName) { var searchStr = location.search.substr(1); if (searchStr.length == 0) return null; var collection = searchStr.split('&'); for (var i = 0; i < collection.length; i++) { var tmp = collection[i].split('='); if (tmp.length < 2) continue; if (tmp[0].toUpperCase() == keyName.toUpperCase()) return tmp[1]; } return null; } var login = function () { username = $("#logonName").val().trim(); if (username) { socket.emit("add user", username); } else { $("#logonName").attr("placeholder", "兄台,怎么称呼?"); } }; //自动登录 var autoLogin = function () { if ($.cookie("UserName") != null) { username = $.cookie("UserName"); socket.emit("add user", username); } } if (getQueryStringValue('autologin') == null) { autoLogin(); } //登录 $("#btnSubmit").click(function () { login(); }); //手机回车 $(window).keydown(function (event) { if (event.which === 13) { login(); return false; } }); //登录事件 socket.on("login", function (data) { connected = true; $.cookie("UserName", username, { expires: 365 }); $loginPage.hide(); $lotteryPage.show(); $("#stateMessage").html("等待开奖"); }); socket.on("loginError", function (data) { connected = false; $("#stateMessage").html("换个称呼吧,朋友"); }); socket.on("user joined", function (data) { $('#messages').html("共有" + data.numUsers + "参与抽签"); }); socket.on("user left", function (data) { console.log(data); $("#messages").html("共有" + data.numUsers + "参与抽签"); }); socket.on("lottery", function (data) { if ($.inArray(username, data.winnerList) > -1) { $("#stateMessage").html("恭喜,您中奖了"); $(".container").css("background", "url(55771332451950460.jpg) no-repeat center").css("color", "white"); } else { $("#stateMessage").html("原来没中奖我也可以这么开心"); } }); window.setInterval(function(){ if(!socket.connected){ $("#stateMessage").html("断线了,刷新吧"); } }, 10000); });
-
好了,允许服务端,然后分别访问控制台和客户端,我们的抽奖小程序完成了。
其他
Socket.IO已经具有众多强大功能的模块和扩展API:
- (session.socket.io)(http session中间件,进行session相关操作)
- socket.io-cookie(cookie解析中间件)
- session-web-sockets(以安全的方式传递Session)
- socket-logger(JSON格式的记录日志工具)
- websocket.MQ(可靠的消息队列)
- socket.io-mongo(使用MongoDB的适配器)
- socket.io-redis(Redis的适配器)
- 等等
此外,社区开发者还为Socket.IO开发了一些开源插件/功能库:
- Java客户端Socket.IO-client.java,可以用于Android的相关应用中
- 用于Socket.IO与iOS应用间进行通信的简单接口SIOSocket
- 基于Netty的Socket.IO服务器端的Java实现Netty-socketio
- 等等