《球球大作戰》原始碼解析:伺服器與客戶端架構

遊資網發表於2019-02-15


系列文章
《球球大作戰》原始碼解析——(1)執行起來
《球球大作戰》原始碼解析:伺服器與客戶端架構
《球球大作戰》原始碼解析:移動演算法
《球球大作戰》原始碼解析(6):碰撞處理

《球球大作戰》原始碼解析(7):遊戲迴圈
《球球大作戰》原始碼解析(8):訊息廣播

鑑於agar.io型別遊戲的火爆場面,一些公司紛紛效仿,一時間出現各種《XX大作戰》型別的遊戲。出於學習的目的,亦是做些技術和方案儲備,接下來會有大概10篇文章,分析下面這款使用nodejs編寫的開源“球球大作戰”。由於該遊戲採用服務端運算、客戶端顯示的方式,服務端的邏輯處理是該原始碼的重點,故而系列文章主要針對服務端。通過這套原始碼,可以學習到“一種基於nodejs的簡單伺服器實現方法”“一種簡單的服務端物理邏輯的實現方式”“一種基於redis pub/sub的跨服設計思想”“nodejs語法、框架及其使用方式”等內容。

系列文章將會分析huytd/agar.io-clone的原始碼,這是一套簡約而不簡單的Agar.IO實現。該專案使用NodeJS開發,使用socket.IO作為網路通訊,使用HTML5實現客戶端。


一、執行起來

下圖為遊戲執行畫面,遊戲規則如下。

1、玩家可以移動滑鼠控制小球

2、當小球吞食場景中的食物或其他玩家控制的小球時,玩家控制的小球會變大

3、小球越大,移動速度越慢

4、小球的質量代表它的大小,質量為它吞食的食物或其他玩家的質量之和

5、遊戲目標是儘可能的吞食其他玩家,使小球變大

6、玩家剛出生時會有無敵屬性,直到它吞食食物

7、每當有玩家進入遊戲,場景中會生成3個食物

8、每當吞食食物時,場景中亦會生成一個新的食物

《球球大作戰》原始碼解析:伺服器與客戶端架構

第一步便是要讓遊戲執行起來,只有執行起來了,才談得上後續的原始碼分析。為了“從零開始”,筆者購買Ubuntu系統的騰訊雲,新的系統幾乎沒有安裝額外軟體,一步一步安裝所需的軟體,然後將遊戲執行起來吧。筆者選用了最低一檔配置的伺服器,花費近50大洋(此處是不是應該發個求贊助的連結?)配置如下圖所示。

《球球大作戰》原始碼解析:伺服器與客戶端架構

1、安裝nodeJs

遊戲使用nodejs開發,那就必須要安裝nodejs,可以有兩種方法安裝。

方法1:輸入sudo apt install nodejs,這是最簡單的安裝方法了。不過使用該方式安裝的程式名叫為nodejs,而不是普遍使用的node。可以使用sudo ln-s/usr/bin/nodejs/usr/bin/node建立名為node的連線,以解決這個問題。

方法2:下載原始碼、編譯、安裝。具體可以參考這篇文章在Ubuntu下安裝Node.JS的不同方式-技術◆學習|Linux.中國-開源社群(文章裡使用的node-v6.9.5要改為最新版的)

完成後,可以使用node-v檢視nodejs版本號,以驗證是否成功安裝。

2、上傳程式碼檔案

從github上下載原始碼,然後上傳到linux伺服器上。如下圖所示,筆者將原始碼上傳到/home/ubuntu/agar.io-clone-master目錄下

《球球大作戰》原始碼解析:伺服器與客戶端架構

3、安裝npm

npm(node package manager)是nodejs的包管理和分發工具,一般安裝nodejs後都需要安裝該軟體,可以使用以下命令安裝:sudo apt install npm

4、安裝gulp

專案使用到了gulp,需要安裝它。gulp是一個前端構建工具,開發者可以使用它在專案開發過程中自動執行常見任務,比如複製檔案,比如替換檔案中某些字元。進入原始碼目錄,執行sudo npm install-g gulp即可安裝。

5、安裝專案所需的包檔案

進入原始碼目錄,執行npm install即可安裝專案所需包檔案。npm install會檢查當前目錄下的package.json檔案,檔案包含了專案所需的模組,npm根據該檔案的描述下載這些檔案並把模組放到./node_modules目錄下。關於package.json的格式可以參考這篇文章package.json for NPM檔案詳解

6、執行伺服器

在原始碼目錄下執行gulp run,可以看到伺服器啟動的提示資訊。

7、執行客戶端

執行瀏覽器,輸入地址即可,筆者的騰訊雲ip為139.199.179.39,由於預設配置了3000埠,所以要輸入http://139.199.179.39:3000/,即可看到如下的遊戲介面。

《球球大作戰》原始碼解析:伺服器與客戶端架構

在筆者的試驗中,該頁面報錯,點選按鈕沒有反應。原因是src/client中的index.html最後面有這麼一句,<script src="//code.jquery.com/jquery-2.2.0.min.js"></script>,該語句用於載入jquery的,而http://code.jquery.com/jquery-2.2.0.min.js無法訪問(或國內網路訪問速度慢),導致報錯。只要換個檔案地址即可,例如改成下面這樣:

<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script >

執行遊戲,服務端也會列印出相應的資訊,如下圖所示。

《球球大作戰》原始碼解析:伺服器與客戶端架構

把遊戲執行起來後,下一步就要分析下游戲的流程了。

二、程式流程

在解析原始碼之前,需要先了解該專案的程式流程,瞭解客戶端和服務端是如何執行和通訊的。本文是wiki文件Game Architecture的翻譯,以幫助讀者從大方向上了解《球球大作戰》。

程式架構

遊戲程式使用NodeJs編寫,服務端通過http://Socket.IO建立WebSocket服務並預設監聽3000號埠。程式還使用ExpressJS建立一個簡單的HTTP伺服器,它負責html頁面的顯示。index.html是遊戲主頁面,它通過Canvas渲染遊戲,通過Javascript指令碼和服務端通訊。

《球球大作戰》原始碼解析:伺服器與客戶端架構

目錄結構該專案由3部分組成:

1、配置檔案,如package.json,config.json等等

2、客戶端程式

3、服務端程式

配置檔案package.json列出了專案所需的庫檔案,讀者只需在專案目錄下執行“npm install”即可自動安裝這些檔案。package.json的格式可以參考下面的文章:

npm package.json屬性詳解

遊戲客戶端

client資料夾裡包含了客戶端所需的程式碼,它是一個簡單的HTML檔案,該檔案會通過canvas繪製遊戲場景、聊天框等元素。

《球球大作戰》原始碼解析:伺服器與客戶端架構

js/app.js是客戶端的邏輯程式碼,它實現了畫面渲染、網路延遲檢測、觀戰模式、聊天等功能,處理了滑鼠輸入、服務端通訊等事項。遊戲採用服務端運算模式,客戶端只是負責將服務端發來的資料顯示到螢幕上,以及接收滑鼠事件。

客戶端程式使用了requestAnimationFrame程式渲染迴圈,而不是使用setInterval,這讓遊戲有著更好的渲染效能。你可以試著修改程式碼,呼叫setInterval方法,看看低效率的渲染是個啥樣子。
(function animloop(){
  requestAnimFrame(animloop);
  gameLoop();
})();

to

setInterval(gameLoop, 16);


遊戲服務端

server/server.js包含了服務端的配置和邏輯處理,配置了諸如食物質量、移動速度、無敵狀態的最大質量,處理了食物顏色計算、碰撞檢測、玩家移動處理等等事項。

《球球大作戰》原始碼解析:伺服器與客戶端架構

所有的遊戲邏輯都在服務端處理,服務端和客戶端的通訊有著下面幾個要點。

1、服務端使用list儲存玩家列表,而不是使用array,使用list儲存食物列表,而不是使用array。服務端儲存著socket列表,用於記錄所有客戶端連線。

2、之前的版本設定了一個定時器,每隔幾秒鐘就產生一些食物,但這種方法的效率不高,會延遲服務端處理速度。所有在此版本中使用了一種新的方式來產生食物,當一個玩家進入遊戲時,程式會隨機產生3個食物(可以修改配置檔案的newFoodPerPlayervariable改變該數值),當玩家吃掉一個食物時,程式會產生另外一個食物(可以修改配置檔案的respawnFoodPerPlayer改變該數值)。如果場景中的食物數量大於50(配置檔案的maxFoodCount),服務端會停止產生新食物。

客戶端服務端通訊

客戶端與服務端通訊可以分為兩個部分,分別是登入認證和遊戲內通訊。

登陸認證

《球球大作戰》原始碼解析:伺服器與客戶端架構

當一個玩家開啟遊戲網頁,他先會看到一個輸入使用者名稱的對話方塊,點選“Play”按鈕後,客戶端發起socket連線,服務端accept連線後發出welcome協議,並把該客戶端的UserID附帶在協議中。

當客戶端收到welcome協議,它會返回附帶使用者名稱的gotit協議。

當服務端收到gotit協議,它會其它的已連線玩家廣播playerJoin協議,告訴他們有新的玩家加入。其它玩家收到該協議後,會在螢幕上繪製這個新加入的角色。

此時,對於新加入的玩家來說,遊戲剛剛開始。

遊戲內通訊

遊戲內通訊分為3個部分,分別是遊戲邏輯、聊天和Ping(測試網路延遲)。

遊戲邏輯

玩家在遊戲中會有移動、吞食食物、吞食其他玩家三種行為,這些邏輯全部由服務端運算,客戶端只是根據運算結果將影像顯示在對應的位置上。

移動

《球球大作戰》原始碼解析:伺服器與客戶端架構

當玩家移動滑鼠,小球會朝著滑鼠的位置移動。客戶端會傳送附帶了目的地座標的playerSendTarget協議。服務端收到協議後會更新小球的運動狀態,然後向該客戶端回覆serverTellPlayerMove協議,然後傳送serverUpdateAllPlayers給其他客戶端,讓全部客戶端更新所有玩家的座標。

小球移動期間,服務端還會檢測小球是否吞食了食物,或者吞食了其他玩家。

吞食食物

《球球大作戰》原始碼解析:伺服器與客戶端架構

服務端維持了users列表和food列表來儲存所有的小球和食物的資訊,如果小球碰到食物,服務端會執行相應的邏輯,增加小球質量、刪除列表裡的食物、產生新的食物。然後服務端廣播serverUpdateAllPlayers和serverUpdateAllFoods協議,讓客戶的更新玩家和食物。

吞食其他玩家

《球球大作戰》原始碼解析:伺服器與客戶端架構

如果小球吞食了其他玩家的小球,服務端會比較兩者的質量和距離,質量小的被吞食。服務端會傳送RIP協議告訴質量下的玩家他死掉了,然後斷開與該玩家的連線,同時在users列表裡刪除他。還會廣播serverUpdateAllPlayers協議通知客戶端。

聊天

聊天的流程如下圖所示

當玩家在聊天框中輸入資訊並按下Enter鍵時,客戶端向服務端傳送playerChat協議,服務端收到協議後廣播serverSendPlayerChat協議。

《球球大作戰》原始碼解析:伺服器與客戶端架構

當客戶端收到serverSendPlayerChat協議時,它會解析該協議,將聊天內容顯示到螢幕上。

Ping(延遲檢測)

網路遊戲都會實現ping機制來檢測客戶端和服務端之間的延遲,而它的實現也很簡單。

《球球大作戰》原始碼解析:伺服器與客戶端架構

檢測開始時,客戶端會儲存當前的開始時間,然後傳送ping協議給服務端,服務端收到後,會返回pong協議。客戶端收到pong協議會計算時間差,如果時間差很大,說明網路延遲很嚴重。

願這份文件能夠協助讀者理解agar.io-clone這個專案,你還可以繼續完善這款遊戲,將它做得更好。也希望各位能夠在專案wiki中分享心得。

三、gulp工具

執行遊戲使用的命令是gulp run,agar.io-clones使用了nodejs開發,gulp是基於nodejs的一個工具,它能夠批量的做一些檔案操作。gulp run意思是執行目錄下gulpfile.js下的run任務,那麼原始碼中使用了gulp的哪些功能呢?這篇文章將會做個簡單介紹。

gulp能自動化地完成javascript/coffee/sass/less/html/image/css等檔案的的測試、檢查、合併、壓縮、格式化、瀏覽器自動重新整理、部署檔案生成,並檢測檔案變化。在實現上,gulp鑑了Unix作業系統的管道(pipe)思想,前一級的輸出,直接變成後一級的輸入。

關於gulp入門,可以參考下面的文章:

一點|gulp詳細入門教程

入門指南-gulp.js中文文件
一個最簡單的示例

要使用gulp根據,當然得先安裝它,有兩種方式安裝,對應於不同的命令引數。

全域性安裝gulp:npm install--global gulp

作為專案的開發依賴(devDependencies)安裝:npm install--save-dev gulp

現在新建一個目錄並建立一個名為gulpfile.js的檔案,在裡面編寫如下程式碼

var gulp = require('gulp');gulp.task('default', function() {  // 將你的預設的任務程式碼放在這});
在目錄下執行gulp,此時程式會搜尋目錄下gulpfile.js檔案中的預設(default)任務,也就是上面程式碼中“//將你的預設的任務程式碼放在這”處的程式碼去執行。“gulp run”即表示執行名為run的任務,相關程式碼可以在專案資料夾下的gulpfile.js中看到。相關程式碼如下

gulp.task('run', ['build'], function () {
    nodemon({
        delay: 10,
        script: './server/server.js',
        cwd: "./bin/",
        args: ["config.json"],
        ext: 'html js css'
    })
    .on('restart', function () {
        util.log('server restarted!');
    });
});

程式碼解析

要看懂上面的程式碼,必須要了解gulp的一些API,知道“nodemon”等單詞到底是什麼意思,實現什麼功能,gulp的api可以參考下面的文章:

一點|gulp教程之gulp中文API

依賴

上面程式碼中的“gulp.task('run',['build'],function(){}”意為run依賴於build,當執行gulp run時,程式會先執行build任務,再執行run任務。

nodemon

先看看nodemon,詳細的解釋可以參考gulp-nodemon

nodemon是一個工具,用於專案程式碼發生變化時可以自動重啟,nodemon本意時檢測專案變化的,對專案做監控的。重啟只是它的一個功能。在上面的程式碼中,相當於執行./server/server.js這個檔案。而這個檔案其實是build任務中生成的。

build任務

接下來看看build任務是什麼樣子的,會發現build任務依賴於build-client、build-server、test、todo這4個任務,也就是說,需要按順序先執行這4個任務,才會執行build。此時我們會發現,程式碼的執行流程是build-client、build-server、test、todo、run

gulp.task('build',['build-client','build-server','test','todo']);

build-client任務

build-client處理了客戶端程式碼的建立,它用到了uglify、webpack和babel。

其中uglify表示壓縮javascript檔案,減小檔案大小(參見一點|gulp教程之gulp-uglify)

webpack表示模組打包,它能幫我們把本來需要在服務端執行的JS程式碼,通過模組的引用和依賴打包成前端可用的靜態檔案(參考《nodejs+gulp+webpack基礎實戰篇》課程筆記(三)--webpack篇-亡命小卒-部落格園)

babel是一個JavaScript轉換編譯器,它可以將ES6(下一代JavaScript規範,新增了一些新的特性和語法)轉換成ES5(可以在瀏覽器中執行的程式碼)。這就意味你可以在一些暫時還不支援某些ES6特性的瀏覽器引擎中,使用ES6的這些特性。比如說,class和箭頭方法。

pipe表示管道,下面的程式碼是指將原始檔(.src)“src/client/js/app.js”通過uglify方法壓縮,然後將壓縮後的結果通過webpack打包,然後通過babel做相容性,最後通過將檔案存入dest指定的目錄下“bin/client/js/”

gulp.task('build-client', ['lint', 'move-client'], function () {  return gulp.src(['src/client/js/app.js'])    .pipe(uglify())    .pipe(webpack(require('./webpack.config.js')))    .pipe(babel({      presets: [        ['es2015', { 'modules': false }]      ]    }))    .pipe(gulp.dest('bin/client/js/'));});
webpack()方法的引數是“require('./webpack.config.js') ”“ ./webpack.config.js”,該檔案的內容如下,它是打包的配置檔案。

module.exports = {
    entry: "./src/client/js/app.js",
    output: {
        path: require("path").resolve("./src/bin/client/js"),
        library: "app",
        filename: "app.js"
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel'
            }
        ]
    }
};

“build-client”依賴於“lint”和“move-client”,先要完成這兩個任務,程式才會執行“build-client”任務。

lint任務

“lint”任務如下所示,它使用了jshint方法。jshint是用來檢測javascript的語法錯誤的。如果有錯誤,就報告fail。

gulp.task('lint', function () {
  return gulp.src(['**/*.js', '!node_modules/**/*.js', '!bin/**/*.js'])
    .pipe(jshint({
          esnext: true
      }))
    .pipe(jshint.reporter('default', { verbose: true}))
    .pipe(jshint.reporter('fail'));
});

move-client任務

“build-client”還依賴於“move-client”程式碼如下,它只是移動一些檔案

gulp.task('move-client', function () {
  return gulp.src(['src/client/**/*.*', '!client/js/*.js'])
    .pipe(gulp.dest('./bin/client/'));
});

build-server任務

build-server任務比較簡單,它也是複製下檔案

gulp.task('build-server', ['lint'], function () {
  return gulp.src(['src/server/**/*.*', 'src/server/**/*.js'])
    .pipe(babel())
    .pipe(gulp.dest('bin/server/'));
});

test任務

build任務依賴於build-client、build-server、test和todo任務,在建了客戶端和服務端檔案後,自然需要對它測試一下,test任務呼叫了mocha方法,它是一個測試方法。

gulp.task('test', ['lint'], function () {
    gulp.src(['test/**/*.js'])
        .pipe(mocha());
});

todo任務

todo任務呼叫了todo方法,該方法會收集符合“src/**/*js”匹配符的檔案資訊,生成一個名為TODO.md的檔案。

gulp.task('todo', ['lint'], function() {  gulp.src('src/**/*.js')      .pipe(todo())      .pipe(gulp.dest('./'));});
生成的TODO.md如下圖所示。

《球球大作戰》原始碼解析:伺服器與客戶端架構

由於實際執行的檔案在是bin/目錄下,如果修改了原始檔,需要重新執行gulp run才能生效。

四、Websocket

執行服務端後,玩家只要開啟瀏覽器,輸入地址和埠,就可以看到遊戲畫面。這就意味著,遊戲服務端開了個http伺服器。Node.js標準庫提供了http模組,其中封裝了一個高效的HTTP伺服器和一個簡易的HTTP客戶端。http.Server是一個基於事件的HTTP伺服器,它的核心由Node.js下層C++部分實現,而介面由JavaScript封裝,兼顧了高效能與簡易性。http.request則是一個HTTP客戶端工具,用於向HTTP伺服器發起請求。關於http服務端的入門,可以參考下面教程。

Node.js學習(11)----HTTP伺服器與客戶端-推酷

安裝http包

使用http模組,必須先安裝它,執行npm install http命令安裝即可。

顯示Html文字

新建一個js檔案,然後輸入如下的程式碼。通過require('http').Server建立一個http伺服器,“http.listen”表示開啟監聽,如下程式碼是監聽3001埠,監聽成功後會在螢幕中列印出“[DEBUG]Listening”。http.on('request'function(){……})表示當服務端收到客戶端的請求時做出怎樣的處理,這裡向客戶端返回html資訊。

var http = require('http').Server()

http.on('request',function(req,res){
        console.log('[DEBUG] on request ' );
        res.writeHead(200,{'Content-Type':'text/html'});
        res.write('<h1>Node.js</h1>');
        res.end('<p>HelloWorld</p>');
});

http.listen(3001, function() {
    console.log('[DEBUG] Listening ' );
});

執行指令碼,然後用瀏覽器開啟3001埠,即可看到html文字。

《球球大作戰》原始碼解析:伺服器與客戶端架構

用express顯示Html檔案

Express是一個基於Node.js平臺的web應用開發框架,可以使用它指定要顯示的網頁檔案。在使用之前需要使用npm install express命令安裝express。

新建js檔案填入下面的程式碼,除了建立http伺服器外,還使用express指定了網頁目錄“__dirname+'/'”,即程式碼檔案的同一目錄下。

var express = require('express');
var app = express();
var http = require('http').Server(app)
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));

console.log("hehe");


http.listen(3001, function() {
    console.log('[DEBUG] Listening ' );
});

在同一目錄下新建index.html,輸入下面的文字。

<html>
<head>
    <title>Ssocket</title>
</head>

<body>
        <P>測試</P>

</body>
</html>

執行服務端,用瀏覽器開啟頁面,將會看到如下網頁。

《球球大作戰》原始碼解析:伺服器與客戶端架構

WebSocket介紹

談到Web實時推送,就不得不說WebSocket。在WebSocket出現之前,很多網站為了實現實時推送技術,通常採用的方案是輪詢(Polling)和Comet技術,Comet又可細分為兩種實現方式,一種是長輪詢機制,一種稱為流技術,這兩種方式實際上是對輪詢技術的改進,這些方案帶來很明顯的缺點,需要由瀏覽器對伺服器發出HTTP request,大量消耗伺服器頻寬和資源。面對這種狀況,HTML5定義了WebSocket協議,能更好的節省伺服器資源和頻寬並實現真正意義上的實時推送。

WebSocket協議本質上是一個基於TCP的協議,它由通訊協議和程式設計API組成,WebSocket能夠在瀏覽器和伺服器之間建立雙向連線,以基於事件的方式,賦予瀏覽器實時通訊能力。既然是雙向通訊,就意味著伺服器端和客戶端可以同時傳送並響應請求,而不再像HTTP的請求和響應。

具體可以參考下面的文章

使用Node.js+Socket.IO搭建WebSocket實時應用-OPEN開發經驗庫

WebSocket簡單例項

下面通過一個簡單的例子介紹WebSocket的使用方法,在安裝WebSocket後編寫如下的程式碼和html檔案。當客戶端發起連線(connection)後,它會列印出“A user connected!”

var express = require('express');
var app = express();
var http = require('http').Server(app)
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));

console.log("hehe");

io.on('connection', function (socket) {
    console.log('A user connected!', socket.handshake.query.type);
})

http.listen(3001, function() {
    console.log('[DEBUG] Listening ' );
});

html程式碼如下所示,頁面中會有一個按鈕,當點選按鈕時,會通過io.connect連線服務端

<html>
<head>
    <title>Ssocket</title>
        http://139.199.179.39:3001/socket.io/socket.io.js</a>">   
</head>

<body>
        <P>測試</P>
        <input type="button" id="btn" value="click" />
        <script type="text/javascript">
                var oBtn = document.getElementById('btn');
                        oBtn.onclick = function(){
                        var socket = io.connect('http://139.199.179.39:3001/');
                        alert("send");
                };
        </script>

</body>
</html>

執行程式,點選客戶端上的按鈕,服務端會顯示“A user connected!”

收發資訊

客戶端和服務端可要相互通訊,在下面的例子中,網頁上有connect和send兩個按鈕,點選send按鈕後,會傳送login協議,服務端收到login協議後,會列印客戶端傳來的資訊。

var express = require('express');
var app = express();
var http = require('http').Server(app)
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));

console.log("hehe");

io.on('connection', function (socket) {
    console.log('A user connected!', socket.handshake.query.type);
        
        socket.on('login', function (data) {
      console.log(data);
    });
})

http.listen(3001, function() {
    console.log('[DEBUG] Listening ' );
});
<html>
<head>
    <title>Socket</title>
        http://139.199.179.39:3001/socket.io/socket.io.js</a>">   
</head>

<body>
        <P>測試</P>
        <input type="button" id="btn1" value="connect" />
        <input type="button" id="btn2" value="send" />
        <script type="text/javascript">
                var oBtn1 = document.getElementById('btn1');
                        oBtn1.onclick = function(){
                        socket = io.connect('http://139.199.179.39:3001/');
                        alert("connect");
                };
               
                var oBtn2 = document.getElementById('btn2');
                        oBtn2.onclick = function(){
                        socket.emit('login', { name: 'LPY' });
                        alert("send");
                };
        </script>

</body>
</html>

執行程式,點選按鈕,服務端將會顯示客戶端login協議傳入的使用者名稱“LPY”,如下圖所示。

客戶端

《球球大作戰》原始碼解析:伺服器與客戶端架構

服務端

《球球大作戰》原始碼解析:伺服器與客戶端架構

客戶端回顯

在下面的程式碼中,服務端收到客戶端的login協議後會恢復客戶端loginBack協議,客戶端收到loginBack協議後會彈出對話方塊。

var express = require('express');
var app = express();
var http = require('http').Server(app)
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));

console.log("hehe");

io.on('connection', function (socket) {
    console.log('A user connected!', socket.handshake.query.type);
        
        socket.on('login', function (data) {
                console.log(data);
                socket.emit('loginBack', { result: 'success' });
    });
})

http.listen(3001, function() {
    console.log('[DEBUG] Listening ' );
});

<html>
<head>
    <title>Socket</title>
        http://139.199.179.39:3001/socket.io/socket.io.js</a>">   
</head>

<body>
        <P>測試</P>
        <input type="button" id="btn1" value="connect" />
        <input type="button" id="btn2" value="send" />
        <script type="text/javascript">
                var oBtn1 = document.getElementById('btn1');
                        oBtn1.onclick = function(){
                                socket = io.connect('http://139.199.179.39:3001/');
                                alert("connect");
                        
                                socket.on('loginBack', function (data) {   
                                        alert(data.result);
                                });
                };
               
                var oBtn2 = document.getElementById('btn2');
                        oBtn2.onclick = function(){
                                socket.emit('login', { name: 'LPY' });
                                alert("send");
                        };
        </script>

</body></html>

執行程式,結果如下圖所示。

《球球大作戰》原始碼解析:伺服器與客戶端架構

還是放個廣告吧,筆者出版的一本書《Unity3D網路遊戲實戰》充分的講解怎樣開發一款網路遊戲,特別對網路框架設計、網路協議、資料處理等方面都有詳細的描述,相信會是一本好書的。

《球球大作戰》原始碼解析:伺服器與客戶端架構

知乎專欄:https://zhuanlan.zhihu.com/pyluo


相關文章