什麼是Websocket
呢?
咱們都知道在Http
協議中,客戶端與服務器端的通訊是靠客戶端發起請求,而後服務器端收到請求再進行迴應,這個過程當中,客戶端是主動的,服務器端是被動的。Websocket
協議就不同了,它是基於TCP
的一種新的網絡協議,它與Http
協議不一樣之處就在於Websocket
能實現服務器端主動推送消息到客戶端,服務器端與客戶端都能發起通訊,這一次,服務器端終於也擁有了主動權。javascript
什麼是socket.io
?socket.io
封裝了Websocket
以及其餘的一些協議,而且實現了Websocket
的服務端代碼。同時還有很強的兼容性,兼容各類瀏覽器以及移動設備。有了它,咱們能更方便快捷地實現服務器端與客戶端之間的實時通信。css
要實現多人聊天室的核心就是區分當前用戶發送的消息與其餘用戶發送的消息,在這裏我經過用戶登陸使用的用戶名來進行區分。因此用戶進入首先展現登陸頁面。html
登陸成功以後,新用戶加入聊天室
若是用戶重名,會彈出提示,保持吳彥祖的登陸狀態,咱們再打開一個標籤,輸入「吳彥祖」查看效果
只有當暱稱惟一時,才容許登陸,咱們再登陸一個查看效果
能夠看到,當新用戶登陸時,其餘在線用戶會收到提示,接下來就是發送消息了
發送的消息是實時推送的,當前用戶發送的消息與其餘用戶發送的消息對話框作了區分。
當用戶退出時,系統也會給出提示,效果以下
怎麼樣,有沒有興趣繼續瞭解呢?下面就開始着手開發吧。前端
後端服務是用的node.js
,因此咱們首先要進行安裝,安裝方法很簡單,我在以前一篇文章也提過。首先在node.js官網下載穩定版本,下載完成後點擊安裝,安裝過程也很簡單,一直next便可,安裝完成會自動添加node
及npm
環境變量。java
檢驗是否安裝成功,在cmd輸入命令 node -v
,回車 及 npm -v
,回車,如出現下圖所示版本信息,表示安裝成功node
新建文件夾chatroom
,在這裏我把它建到D盤根目錄下。打開cmd,定位到剛建的chatroom
文件夾下,輸入npm install socket.io
安裝socket.io
jquery
安裝完成以後,能夠看到文件夾下多了node_modules
文件,裏面全是剛下載的socket.io
依賴包。git
在chatroom
文件夾下新建頁面文件index.html
,樣式chat.css
,後端jsapp.js
,前端jschat.js
,並下載jquery.min.js,socket.io.js。再下載一張圖片做爲用戶頭像,放在images/user/
下。
目錄結構以下
github
好了,環境搭建完成,開始擼碼吧。web
在app.js裏面構建服務器
/*app.js*/ /*構建http服務*/ var app = require('http').createServer() /*引入socket.io*/ var io = require('socket.io')(app); /*定義監聽端口,能夠自定義,端口不要被佔用*/ var PORT = 8081; /*監聽端口*/ app.listen(PORT); console.log('app listen at'+PORT);
接着啓動服務
打開cmd
,定位到app.js
所在目錄,輸入node app.js
,如圖所示,打印出了咱們寫的內容,表示服務啓動成功。
先給你們簡單講一下服務器端與客戶端通訊的基本方法
你們能夠看一下socket.io的文檔
(1)socket.emit
客戶端與服務器端之間發送消息是用emit
例如客戶端向服務端發送登陸請求socket.emit('login',{username:uname})
login
是自定義的事件,後面是帶的參數
(2)socket.on
服務器端要接收客戶端發送的login
事件,就得對該事件進行監聽socket.on('login',function(data){})
在回調函數中進行處理
同理,服務器端也能夠向客戶端發送事件,只要客戶端也對該事件進行監聽就行
(3)io.sockets.emit
服務器端向鏈接的全部客戶端發送消息得用io.sockets.emit
(4)socket.broadcast.emit
給除了本身之外的客戶端廣播消息
/*app.js*/ /*構建http服務*/ var app = require('http').createServer() /*引入socket.io*/ var io = require('socket.io')(app); /*定義監聽端口,能夠自定義,端口不要被佔用*/ var PORT = 8081; /*監聽端口*/ app.listen(PORT); /** *監聽客戶端鏈接 *io是咱們定義的服務端的socket *回調函數裏面的socket是本次鏈接的客戶端socket *io與socket是一對多的關係 */ io.on('connection', function (socket) { /*全部的監聽on,與發送emit都得寫在鏈接裏面,包括斷開鏈接*/ }) console.log('app listen at'+PORT);
index.html
頁面中需引入socket.io.js
,socket.io.js下載地址
/*index.html*/ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"> <title>聊天室</title> <link type="text/css" rel="stylesheet" href="css/chat.css"> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/socket.io.js"></script> <script type="text/javascript" src="js/chat.js"></script> </head> <body> /*登陸界面*/ <div class="login-wrap"> <div class="login-con"> <h3>用戶登陸</h3> <input type="text" placeholder="請輸入暱稱" id="loginName"> <button class="login-btn">登陸</button> </div> </div> /*聊天界面,一開始隱藏,用戶登陸成功後再顯示*/ <div class="chat-wrap hide"> <h1>多人聊天室</h1> <div class="chat-con clearfix"></div> <div class="bottom"> <input type="text" id="sendtxt"> <button class="sendBtn">發送</button> </div> </div> </body> </html>
樣式能夠本身編寫,我這裏隨便寫了一下
/*公共樣式*/ *{padding:0; margin:0;} html,body{width:100%;height: 100%;} .clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden} .clearfix{*zoom:1} .cred{color:#f03e3e;} .cgreen{color:#459d36;} .hide{display:none;} .fr{float:right;} .fl{float: left;} .rela{position: relative;} .abs{position:absolute;} h1{position: fixed; z-index:20; width: 100%; height:50px; line-height:50px; font-size:20px; left: 0; top: 0; background: #000; color: #fff;} /*登陸界面*/ .login-wrap{background:#e7e7e7;width:100%;height:100%; text-align:center;} .login-con{padding-top: 50px;} .login-con h3{margin-bottom: 20px;} .login-con input{width:60%; display:block; margin:0 auto; height: 40px; line-height: 40px; margin-bottom: 20px;} .login-con button{width:60%;display:block; margin:0 auto; height: 40px; line-height:40px; border:none; background:#459d36; color:#fff; border-radius:5px;} /*聊天界面*/ .chat-wrap{width: 100%; height: 100%;overflow-y:scroll; background:#e7e7e7; text-align:center;} .chat-con{padding: 50px 0; background:#e7e7e7;} .chat-con p{display:inline-block; padding:5px 10px; background:#999;border-radius:5px; color:#fff; margin:5px 0;} .bottom{position:fixed;bottom:0; left: 0; width:100%; height: 50px; background: #fff;} .bottom input{width: 78%; height: 50px; line-height: 50px; float:left;border:none;} .bottom button{width: 20%;height: 50px; float: right; border:none; background:#459d36;color: #fff;} .chat-item{width:100%; margin-bottom:20px;} .item-right .message{background: #62b900;} .item-left .message{background: #fff; margin-top:20px;} .item-left .img{margin-right:10px;} .item-left .uname{font-size:12px; left:50px; top:0;} .chat-item .message{width:60%;display:block; padding:10px;border-radius:5px; margin-right:10px;} .chat-item .img{display:inline-block; width:40px; height:40px; background:url(../images/user/user.jpg) no-repeat; background-size:100% 100%;}
客戶端
瀏覽器端將得到的用戶輸入的暱稱信息,發送到服務器端,告訴服務器端我要觸發login
事件。
在客戶端chat.js
中發送登陸事件
/*chat.js*/ $(function(){ /*創建socket鏈接,使用websocket協議,端口號是服務器端監聽端口號*/ var socket = io('ws://localhost:8081'); /*定義用戶名*/ var uname = null; /*登陸*/ $('.login-btn').click(function(){ uname = $.trim($('#loginName').val()); if(uname){ /*向服務端發送登陸事件*/ socket.emit('login',{username:uname}) }else{ alert('請輸入暱稱') } }) })
服務器端
服務器端監聽login
事件,在後臺打印出獲取到的暱稱信息。
在服務器端app.js
中監聽登陸事件
/*app.js*/ var app = require('http').createServer() var io = require('socket.io')(app); var PORT = 8081; app.listen(PORT); io.on('connection', function (socket) { /*監聽登陸*/ socket.on('login',function(data){ console.log(data) }) }) console.log('app listen at'+PORT);
注:更改了app.js,需再次啓動服務才能看見效果
打開cmd,按Ctrl
+C
退出上次服務,再次輸入node app.js
啓動服務,打開瀏覽器,查看效果
能夠看到,點擊登陸按鈕時,服務器端打印出了接收到的用戶名信息,沒問題,繼續往下寫登陸成功事件。
因爲沒有使用到數據庫,因此咱們就定義一個用戶數組,用戶每次登陸以後,咱們就判斷該暱稱是否已存在,若是已存在就彈出提示,轉到登陸失敗事件,若是該暱稱不存在數組裏面,就視爲新用戶,轉到登陸成功事件,而且將該暱稱存入數組。
服務器端
/*app.js*/ var app = require('http').createServer() var io = require('socket.io')(app); var PORT = 8081; /*定義用戶數組*/ var users = []; app.listen(PORT); io.on('connection', function (socket) { /*是不是新用戶標識*/ var isNewPerson = true; /*當前登陸用戶*/ var username = null; /*監聽登陸*/ socket.on('login',function(data){ for(var i=0;i<users.length;i++){ if(users[i].username === data.username){ isNewPerson = false break; }else{ isNewPerson = true } } if(isNewPerson){ username = data.username users.push({ username:data.username }) /*登陸成功*/ socket.emit('loginSuccess',data) /*向全部鏈接的客戶端廣播add事件*/ io.sockets.emit('add',data) }else{ /*登陸失敗*/ socket.emit('loginFail','') } }) }) console.log('app listen at'+PORT);
客戶端
/*chat.js*/ $(function(){ /*創建socket鏈接,使用websocket協議,端口號是服務器端監聽端口號*/ var socket = io('ws://localhost:8081'); /*定義用戶名*/ var uname = null; /*登陸*/ $('.login-btn').click(function(){ uname = $.trim($('#loginName').val()); if(uname){ /*向服務端發送登陸事件*/ socket.emit('login',{username:uname}) }else{ alert('請輸入暱稱') } }) /*登陸成功*/ socket.on('loginSuccess',function(data){ if(data.username === uname){ checkin(data) }else{ alert('用戶名不匹配,請重試') } }) /*登陸失敗*/ socket.on('loginFail',function(){ alert('暱稱重複') }) /*新人加入提示*/ socket.on('add',function(data){ var html = '<p>系統消息:'+data.username+'已加入羣聊</p>'; $('.chat-con').append(html); }) /*隱藏登陸界面 顯示聊天界面*/ function checkin(data){ $('.login-wrap').hide('slow'); $('.chat-wrap').show('slow'); } })
再次重啓服務,打開瀏覽器查看效果,登陸成功效果如上面圖2所示,登陸失敗效果如上面圖3所示。
退出登陸,只需服務器端在用戶數組裏面刪除退出的用戶便可。
服務器端
/*app.js*/ /*退出登陸*/ /*寫在io.on('connection', function (socket) {})裏面*/ socket.on('disconnect',function(){ /*向全部鏈接的客戶端廣播leave事件*/ io.sockets.emit('leave',username) users.map(function(val,index){ if(val.username === username){ users.splice(index,1); } }) })
客戶端
/*chat.js*/ /*退出羣聊提示*/ socket.on('leave',function(name){ if(name != null){ var html = '<p>FBI warning:'+name+'已退出羣聊</p>'; $('.chat-con').append(html); } })
客戶端
/*chat.js*/ /*發送消息*/ $('.sendBtn').click(function(){ sendMessage() }); $(document).keydown(function(event){ if(event.keyCode == 13){ sendMessage() } }) function sendMessage(){ var txt = $('#sendtxt').val(); $('#sendtxt').val(''); if(txt){ socket.emit('sendMessage',{username:uname,message:txt}); } }
服務器端
/*app.js*/ socket.on('sendMessage',function(data){ io.sockets.emit('receiveMessage',data) })
客戶端
/*chat.js*/ /*接收消息*/ socket.on('receiveMessage',function(data){ showMessage(data) }) /*顯示消息*/ function showMessage(data){ var html if(data.username === uname){ html = '<div class="chat-item item-right clearfix"><span class="img fr"></span><span class="message fr">'+data.message+'</span></div>' }else{ html='<div class="chat-item item-left clearfix rela"><span class="abs uname">'+data.username+'</span><span class="img fl"></span><span class="fl message">'+data.message+'</span></div>' } $('.chat-con').append(html); }
到這裏,一個簡單的多人聊天室已基本實現了,先回顧一下準備工做
(1)下載node.js;
(2)安裝socket.io;npm install socket.io
(3)服務器端構建http服務,引入socket.io,並設置監聽端口
var app = require('http').createServer() var io = require('socket.io')(app); var PORT = 8081; app.listen(PORT);
(4)客戶端進行socket鏈接,使用websocket協議var socket = io('ws://localhost:8081');
再回顧一下整個邏輯流程:
(1)客戶端獲取用戶輸入暱稱,發送給服務器端;
(2)服務器端接收暱稱,判斷是否新用戶,是則發送登陸成功事件,不然發送登陸失敗事件;
(3)客戶端收到服務器端發送的登陸成功或失敗事件,進行相應處理;
(4)瀏覽器端獲取登陸用戶輸入的消息,將消息與用戶暱稱一塊兒發送給服務器端;
(5)服務器端接收到用戶發送的消息,廣播該消息給當前鏈接的全部客戶端;
(6)客戶端接收服務器端發送來的消息,判斷暱稱是不是本身,進行相應對話框顯示
最後附上github地址