繼上一篇介紹了基於Nodejs的http服務和文件操做的內容後,本篇內容主要介紹前端工程師在平常工做中較少接觸到的TCP相關知識內容,從Nodejs的TCP模塊入手,經過實例看看TCP是怎麼一回事。javascript
tcp是什麼?
tcp是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議,TCP層是位於IP層之上,應用層之下的中間層。與咱們接觸最頻繁的http請求就是基於它,相比於http,它沒有超時時間,正常狀況下它能夠一直保持鏈接。前端
tcp的特色
1.三次握手java
不少人都知道TCP經典的」三次握手四次揮手「,如此繁瑣的確認發包所帶來了什麼呢? node
首先要明確三次握手到底要幹什麼。客戶端要與服務器進行數據交換,可是服務器在雲端,客戶端也不知道服務器在不在線,因此要尋找一種方式覈驗一下遠端的服務器在不在線,」三次握手「正是覈驗的方式。nginx
再來看看步驟,先是客戶機發起一個請求鏈接包,代表本身要鏈接到服務器上,而後服務器收到請求後,會回覆一個請求,這個請求會作兩件事,先要告訴遠端的客戶機你剛剛連了個人那步操做我收到了,還要肯定本身也能連上遠端的客戶機。客戶端接收到後,先知道了本身發過的第一步請求沒問題,知道本身能夠放心的給服務器發請求了,可是服務器殊不知道能不能給客戶端放心的發數據,因此客戶端還要發起一次回答服務器的請求,此次請求的目的就是讓服務器肯定本身是能夠連上客戶端的。三次握手完成了,能夠開心的發送數據了。git
2.四次揮手github
四次揮手我理解更多的意義是在於機器資源的釋放。web
來看看步驟,當客戶端與服務端完成數據傳輸後,客戶端發出請求包,代表個人數據傳輸完了,可是服務器並無傳輸完成,因此會一邊傳輸本身的數據一邊給客戶端確認收到結束的標誌,從而釋放本身與客戶端的相關等待資源,而後服務端繼續發本身未完成的數據,發送完成後,再次發送一個請求包,服務端的數據也發完了,客戶端此時收到請求包後進行確認,客戶端確認完成回覆客戶端,鏈接可斷開,資源釋放。typescript
爲何更多的意義是一種資源釋放的做用呢,若是兩端把數據都發完了後均只發送一次包告訴對方數據完了,而不發送給對方確認包能夠嗎?我理解是能夠的,可是爲了保證發的第一次結束確認包能獲得對方回覆確實收到了而不是丟失,因此各自要多一次確認包,若是丟失了回傳的確認包,則發起的一方不論是過去時候丟了仍是回來的時候丟了都會從新發起確認,從而耗費資源。shell
Hello World入門
使用Nodejs的net模塊來創建一個TCP服務器。
const net = require('net');
net.createServer(function(socket){ console.log('recive a connect'); console.log(socket);}).listen(8000, function(){ console.log('TCPServer listen on 8000');})
使用telnet發起TCP請求進行測試。
telnet 127.0.0.1 8000#Trying 127.0.0.1...#Connected to localhost.#Escape character is '^]'.
telnet命令簡介
telnet命令通常用來作遠程登陸,跟ssh命令一個做用,屬因而TCP/IP協議族中的一員,是Internet遠程登錄服務的標準協議和主要方式。可是爲何不多見甚至沒聽過telnet還能遠程登陸呢?是由於telnet採用明文傳送報文,安全性很差,因此如今用的多且熟悉的遠程登陸都是經過ssh(security shell)來完成的。
而telnet命令還有一個很是強大的做用,用來肯定遠程服務端口是否開啓可用,它的實質其實就是發起一個的數據包而後經過可否接收到回傳的包來進行測試。
價值過億的AI機器人核心代碼
在上面tcp服務器的代碼之上稍稍修改一下,一段價值過億的AI機器人代碼寫好了。
// tcpServer.jsconst net = require('net');
net.createServer(function(socket){ console.log('recive a connect'); /* * @description 添加事件監聽器,當client發送數據給服務器時,事件會觸發 */ socket.on('data', function (data) { const message = data.toString().trim(); let response = `機器人:${message}`; if (response.indexOf('?') > -1) { response = String.prototype.slice.apply(response, [0, -2]) + '!'; } // 過濾空消息 if (message) { socket.write(response, function(){ console.log(`${response} has send!`); }) } });}).listen(8000, function(){ console.log('TCPServer listen: 8000');})
使用node tcpServer.js啓動TCP服務器,進行輸入內容測試。
這裏由於在telnet命令下,這裏輸入中文會亂碼,因此筆者使用nc命令進行測試,nc是一個更強大的網絡工具命令,被稱之爲網絡工具界的」瑞士軍刀「,這裏只用了簡單的探測功能,筆者以前使用過它作端口掃描與文件傳輸,強大到使人驚豔,後續有機會專門介紹一下這個命令,沒有安裝nc的能夠先安裝一下,固然若是你的機器telnet下不亂碼的話,也可使用telenet進行測試。
實現一個簡易版的「微信」
這裏簡單作一個相似於微信實時通信工具,來看看 TCP連接下多客戶端之間的通訊是怎麼作的。
服務器端代碼
const net = require('net');
// 緩存在線的用戶const users = {};
// 建立TCP服務器net.createServer(function(socket){ /* 發送數據 */ socket.write(JSON.stringify({ type: 'system', message: '你已經成功鏈接了!' }));
/* 監聽data事件 */ socket.on('data',function(data){ const msg = JSON.parse(data.toString()); if (msg.type === 'registe') { // 註冊用戶,暫存socketj進全局變量users users[msg.userId] = socket; } else if(msg.type === 'singleMsg') { // 發送消息 if (users[msg.targetId]) { //首先查看全局變量裏是否存在用戶 users[msg.targetId].write(data, function(){ console.log(`${JSON.stringify(msg)} 已經被髮送!`); }); } else { //不存在則認爲不在線 const resMsg = JSON.stringify({ type: 'error', message: `發送失敗了~,${msg.targetId}用戶不在線!` }) users[msg.userId].write(resMsg, function(){ console.log(`${JSON.stringify(resMsg)} 已經被髮送!`); }); } } })}).listen(8000,function(){ console.log('TCPServer listen: 8000');})
客戶端代碼:
/** * 構建TCP客戶端 */const client = require('net').Socket();
function genClient(userId) { return new Promise(resolve => { // 設置鏈接的服務器 client.connect(8000, '127.0.0.1', function () { // 向服務器發送數據 client.write(JSON.stringify({"type": "registe", userId })); resolve(client); })
// 監聽服務器傳來的data數據 client.on('data', function (data) { let msg; try { msg = JSON.parse(data.toString()) } catch(err) { msg = data.toString(); } const { type, userId, message } = msg; // 判斷是哪類消息,除了系統消息與錯誤消息均認爲用戶消息 if (type === 'error') { console.log(message); } else if (type === 'system') { console.log(`系統消息:${message}`); } else { console.log(`${userId}用戶說:${message}`); } }) })}
module.exports = genClient;
const genClient = require('./tcpClient');
const userId = 'userId:100001';genClient(userId).then(client => { const msg = { userId, type: 'singleMsg', targetId: 'userId:100002', message: '你好' }; // 用定時器模擬用戶在發送消息 setInterval(()=>{ client.write(JSON.stringify(msg)); }, 3000)})
代碼運行結果:
這裏模擬了兩個用戶之間的即時消息通信,比較簡單。簡單說一下思路,當一個新用戶來的時候,將其帶來的userId做爲主鍵,存進全局變量中,當有另外一用戶要發消息時,先從在線用戶緩存之中查找其帶來的接收方ID中是否存在,存在即表明在線,能夠發送消息,不然告知用戶,接收方不在線。
本文所用的的代碼都可在下面找到,有興趣的clone下來動手練習。
文章用到的代碼都可在此獲取:
https://github.com/FantasyGao/Practice-book/tree/master/nodejs/tcp
若是你、喜歡探討技術,或者對本文有任何的意見或建議,你能夠掃描下方二維碼,關注微信公衆號「魚頭的Web海洋」,隨時與魚頭互動。歡迎!衷心但願能夠碰見你。
本文分享自微信公衆號 - 魚頭的Web海洋(krissarea)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。