웹소켓이 왜 필요한가요?
기존의 실시간 인터랙티브 게임이나 적극적으로 메시지를 보내는 서버의 동작(예: 푸시 서비스)의 경우 WeChat에서 하고 싶다면 폴링을 사용할 수 있지만, 이는 자원을 너무 많이 소모하고, 요청이 많아지면 서버의 부담도 커지며, 지연 문제도 심각하다. 자체 개발한 앱이라면 이러한 문제를 해결하기 위해 많은 팀이 자체 소켓을 구축하고 TCP 긴 링크와 사용자 정의 프로토콜을 사용하여 상대적으로 실시간 데이터로 서버와 상호 작용합니다. 유능한 팀이 있다면 이 접근 방식을 채택하는 데 큰 문제가 없습니다. 그러나 소규모 팀에서는 많은 문제를 디버깅하고 해결하는 데 많은 시간을 소비해야 할 수 있으며 이는 비용 측면에서 가치가 없습니다.
H5는 웹페이지의 긴 링크 문제를 해결하기 위해 webSocket을 도입했으며, WeChat 애플릿도 websocket을 지원합니다. 이는 매우 중요한 기능이므로 이 기사 시리즈에서는 웹소켓에 대해 논의하는 데 한 기사를 집중할 것입니다.
WebSocket은 기본적으로 전이중 데이터 전송을 제공하는 TCP 연결입니다. 한편으로는 폴링으로 인한 잦은 연결 설정과 연결 끊김으로 인한 성능 저하를 방지할 수 있는 반면, (긴 링크이기 때문에) 실시간으로 양방향으로 데이터를 전송할 수 있으며, WebSocket은 교차를 허용합니다. -도메인 통신(여기서 가능성이 있음) 도메인 간 보안 문제는 서버에서 해결되어야 합니다. 현재 IE 이외의 브라우저는 이미 webSocket을 매우 잘 지원하고 있습니다. WeChat 애플릿이 다시 출시되면 더 많은 인기를 얻게 될 것입니다.
새로운 데모, 더 흥미로운 미니 게임, 지뢰 찾기의 멀티플레이어 버전, 더 정확하게는 금 채굴의 멀티플레이어 버전을 디자인해 봅시다.
게임의 규칙은 다음과 같습니다. 천둥을 금으로 교체하면 각 사람이 한 번씩 차례를 맡습니다. (A가 채굴을 마치고 B의 차례입니다.) , B는 채굴 후 다시 클릭할 수 있습니다. 금이 당신의 것이더라도 게임은 계속됩니다. 필드의 금이 모두 채굴됩니다. 지뢰 찾기와 마찬가지로 숫자는 주변에 금이 얼마나 있는지 나타내며, 사용자는 필드에 공개된 숫자를 기반으로 어느 사각형에 금이 포함되어 있는지 추측할 수 있습니다.
이 인터랙티브 게임의 어려움은 사용자의 클릭 동작이 서버로 전송되어야 하고, 서버는 이를 실시간으로 다른 플레이어의 애플리케이션에 푸시해야 한다는 것입니다. 또한, 동일한 그리드를 반복적으로 클릭하지 않도록 사용자 자신도 상대방의 작업 중에 실시간으로 전송되는 데이터를 수신해야 합니다. 간단히 말하면 서버에 작업을 보고해야 하고, 서버는 실시간으로 메시지를 푸시해야 합니다. 전체 모델을 단순화하기 위해 플레이어 A가 클릭을 마친 후에는 플레이어 B가 작업을 마친 후 플레이어 A가 클릭할 수 있도록 규정했습니다.
이 기능은 여러 단계를 거쳐 구현됩니다.
1. 첫 번째 단계는 지뢰찾기 맵 장면을 생성하는 것입니다
이 알고리즘은 상대적입니다. 간단하게 설명해보자. 특정 행이나 열을 무작위로 선택하면 그리드를 찾아 금색으로 표시할 수 있습니다(-1은 금색을 의미함). mimeCnt는 생성될 골드의 양을 나타내며, 동일한 방식으로 mimeCnt 랜덤 그리드를 주기적으로 표시합니다. 생성이 완료된 후 루프를 사용하여 이러한 -1 그리드를 스캔하고 주변 그리드에 1을 추가합니다. 물론 1을 추가하려면 금이 아닌 그리드여야 합니다. 코드는 여기에 있습니다.
increaseArround는 이 금 그리드를 둘러싼 그리드에 1을 추가하는 데 사용됩니다. 구현은 비교적 간단합니다.
genMimeArr()을 실행하면 무작위로 생성된 결과는 다음과 같습니다.
-1은 금을 의미합니다. 살펴보니 별 문제 없을 것 같았습니다. 다음으로 webSocket에 연결하겠습니다.
(js 버전입니다. 사실 맵 씬을 생성하는 작업은 백그라운드에서 생성됩니다. 이번 js 버전은 데모일 뿐 알고리즘은 동일합니다.)
2. WebSocket을 지원하는 서버가 필요합니다
이 예에서는 Python의 tornado 프레임워크를 사용하여 구현합니다(tornado는 tornado.websocket 모듈을 제공합니다). 물론 독자는 webSocket용으로 특별히 설계된 js 언어 서버인 Socket.io를 사용할 수도 있습니다. 또한 webSocket(플래시 또는 Comet 구현)을 지원하지 않는 브라우저와의 호환성도 제공합니다.
저자는 tornado를 선호합니다. 저는 수년 동안 백그라운드 개발을 해왔습니다. 가장 많이 사용되는 프레임워크 중 하나가 NIO 모델인데, 동일한 rps와 java가 700이 필요할 수 있습니다. -800M. 메모리, Tornado는 30-40M만 필요하므로 4G 메모리가 있는 시스템에서 수백 개의 Tornado 서비스를 실행할 수 있지만, 죄송하지만 Java는 3개의 가상 시스템만 실행할 수 있습니다. 마이크로서비스 시대에 이는 소규모 기업에게 매우 중요합니다. 물론 독자가 Java에 익숙하다면 netty 프레임워크를 선택하여 사용해 볼 수도 있습니다.
webSocket에 tornado를 사용하는 또 다른 이점은 동일한 서비스(포트)에서 webSocket과 http 프로토콜을 모두 지원할 수 있다는 것입니다. 토네이도의 공식 데모 코드는 두 프로토콜을 동시에 사용하는 방법을 보여줍니다. 이 게임에서는 다음과 같이 사용할 수 있습니다. 사용자가 홈페이지에 들어가서 http 프로토콜을 사용하여 현재 방 번호와 데이터를 가져옵니다. 홈페이지가 가장 많이 열려 있기 때문에 홈페이지에 들어간 사용자가 반드시 게임을 할 필요는 없습니다. 따라서 홈페이지에 webSocket 링크를 설정할 필요가 없습니다. webSocket 링크는 주로 빈번한 요청 및 푸시 작업을 해결하는 데 사용됩니다. 홈 페이지에는 요청 작업이 하나만 있습니다. 방 번호를 선택한 후 다음 게임 페이지로 이동하여 webSocket 링크 설정을 시작하세요.
3. 클라이언트
WeChat 애플릿 개발 도구를 사용하는 경우 도구 내에서 직접 연결 시 도메인 이름 보안 오류가 보고됩니다. 안전한 도메인 이름만 필요합니다. 그래서 같은 방식으로 여기서는 계속해서 도구의 소스 코드를 수정하고 관련 줄만 변경합니다. 수정 방법은 다음과 같습니다.
asdebug.js에서 다음 줄을 찾아 다음과 같이 변경합니다. 만약 (거짓) .
`if (!i(r, "webscoket"))
수정하기 너무 귀찮은 독자님들도 제가 크랙한 IDE를 바로 사용하실 수 있습니다. 웹소켓 링크를 시작하는 코드도 비교적 간단합니다.
`wx.connectSocket({ url: webSocketUrl, });
在调用这个请求代码之前,先添加下事件监听,这样才知道有没有连接成功:ff9d32c555bb1d9133a29eb4371c1213 `
wx.onSocketOpen(function(res){ console.log('websocket Open.'); });
`连接失败的事件:
wx.onSocketError(function(res){ console.log('websocket fail'); }) `
수신됨 서버에서 메시지가 전송될 때 트리거되는 이벤트:
` wx.onSocketMessage(function(res){ console.log('received msg: ' + res.data); })
当链接建立之后,发送消息的方法如下:ff9d32c555bb1d9133a29eb4371c1213 `
Message sent
링크를 설정하려면 wx.connectSocket이 성공하기 전에 여러 번의 핸드셰이크와 일정 시간이 필요하므로 wx.sendSocketMessage 메시지는 오류를 보고합니다. 연결이 성공적으로 설정되지 않은 경우 처음으로 링크가 설정될 때 데이터가 전송될 정보를 저장하는 데 사용됩니다. 순회되고 메시지가 하나씩 꺼내지고 다시 전송됩니다. 이 로직을 다음과 같이 send 메소드로 캡슐화합니다:
` function sendSocketMessage(msg) { if (typeof(msg) === 'object') { // 只能发送string msg = JSON.stringify(msg); } if (socketOpened) { // socketOpened变量在wx.onSocketOpen时设置为true wx.sendSocketMessage({ data:msg }); } else { // 发送的时候,链接还没建立 socketMsgQueue.push(msg); } }
1. 홈페이지 항목
모델을 단순화하고 webSocket에 집중하기 위해 홈페이지를 방번호를 직접 입력할 수 있는 형태로 만들었습니다. 시간과 능력이 있는 독자라면 홈 페이지를 방 목록으로 만들어 각 방에 몇 명이 플레이하고 있는지 표시할 수 있다. 한 사람만 있는 사람도 들어가서 함께 놀 수 있다. 나중에 보기 모드를 추가할 수도 있고, 다른 사람의 방을 클릭하여 다른 사람이 어떻게 플레이하는지 볼 수도 있습니다.
방 번호의 입력 구성 요소를 채우고, 이벤트를 추가하고, 해당 값을 가져옵니다. event.detail.value 및 setData를 이 페이지에 가져옵니다.
"게임 시작"을 클릭한 다음 앱의 globalData에 방 번호를 저장한 다음 wx.navigateTo를 메인 게임 페이지 인덱스로 이동합니다.
이 페이지는 비교적 간단합니다.
2. 메인 게임 페이지
웹소켓 링크를 처리하는 데 특별히 사용되는 websocket/connect.js 모듈을 캡슐화합니다. 두 가지 주요 방법이 있습니다. connect는 webSocket 링크를 시작하고 send는 데이터를 보내는 데 사용됩니다.
인덱스 메인 페이지:
초기화 상태, 9x9 그리드, 각 그리드는 실제로 버튼 버튼입니다. 우리가 생성한 지도 장면 데이터는 각 그리드에 해당합니다. 예를 들어 1은 주변 지역에 금이 1개 있음을 의미하고, 0은 주변 지역에 금이 없음을 의미하며, -1은 이 그리드가 금임을 의미합니다. 더 많이 찾을수록 점수가 높아집니다.
여기서는 보안 문제를 논의합니다. 믿거나 말거나: 프런트 엔드에서 수행되는 대부분의 보안 조치는 신뢰할 수 없습니다. 위 그림의 행렬(각 그리드 뒤의 데이터)은 프론트 엔드에 배치하면 안 됩니다. 왜냐하면 js 코드를 디버깅할 수 있기 때문입니다. 해당 변수에 중단점을 설정하면 전체 행렬 데이터를 볼 수 있고 알 수 있습니다. 어떤 그리드 당신이 금이라면 속일 수 있습니다. 이것은 매우 불공평합니다. 따라서 가장 좋은 방법은 이러한 행렬 데이터를 백엔드에 저장하는 것입니다. 사용자가 조작할 때마다 사용자가 클릭한 좌표가 백그라운드로 전송되고, 그러면 백그라운드는 해당 좌표가 어떤 데이터인지 판단하여 프런트엔드로 반환합니다. 많은 양의 데이터 전송이 필요한 것처럼 보이는 이 대화형 방식은 실제로 리소스를 낭비하지 않습니다. 사용자의 모든 클릭 동작이 백그라운드에 보고되어 게임의 다른 플레이어가 어떤 그리드를 클릭했는지 알 수 있기 때문입니다. 어차피 데이터는 전송되어야 하므로 좌표도 전송해야 하는데, 이런 방식으로 프런트 엔드에서는 어떤 그리드에 어떤 데이터가 포함되어 있는지 알 필요가 없습니다. 왜냐하면 백그라운드의 푸시 메시지가 알려주기 때문입니다.
이런 방식으로 우리는 프런트엔드에 매트릭스 데이터를 저장하는 문제를 우회합니다. 하지만 어떤 그리드가 열려 있는지, 내부에 어떤 데이터가 있는지 등 현재 행렬 상태를 저장하려면 배열이 필요합니다. 즉, 열린 필드에 그리드를 저장해야 합니다. 따라서 백그라운드에서 두 개의 데이터를 저장해야 합니다. 하나는 모든 매트릭스 데이터, 즉 지도 장면 데이터이고, 다른 하나는 양 당사자의 인터페이스를 동기화하는 데 사용되는 현재 상태 데이터입니다.
3. 엔드 페이지
게임 종료 판정 조건은 필드의 모든 골드가 채굴된 것입니다. 이 조건은 백그라운드에서도 판단됩니다.
사용자가 금을 채굴할 때마다 그 금이 마지막인지 확인하는 추가 판단 로직이 백그라운드에 추가됩니다. 그렇다면 게임의 모든 플레이어에게 오버 유형 메시지를 보냅니다.
플레이어 단말기가 이 메시지를 받으면 현재 게임을 종료하고 종료 페이지로 이동합니다.
没有专门的设计师,随便网上偷了张图片贴上去,界面比较丑。下方显示自己的得分和当前的房间号。
1、代码结构
前端代码,分了几个模块:pages放所有的页面,common放通用的模块,mime放挖金子的主逻辑(暂时没用到),res放资源文件,websocket放webSocket相关的处理逻辑。
后台代码,读者稍微了解一下就行了,不讨论太多。里面我放了docker文件,熟悉docker的读者可以直接一个命令跑起整个服务端。笔者在自己的服务器上跑了这个webSocket服务,ip和端口已经写在前端代码里,读者轻虐。可能放不久,读者可以自己把这个服务跑起来。
2、消息收发
(1)消息协议
我们简单地定义下,消息的格式如下。 发送消息:
`{type: 'dig', …}
服务器返回的消息:
{errCode: 0, data: {type: 'dig', …} }
因为webSocket类型的消息跟传统的http请求不太一样,http请求没有状态,一个请求过去,一会儿就返回,返回的数据肯定是针对这个请求的。而webSocket的模型是这样的:客户端发过去很多请求,然后也不知道服务器返回的数据哪个是对应哪个请求,所以需要一个字段来把所有的返回分成多种类型,并进行相应的处理。
(2)发送消息
发送消息就比较容易了,上面我们定义了一个send方法及未连接成功时的简单的消息列表。
(3)接收消息
读者在阅读代码的时候,可能会有一个疑惑,websocket/connect.js里只有send发送方法,而没有接收推送消息的处理,那接收消息的处理在哪?怎么关联起来的?
websocket/目录里面还有另一个文件,msgHandler.js,它就是用来处理接收消息的主要处理模块:
从服务器推送过来的消息,主要有这三种类型:1挖金子操作,可能是自己的操作,也可能是对方的操作,里面有一个字段isMe来表示是否是自己的操作。接收到这类消息时,会翻转地图上相应的格子,并显示出挖的结果。2创建或进入房间的操作,一个房间有两个用户玩,创建者先开始。3游戏结束的消息,当应用接收到这类消息时,会直接跳转到结束页面。
这个处理逻辑,是在websocket/connect.js的wx.onSocketMessage回调里关联上的。
在消息的收发过程中,每个消息交互,调试工具都会记录下来。可以在调试工具里看到,在NetWork->WS里就可以看到:
3、前端挖金子
代码如下:
var websocket = require('../../websocket/connect.js'); var msgReceived = require('../../websocket/msgHandler.js'); Page({ data: { mimeMap: null, leftGolds: 0, // 总共有多少金子 score: 0, // 我的得分 roomNo: 0 // 房间号 }, x: 0, // 用户点中的列 y: 0, // 用户点中的行 onLoad: function () { var roomNo = app.getRoomNo(); this.setData({ roomNo: roomNo }); // test // websocket.send('before connection'); if (!websocket.socketOpened) { // setMsgReceiveCallback websocket.setReceiveCallback(msgReceived, this); // connect to the websocket websocket.connect(); websocket.send({ type: 'create' }); } else { websocket.send({ type: 'create', no: roomNo }); } }, digGold: function(event) { // 不直接判断,而把坐标传给后台判断 // 被开过的就不管了 if (event.target.dataset.value < 9) { return; } // 取到这格的坐标 this.x = parseInt(event.target.dataset.x); this.y = parseInt(event.target.dataset.y); console.log(this.x, this.y); // 上报坐标 this.reportMyChoice(); }, reportMyChoice: function() { roomNo = app.getRoomNo(); websocket.send({ type: 'dig', x: this.x, y: this.y, no: roomNo }); }, });
在page的onLoad事件里,先更新界面上的房间号信息。然后开始我们的重点,websocket.connect发起webSocket链接,websocket是我们封装的模块。然后把我们msgHandler.js处理逻辑设置到服务端推送消息回调里面。接着,发送一个create消息来创建或加入房间。服务端会对这个消息做出响应,返回本房间的地图场景数据。
digGold是每个格子的点击事件处理函数。这儿有一个逻辑,一个格子周边最多有8个格子,所以每个格子的数据最大不可能大于8,上面代码中可以看到有一个9,这其实是为了跟0区分,用来表示场上目前的还没被翻开的格子的数据,用9来表示,当然你也可以用10,100都行。
wxml的矩阵数据绑定代码如下:
` <view wx:for="{{mimeMap}}" wx:for-item="row" wx:for-index="i" class="flex-container"> <button wx:for="{{row}}" wx:for-item="cell" wx:for-index="j" class="flex-item {{cell<0?'gold':''}} {{cell<9?'open':''}}" bindtap="digGold" data-x="{{j}}" data-y="{{i}}" data-value="{{cell}}"> {{cell<9?(cell<0?'*':cell):"-"}} </button> </view>
4、服务端实现
简单的提一下就好,因为后台不是本系列文章的重点,虽然这个demo的开发也花了大半的时候在写后台。前后端的消息交互,借助了webSocket通道,传输我们自己定义格式的内容。上面有个截图显示了后台代码目录的结构,划分得比较随意,handlers里存放了的是主要的处理逻辑。webSocketHandler是入口,在它的on_message里,对收到的客户端的消息,根据类型进行分发,dig类型,分发到answerHandler去处理,create类型,分发到roomHandler里去处理。
还有一点稍微提一下,本例子中的后台webSocket消息处理也跟传统的http处理流程有一点不一样。就是在最后返回的时候,不是直接返回的,而是广播的形式,把消息发送给所有的人。比如用户A点击了格子,后台收到坐标后,会把这个坐标及坐标里的数据一起发送给房间里的所有人,而不是单独返回给上报坐标的人。只是会有一个isMe字段来告诉客户端是否是自己的操作。
总之,在做webSocket开发的时候,上面提到的,前后端都可能会有一些地方跟传统的http接口开发不太一样。读者尝试在做webSocket项目的时候,转换一下思维。
最后提下一个注意点:微信小程序的websocket链接是全局只能有一个,官方提示:“ 一个微信小程序同时只能有一个 WebSocket 连接,如果当前已存在一个 WebSocket 连接,会自动关闭该连接,并重新创建一个 WebSocket 连接。 ”
위 내용은 WeChat 애플릿 개발을 위한 웹소켓 예제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!