本教程將創(chuàng)建一個(gè)基本的聊天應(yīng)用。 它幾乎不要求你事先了解 Node.JS 或 Socket.IO ,適合所有知識(shí)層次的用戶。
使用流行的 web 應(yīng)用技術(shù)棧 —— 比如 LAMP (PHP) —— 來(lái)編寫聊天應(yīng)用通常是很困難的。它包含了輪詢服務(wù)器以檢測(cè)變化,還要追蹤時(shí)間戳,并且這種實(shí)現(xiàn)是比較慢的。
大多數(shù)實(shí)時(shí)聊天系統(tǒng)通?;?socket 來(lái)構(gòu)建。 Socket 為客戶端和服務(wù)器提供了雙向通信機(jī)制。
這意味著服務(wù)器可以 推送 消息給客戶端。無(wú)論何時(shí)你發(fā)布一條消息,服務(wù)器都可以接收到消息并推送給其他連接到服務(wù)器的客戶端。
首先要制作一個(gè) HTML 頁(yè)面來(lái)提供表單和消息列表。我們使用了基于 Node.JS 的 web 框架 express
。 請(qǐng)確保安裝了 Node.JS。
首先創(chuàng)建一個(gè) package.json
來(lái)描述我們的項(xiàng)目。 推薦新建一個(gè)空目錄 (這里使用 chat-example
)。
{
"name": "socket-chat-example",
"version": "0.0.1",
"description": "my first socket.io app",
"dependencies": {}
}
要保存 dependencies
信息, 可以用 npm install --save
:
npm install --save express@4.15.2
express 已經(jīng)安裝好了。我們現(xiàn)在新建一個(gè) index.js
文件來(lái)創(chuàng)建應(yīng)用。
var app = require('express')();
var http = require('http').Server(app);
app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
這段代碼作用如下:
app
作為 HTTP 服務(wù)器的回調(diào)函數(shù) (見(jiàn)第 2 行)。/
來(lái)處理首頁(yè)訪問(wèn)。運(yùn)行 node index.js
就可以看到如下畫面:
在瀏覽器中訪問(wèn) http://localhost:3000
:
目前在 index.js
中我們是通過(guò) res.send
返回一個(gè) HTML 字符串。 如果我們將整個(gè)應(yīng)用的 HTML 代碼都放到應(yīng)用代碼里,代碼結(jié)構(gòu)將變得很混亂。 替代的方法是新建一個(gè) index.html
文件作為服務(wù)器響應(yīng)。
現(xiàn)在我們用 sendFile
來(lái)重構(gòu)之前的回調(diào):
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
index.html
內(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>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
</body>
</html>
現(xiàn)在重啟進(jìn)程 (按 Control+C ,然后再次運(yùn)行 node index
) ,刷新頁(yè)面顯示如下:
Socket.IO 由兩部分組成:
socket.io
socket.io-client
開(kāi)發(fā)環(huán)境下, socket.io
會(huì)自動(dòng)提供客戶端。正如我們所見(jiàn),到目前為止,我們只需要安裝一個(gè)模塊:
npm install --save socket.io
這會(huì)安裝模塊并添加依賴到 package.json
。在 index.js
文件中添加該模塊:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
io.on('connection', function(socket){
console.log('a user connected');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
我們通過(guò)傳入 http
(HTTP 服務(wù)器) 對(duì)象初始化了 socket.io
的一個(gè)實(shí)例。 然后監(jiān)聽(tīng) connection
事件來(lái)接收 sockets, 并將連接信息打印到控制臺(tái)。
在 index.html 的 </body>
標(biāo)簽中添加如下內(nèi)容:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
這樣就加載了 socket.io-client
。 socket.io-client 暴露了一個(gè) io
全局變量,然后連接服務(wù)器。
請(qǐng)注意我們?cè)谡{(diào)用 io()
時(shí)沒(méi)有指定任何 URL,因?yàn)樗J(rèn)將嘗試連接到提供當(dāng)前頁(yè)面的主機(jī)。
重新加載服務(wù)器和網(wǎng)站,你將看到控制臺(tái)打印出 “a user connected”。
嘗試打開(kāi)多個(gè)標(biāo)簽頁(yè),可以看到多條信息:
每個(gè) socket 還會(huì)觸發(fā)一個(gè)特殊的 disconnect
事件:
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
你可以多次刷新標(biāo)簽頁(yè)來(lái)查看效果:
Socket.IO 的核心理念就是允許發(fā)送、接收任意事件和任意數(shù)據(jù)。任意能被編碼為 JSON 的對(duì)象都可以用于傳輸。二進(jìn)制數(shù)據(jù) 也是支持的。
這里的實(shí)現(xiàn)方案是,當(dāng)用戶輸入消息時(shí),服務(wù)器接收一個(gè) chat message
事件。index.html
文件中的 script
部分現(xiàn)在應(yīng)該內(nèi)容如下:
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js" rel="external nofollow" ></script>
<script>
$(function () {
var socket = io();
$('form').submit(function(){
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
});
</script>
在 index.js
中打印出 chat message
事件:
io.on('connection', function(socket){
socket.on('chat message', function(msg){
console.log('message: ' + msg);
});
});
結(jié)果應(yīng)該如以下視頻所示:
廣播
接下來(lái)的目標(biāo)就是讓服務(wù)器將消息發(fā)送給其他用戶。
要將事件發(fā)送給每個(gè)用戶,Socket.IO 提供了 io.emit
方法:
io.emit('some event', { for: 'everyone' });
要將消息發(fā)給除特定 socket 外的其他用戶,可以用 broadcast
標(biāo)志:
io.on('connection', function(socket){
socket.broadcast.emit('hi');
});
為了簡(jiǎn)單起見(jiàn),我們將消息發(fā)送給所有用戶,包括發(fā)送者。
io.on('connection', function(socket){
socket.on('chat message', function(msg){
io.emit('chat message', msg);
});
});
在客戶端中,我們捕獲 chat message
事件,并將消息添加到頁(yè)面中?,F(xiàn)在客戶端代碼應(yīng)該如下:
<script>
$(function () {
var socket = io();
$('form').submit(function(){
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
});
</script>
就這樣,聊天應(yīng)用就完成了,大約只有 20 行代碼! 最終效果如下:
課外練習(xí)
下面是一些可以做的優(yōu)化:
可以從 GitHub 獲取。
$ git clone https://github.com/socketio/chat-example.git
更多建議: