已经把这事搁置很久了,前一段时间主要是毕业的事情比较多,现在终于能有一点时间继续写了。好了,进入正题,本节的重点在于WebSocket。
由于各大浏览器对WebSocket的支持各不相同,所以直接使用浏览器提供的API会比较复杂。Socket.IO对这些API做了封装,并且还使用其他方式对不支持WebSocket的浏览器做了支持,使用Socket.IO会减轻很多工作量。
使用Socket.IO只需要在express里安装Socket.IO模块,同时在页面中加入/socket.io/socket.io.js的引用,服务端包含socket.io包即可。服务端代码如下:
var express = require('express') , routes = require('./routes'); var app = module.exports = express.createServer(); var io = require('socket.io').listen(app); // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); app.set('view options',{layout:false}); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); app.configure('development', function(){ app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); }); app.configure('production', function(){ app.use(express.errorHandler()); }); // Routes app.get('/', routes.index); var conns = {}; var userNames = {}; var sids = {}; var onLineList = []; io.sockets.on('connection',function(socket){ var sid=socket.id; conns[sid]=socket; socket.on('submitUserName',function(userName, fn) { if (userNames[userName]) { fn("have"); } else { for (var i = onLineList.length - 1; i >= 0; i--) { var tmpSid = userNames[onLineList[i]]["sid"]; conns[tmpSid].emit('addNewUserToList', userName); } var userInfo = {}; userInfo["sid"] = sid; userNames[userName] = userInfo; sids[sid] = userName; onLineList.push(userName); fn("not_have"); } }); socket.on('getOnlineList', function(userName, fn){ fn(onLineList); }); socket.on('setCompetitor', function(competitor, fn){ var myName = sids[socket.id]; for (var i = onLineList.length -1; i >= 0; i--) { if (onLineList[i] == myName) { onLineList.splice(i, 1); } } for (var i = onLineList.length -1; i >= 0; i--) { if (onLineList[i] == competitor) { onLineList.splice(i, 1); } } var deletedUser = [myName, competitor]; for (var i = onLineList.length -1; i >= 0; i--) { var tmpSid = userNames[onLineList[i]]["sid"]; conns[tmpSid].emit('deleteUserFromList', deletedUser); } if (!userNames[myName]["competitor"] && !userNames[competitor]["competitor"]) { userNames[myName]["competitor"] = competitor; userNames[competitor]["competitor"] = myName; var comSid = userNames[competitor]["sid"]; conns[comSid].emit('beginChess', 'begin'); fn("success"); socket.on('goChess', function(data){ conns[comSid].emit('comGoChess', data); }); conns[comSid].on('goChess', function(data){ socket.emit('comGoChess', data); }); } else { fn("fail"); } }); }); app.listen(3000, function(){ console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); });
代码前半部分都是Express框架的设置部分,然后io.sockets.on();部分开始监听来自浏览器端的WebSocket连接。连接建立之后,会将该连接存起来,并在该连接上设置一些可能的监听事件。
socket.on('submitUserName',function(userName, fn){}); //用户提交自己的名字时触发的事件 socket.on('getOnlineList', function(userName, fn){}); //用户获取在线等待配对用户列表时触发的事件 socket.on('setCompetitor', function(competitor, fn){}); //用户选中一个对手之后设置对手的事件
浏览器端在触发事件时会发送一些数据给服务端,比如socket.on(‘submitUserName’,function(userName, fn){})中,userName是浏览器端发送过来的用户名,而fn则是浏览器端定义的一个函数回调函数,当服务端完成某些操作后,会调用fn函数,相当于浏览器端得到了服务端的响应。当然,这里也可以不设置fn,只发送数据。关于socket.io的具体情况可以查看官方文档,地址:http://socket.io/#how-to-use
浏览器端会根据用户的一些操作触发一些服务端事件,部分代码如下:
$(function(){ $("#chessCanvas").hide(); $("#PiecesCanvas").hide(); $("#onlinelist").hide(); socket = io.connect(); window.submitUserName = function(){ var userName = $("#username").val(); if (userName) { socket.emit('submitUserName', userName, function(data){ if (data == "have") { alert("用户名已经被使用,请重新取一个更酷的名字吧!"); $("#username").val(""); $("#username").focus(); } else { socket.emit('getOnlineList', userName, function(list){ $.each(list, function(i, item){ if (item == userName){ return true; } var str = '<div id="' + item + '"><input type="radio" name="onlineuser" value="' + item + '" /><span>' + item + '</span></div>'; $("#namelist").append(str); }); $("#login").hide(); $("#onlinelist").show(); }); } }); socket.on('addNewUserToList', function(data){ var str = '<div id="' + data + '"><input type="radio" name="onlineuser" value="' + data + '" /><span>' + data + '</span></div>'; $("#namelist").append(str); }); socket.on('deleteUserFromList', function(data) { $.each(data, function(i, item) { $("#namelist > #" + item).remove(); }); }); socket.on('beginChess', function(data) { showChess('red', 'wait', socket); }); } else { alert("请输入用户名!"); } }; window.beginChess = function(){ var competitor = $("input[name='onlineuser']:checked").val(); if (competitor) { socket.emit('setCompetitor', competitor, function(data){ if (data == "success") { showChess('black', 'idle', socket); } else { //未配对成功的情况 } }); } else { alert("请选择一个对手!"); } }; function showChess(ourColor, status, sock) { $("#onlinelist").hide(); $("#chessCanvas").show(); $("#PiecesCanvas").show(); window.chss = chess("chessCanvas", "PiecesCanvas", sock); window.chss.ourColor = ourColor; window.chss.status = status; window.chss.initPieces(); } });
当用户输入完用户名之后,点击登录,就会使用socket.emit(‘submitUserName’, userName, function(data){})触发服务端响应。同时,浏览器端也会监听一些服务端事件,例如socket.on(‘beginChess’, function(data) {})就监听了服务端发起的棋局开始事件。这就是WebSocket的强大之处,浏览器可以被动接收服务端发送的消息,换个角度说,这一刻的浏览器就是C/S结构中的服务端,而服务器则是C/S结构中的客户端。当棋局开始后,浏览器使用如下代码监听服务器发送过来的对方走子情况,并在本地执行。
//监听服务端发送过来的对方棋子移动的事件 me.socket.on('comGoChess', function(movement) { var lineX = 10 - movement.toLineX; var lineY = 11 - movement.toLineY; var comPiece = movement.piece; me.pieceMoveTo('opposite', comPiece, lineX, lineY); me.status = 'idle'; });
当然,在本地走子之后,也会将走子信息发送给服务器,服务器再发送到对方。本子走子信息通过如下代码向服务端发送。
var movement = {}; movement["piece"] = me.activePiece; movement["toLineX"] = lineX; movement["toLineY"] = lineY; me.socket.emit('goChess', movement);