互聯網的運作,最根本的驅動就是信息的交互,NodeJS 在數據交互這一塊做的很帶感,異步編程讓人很愜意,關於 NodeJS 的數據通信,最基礎的兩個模塊是 NET 和 HTTP,前者是基於 TCP 的封裝,后者本質還是 TCP 層,只不過做了比較多的數據封裝,我們視之為更高層。
本文先述說 NodeJS 的 NET 模塊工作機制,下次再談一談 HTTP 模塊。
本文地址:http://www.cnblogs.com/hustskyking/p/nodejs-net-module.html,轉載請注明源地址。
一、服務器和客戶端之間的交互
NodeJS 底層支撐是 v8,v8 是用 C++ 編寫的一個編譯和運行 JavaScript 代碼的庫,說到 TCP/UDP,寫 C/C++ 的童鞋肯定不會感到陌生,在建立 socket 連接的時候,基本都會涉及到相關的知識。
這里先解釋下服務器端和客戶端之間的一些共性和差異。關於數據交互,我們可以想象成,Server 與 Client 之間建立了一個管道(pipe),這個管道有兩個分支,一個是用於發送 S 到 C 的數據,一個是用於發送 C 到 S 的數據。那么這個管道是如何建立的呢?
首先,Server 監聽本地的某個端口(所謂端口,可以理解成對外交流的攤鋪),Client 很明確自己要跟誰去交流,他去訪問 Server 的那個攤鋪,於是兩者之間就可以溝通了。所以 Server 跟 Client 之間的差異是十分明顯的,Server 會監聽端口,而 Client 去訪問端口。
Unix/Linux 系統跟 windows 有些不同,他可以去監聽端口,也可以去監聽文件,也就是說他可以把端口和文件都當做對外交流的攤鋪。那么 Client 可以通過訪問一個文件與 Server 建立起 pipe。
二、Node 如何開啟一個 TCP 服務器
在電腦上安裝好了 Node 之后,我們就可以引用 Node 提供的模塊,Node 內置了很多模塊,如文件處理(FireSystem)、控制台(Console)、數據流(Stream)等等,這些我會在以后的文章中提到。建立 TCP 連接需要用到的是 Node 的 NET 模塊。使用一個模塊十分簡單:
var net = require('net');
net
是一個系統模塊,也就是安裝 Node 之后自帶的模塊,沒必要對他感到畏懼,其實他的內部也是十分簡單的:
var Net = function(){}; Net.methodA = function (){}; Net.methodB = function (){}; module.exports = Net;
我們可以簡單理解 net 模塊的內部實現,他就是一個 Net 類,上面綁定了很多的 methods,require 之后,相當於返回一個 Net 類,此時我們就可以盡情使用 Net 中定義的所有方法和屬性了。
Node 中開啟一個 TCP 服務器:
// server.js var net = require('net'); var server = net.createServer(function(socket) { //'connection' listener console.log('server connected'); socket.on('end', function() { console.log('server disconnected'); }); socket.on('data', function(){ socket.end('hello\r\n'); }); }); server.listen(8124, function() { //'listening' listener console.log('server bound'); });
上面這段代碼應該很好理解,首先 net.createServer
創建一個 TCP 服務,這個服務綁定(server.listen)在 8124 這個端口上,創建 Server 后我們看到有一個回調函數,這個回調函數的實現方式是怎么樣的呢?
net.createServer = function(callback){ // 每次客戶端連接都會新建一個 socket var socket = new Socket(); callback && callback(socket); };
在調用上面函數的時候傳入一個參數,這個參數也是函數,並且接受了 socket ,這個由其他方法構造的一個管道(pipe),他的作用就是用來數據交互的。第一節中我們說到了,pipe 是需要 Client 跟 Server 打招呼才能建立的,如果此刻沒有客戶端訪問 Server,這個 socket 就不會存在了。
三、寫一個客戶端程序與服務器交互
既然 Socket ,也就是管道(pipe)還沒有存在,那肯定是不會存在通訊的,下面來寫一個客戶端程序:
// client.js var net = require("net"); var client = net.connect({port: 8124}, function(){ console.log('client connected'); client.write('world!\r\n'); }); client.on('data', function(data) { console.log(data.toString()); client.end(); }); client.on('end', function() { console.log('client disconnected'); });
net.connect
顧名思義,就是連接到服務端,第一個參數是對象,設置端口(port)為 8124,也就是我們服務器監聽的端口,由於沒有設置 host 參數,那默認就是 localhost (本地)。在 Server 中,socket 是管道的一端,而在 client 中,client 本身就是管道的一端,如果是多個客戶端連接 Server,Server 會新建多個 socket,每個 socket 對應一個 client。
數據的通信就十分簡單了,首先運行服務器程序:
node server.js
此時便會有一個服務器監聽 8124 端口,然后打開一個客戶端程序:
node client.js
那么兩者之間的信息交互就開始了。具體他們是怎么交流的呢?
四、基於事件的哲學
首先我們要說一說 NodeJS 的 EventEmitter 模塊。這個模塊就是一個事件中心,之前寫過相關的內容,可以看看簡介版的 EventEmitter,戳我。EventEmitter 也就是如此,可以 on 添加事件到事件池,也可以 trigger 觸發事件,當然可以從事件池中刪除事件 off。
NET 模塊是繼承 EventEmitter 的,所以他創建的很多對象可以:
client.on('data', function(data) { console.log(data.toString()); client.end(); });
如上綁定很多自定義的事件,等到交互中需要信息交流的時候再觸發。就拿上面這句代碼來說,client 綁定了一個 data 事件,這個事件會在 Server 有信息傳過來的時候觸發,他所做的工作,先打印傳過來的數據,然后 end() 關閉這個管道(pipe)。
JavaScript 是基於事件的一門語言,幾乎所有的動作都是由事件驅動的,這個在異步編程中顯得十分突出。
五、相關 API 的枚舉
Server 除了有 listen 函數外,還有很多的接口:
Server.close([callback])
,停止監聽,那么之前的所有管道也就沒有用了。-
Server.maxConnections
,Server 的最大連接數,這個連接數是有上限的(跟系統有關),我們也可以自己設定連接數的最大上限(不超過系統最大連接數)。 -
Server.address()
,在 listen 之后可以通過這個函數拿到服務器的相關信息。// grab a random port. server.listen(function() { address = server.address(); console.log("opened server on %j", address); });
還有 write、end、destroy、pause、resume 等等很多豐富的接口,可以在這里查看詳情http://nodejs.org/api/net.html。
六、小結
本來打算寫一個聊天室,但是這種簡單的代碼網絡上俯拾皆是,本文目的是說清楚 TCP 連接在服務器和客戶端之間的交互過程,深入的話題留到下次談。
七、參考資料