摘要:前端邏輯搞定之后,思考一下這個聊天室的交互是怎么實現的。在前端監聽一個事件,這個事件的觸發條件是成功和服務端建立連接。攜帶一個參數,即用戶的輸入。別人發送的消息現在就需要在前端建立一個響應服務端有新消息的監聽事件了。
一些廢話:)
最近在學校比較閑,終于有這么一塊時間可以自由支配了,所以內心還是十分的酸爽舒暢的。當然了,罪惡的事情也是有的,比如已經連續一周沒有吃早飯了,其實現在回頭想想,真的不能怪我啊,因為最近的天氣實在是太!冷!了!好吧為了減少賴床的罪惡感,還是學(gǎo)點(diǎn)東(shì)西(qing)好了。不說廢話了,還是進入正題。
進入正題這個丑陋無比的聊天室,暫時給他后面加個“v1.0”吧,畢竟也是沒有經過什么迭代,寫好就直接放出來了,當然也有很多可以再搞搞的地方,比如:
[ ] 支持發送圖片
[ ] 支持發送表情
[ ] 顯示在線用戶名單
其實這里還是有很多想象空間的,不是重點也就不展開說了。
在寫這個demo的時候,我是邊學邊寫的狀態,學習資料以劉哇勇大神的Node.js+Web Socket 打造即時聊天程序嗨聊為主,主流搜索引擎和我最喜歡的技術社區SegmentFault為輔。
源碼已經上傳至我的github, clone到本地以后在terminal中運行下面兩條命令:
npm install node server
然后打開瀏覽器,訪問localhost,就可以在不聯網的情況下看到這個demo啦。
預覽輸入用戶名完成登陸
然后就可以開始和在線的人聊天了
當然啦,Node.js是必不可少的,這里推薦兩個很棒的Node.js教程:
Node入門
Node.js包教不包會
Node.js可以實現用短短的幾行代碼就起一個服務器
var http = require("http"); http.createServer(function(request, response){ response.writeHead(200, {"Content-type":"text/plain"}); response.write("Hey you, my name is kyrieliu~"); response.end(); }).listen(8080);
當你在Terminal執行這段代碼以后,訪問http://localhost/:8080,就可以看到一行字:Hey you, my name is kyrieliu~
這就代表你的node服務已經架起來了,阿西,js寫后臺邏輯,用腳指頭想想都會覺得是一件很酷的事情呢
另外,還用到了兩個包模塊:
express
socket.io
UIexpress是node.js中管理路由響應請求的模塊,根據請求的URL返回相應的HTML頁面。這里我們使用一個事先寫好的靜態頁面返回給客戶端,只需使用express指定要返回的頁面的路徑即可。如果不用這個包,我們需要將HTML代碼與后臺JavaScript代碼寫在一起進行請求的響應,不太方便。
socket.io封裝了websocket,同時包含了其它的連接方式,比如Ajax。原因在于不是所有的瀏覽器都支持websocket,通過socket.io的封裝,你不用關心里面用了什么連接方式。你在任何瀏覽器里都可以使用socket.io來建立異步的連接。
界面就像第一眼看到的那樣簡(chǒu)單(lòu),不過“麻雀雖小,五臟俱全”,該有的東西還是得有,這里就直接貼DOM結構。
直接看注釋,就能清晰的看到這只小麻雀的“心”、“肝”、“脾”、“肺”四個部分。(“腎”呢?哼,你以為我的新手機怎么來的?)
至于那些辣眼睛的類名,是因為項目里用到了Bootstrap,也算是偷了個懶。
UI搞定之后,思考一下這個聊天室的交互是怎么實現的。
“你前面不是說了,用websocket嘛。”
此話不假,不過這里我指的是交互,畢竟你寫一個程序的話,對程序內的邏輯必須做到“吹毛求疵”(我這個成語用對了沒)
與服務端建立連接
輸入昵稱完成登錄
發送消息
接受消息
仔細想想好像大概就這么多了,那就開始逐一攻破
與服務端建立連接這里要注意,因為是一個聊天系統,所以與服務端建立連接的方式不同于往常,這里用到的協議是HTTP WebSocket,從而實現持久連接。
簡單的解釋一下,這里的“持久”,是相對于HTTP這種“非持久”的協議來說的(閣下的意思是,HTTP的夫人會很羨慕WebSocket的夫人咯)。
通過閱讀Ovear在知乎上的回答,大致說一下這兩個協議之間的區別。
HTTP的生命周期大概是這樣的,一個request,一個response,這次請求就結束了;HTTP 1.1中進行了改進,增加了一個keep-alive,效果是在這次HTTP連接中,可以發送多個request,接受多個response,但本質上,request = response,也就是說,請求和響應永遠是一一對應的,沒有request時,服務端不能主動response。
當客戶端與服務端完成協議升級以后(HTTP -> WebSocket),就建立了一個持久連接,有多持久呢?這個連接可以持續存在知道客戶端或服務端某一方主動的關閉連接。與HTTP最大的不同是,此時的服務端可以主動推送消息給客戶端咯。在這個項目中,我們用socket.io這個包模塊來實現WebSocket,socket.io不僅實現了對WebSocket的封裝,還將連同Ajax輪詢和其他實時通信方式封裝成了通用的接口,這么做的原因是,當服務器不支持WebSocket時,可以轉換為其他的實現方式,嘖嘖嘖,堪稱縱享絲滑
接下來就是實現的部分,前端在引入了socket.io.js這個文件以后應該怎么做呢?
Talk is cheap, show you the CODE.
var socket = io.connect();
對,就是這么簡單,不信你去看官方文檔。
輸入昵稱完成登錄這里的“登錄”,不是真正的登錄,當執行完 io.connect() 之后,這個連接就算已經建立了,這里是在處理一些交互上的行為。
在前端監聽一個connect事件,這個事件的觸發條件是:成功和服務端建立連接。
socket.on("connect",function(){ //do something });
回調里面是此時要完成的DOM操作,比如:
改變提示文字(初始是“Connecting to server......”)
顯示遮蓋層
聚焦文本框
當用戶輸入自己的昵稱點擊登錄按鈕后,當前socket觸發一個login事件到服務端:
socket.emit("login",nickname);
攜帶一個參數,這個參數就是用戶輸入的昵稱。
當服務端對這個昵稱進行合法性檢測,通過時觸發:
socket.on("loginSuccess", function(){ //1. 隱藏登錄層 //2. 用戶可以愉快和別人聊天了~ });
如果用戶輸入的昵稱不合法,則觸發:
socket.on("loginFailed", function(){ //1. 提示用戶昵稱哪里出問題了 //2. 等待用戶重新輸入 });
注意這里的事件名稱,如login、loginSuccess、loginFailed都是自定義的,只要保證和服務端的一致就ok了。
發送消息想像一下用戶發送消息這個動作,分解一下:輸入文本 -> 點擊發送。也就是這倆了,ok,這里需要給發送按鈕掛上一個事件,告訴服務端,“服務端服務端,這里是socket XXX,我給你發了一個消息哦,注意查收,over。”
socket.emit("msgSend",msg);
攜帶一個參數,即用戶的輸入。
接受消息接受消息這個邏輯有三種情況
自己發送的消息
別人發送的消息
系統的提示信息
莫慌,一個一個來看。
自己發送的消息直接顯示在聊天消息的面板,接收自己發送的消息不用和后臺交互,只需要告訴后臺我給大家發了這條消息即可。當然啦,你也可以仿照微信對自己發送的消息進行處理:發送的瞬間將自己的消息添加聊天面板 -> 給旁邊放個小菊花或者loading的字樣 -> 與后臺進行交互 -> 成功則隱藏小菊花;失敗則將小菊花變成紅色感嘆號暗示用戶發送失敗。
現在就需要在前端建立一個響應服務端“有新消息”的監聽事件了。
socket.on("newMsg", function(nickname, msg){ //顯示這條新消息 });
回調函數里面有兩個參數,nickname和msg,分別是消息發送者的昵稱和消息內容,這倆是怎么來的呢?不要急,后面會在服務端邏輯里面講到,這里你只需要知道,在前端接受新消息的時候,因為牽扯到展示新消息,所以需要這兩個參數。
關于系統的提示信息,主要分為兩個:
提示新加入和退出的用戶
展示當前在線的用戶數
大概是這個樣子,所以需要在前端對系統事件進行監聽
socket.on("system", function(nickname, count, type){ //1.根據系統事件類型(新加入或離開)來提示用戶 //2.修改在線用戶數量 });
這里的三個參數也都是必不可少的,nickname代表觸發系統事件的用戶的昵稱,count表示當前在線的用戶數量,type表示事件類型(加入/離開)。同樣,這三個參數也都是服務端傳過來的。
后臺邏輯與前端對應,后臺的邏輯主要分為以下幾個部分
起服務
建立連接
用戶登錄
接受用戶發送的消息并廣播之
系統消息的處理
起服務var express = require("express"); var app = express(); var server = require("http").createServer(app); var io = require("socket.io")(server); app.use("/",express.static(__dirname + "/www")); server.listen(8080);
因為我把前端文件(html/js/css)放到了www這個文件夾內,所以用express指定返回給瀏覽器的頁面路徑現在這樣。
當然,除了express以外,也要引入socket.io模塊并綁定到服務器。
服務起好了,怎么建立連接呢?
io.on("connection", function(socket){ //do something });
就...這樣...?
昂。
你沒有看錯,我也沒有寫錯,這里對應前端邏輯的:
var socket = io.connect(); socket.on("connect", function(){ //do something });
連接建立了以后,所有關于socket活動的邏輯就可以開始寫了。(FYI:當然,是寫在這個connection事件的回調里面)
用戶登錄還記得前端觸發的登錄事件叫什么嘛
socket.emit("login", nickname);
叫login,而且還攜帶了一個參數——用戶想給自己起的昵稱nickname。好,我們來寫對應的后臺邏輯
socket.on("login", function(nickname){ //do something });
這里的do something要做什么呢?即對用戶輸入的昵稱進行合法性校驗,比如是否已經存在、長度限制、符號限制等。
球都麻袋,好像有哪里不對...
長度限制和符號限制?這倆哥們根本就不用放在服務器上做嘛,直接在前端就搞了。所以我們的問題只剩一個了——昵稱的唯一性。
既然要檢測昵稱是否唯一,首先得有一個當前在線用戶昵稱的總集,不然去哪里檢測昵稱是否存在嘞?
所以要在全局維護一個數組,保存當前在線用戶的昵稱
var users = [];
在這個數組里找用戶通過login事件傳過來的nickname,如果不存在,說明當前昵稱合法,用戶可以叫這個名字,那么
socket.nickname = nickname;//記錄下當前socket的nickname users.push(nickname); socket.emit("loginSuccess");//觸發loginSuccess事件
如果昵稱已經存在了,就觸發一登錄失敗事件,前端再做相應的交互即可。
socket.emit("loginFailed");接收用戶發送的消息并
按照約定好的事件名來寫服務端的監聽程序
socket.on("msgSend", function(msg){ socket.broadcast.emit("newMsg", socket.nickname, msg); });
這里調用的api是socket的廣播事件,效果是廣播消息到除了當前socket以外的所有socket。
系統消息的處理剩下的工作就是處理系統消息了,首先要明確有哪些系統消息
提示用戶加入
提示用戶離開
更新在線用戶數
當用戶輸入的昵稱通過合法性校驗以后,系統提示新加入的用戶
io.sockets.emit("system",nickname, users.length, "login");
io.sockets.emit()
的作用是向當前所有socket觸發一個事件,這里要區別于socket.broadcast.emit()。
仿照上面的代碼,寫出當用戶離開時的廣播事件:
io.sockets.emit("system", nickname, users.length, "logout");
但是要寫在哪里呢?這時候,就需要在服務端額外的監聽一個斷開事件
socket.on("disconnect", function(){ var index = users.indexOf(socket.nickname); users.splice(index, 1);//將斷開用戶的昵稱從全局數組users中刪除 io.sockets.emit("system", socket.nickname, users.length, "logout"); });總結
至此,一個基于Node.js的聊天室就算擼成了,當然還有許多可以優化的地方,不過核心功能也就這些,能看到這里的都是好漢,因為自己寫完看了一遍,感覺真像是老太太的裹腳布——又臭又長
好啦,最后打個廣告,誒我就不說是什么,好奇的童鞋自己掃掃看吧~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81061.html
摘要:前端邏輯搞定之后,思考一下這個聊天室的交互是怎么實現的。在前端監聽一個事件,這個事件的觸發條件是成功和服務端建立連接。攜帶一個參數,即用戶的輸入。別人發送的消息現在就需要在前端建立一個響應服務端有新消息的監聽事件了。 一些廢話:) 最近在學校比較閑,終于有這么一塊時間可以自由支配了,所以內心還是十分的酸爽舒暢的。當然了,罪惡的事情也是有的,比如已經連續一周沒有吃早飯了,其實現在回頭想想...
摘要:畫字首先我在畫布上畫了個點,用這些點來組成我們要顯示的字,用不到的字就隱藏起來。星星閃爍效果這個效果實現很簡單,就是讓星星不停的震動,具體就是讓點的目的地坐標不停的進行小范圍的偏移。 哈哈哈哈!!!當我說在寫這邊文章的時候,妹子已經追到了,哈哈哈哈哈!!! 其實東西是一年前寫的,妹子早就追到手了,當時就是用這個東西來表白的咯,二話不說,先看效果(點擊屏幕可顯示下一句) showImg(...
摘要:組件結構同組件結構通過方法獲取元素的大小及其相對于視口的位置,之后對提示信息進行定位。可以用來進行一些復雜帶校驗的彈窗信息展示,也可以只用于簡單信息的展示。可以通過屬性來顯示任意標題,通過屬性來修改顯示區域的寬度。 手把手教你擼個vue2.0彈窗組件 在開始之前需要了解一下開發vue插件的前置知識,推薦先看一下vue官網的插件介紹 預覽地址 http://haogewudi.me/k...
閱讀 3569·2023-04-25 14:20
閱讀 1186·2021-09-10 10:51
閱讀 1149·2019-08-30 15:53
閱讀 454·2019-08-30 15:43
閱讀 2312·2019-08-30 14:13
閱讀 2790·2019-08-30 12:45
閱讀 1202·2019-08-29 16:18
閱讀 1159·2019-08-29 16:12