前言
OSI網路模型分七層,由下至上分別為:物理層
、資料鏈路層
、網路層
、傳輸層
、會話層
、表示層
、應用層
,TCP協議處於傳輸層。
Node.js中TCP
Node.js中的 net
模組提供了對TCP協議的封裝,使用 net
模組可以輕鬆的構建一個TCP伺服器,或構建一個連線TCP伺服器的客戶端。
net模組建立一個伺服器
let net = require('net')
let server = net.createServer()
server.on('connection', function (socket) {
// socket套接字 會話,http有請求 響應
console.log('connection success')
})
server.listen(3000, function () {
console.log('server start at 3000')
})
複製程式碼
這樣就建立了一個簡單的服務。啟動在3000埠,並且會監聽客戶端的連線。socket
表示套接字,是一個可讀可寫的雙工流,可以理解為客戶端和服務端之間的會話。
接收資料
socket.setEncoding('utf8')
socket.on('data', function (data) {
console.log(data)
})
複製程式碼
響應資料
socket.write('nice to meet you')
複製程式碼
關閉客戶端
socket.end('end...') // 關閉客戶端
複製程式碼
關閉服務端
server.close(); // 如果觸發close事件就不會再接收新的請求了
server.unref(); // 也表示關閉 ,沒有客戶端連線會自己關閉(不會觸發close事件)
server.on('close', function () {
console.log('server closed')
})
複製程式碼
close
呼叫後, 服務端不再接收新的請求,當沒有客戶端連線,會觸發close
事件,並關閉服務端
unref
呼叫後,服務端繼續接受新的請求,當沒有客戶端連線,不會觸發close
事件,並關閉服務端。
設定最大連線數
server.maxConnections = 2; // 設定最大連線數,超過數量不能連線
複製程式碼
聊天室的實現
有了上面的知識,我們可以動手實現一個聊天室。
需求
- 當前使用者線上數,最多可連線的使用者數;
- 輸入
l:
, 檢視當前使用者列表; - 輸入
s:zs: hello
, 向張三傳送訊息; - 輸入
r: ls
, 給自己重新命名為ls; - 輸入
b: nice to meet you
, 向其他使用者廣播訊息
建立服務
let net = require('net');
let server = net.createServer((socket) => {
let key = socket.remoteAddress + socket.remotePort
console.log(key)
})
server.listen(3000, function () {
console.log('server start at 3000')
})
複製程式碼
快取客戶端資訊
let client = {}
let server = net.createServer((socket) => {
// 顯示歡迎資訊
server.maxConnections = 3;
server.getConnections(function (err, count) {
socket.write(`歡迎到來,當前使用者 ${count},總容納 ${server.maxConnections}\r\n`)
})
// 快取使用者
let key = socket.remoteAddress + socket.remotePort
client[key] = {nickname: '匿名',socket}
})
複製程式碼
快取客戶端資訊到 client
中,以客戶的ip+port作為key值,nickname預設為匿名,並儲存回話socket。
處理客戶端輸入的指令
socket.setEncoding('utf8')
socket.on('data', function (chunk) {
chunk = chunk.replace(/\r\n/,'')
let [command, target, content] = chunk.split(':')
switch (command) {
case 'l': //檢視使用者列表
showList(socket);
break;
case 's': //私聊
charTo(target,content, client[key].nickname);
break;
case 'r': //重新命名
rename(key, target);
break;
case 'b': //想其他使用者廣播
broadcast(key, target, client[key].nickname);
break;
default:
break;
}
})
複製程式碼
處理函式的實現
showList
函式的實現
function showList(socket) {
let users = []
Object.values(client).forEach(user => {
users.push(user.nickname)
})
socket.write(`當前使用者列表:\r\n ${users.join('\r\n')} \r\n`)
}
複製程式碼
charTo
函式的實現
function charTo(target, content, source) {
let targetSocket;
Object.values(client).forEach(user => {
if(user.nickname === target) {
targetSocket = user.socket
}
})
targetSocket.write(`${source}:${content}\r\n`)
}
複製程式碼
rename
函式的實現
function rename(key, content) {
client[key].nickname = content;
client[key].socket.write(`重新命名為: ${content}\r\n`)
}
複製程式碼
broadcast
函式的實現
function broadcast(key, content, nickname) {
Object.keys(client).forEach(user => {
if(user !== key) {
client[user].socket.write(`${nickname}: ${content}\r\n`)
}
})
}
複製程式碼
成果檢驗
我們的建議聊天室就做好了,大家可以使用 PuTTY
這個軟體作為客戶端傳送tcp請求;
下面是我測試的結果。
結語
自己實現一個聊天室的過程還是挺有意思的,歡迎喜歡搗鼓的同學多多交流。