HTML+JS+websocket 实例,联机“游戏王”对战(十)- 搭建游戏服务端

目录

HTML+JS+websocket 实例,联机“游戏王”对战 1
HTML+JS+websocket 实例,联机“游戏王”对战 2 - 联机模式
HTML+JS+websocket 实例,联机“游戏王”对战 3 - 界面布局
HTML+JS+websocket 实例,联机“游戏王”对战 4 - 卡组系统
HTML+JS+websocket 实例,联机“游戏王”对战 5 - 卡片选中系统
HTML+JS+websocket 实例,联机“游戏王”对战 6 - 卡片放置,战场更新
HTML+JS+websocket 实例,联机“游戏王”对战 7 - 墓地,副控制面板
HTML+JS+websocket 实例,联机“游戏王”对战 8 - 返回手卡,卡组
HTML+JS+websocket 实例,联机“游戏王”对战 9 - 实现简单 websocket 通信
HTML+JS+websocket 实例,联机“游戏王”对战 10 - 搭建游戏服务端
HTML+JS+websocket 实例,联机“游戏王”对战 11 - 客户端消息的收发
HTML+JS+websocket 实例,联机“游戏王”对战 12 - 消息发送具体场景
HTML+JS+websocket 实例,联机“游戏王”对战 13 - 实机演示

搭建游戏 WebSocket 服务端

第二章联机模式的那篇博文里我们提到过,这个游戏双方玩家的通信方法是通过服务端做中介来相互传递指令。当一方玩家执行某个操作时,会将该操作的相关指令发给服务端,服务端接收后简单处理一下传给另外一位玩家,之后另外一位玩家接收并重现该指令,这样就在自己的界面里同步了对方玩家所执行的操作,比如抽牌,召唤,放置等等。

LAN_structure2.png

现在轮到我们来具体实现一下这个做中介的服务端:

这个服务端的消息传递机制很像上一章csdn大佬博文中的聊天室案例。我们首先在服务端构建一个空的用户数组 players,当每有玩家初次连接服务端的时候,我们会获取并保存该玩家的基本信息(玩家连接的时候顺便发过来),包括玩家的通信地址以及玩家id。

当所有玩家均建立连接后,如果某一位玩家执行操作并发送指令,我们首先会检查该指令所携带的玩家信息,比如玩家id。当我们通过id确认这是已连接的玩家发送的信息后,我们就通过循环的方法将他的指令发给除他之外的所有已连接的玩家。像聊天室一样,某位用户发送的消息将被服务端群发给其他用户,确保每个用户的屏幕上都出现该消息,而发送人自己的屏幕一般通过本地的方法展示发送的内容,无需经过服务器之手(一般情况下)。

这样的传递方法对于这个只有两位玩家的应用来说貌似鸡肋了一些,但扩展性好,如果想扩展成游戏平台之类的这种机制是需要的。

在传递消息前,我们需要规定一下消息格式以便服务端做统一处理:

var message_hand = JSON.stringify({
        "type": "message",  //向服务器告知的消息类型
        "pid": playerID,  //向服务器告知的本玩家ID
        "msgtype": "updateHand",  //向对方玩家告知的更新类型
        "updateType": updateType,  //向对方玩家告知增/减手卡
        "handNo": handNo  //向对方玩家告知被更新的卡槽
    });

这里是游戏中的一个例子(在客户端里),一个手牌操作相关的消息,本质上是js对象,通过 JSON.stringify 编码成JSON字符串,方便传递(通信接口规定的格式)。我们暂且只看消息中的最上面两行变量,这是我们游戏中每个类型的消息都必带的信息:

变量 含义
type 发送的消息类型
pid Player ID,即玩家id

前文说到,我们的服务端靠玩家id来识别接收消息的源头,避免转发消息时发回给发送者本人,玩家id在这里就起到一个识别码的作用。注:玩家id必须是独一无二的,这里本来想动态生成uuid,但貌似相关函数失效了,暂时用自定的字符串代替。

type 代表这条消息的类型,这个变量的存在与我们服务端的消息传递机制有关。因为客户端与服务端的通信分为初次连接与常规通信,不同情况服务端执行的功能不一样。初次连接时服务端需要记录玩家相关信息并保存,常规通信时只需转发消息。


接下来我们来看下客户端与服务端之间的详细通信机制

首先客户端初次与服务端建立连接的时候,客户端会触发 ws.onopen 函数,我们在此函数中设置向服务端发送一则消息,消息内容只包含 type 与 pid:

//初次与服务端建立连接时触发
ws.onopen = function() {
    /*初次与服务端建立连接时告知玩家的pid让服务器存下来 */
    var message = JSON.stringify({
        "type": "connection",  //向服务器告知的消息类型
        "pid": playerID,  //向服务器告知的本玩家ID
    });
    wsSend(message);  
}

接下来看服务端:

//每一个客户端和服务端建立连接时触发
wss.on('connection', function (ws) {
    players.push({"pid": "null", "ws": ws});  //记录连接上的玩家信息,客户端发送的pid不可相同(待优化)
    playerIndex += 1;

    //收到客户端发送的消息时触发
    ws.on('message', function (message) {
        var msg = JSON.parse(message);
        var type = msg.type;
        var pid = msg.pid; //客户端每次需发送自己的pid

        switch(type) {
            case "connection": //如果有玩家首次建立连接
                players[playerIndex].pid = pid;
                console.log("client [%s] has connected", pid);
                break;
            case "message":
                wsSend(pid, msg);  //将一位玩家发来的消息原封不动发给另一位玩家
                console.log("client [%s] send message [%s]", pid, msg.msgtype);
                break;
        }
    });
    
    //SIGINT这个信号是系统默认信号,代表信号中断,就是ctrl+c
    process.on('SIGINT', function () {
        console.log("Closing things");
        process.exit();
    });
    
});

服务端接收消息后会首先将作为消息载体的JSON字符串通过 JSON.parse 函数解析成js对象,也就是还原本来样貌。

之后服务端将通过 type 判断消息的类型是属于初次连接(connection)还是常规消息(message)。初次连接意味着该客户端首次与服务端通信,之前没有保存它的任何信息。

如果是常规消息,说明该客户端已经连接上了,我们将该客户端的id与消息内容传入消息转发函数 wsSend,id用做识别码。wsSend 遍历所有已保存的玩家信息,将消息内容发给除发送者之外的玩家:

function wsSend(pid, content) {
    for (var i=0; i < players.length; i++) {
        if (players[i].pid != pid) {  //若pid不匹配则发送,即发给另一位玩家而非自己
            var clientSocket = players[i].ws;
            if (clientSocket.readyState == WebSocket.OPEN) {
                clientSocket.send(JSON.stringify(content));
            }
        }
    }
}

最后贴一下服务端 ygo-server.js 完整代码:



//引入ws模块,初始化websocket服务端
var WebSocket = require('ws');
var WebSocketServer = WebSocket.Server,
wss = new WebSocketServer({port: 9999});

//客户端数组,储存客户端基本信息
var players = [];
var playerIndex = -1;

function wsSend(pid, content) {
    for (var i=0; i < players.length; i++) {
        if (players[i].pid != pid) {  //若pid不匹配则发送,即发给另一位玩家而非自己
            var clientSocket = players[i].ws;
            if (clientSocket.readyState == WebSocket.OPEN) {
                clientSocket.send(JSON.stringify(content));
            }
        }
    }
}


//每一个客户端和服务端建立连接时触发
wss.on('connection', function (ws) {
    players.push({"pid": "null", "ws": ws});  //记录连接上的玩家信息,客户端发送的pid不可相同(待优化)
    playerIndex += 1;

    //收到客户端发送的消息时触发
    ws.on('message', function (message) {
        var msg = JSON.parse(message);
        var type = msg.type;
        var pid = msg.pid; //客户端每次需发送自己的pid

        switch(type) {
            case "connection": //如果有玩家首次建立连接
                players[playerIndex].pid = pid;
                console.log("client [%s] has connected", pid);
                break;
            case "message":
                wsSend(pid, msg);  //将一位玩家发来的消息原封不动发给另一位玩家
                console.log("client [%s] send message [%s]", pid, msg.msgtype);
                break;
        }
    });
    
    //SIGINT这个信号是系统默认信号,代表信号中断,就是ctrl+c
    process.on('SIGINT', function () {
        console.log("Closing things");
        process.exit();
    });
    
});

下一章我们来介绍客户端的联机功能,实现完整的消息发送,接收机制。

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

推荐阅读更多精彩内容