多人房間javascript
高併發html
低延時vue
數據可靠java
...node
那麼怎麼去實現這些功能呢,下面我將會帶着你們一塊兒去探尋遊戲服務器的奧祕webpack
我不是巨人,我只是站在巨人的肩膀上
我將會分城多個章節去研究遊戲服務器的開發;依舊是 自上而下,由表及內,由淺入深。git
準備工做
github
新建一個git項目 game-serverweb
思考方向
多人房間:進入房間的用戶,能夠感知到該房間內其餘的用戶,其餘用戶也能夠感知該用戶。網絡聊天室就是最多見的多人聊天的實現,ex. Slack 等。ok!work!work!vue-router
多人聊天室根據業務拆成 服務端和客戶端,先後端分離;
mkdir game-server //新建項目目錄
服務端咱們選擇了兼容性最好的socket.io
cd game-server mkdir gm-server //服務端 cd gm-server && npm init -y //默認初始化 npm install --save socket.io
因爲最近正在學習vue.js,就順手拿vue來練練手
vue init webpack gm-client //使用vue官方推薦的項目構建工具vue-cli來初始化客戶端,依舊eslint,單元測試、端到端測試的都選n
個人客戶端才用的是 vue+vuex+vue-router來進行開發,若是對vue+vuex+vue-router 三者結合有些許生疏的話,能夠參考[vue+vuex+vue-router] 強擼一發暗黑風 markdown 日記應用;因此重複的我就不贅述了,咱們把重心放在具體實現上。
我有一個愛好,但願在紙上畫畫寫寫,作到心中有物,言之有物
上面兩張圖夠簡單吧,加入房間頁面 和 聊天頁面,同理,路由也就有了兩個join 和 index
cd gm-client npm install -D vuex vue-router socket.io
//index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>gm-client</title> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
cd src mkdir views mkdir vuex touch router.js
//main.js import Vue from 'vue' import App from './App' import VueRouter from 'vue-router' import {get_token} from './vuex/getters' import store from './vuex/store' import configRouter from './router' Vue.use(VueRouter) var router = new VueRouter(); configRouter(router) router.beforeEach((transition)=>{ const token = get_token(store.state) if(transition.to.auth){ if(token){ transition.next() }else { const redirect = encodeURIComponent(transition.to.path); transition.redirect({ name: 'join', query: { redirect } }); } }else { transition.next() } }) router.start(Vue.extend(App),'#app') export default router;
//app.vue <template> <div id="main"> <button id="delay1" v-bind:class="[delay_flag?'green':'red']"></button> <button id="delay2" v-bind:class="[delay_flag?'green':'red']"></button> <button id="delay3" v-bind:class="[delay_flag?'green':'red']"></button> <button id="delay4" v-bind:class="[delay_flag?'green':'red']"></button> <span id="delay_flag">{{get_delay}}ms</span> <router-view></router-view> </div> </template> <script> import {get_delay} from './vuex/getters' import {connect} from './vuex/actions' import store from './vuex/store'; export default { store, vuex:{ getters:{ get_delay }, actions:{ connect } }, ready(){ this.connect() }, computed:{ delay_flag(){ return this.get_delay<60 } } } </script> <style> html { height: 100%; } body { width: 100%; height: 100%; padding:0 0; margin:0 0; } #main { width:500px; margin: 0 auto; height: 100%; } .green { background-color:#86e468; } .red { background-color:red; } #delay1 { padding:0 0; width:5px; height:5px; border-radius: 50%; border:none; } #delay2 { padding:0 0; width:7px; height:7px; border-radius: 50%; border:none; } #delay3 { padding:0 0; width:9px; height:9px; border-radius: 50%; border:none; } #delay4 { padding:0 0; width:11px; height:11px; border-radius: 50%; border:none; } #delay_flag { font-size:5px; } </style>
//router.js export default (router)=>router.map({ '/':{ name:'join', component:require('./views/join') }, '/index':{ name:'index', component:require('./views/index'), auth:true } })
根據vuex的核心思想
cd vuex touch store.js //管理state和mutations touch actions.js //管理dispatch touch getters.js //經過純粹的函數獲取到state的值
//store.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { token:'', account:'', room:'', socket:undefined, delay:0, messages:{} } const mutations = { CHANGE_SOCKET (state,socket){ state.socket = {...socket} }, CHANGE_ACCOUNT (state,account){ state.account = account }, CHANGE_ROOM (state,room){ state.room = room }, CHANGE_DELAY (state,delay){ state.delay = delay }, CHANGE_TOKEN (state,token){ state.token = token }, CHANGE_MESSAGES (state,data){ if(state.messages[data.room] && state.messages[data.room].length){ state.messages[data.room].splice(0,0,data) }else{ console.log(data) var new_room = {} Object.defineProperty(new_room,data.room.toString(),{ value: [], writable: true, enumerable: true, configurable: true }) console.log(new_room) state.messages = Object.assign({},state.messages,new_room) console.log(state.messages) state.messages[data.room].push(data) } } } export default new Vuex.Store({ state, mutations })
//actions.js import io from 'io'; let socket; import store from './store' import router from '../main' export const connect = ({dispatch}) =>{ socket = io('http://localhost:3000') dispatch('CHANGE_SOCKET',socket) start_socket() } export const input_account = ({dispatch},e) => dispatch('CHANGE_ACCOUNT',e.target.value); export const input_room = ({dispatch},e) => dispatch('CHANGE_ROOM',e.target.value); export const join = ({dispatch},account,room)=>{ socket.emit('join',{account,room}) } export const post_message = ({dispatch},room,content) =>{ socket.emit('post',{room:room,content:content}) } function start_socket(){ socket.on('conn',function(data){ console.log(data) }) socket.on('heart',function(_data){ var data = {..._data,timestamp:new Date().getTime()} store.dispatch('CHANGE_DELAY',data.timestamp-data._timestamp) }) socket.on('join',function(_data){ store.dispatch('CHANGE_TOKEN',_data._id) router.go({name:'index'}) }) socket.on('message',function(_data){ console.log(_data) store.dispatch('CHANGE_MESSAGES',_data) }) }
//getters.js export const get_account = (state) => state.account; export const get_room = (state) => state.room; export const get_delay = (state) => state.delay; export const get_token = (state) => state.token; export const get_messages = (state) => state.messages;
兩個路由對應兩個界面
//join.vue <template> <div id="join-form" v-on:keyup.enter="join_btn"> <h1>多人聊天室</h1> <input @input="input_account" value="{{account}}" placeholder="用戶名"><br/> <input @input="input_room" value="{{room}}" placeholder="房間名"><br/> <button @click.prevent.stop="join_btn">進入房間</button> </div> </template> <script> import {input_account,join,input_room} from '../vuex/actions' import {get_account,get_room} from '../vuex/getters' export default { vuex:{ actions:{ input_account, join, input_room }, getters:{ account:get_account, room:get_room } }, methods:{ join_btn(){ this.join(this.account,this.room) } } } </script> <style> #join-form { width:500px; margin:0 auto; text-align: center; } </style>
//index.vue <template> <div id="chat-room"> <h1>[{{room}}]: welcome {{account}}</h1> <div id="main"> <ul> <li v-for="message in room_messages"> {{message.from_account}}:{{message.content}} </li> </ul> </div> <div id="post_block"> <input v-on:keyup.enter="post_btn" v-model="content" > <button v-on:keyup.enter="post_btn" @click="post_btn">發送</button> </div> </div> </template> <script> import {get_account,get_room,get_messages} from '../vuex/getters' import {post_message} from '../vuex/actions' export default { data(){ return { content:'' } }, vuex:{ getters:{ account:get_account, room:get_room, messages:get_messages }, actions:{ post_message } }, methods:{ post_btn(){ this.post_message(this.room,this.content) this.content= '' } }, computed:{ room_messages:{ get(){ return this.messages[this.room] } } } } </script> <style scoped> #chat-room { width: 500px; margin:0 auto; text-align: left; } #main{ width:100%; height:400px; overflow: scroll; font-size:10px; text-align: left; background-color: #f2f2f2; } #post_block{ float:right; width:200px; height:100px; } </style>
cd gm-server && node index.js //再開一個terminal cd gm-client npm run dev
至此,聊天室服務端和客戶端可以跑起來了,你們能夠下載源代碼去試一試,也能夠本身擼出新高度,本文旨在自我學習與分享。若有錯誤或者不理解的能夠留言。