已经把这事搁置很久了,前一段时间主要是毕业的事情比较多,现在终于能有一点时间继续写了。好了,进入正题,本节的重点在于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);
到这里,仅仅实现了配对,同步走子的功能。判断输赢和结束棋局重来棋局等功能还未实现。最后,再放截图一张。