Cover
效果圖
老樣子,還是先放個效果圖谎倔,動態(tài)圖鸟辅,有點大(4M)請耐心等加載氛什。
隨便說說
最近在做東西的時候有一個對戰(zhàn)功能,需要用到Socket技術(shù)匪凉,于是了解了一番相關(guān)的實現(xiàn)方案枪眉,最后選擇了Nodejs以及基于Node的socket.io 最主要的原因還是Node比較好寫接癌,相比于PHP好多個函數(shù)來說簡潔太多了政模。
本文雖說是從0開始伦泥,但不是說無編程基礎(chǔ)的也可以望忆,至少要懂以下東西琐簇。
- 基礎(chǔ)的HTML
- Javascript語法扮饶,很基礎(chǔ)的jQuery語法
因為本文是說從0開始辩昆,所以必須要說一下Socket是什么東西盲再,有較好基礎(chǔ)的可以跳過這段直接到 如何開始 處開始蛋济。
為什么要使用Socket
不引官話棍鳖,我直接按照我的理解通俗來說。通常的網(wǎng)站或是其他的聯(lián)網(wǎng)的應(yīng)用瘫俊,因為要獲取數(shù)據(jù)鹊杖,都需要發(fā)送一個請求,這個請求你可以將它看作一個網(wǎng)址扛芽,比如你在瀏覽器鍵入網(wǎng)址"www.baidu.com"回車骂蓖,就是請求了這個地址,然后它會返回給你一些東西川尖,前面的就會給你返回百度首頁的頁面登下,瀏覽器自己會解析它(這又涉及到了其他的方面,我們暫且不談)叮喳。
所以通常的用戶和服務(wù)器的通信就只能是:用戶發(fā)送請求 -> 服務(wù)器接受請求 -> 服務(wù)器返回結(jié)果 -> 用戶收到結(jié)果被芳。在這種情況下,想要做即時應(yīng)用就無法實現(xiàn)馍悟。
想想一下畔濒,現(xiàn)在你和別人在對戰(zhàn),你打掉了他8點血锣咒,你可以告訴服務(wù)器這個信息侵状,但是服務(wù)器沒有辦法在你告訴它之后赞弥,也迅速告訴你的對手,他掉了8點血趣兄。除非對手當時發(fā)送一個請求绽左,來查看自己當前血量。但是游戲是實時的艇潭,不可能你每隔幾秒鐘就詢問服務(wù)器更新一下自己的血量拼窥。
這里的需求就是讓己方的數(shù)據(jù)發(fā)送之后即時發(fā)送給對方,那么只需要服務(wù)器也能夠以主動發(fā)送數(shù)據(jù)給用戶即可蹋凝,所以需要一種客戶端<=>服務(wù)器雙向通信的技術(shù)鲁纠,就是Socket。
BTW
本文也相當于是Socket.io官方Demo的翻譯鳍寂,想看原教程的可以去看看原文房交。
如何開始
那我們知道了為什么要用Socket之后,我們來盤算一下該如何完成這個項目伐割。
首先我們要用Nodejs,那就要去安裝Nodejs刃唤,網(wǎng)上教程很多隔心,我不想贅述,給你們一篇我覺得適合的教程:Nodejs環(huán)境搭建
裝好之后在cmd中鍵入 node -v
與 npm -v
尚胞,顯示版本號就說明安裝沒有問題了硬霍。
然后我們先新建一個工程文件夾,名字就叫node-chat
笼裳,在里面我們新建兩個文件唯卖,一個是server.js
,一個是index.html
躬柬,注意后綴名拜轨。
然后先安裝兩個東西。
打開CMD
因為npm是國外的庫可能有點慢允青,可以安裝cnpm橄碾,執(zhí)行下面命令
npm install -g cnpm --registry=https://registry.npm.taobao.org
然后輸入
cnpm install --save express
等待安裝成功,再安裝
cnpm install --save socket.io
都安裝成功之后我們就可以開始著手Coding了颠锉。
功能分析
我們要做一個聊天室法牲,簡單起見,就不做私聊的功能了琼掠,那么我們想要的功能可以是這些:
- 每個人有自己的昵稱拒垃,在進入聊天室的時候自己輸入。
- 每個人都可以發(fā)言
- 有一個區(qū)域用來展示所有的發(fā)言瓷蛙,并且要實時更新
- 有人加入的時候提示xxx加入了群聊
開始Coding
Nodejs之 Hello World
Nodejs是用來當服務(wù)器的悼瓮,所以我們先來用Nodejs搭建一個服務(wù)器戈毒。其中用到了express這個框架,不知道的想知道可以搜搜相關(guān)知識谤牡,不想知道直接不要管也沒關(guān)系副硅。
讓我們在server.js中寫下以下代碼:
var app = require('express')(); //引入express庫
var http = require('http').Server(app); //將express注冊到http中
//當訪問根目錄時,返回Hello World
app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
});
//啟動監(jiān)聽翅萤,監(jiān)聽3000端口
http.listen(3000, function(){
console.log('listening on *:3000');
});
保存之后恐疲,我們在命令行cd到server.js所在的文件夾下,執(zhí)行
node server.js
跟下面圖片相同就說明成功了
然后打開瀏覽器套么,輸入localhost:3000
回車培己。
OK,Node搭建的服務(wù)器就OK了胚泌。接下來省咨,我們讓瀏覽器進入的時候,跳轉(zhuǎn)到我們的index.html頁面玷室,我們在那寫聊天室的界面零蓉。
跳轉(zhuǎn)到聊天室頁面
聊天室頁面的實現(xiàn)
在index.html
中輸入以下代碼:
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>發(fā)送</button>
</form>
</body>
</html>
保存。
根目錄跳轉(zhuǎn)
前面我們在server.js
中寫了根目錄返回hello world穷缤,現(xiàn)在我們讓它跳轉(zhuǎn)到聊天室頁面敌蜂。將server.js
中的
app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
});
替換為
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
然后在CMD中ctrl+c結(jié)束,重新運行node server.js
津肛。
現(xiàn)在在瀏覽器中訪問localhost:3000
章喉,已經(jīng)跳轉(zhuǎn)到聊天頁面了。
引入Socket
繼續(xù)修改server.js身坐,增加下方標注內(nèi)容:
var app = require('express')();
var http = require('http').Server(app);
//new addition
var io = require('socket.io')(http);
//end
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
//new addition
io.on('connection', function(socket){
console.log('a user connected');
});
//end
http.listen(3000, function(){
console.log('listening on *:3000');
});
新加入的內(nèi)容的意思就是秸脱,當有新的socket連接成功之后,就打印一下信息部蛇。
然后編輯inde.js
摊唇,添加下方標注內(nèi)容:
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
//new addition
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
//end
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>發(fā)送</button>
</form>
</body>
//new addition
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io()
</script>
//end
</html>
在上面的內(nèi)容中,引入了jQuery與socket.io涯鲁,并且在頁面加載完成之后遏片,新建了一個io對象。
然后我們重新啟動node服務(wù)撮竿,訪問localhost:3000
吮便。就會有以下結(jié)果。
添加交互
當用戶有動作的時候幢踏,我們要顯示在聊天面板中髓需,就插入一個li
標簽好了。在index.html
的script中寫一個方法房蝉,后面顯示什么內(nèi)容僚匆,就調(diào)用這個方法在面模板中插入一條li
就好了微渠。
將以下代碼寫到index.html
中。
function addLine(msg) {
$('#messages').append($('<li>').text(msg));
}
等等咧擂,我們好像忘了什么事情
在前面功能分析的時候逞盆,我們就說用戶剛進來的時候,需要輸入自己的昵稱松申。
我們現(xiàn)在就先把這個功能實現(xiàn)一下云芦。
在index.html
的script最前面,我們先彈出一個prompt()
來詢問用戶的昵稱贸桶,然后將用戶名發(fā)送給后端舅逸,讓后端告訴大家這個用戶進來了接。
發(fā)送昵稱給后端
新用戶發(fā)送自己的昵稱給服務(wù)器后皇筛,要讓所有處在聊天室的人知道琉历,所以服務(wù)器要發(fā)一個廣播。發(fā)送數(shù)據(jù)給后端水醋,調(diào)用socket的emit方法旗笔,這里指定一個事件名字叫join
,然后后端會處理這個事件拄踪。
然后現(xiàn)在我們的script標簽中是這樣的换团。
<script>
var name = prompt("請輸入你的昵稱:");
var socket = io()
//發(fā)送昵稱給后端
socket.emit("join", name)
function addLine(msg) {
$('#messages').append($('<li>').text(msg));
}
</script>
后端發(fā)送廣播
為了在后端區(qū)分用戶,我們用一個usocket
數(shù)組來保存每一個用戶的socket
實例宫蛆。
首先監(jiān)聽join
事件,在接收到昵稱之后的猛,將該用戶的加入聊廣播給所有用戶耀盗。
server.js
代碼如下:
為了限制篇幅長度,從此處開始貼上來的代碼有省略卦尊,請參考上下文確定位置叛拷。
var usocket = []; //全局變量
...
io.on('connection', function(socket){
console.log('a user connected')
//監(jiān)聽join事件
socket.on("join", function (name) {
usocket[name] = socket
io.emit("join", name) //服務(wù)器通過廣播將新用戶發(fā)送給全體群聊成員
})
});
...
然后在index.html
的script中添加以下代碼,接受到新用戶加入事件的處理:
...
//發(fā)送昵稱給后端
socket.emit("join", name)
//收到服務(wù)器發(fā)來的join事件時
socket.on("join", function (user) {
addLine(user + " 加入了群聊")
})
...
現(xiàn)在我們再來測試一下岂却,node server.js
開啟后端服務(wù)忿薇,然后訪問localhost:3000
,輸入用戶名iimT
然后可以看到
然后新開一個標簽頁躏哩,輸入用戶名iimY
在iimY的面板中署浩,可以看到
再回到iimT的面板中,看到新提示的iimY加入了群聊
臨時加個小功能
為了更好的辨識每個面板是誰扫尺,我們在用戶輸入自己的昵稱之后筋栋,將網(wǎng)頁標題改成XXX的聊天
。
只需要新加一行代碼:
...
//發(fā)送昵稱給后端正驻,并更改網(wǎng)頁title
socket.emit("join", name)
document.title = name + "的群聊" //new addition
...
處理發(fā)送和接受消息
【一】index.html 將用戶消息發(fā)送給服務(wù)器
用戶輸入消息弊攘,然后點擊發(fā)送我們將用戶的消息發(fā)送給服務(wù)器抢腐,然后服務(wù)器將消息廣播給所有的用戶。
我們需要給form
的提交綁定一個事件襟交,讓它來處理新消息的發(fā)送迈倍。
$('form').submit(function () {
//solve code
})
在綁定的事件中,我們需要逐步做以下事情:
- 獲取用戶輸入的消息捣域。
var msg = $("#m").val()
- 發(fā)送事件給服務(wù)器啼染,事件名就叫
message
吧,發(fā)送的數(shù)據(jù)就是msg
竟宋。
socket.emit("message", msg) //將消息發(fā)送給服務(wù)器
- 將消息輸入框置空提完。
$("#m").val("") //置空消息框
- 阻止
form
的提交事件。
return false //阻止form提交
最后丘侠,我們給form
綁定的事件如下:
...
//當發(fā)送按鈕被點擊時
$('form').submit(function () {
var msg = $("#m").val() //獲取用戶恮的信息
socket.emit("message", msg) //將消息發(fā)送給服務(wù)器
$("#m").val("") //置空消息框
return false //阻止form提交
})
...
【二】server.js 服務(wù)器接受消息
只需要兩步:
- 監(jiān)聽前端的
message
事件 - 在監(jiān)聽到事件之后徒欣,將新消息廣播給全體用戶,這里也將廣播事件的名字叫
message
吧蜗字。
代碼如下:
io.on('connection', function(socket){
console.log('a user connected')
socket.on("join", function (name) {
usocket[name] = socket
io.emit("join", name)
})
//new addition
socket.on("message", function (msg) {
io.emit("message", msg) //將新消息廣播出去
})
});
【三】index.html 客戶接受新消息
客戶接收到服務(wù)器發(fā)來的message
事件打肝,將新消息呈現(xiàn)在面板中。
...
socket.on("join", function (user) {
addLine(user + " 加入了群聊")
})
//接收到服務(wù)器發(fā)來的message事件
socket.on("message", function(msg) {
addLine(msg)
})
//當發(fā)送按鈕被點擊時
$('form').submit(function () {
var msg = $("#m").val() //獲取用戶輸入的信息
...
整個過程相當于挪捕,用戶a將自己的消息用
message
事件發(fā)送給服務(wù)器粗梭,服務(wù)器監(jiān)聽message
事件接收其中的消息,將消息用事件message
廣播給全體用戶级零,全體用戶監(jiān)聽message
事件断医,將事件接受到的消息呈現(xiàn)在聊天框中。PS:這一段是整個socket編程的核心奏纪,可以多讀幾遍細細理解鉴嗤。
最終測試
那么現(xiàn)在我們就完成了整個聊天室的功能,來測試一下吧序调。
1. 開啟服務(wù)器node server.js
醉锅。
2. 開一個標簽,輸入地址localhost:3000
发绢,回車硬耍,填寫昵稱為iimT
。
3. 再開一個標簽边酒,輸入地址localhost:3000
经柴,回車,填寫昵稱為iimY
墩朦。
然后開始讓兩個人互相發(fā)送消息吧口锭。我這里用一張動態(tài)圖來顯示最終效果,也就是本文最開頭的那張效果圖。
PS: 圖大概有4M鹃操,所以需要點時間加載
至此韭寸,聊天室以就做完了。