WebSocket: 从狼吃羊说起

缘起

大概是大三暑假的时候吧,当时突然想起自己童年玩的一款棋类游戏,网上搜了搜也没有可以在线对战的,恰巧当时会一点前端的知识,于是就想着当个项目刚好练练手吧。小时候一般会和小伙伴,找来石头当狼,杏核当羊,然后在院子里用粉笔画上棋盘,就开始大战了。当时也没有手机拍照,在网上找到一张图,大家想象下吧,哈哈。

应该是很久之前流传下来的游戏了,但现在应该也不会有人再玩了,还是很可惜的。至于为什么突然想起说这个事,因为下周六要去字节跳动面试前端了,竟然要面临人生的第一次找工作了,没啥经验,这几天就多整理整理知识点吧。

行动

说实话当时对前端真的就只是了解的程度(虽然现在也是),也没做过什么项目,但是我们可以面向谷歌/github 编程呀。

第一步就先做一个单机版的狼吃羊吧,怎么做呢,怎么画棋呢?怎么控制走动呢。于是找到了中国象棋的 js 实现

然后只需要解决三件事。

  1. 怎么响应用户对棋子的点击?

    开始自己的想法是根据坐标判断点击是不是在某个矩形区域内。参考上边的 github ,学到了一个更简单粗暴的方法,每个棋子用图片表示,然后每个图片添加响应事件即可。

  2. 根据什么显示棋子位置?

    直接用一个二维数组,1 代表显示图片,0 代表不显示即可。

  3. 怎么制定棋走的规则?

    简单粗暴些,由于狼吃羊只有两种角色,棋盘的位置点也不多。直接根据每个点写它具体的规则即可。当时就是为了快点写出来,麻不麻烦也没考虑,有时候无脑暴力真的是最省脑子的方法,关键它还有用。

毕竟是快两年前的事情了,印象深的就是上边的了,然后就是一些细节的东西了,边调边改吧,自己就开始写了,一点一点最后完成了单机版的。也就是只能两个人在一台手机上玩。当然这个时代,大家更喜欢联网一起玩游戏。

WebSocket 初见

网上对战的棋类游戏, 这怎么做呀?网上找的 js 棋类都是单机的,这可怎么办呀,ctrl + C , ctrl + V 大法不好使了呀?

这可不行,让我思考一下,两个人互相下棋,我们只需要互相传一下自己走棋的位置,就传一个坐标即可。这么简单的数据,怎么传呢?大一暑假写 MFC 的时候用过 socket,但这是 JS 呀,咋办嘞。

传数据,互相传数据,聊天室呀!我要是学会了聊天室传数据,这狼吃羊改改不就行了,于是,嘿嘿嘿,又被我找到了一个项目。

太多了,找到了一个符合自己的,然后就是学起来了。于是就了解到了 websocket 和 node.js。

WebSocket 探究

前端数据的传输我们都用的 HTTP 协议,HTTP 协议的一个特征就是,客户端向服务端情况数据,服务端返回数据给客户端。也就是客户端不问,服务端并不会理会客户端。

那么我们的聊天室用 HTTP 协议怎么实现呢?我打开聊天框,我怎么知道啥时候会有消息过来,我总不能不停的去问服务端吧!

传统轮训(Traditional Polling)

bingo!你说出了一种解决方案,传统轮训(Traditional Polling),用一个定时器,每隔一段时间就像服务器请求一下。

1
2
3
4
5
setInterval(function() {
$.get("/path/to/server", function(data, status) {
console.log(data);
});
}, 10000);

但。。。太不优雅了吧,万一服务器 10 分钟都没消息,那我每隔 10 秒访问一次,也太浪费时间了吧。于是,又诞生了一种方案。

长轮训(Long Polling)

长轮训(Long Polling),客户端向服务器查询数据,服务器如果没有新数据,之前的话就立刻告诉客户端我没有新数据。而现在的话,服务器端保持这个连接,然后有了新数据以后再传回给客户端。然后客户端收到数据,再次向服务端要数据,服务端有数据的话就给客户端,没有的话就保持住连接直到有了,然后重复上边的过程。

聊天室似乎可以实现了,早期的 web QQ 大概就是这种策略吧。但是,现在还是客户端请求,服务端才给回复,服务端能不能主动点,有了消息直接发给客户端,非得等客服端去要吗?有方法的!

WebSocket

我们的主角出现了,是他,就是他,实现了客户端和服务端互相主动发送数据,服务端终于主动了起来。

首先 WebSoket 通过 HTTP 协议建立连接,然后就可以互相发数据了,底层用的 TCP。那么干脆让我们看一下,聊天室怎么实现吗,当然直接贴别人的代码吧,哈哈。

协议只是一些规范,我们还需要去实现它,怎么实现 WebSocket 呢?本着不重复造轮子(自己也实现不了呀)的原则,我们直接用别人的库吧,socket.io ,已经帮我们实现好了,我们根据它提供的 API 用就好了。还有一个好处是这个库还兼容不支持 Websocket 的浏览器,自动改成长轮训或者其他算法。直接把 socket.io 官网给的例子贴过来。

客户端 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
#messages { margin-bottom: 40px }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io( "ws://127.0.0.1:3000");
$('form').submit(function(){
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
window.scrollTo(0, document.body.scrollHeight);
});
});
</script>
</body>
</html

就不用服务器跑了,所以建立连接的时候,上边写的是 127.0.0.1。然后把本地当作服务器端,看下服务器段的代码。

服务端 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;

app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
socket.on('chat message', function(msg){
io.emit('chat message', msg);
});
});

http.listen(port, function(){
console.log('listening on *:' + port);
});

即使没有学过他的 API,也差不多可以看懂吧。是事件驱动的,on 监听事件,emit 发送事件,这样就完成了服务器端和客户端的互相交流信息。

咦,等一下,服务器端怎么跑程序呀,js 不是运行在浏览器里吗?这就得靠 node.js 了。有了它,我们再也不需要把 js 只跑到浏览器中了,因此 js 的领域从前端也扩展到了后端,真的是万事皆可 js。

看一下效果吧。

首先把 index.js 跑起来,监听客服端的请求。

然后浏览器打开两个聊天页面。

在第一个页面发送消息。

打开第二个页面,会发现收到了第一个页面的消息。

完结,撒花。完结,撒花。等等等等等!我们不是做聊天室,我们是要做狼吃羊。对不起,有点激动。

我们理一下,现在已经可以互相传东西了,那么我们只需要一台服务器做中转,A 和 B 对战。A 把消息传给服务器,服务器转给 B。B 把消息传给服务器,服务器转给 A。思想有了,然后就是看一看 socket.io 的 API,用起来就可以了。不过一些创建房间,记录对方的 id 的一些逻辑还是挺烧脑的。

再探 web Socket

监测下网络,让我们看一下 web Socket 到底怎么连接的。

看到了一个 websocket 类型的,让我们看下细节。

值得注意的是,建立连接使用的是 HTTP 协议,状态码是 101,第一次遇到。然后就是 WebSocket 一些相关的字段,目的就是试探对方是否支持 WebSocket,试探成功的话,双方就会建立长连接,就可以互相发送消息了。

结束

就这样,地球上第一款 Web 大型棋类益智对战游戏「狼吃羊」诞生了!在线试玩地址:http://game.windliang.cc,用手机打开,PC 端没有适配。当然,界面和图标的设计还得感谢下女朋友,哈哈哈哈哈。

windliang wechat