這篇文章將通過開發一個簡單聊天室的方式,介紹node.js的net模塊。
一、第一版,只向客戶端發送信息
我們先實現一個簡單的版本,代碼如下:
var net=require('net'); var chatServer=net.createServer(); chatServer.on('connection',function(client){ client.write('hi!\n'); client.write('bye!\n'); client.end(); }) chatServer.listen(9001);
代碼講解:
1.因為我們要使用tcp作為通信協議,node中tcp相關的類是放在net模塊中的,所以我需要先引用net。
2.通過net.createServer()就為我們創建了一個tcp的服務器。
3.接下來使用on方法實現對connection事件的監聽。每當有一個新的客戶端連接到我們的tcp服務器的時候,都會觸發connection對應的方法,向客戶端輸出‘hi!bye!’的文字 信息。然后通過client.end()關閉連接。
4.程序最后通過chatServer.listen(9001);實現對9001端口的監聽。
運行服務器:
使用webstorm的調試工具運行我們的服務器。
運行客戶端:
打開window的命令行工具,
輸入如下命令:
telnet 127.0.0.1 9001
回車,輸出如下結果:
note:
自己實踐上面例子的時候遇到了點小狀況,我最開始使用的是9000端口,運行的時候程序要報錯。
events.js:66 throw arguments[1]; // Unhandled 'error' event ^ Error: listen EADDRINUSE at errnoException (net.js:769:11) at Server._listen2 (net.js:909:14) at listen (net.js:936:10) at Server.listen (net.js:985:5) at Object.<anonymous> (D:\workspace\nodejs\chatroom\chatServer.js:8:12) at Module._compile (module.js:449:26) at Object.Module._extensions..js (module.js:467:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Module.runMain (module.js:492:10)
‘EADDRINUSE’這個東西讓人很費解,完全不知道是什么意思,借助有道的翻譯,意思是:錯誤地址使用。‘EADDRINUSE’應該是‘error address in use’的縮寫。后來借助google找到了合理的解釋,說是你監聽的端口已經被使用了,我把端口換成了9001,一切正常。
當遇到異常的時候,不知道node有沒有提供幫助的地方,光靠他提供的異常信息提示真是解決不了問題啊。或許最好的幫手就是google。
二、第二版,可以接收客戶端信息:
代碼如下:
var net=require('net'); var chatServer=net.createServer(); var clientList=[]; chatServer.on('connection',function(client){ client.write('hi!\n'); clientList.push(client); client.on('data',function(data){ for(var i= 0,len=clientList.length;i<len;i++){ if(client!=clientList[i]){ clientList[i].write(data); } } console.log(data); }); }) chatServer.listen(9001);
這版比第一版有兩個變化點,一是增加了client的on方法,二是移出了client.end();方法。client的on方法添加data事件,這樣客戶端每次發送數據,服務器都可以接收到。移出client.end()是因為,如果我們關閉了鏈接,客戶端再發送新數據,服務器就無法接收了。
我們來看運行結果,其中‘hello’是客戶端輸入信息,紅線框住的是服務器端接收到的信息,注意它是二進制數據,需要我們做相應的處理才能轉換成字符串,后面的課程會有介紹。
三、第三版,實現客戶端和服務器端的相互通信:
在這里我們需要把每個客戶端都放到一個數組變量里面緩存起來,遍歷數組,使用client.write()方法對每個客戶端做出反應。我們來看代碼:
var net=require('net'); var chatServer=net.createServer(); var clientList=[]; chatServer.on('connection',function(client){ client.write('hi!\n'); clientList.push(client); client.on('data',function(data){ for(var i= 0,len=clientList.length;i<len;i++){ if(client!=clientList[i]){ clientList[i].write(data); } } console.log(data); }); }) chatServer.listen(9001);
我們創建了clientList來存放客戶端連接,每當有新連接進來的時候,把client對象保存入數組。接下來,判斷遍歷到的client是否是當前client,不是的話輸出data。
note:
這里大家注意‘len=clientList.length’部分,我們把clientList.length存入變量len,這樣可以提高程序的性能。在 i<len 運算的時候,就不用每次再去取clientList.length了,大家可以在自己的程序里也使用這樣的方式,特別是數組比較大的時候。
我們來看運行結果,要打開多個telnet鏈接,紅色為輸入信息,藍色為輸出信息:
四、最終版
我們增加了客戶斷開鏈接的 end 事件,在end事件和發送消息的過程中會清理不存在的客戶端。
var net=require('net'); var chatServer=net.createServer(); var clientList=[]; chatServer.on('connection',function(client){ client.name=client.remoteAddress+':'+client.remotePort; broadcast('hi,'+ client.name +' join!\r\n',client); client.write('hi,'+ client.name +'!\r\n'); clientList.push(client); client.on('data',function(data){ broadcast(client.name+' say:'+ data+'\r\n',client); }); client.on('end',function(){ broadcast('hi,'+ client.name +' quit!\r\n',client); clientList.splice(clientList.indexOf(client),1); }); }) function broadcast(message, client) { var cleanup=[]; for(var i= 0,len=clientList.length;i<len;i++){ if(client!==clientList[i]){ if(clientList[i].writable){ clientList[i].write(message); }else{ cleanup.push(clientList[i]); clientList[i].destroy(); } } } for(var i= 0,len=cleanup.length;i<len;i++){ clientList.splice(clientList.indexOf(cleanup[i]),1); } } chatServer.listen(9001);
運行效果:
三個客戶鏈接到服務器,其中一個向另外兩個說‘hello’。
其中一個客戶關閉鏈接:
今天的例子到此為止。