前面文章我們對聊天的協議進行了簡單的封裝:C#實作WebSocket服務器:(04)實作聊天室-協議和后端部分
這里我們介紹下前端的封裝和實作以及演示,
前端是基于Vue來做的,理解起來也不復雜,
0、HTML結構
結構很簡單:用戶登錄框div.login、訊息發送框div.inputs、訊息顯示區div.wrapper-contents,
<div class="wrapper">
<div id="app">
<div class="login" v-if="loginStatus !== 2">
<p><input type="text" v-model="name" placeholder="請輸入名稱登錄" :disabled="loginStatus !== 0" /></p>
<p><input type="button" value="登錄" @click="login" :disabled="loginStatus !== 0" /></p>
</div>
<div v-else>
<div class="wrapper-contents">
<div class="contents" ref="contents">
<div v-for="(msg, index) in messages" :key="index" :class="getClass(msg)">
<component :is="contents" :msg="msg"></component>
</div>
</div>
</div>
<div class="inputs ">
<input v-model="message" type="text" @keyup.enter="post" placeholder="輸入訊息發送" />
<div class="buttons">
<button @click="post">發送</button>
<button @click="quit">離開</button>
</div>
</div>
</div>
</div>
</div>
1、websocket封裝
擴展了下事件功能,
1、將服務器發送給客戶端的訊息(login、enter,post,exit)映射成事件(@login,@enter,@post,@exit),方便下游程式處理,
2、將客戶端請求協議封裝成具體的方法:login、send、quit,
代碼也是流水賬邏輯,不難理解,就是注意login的邏輯是異步的:先連接服務器,連接事件后發送登錄,登錄回應收到后才會提交@login事件,
/**
* 管理websocket連接
* @param {String} wsUrl websocket地址
*/
function connection (wsUrl) {
this.wsUrl = wsUrl;
this.socket = null;
this.status = 0;
this.__events = {};
}
/**
* 簡單的事件注冊,
* @param {String} ev
* @param {Function} handler
* @param {any} context
*/
connection.prototype.on = function (ev, handler, context) {
this.__events[ev] = { handler, once: false, context: context || this };
}
/**
* 注冊一次性事件
* @param {String} ev
* @param {Function} handler
* @param {any} context
*/
connection.prototype.once = function (ev, handler, context) {
this.__events[ev] = { handler, once: true, context: context || this };
}
/**
* 呼叫事件
* @param {String} ev
* @param {...any} args
* @returns
*/
connection.prototype.emit = function (ev, ...args) {
if (!this.__events[ev]) return;
const handler = this.__events[ev];
handler.handler.apply(handler.context, args);
if (handler.once === true) {
this.__events[ev] = null;
}
};
/**
* 發送訊息
* @param {String} action
* @param {Object} payload
* @returns
*/
connection.prototype.send = function (action, payload) {
if (this.status !== 2) return;
this.socket.send(JSON.stringify({ action, payload }));
}
/**
* 退出
* @returns
*/
connection.prototype.quit = function () {
if (this.status !== 2) return;
this.socket.send(JSON.stringify({ action: 'quit', payload: {} }));
}
/**
* 登錄
* @param {String} name 用戶名
* @returns
*/
connection.prototype.login = function (name) {
if (this.status !== 2) {
this.once('wait-connected', () => this.send('login', { name: name }));
if (this.status === 0) this.connect();
return;
}
this.send('login', { name: name });
};
/**
* 連接服務器
*/
connection.prototype.connect = function () {
this.status = 1;
const that = this
const webSocket = new WebSocket(this.wsUrl);
that.emit('connecting');
webSocket.onopen = function () {
that.socket = webSocket;
that.status = 2
that.connectFailedCount = 0;
that.emit('connected');
that.emit('wait-connected');
}
webSocket.onmessage = function (ev) {
try {
const payload = JSON.parse(ev.data)
that.emit('@' + payload.action, payload.payload);
} catch (ex) {
}
};
webSocket.onclose = function (ev) {
that.status = 0;
that.emit('close', ev);
}
webSocket.onerror = function (ev) {
that.status = 0;
that.emit('error', ev);
}
}
2、業務邏輯封裝
封裝的內容是Vue實體,以及在Vue實體中訂閱、發送訊息和對頁面進行操作、展示,
對滾動條作了簡單的防抖處理,
也是流水賬,不復雜,
/**
* 防抖函式
* @param {Function} fn
* @param {Number} timeout
* @returns
*/
function lazyFunction (fn, timeout) {
var timer = 0;
return function () {
if (timer) window.clearTimeout(timer);
var args = arguments, that = this;
timer = window.setTimeout(function () {
fn.apply(that, args)
}, timeout);
};
}
/**
* 實體化Vue
*/
new Vue({
el: '#app',
data () {
return {
url: 'ws://127.0.0.1:4189/',
me: null,
loginHandler: null,
name: '',
connection: null,
loginStatus: 0,
message: '',
messages: []
}
},
watch: {
messages () {
this.updateScroll();
}
},
created () {
/**
* 初始化connection,注冊各種事件
* @ 開頭的事件為服務器發送的訊息
*/
const conn = this.connection = new connection(this.url);
conn.on('connecting', () => this.loginStatus = 1);
conn.on('close', () => this.loginStatus = 0);
conn.on('error', () => this.loginStatus = 0);
conn.on('@login', function (payload) {
this.me = { name: this.name, id: payload.connectionId }
this.loginStatus = 2;
}, this);
conn.on('@enter', (payload) => this.messages.push({ type: 'log', message: `${payload.name} 進入聊天室` }), this);
conn.on('@exit', (payload) => this.messages.push({ type: 'log', message: `${payload.name} 離開聊天室` }), this);
conn.on('@post', (payload) => this.messages.push({ type: 'post', payload }), this)
},
methods: {
/**
* 更新滾動條
*/
updateScroll: lazyFunction(function () {
this.$nextTick(() => {
const contentsRef = this.$refs['contents']
contentsRef.scrollTop = contentsRef.scrollHeight
});
}, 30),
/**
* 設定樣式
* @param {Object} msg
* @returns
*/
getClass (msg) {
if (msg.type === 'log') return 'message-type-log';
return [
'message-type-' + msg.type,
'message-owner-' + (msg.payload.connectionId === this.me.id ? 'mine' : 'user')
].join(' ')
},
/**
* 登錄
* @returns
*/
login () {
if (!this.name) {
return;
}
this.connection.login(this.name);
},
/**
* 發布訊息
* @returns
*/
post () {
if (!this.message) return;
this.connection.send('post', { message: this.message });
this.message = '';
},
/**
* 退出
*/
quit () {
this.connection.quit();
}
},
computed: {
/**
* 渲染訊息條目
* @returns
*/
contents () {
const me = this.me;
return {
props: {
msg: { type: Object, required: true }
},
render (h) {
if (this.msg.type === 'log') {
return h('span', [this.msg.message]);
}
return [
h('div',
{
'class': 'message-content'
},
[
h('label', [this.msg.payload.connectionId === me.id ? '我' : this.msg.payload.name]),
h('div', [this.msg.payload.message])
])
];
}
}
}
}
});
3、演示
聊天服務器程式實作很簡單,我們把之前OnWebSocket改成了GetMessager,方法回傳一個Messager給父類即可,
public class Server : HttpServerBase
{
public Server() : base()
{
//設定根目錄
WebRoot = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web"));
}
protected override Messager GetMessager(HttpRequest request, Stream stream)
{
return new Connection(stream);
}
}
完整專案托管:https://github.com/hooow-does-it-work/websocket-chat
前端內容:https://github.com/hooow-does-it-work/websocket-chat/tree/main/bin/Release/web
運行服務器,瀏覽器訪問:http://127.0.0.1:4189/chat.html,多開幾個頁面,相互發送訊息,
class Program
{
static void Main(string[] args)
{
StartWebSocketServer(4189);
Console.ReadLine();
}
private static void StartWebSocketServer(int port)
{
HttpServerBase server = new Chat.Server();
try
{
server.Start("0.0.0.0", port);
Console.WriteLine("WebSocket服務器啟動成功,監聽地址:" + server.LocalEndPoint.ToString());
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
分別用Alice,Bob,Lily順序登錄,相互發送訊息,測驗各功能,



4、總結
這兩篇文章主要是對我們前面對WebSocket協議的實作,通過自定義payload內容實作一個簡單的聊天室,
可以實作多聊天室、聊天室切換功能,后端代碼都實作了,只是我們前端沒去實作,
到此為止,所有關于WebSocket的介紹和演示都完成了,
完整專案托管地址:https://github.com/hooow-does-it-work/websocket-chat
依賴專案(注意是sync-stable分支,不是main分支):https://github.com/hooow-does-it-work/iocp-sharp/tree/sync-stable
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/301009.html
標籤:其他
