[遊戲服務器]第一章:多人聊天室-客戶端

遊戲服務器


  • 多人房間javascript

  • 高併發html

  • 低延時vue

  • 數據可靠java

  • ...node

那麼怎麼去實現這些功能呢,下面我將會帶着你們一塊兒去探尋遊戲服務器的奧祕webpack

我不是巨人,我只是站在巨人的肩膀上
我將會分城多個章節去研究遊戲服務器的開發;依舊是 自上而下,由表及內,由淺入深。git

第一章:解決多人房間問題


準備工做github

思考方向
多人房間:進入房間的用戶,能夠感知到該房間內其餘的用戶,其餘用戶也能夠感知該用戶。網絡聊天室就是最多見的多人聊天的實現,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

//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>
初始化src目錄
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

//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設計


根據vuex的核心思想

初始化vuex目錄

cd vuex
touch store.js //管理state和mutations
touch actions.js //管理dispatch
touch getters.js //經過純粹的函數獲取到state的值
store.js 實現

//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

//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

//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;

views頁面實現


兩個路由對應兩個界面

join.vue 加入房間頁面
//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頁面,聊天頁面

//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

至此,聊天室服務端和客戶端可以跑起來了,你們能夠下載源代碼去試一試,也能夠本身擼出新高度,本文旨在自我學習與分享。若有錯誤或者不理解的能夠留言。

相關文章
相關標籤/搜索