初步研究node中的網(wǎng)絡(luò)通信模塊

閱讀:485 2023-11-05 19:35:37

目前,我們處于互聯(lián)網(wǎng)時(shí)代,互聯(lián)網(wǎng)產(chǎn)品百花齊放。例如,當(dāng)打開瀏覽器,可以看到各種信息,瀏覽器是如何跟服務(wù)器進(jìn)行通信的?當(dāng)打開微信跟朋友聊天時(shí),你是如何跟朋友進(jìn)行消息傳遞的?這些都得靠網(wǎng)絡(luò)進(jìn)程之間的通信,都得依賴于socket。那什么是socket?node中有哪些跟網(wǎng)絡(luò)通信有關(guān)的模塊?這些問題是本文研究的重點(diǎn)。

1. Socket

Socket源于Unix,而Unix的基本哲學(xué)是『一些皆文件』,都可以用『打開open ==> 讀/寫(read/write) ==> 關(guān)閉(close)』模式來操作,Socket也可以采用這種方法進(jìn)行理解。關(guān)于Socket,可以總結(jié)如下幾點(diǎn):

  • 可以實(shí)現(xiàn)底層通信,幾乎所有的應(yīng)用層都是通過socket進(jìn)行通信的,因此『一切且socket』
  • 對(duì)TCP/IP協(xié)議進(jìn)行封裝,便于應(yīng)用層協(xié)議調(diào)用,屬于二者之間的中間抽象層
  • 各個(gè)語言都與相關(guān)實(shí)現(xiàn),例如C、C++、node
  • TCP/IP協(xié)議族中,傳輸層存在兩種通用協(xié)議: TCP、UDP,兩種協(xié)議不同,因?yàn)椴煌瑓?shù)的socket實(shí)現(xiàn)過程也不一樣

初步研究node中的網(wǎng)絡(luò)通信模塊

2. node中網(wǎng)絡(luò)通信的架構(gòu)實(shí)現(xiàn)

node中的模塊,從兩種語言實(shí)現(xiàn)角度來說,存在javscript、c++兩部分,通過 process.binding 來建立關(guān)系。具體分析如下:

  • 標(biāo)準(zhǔn)的node模塊有net、udp、dns、http、tls、https等
  • V8是chrome的內(nèi)核,提供了javascript解釋運(yùn)行功能,里面包含tcp_wrap.h、udp_wrap.h、tls_wrap.h等
  • OpenSSL是基本的密碼庫,包括了MD5、SHA1、RSA等加密算法,構(gòu)成了node標(biāo)準(zhǔn)模塊中的 crypto
  • cares模塊用于DNS的解析
  • libuv實(shí)現(xiàn)了跨平臺(tái)的異步編程
  • http_parser用于http的解析

初步研究node中的網(wǎng)絡(luò)通信模塊

3. net使用

net模塊 是基于TCP協(xié)議的socket網(wǎng)路編程模塊,http模塊就是建立在該模塊的基礎(chǔ)上實(shí)現(xiàn)的,先來看看基本使用方法:

復(fù)制
// 創(chuàng)建socket服務(wù)器 server.js   const net = require('net')   const server = net.createServer();   server.on('connection', (socket) => {   socket.pipe(process.stdout);   socket.write('data from server');   });   server.listen(3000, () => {   console.log(`server is on ${JSON.stringify(server.address())}`);   });   // 創(chuàng)建socket客戶端 client.js   const net = require('net');   const client = net.connect({port: 3000});   client.on('connect', () => {   client.write('data from client');   });   client.on('data', (chunk) => {   console.log(chunk.toString());   client.end();   });   // 打開兩個(gè)終端,分別執(zhí)行`node server.js`、`node client.js`,可以看到客戶端與服務(wù)器進(jìn)行了數(shù)據(jù)通信。 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

使用 const server = net.createServer(); 創(chuàng)建了server對(duì)象,那server對(duì)象有哪些特點(diǎn):

復(fù)制
// net.js   exports.createServer = function(options, connectionListener) {   return new Server(options, connectionListener);   };  function Server(options, connectionListener) {   EventEmitter.call(this);   ...   if (typeof connectionListener === 'function') {   this.on('connection', connectionListener);   }   ...   this._handle = null;   }   util.inherits(Server, EventEmitter); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

上述代碼可以分為幾個(gè)點(diǎn):

  • createServer 就是一個(gè)語法糖,幫助new生成server對(duì)象
  • server對(duì)象繼承了EventEmitter,具有事件的相關(guān)方法
  • _handle是server處理的句柄,屬性值最終由c++部分的 TCP 、 Pipe 類創(chuàng)建
  • connectionListener也是語法糖,作為connection事件的回調(diào)函數(shù)

再來看看connectionListener事件的回調(diào)函數(shù),里面包含一個(gè) socket 對(duì)象,該對(duì)象是一個(gè)連接套接字,是個(gè)五元組(server_host、server_ip、protocol、client_host、client_ip),相關(guān)實(shí)現(xiàn)如下:

復(fù)制
function onconnection(err, clientHandle) {   ...   var socket = new Socket({  ...   });   ...   self.emit('connection', socket);   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

因?yàn)镾ocket是繼承了 stream.Duplex ,所以Socket也是一個(gè)可讀可寫流,可以使用流的方法進(jìn)行數(shù)據(jù)的處理。

接下來就是很關(guān)鍵的端口監(jiān)聽(port),這是server與client的主要區(qū)別,代碼:

復(fù)制
Server.prototype.listen = function() {   ...   listen(self, ip, port, addressType, backlog, fd, exclusive);   ...   }   function listen(self, address, port, addressType, backlog, fd, exclusive) {   ...   if (!cluster) cluster = require('cluster');   if (cluster.isMaster || exclusive) {   self._listen2(address, port, addressType, backlog, fd);   return;   }   cluster._getServer(self, {   ...   }, cb);   function cb(err, handle) {  ...   self._handle = handle;   self._listen2(address, port, addressType, backlog, fd);   ...   }   }   Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {   if (this._handle) {   ...   } else {   ...   rval = createServerHandle(address, port, addressType, fd);   ...   this._handle = rval;   }   this._handle.onconnection = onconnection;   var err = _listen(this._handle, backlog);   ...   }   function _listen(handle, backlog) {   return handle.listen(backlog || 511);   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

上述代碼有幾個(gè)點(diǎn)需要注意:

  • 監(jiān)聽的對(duì)象可以是端口、路徑、定義好的server句柄、文件描述符
  • 當(dāng)通過cluster創(chuàng)建工作進(jìn)程(worker)時(shí),exclusive判斷是否進(jìn)行socket連接的共享
  • 事件監(jiān)聽最終還是通過TCP/Pipe的listen來實(shí)現(xiàn)
  • backlog規(guī)定了socket連接的限制,默認(rèn)最多為511

接下來分析下listen中最重要的 _handle 了,_handle決定了server的功能:

復(fù)制
function createServerHandle(address, port, addressType, fd) {   ...   if (typeof fd === 'number' && fd >= 0) {   ...   handle = createHandle(fd);   ...   } else if(port === -1 && addressType === -1){   handle = new Pipe();   } else {   handle = new TCP();  }   ...   return handle;   }   function createHandle(fd) {   var type = TTYWrap.guessHandleType(fd);   if (type === 'PIPE') return new Pipe();   if (type === 'TCP') return new TCP();   throw new TypeError('Unsupported fd type: ' + type);   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

_handle 由C++中的Pipe、TCP實(shí)現(xiàn),因而要想完全搞清楚node中的網(wǎng)絡(luò)通信,必須深入到V8的源碼里面。

4. UDP/dgram使用

跟net模塊相比,基于UDP通信的dgram模塊就簡(jiǎn)單了很多,因?yàn)椴恍枰ㄟ^三次握手建立連接,所以整個(gè)通信的過程就簡(jiǎn)單了很多,對(duì)于數(shù)據(jù)準(zhǔn)確性要求不太高的業(yè)務(wù)場(chǎng)景,可以使用該模塊完成數(shù)據(jù)的通信。

復(fù)制
// server端實(shí)現(xiàn)   const dgram = require('dgram');   const server = dgram.createSocket('udp4');   server.on('message', (msg, addressInfo) => {   console.log(addressInfo);   console.log(msg.toString());   const data = Buffer.from('from server');   server.send(data, addressInfo.port);   });   server.bind(3000, () => {   console.log('server is on ', server.address());   });   // client端實(shí)現(xiàn)   const dgram = require('dgram');   const client = dgram.createSocket('udp4');   const data = Buffer.from('from client');   client.send(data, 3000);   client.on('message', (msg, addressInfo) => {  console.log(addressInfo);   console.log(msg.toString());   client.close();  }); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

從源碼層面分析上述代碼的原理實(shí)現(xiàn):

復(fù)制
exports.createSocket = function(type, listener) {   return new Socket(type, listener);   };   function Socket(type, listener) {   ...   var handle = newHandle(type);   this._handle = handle;   ...   this.on('message', listener);   ...   }   util.inherits(Socket, EventEmitter);   const UDP = process.binding('udp_wrap').UDP;   function newHandle(type) {   if (type == 'udp4') {   const handle = new UDP();   handle.lookup = lookup4;   return handle;   }   if (type == 'udp6') {   const handle = new UDP();   handle.lookup = lookup6;   handle.bind = handle.bind6;   handle.send = handle.send6;   return handle;   }   ...   }   Socket.prototype.bind = function(port_ /*, address, callback*/) {   ...   startListening(self);   ...   }   function startListening(socket) {   socket._handle.onmessage = onMessage;   socket._handle.recvStart();   ...   }  function onMessage(nread, handle, buf, rinfo) {   ...  self.emit('message', buf, rinfo);   ...   }   Socket.prototype.send = function(buffer, offset, length, port, address, callback) {   ...   self._handle.lookup(address, function afterDns(ex, ip) {   doSend(ex, self, ip, list, address, port, callback);   });   }   const SendWrap = process.binding('udp_wrap').SendWrap;   function doSend(ex, self, ip, list, address, port, callback) {   ...   var req = new SendWrap();   ...   var err = self._handle.send(req, list, list.length, port, ip, !!callback);   ...   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

上述代碼存在幾個(gè)點(diǎn)需要注意:

  • UDP模塊沒有繼承stream,僅僅繼承了EventEmit,后續(xù)的所有操作都是基于事件的方式
  • UDP在創(chuàng)建的時(shí)候需要注意ipv4和ipv6
  • UDP的_handle是由UDP類創(chuàng)建的
  • 通信過程中可能需要進(jìn)行DNS查詢,解析出ip地址,然后再進(jìn)行其他操作

5. DNS使用

DNS(Domain Name System)用于域名解析,也就是找到host對(duì)應(yīng)的ip地址,在計(jì)算機(jī)網(wǎng)絡(luò)中,這個(gè)工作是由網(wǎng)絡(luò)層的ARP協(xié)議實(shí)現(xiàn)。在node中存在 net 模塊來完成相應(yīng)功能,其中dns里面的函數(shù)分為兩類:

依賴底層操作系統(tǒng)實(shí)現(xiàn)域名解析,也就是我們?nèi)粘i_發(fā)中,域名的解析規(guī)則,可以回使用瀏覽器緩存、本地緩存、路由器緩存、dns服務(wù)器,該類僅有 dns.lookup

該類的dns解析,直接到nds服務(wù)器執(zhí)行域名解析

復(fù)制
const dns = require('dns');   const host = 'bj.meituan.com';   dns.lookup(host, (err, address, family) => {   if (err) {   console.log(err);   return;   }   console.log('by net.lookup, address is: %s, family is: %s', address, family);   });   dns.resolve(host, (err, address) => {   if (err) {   console.log(err);   return;   }   console.log('by net.resolve, address is: %s', address);   })   // by net.resolve, address is: 103.37.152.41   // by net.lookup, address is: 103.37.152.41, family is: 4 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

在這種情況下,二者解析的結(jié)果是一樣的,但是假如我們修改本地的/etc/hosts文件呢

復(fù)制
// 在/etc/host文件中,增加:   10.10.10.0 bj.meituan.com   // 然后再執(zhí)行上述文件,結(jié)果是:   by net.resolve, address is: 103.37.152.41   by net.lookup, address is: 10.10.10.0, family is: 4 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

接下來分析下dns的內(nèi)部實(shí)現(xiàn):

復(fù)制
const cares = process.binding('cares_wrap');   const GetAddrInfoReqWrap = cares.GetAddrInfoReqWrap;   exports.lookup = function lookup(hostname, options, callback) {   ...   callback = makeAsync(callback);   ...   var req = new GetAddrInfoReqWrap();   req.callback = callback;   var err = cares.getaddrinfo(req, hostname, family, hints);   ...   }   function resolver(bindingName) {   var binding = cares[bindingName];   return function query(name, callback) {   ...   callback = makeAsync(callback);   var req = new QueryReqWrap();   req.callback = callback;   var err = binding(req, name);   ...   return req;   }   }   var resolveMap = Object.create(null);   exports.resolve4 = resolveMap.A = resolver('queryA');   exports.resolve6 = resolveMap.AAAA = resolver('queryAaaa');   ...   exports.resolve = function(hostname, type_, callback_) {   ...   resolver = resolveMap[type_];   return resolver(hostname, callback);   ...  } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

上面的源碼有幾個(gè)點(diǎn)需要關(guān)注:

  • lookup與resolve存在差異,使用的時(shí)候需要注意
  • 不管是lookup還是resolve,均依賴于cares庫
  • 域名解析的type很多: resolve4、resolve6、resolveCname、resolveMx、resolveNs、resolveTxt、resolveSrv、resolvePtr、resolveNaptr、resolveSoa、reverse

6. HTTP使用

在WEB開發(fā)中,HTTP作為***、最重要的應(yīng)用層,是每個(gè)開發(fā)人員應(yīng)該熟知的基礎(chǔ)知識(shí),我面試的時(shí)候必問的一塊內(nèi)容。同時(shí),大多數(shù)同學(xué)接觸node時(shí),首先使用的恐怕就是http模塊。先來一個(gè)簡(jiǎn)單的demo看看:

復(fù)制
const http = require('http');   const server = http.createServer();   server.on('request', (req, res) => {   res.setHeader('foo', 'test');   res.writeHead(200, {   'Content-Type': 'text/html',   });   res.write('');   res.end(``);   });   server.listen(3000, () => {   console.log('server is on ', server.address());   var req = http.request({ host: '127.0.0.1', port: 3000});   req.on('response', (res) => {   res.on('data', (chunk) => console.log('data from server ', chunk.toString()) );   res.on('end', () => server.close() );   });   req.end();   });   // 輸出結(jié)果如下:   // server is on { address: '::', family: 'IPv6', port: 3000 }   // data from server 頭
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

針對(duì)上述demo,有很多值得深究的地方,一不注意服務(wù)就掛掉了,下面根據(jù)node的 官方文檔 ,逐個(gè)進(jìn)行研究。

6.1 http.Agent

因?yàn)镠TTP協(xié)議是無狀態(tài)協(xié)議,每個(gè)請(qǐng)求均需通過三次握手建立連接進(jìn)行通信,眾所周知三次握手、慢啟動(dòng)算法、四次揮手等過程很消耗時(shí)間,因此HTTP1.1協(xié)議引入了keep-alive來避免頻繁的連接。那么對(duì)于tcp連接該如何管理呢?http.Agent就是做這個(gè)工作的。先看看源碼中的關(guān)鍵部分:

復(fù)制
function Agent(options) {   ...   EventEmitter.call(this);   ...   self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets;   self.maxFreeSockets = self.options.maxFreeSockets || 256;   ...   self.requests = {}; // 請(qǐng)求隊(duì)列   self.sockets = {}; // 正在使用的tcp連接池   self.freeSockets = {}; // 空閑的連接池   self.on('free', function(socket, options) {   ...  // requests、sockets、freeSockets的讀寫操作   self.requests[name].shift().onSocket(socket);   freeSockets.push(socket);   ...   }   }   Agent.defaultMaxSockets = Infinity;   util.inherits(Agent, EventEmitter);   // 關(guān)于socket的相關(guān)增刪改查操作   Agent.prototype.addRequest = function(req, options) {   ...   if (freeLen) {   var socket = this.freeSockets[name].shift();   ...   this.sockets[name].push(socket);   ...   } else if (sockLen < this.maxSockets) {   ...   } else {   this.requests[name].push(req);   }   ...   }   Agent.prototype.createSocket = function(req, options, cb) { ... }   Agent.prototype.removeSocket = function(s, options) { ... }   exports.globalAgent = new Agent(); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

上述代碼有幾個(gè)點(diǎn)需要注意:

  • maxSockets默認(rèn)情況下,沒有tcp連接數(shù)量的上限(Infinity)
  • 連接池管理的核心是對(duì) sockets 、 freeSockets 的增刪查
  • globalAgent會(huì)作為http.ClientRequest的默認(rèn)agent

下面可以測(cè)試下agent對(duì)請(qǐng)求本身的限制:

復(fù)制
// req.js   const http = require('http');   const server = http.createServer();   server.on('request', (req, res) => {   var i=1;   setTimeout(() => {   res.end('ok ', i++);   }, 1000)   });   server.listen(3000, () => {   var max = 20;   for(var i=0; i   var req = http.request({ host: '127.0.0.1', port: 3000});   req.on('response', (res) => {   res.on('data', (chunk) => console.log('data from server ', chunk.toString()) );   res.on('end', () => server.close() );  });   req.end();   }   });   // 在終端中執(zhí)行time node ./req.js,結(jié)果為:   // real 0m1.123s   // user 0m0.102s   // sys 0m0.024s   // 在req.js中添加下面代碼   http.globalAgent.maxSockets = 5;  // 然后同樣time node ./req.js,結(jié)果為:   real 0m4.141s   user 0m0.103s   sys 0m0.024s 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

當(dāng)設(shè)置maxSockets為某個(gè)值時(shí),tcp的連接就會(huì)被限制在某個(gè)值,剩余的請(qǐng)求就會(huì)進(jìn)入 requests 隊(duì)列里面,等有空余的socket連接后,從request隊(duì)列中出棧,發(fā)送請(qǐng)求。

6.2 http.ClientRequest

當(dāng)執(zhí)行http.request時(shí),會(huì)生成ClientRequest對(duì)象,該對(duì)象雖然沒有直接繼承Stream.Writable,但是繼承了http.OutgoingMessage,而http.OutgoingMessage實(shí)現(xiàn)了write、end方法,因?yàn)榭梢援?dāng)跟stream.Writable一樣的使用。

復(fù)制
var req = http.request({ host: '127.0.0.1', port: 3000, method: 'post'});   req.on('response', (res) => {   res.on('data', (chunk) => console.log('data from server ', chunk.toString()) );   res.on('end', () => server.close() );   });   // 直接使用pipe,在request請(qǐng)求中添加數(shù)據(jù)   fs.createReadStream('./data.json').pipe(req); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

接下來,看看http.ClientRequest的實(shí)現(xiàn), ClientRequest繼承了OutgoingMessage:

復(fù)制
const OutgoingMessage = require('_http_outgoing').OutgoingMessage;   function ClientRequest(options, cb) {   ...   OutgoingMessage.call(self);   ...   }   util.inherits(ClientRequest, OutgoingMessage); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

6.3 http.Server

http.createServer其實(shí)就是創(chuàng)建了一個(gè)http.Server對(duì)象,關(guān)鍵源碼如下:

復(fù)制
exports.createServer = function(requestListener) {   return new Server(requestListener);   };   function Server(requestListener) {   ...   net.Server.call(this, { allowHalfOpen: true });   if (requestListener) {   this.addListener('request', requestListener);   }   ...   this.addListener('connection', connectionListener);   this.timeout = 2 * 60 * 1000;  ...   }   util.inherits(Server, net.Server);   function connectionListener(socket) {   ...   socket.on('end', socketOnEnd);   socket.on('data', socketOnData)   ...   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

有幾個(gè)需要要關(guān)注的點(diǎn):

  • 服務(wù)的創(chuàng)建依賴于net.server,通過net.server在底層實(shí)現(xiàn)服務(wù)的創(chuàng)建
  • 默認(rèn)情況下,服務(wù)的超時(shí)時(shí)間為2分鐘
  • connectionListener處理tcp連接后的行為,跟net保持一致

6.4 http.ServerResponse

看node.org官方是如何介紹server端的response對(duì)象的:

This object is created internally by an HTTP server–not by the user. It is passed as the second parameter to the ‘request’ event.

The response implements, but does not inherit from, the Writable Stream interface.

跟http.ClientRequest很像,繼承了OutgoingMessage,沒有繼承Stream.Writable,但是實(shí)現(xiàn)了Stream的功能,可以跟Stream.Writable一樣靈活使用:

復(fù)制
function ServerResponse(req) {   ...   OutgoingMessage.call(this);   ...   }   util.inherits(ServerResponse, OutgoingMessage); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

6.5 http.IncomingMessage

An IncomingMessage object is created by http.Server or http.ClientRequest and passed as the first argument to the ‘request’ and ‘response’ event respectively. It may be used to access response status, headers and data.

http.IncomingMessage有兩個(gè)地方時(shí)被內(nèi)部創(chuàng)建,一個(gè)是作為server端的request,另外一個(gè)是作為client請(qǐng)求中的response,同時(shí)該類顯示地繼承了Stream.Readable。

復(fù)制
function IncomingMessage(socket) {   Stream.Readable.call(this);   this.socket = socket;   this.connection = socket;   ...   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

util.inherits(IncomingMessage, Stream.Readable);

7. 結(jié)語

上面是對(duì)node中主要的網(wǎng)絡(luò)通信模塊,粗略進(jìn)行了分析研究,對(duì)網(wǎng)絡(luò)通信的細(xì)節(jié)有大概的了解。但是這還遠(yuǎn)遠(yuǎn)不夠的,仍然無法解決node應(yīng)用中出現(xiàn)的各種網(wǎng)絡(luò)問題,這邊文章只是一個(gè)開端,希望后面可以深入了解各個(gè)細(xì)節(jié)、深入到c++層面。

相關(guān)文章
{{ v.title }}
{{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
你可能感興趣
推薦閱讀 更多>
推薦商標(biāo)

{{ v.name }}

{{ v.cls }}類

立即購買 聯(lián)系客服
主站蜘蛛池模板: 亚洲成av人片在线观看天堂无码| 国产在线拍揄自揄拍无码| 精品高潮呻吟99av无码视频| 精品无码久久久久国产| 日韩乱码人妻无码中文字幕视频| 综合无码一区二区三区| 麻豆AV无码精品一区二区| 国产成人无码专区| 亚洲日韩中文字幕无码一区| 久久人妻少妇嫩草AV无码蜜桃| 秋霞无码一区二区| 在线a亚洲v天堂网2019无码| 亚洲av无码专区亚洲av不卡| 国模无码人体一区二区| 性色av无码免费一区二区三区 | 免费无码专区毛片高潮喷水| 无码人妻精品中文字幕免费 | 亚洲av无码专区在线| 国产免费黄色无码视频| 久久久无码精品人妻一区| 无码H肉动漫在线观看| 中文无码久久精品| yy111111少妇影院里无码| 全免费a级毛片免费看无码| 6080YYY午夜理论片中无码| 免费无遮挡无码永久视频| 亚洲乱亚洲乱妇无码麻豆| 超清无码熟妇人妻AV在线电影| 国产精品无码一二区免费| 国产精品无码久久四虎| 午夜爽喷水无码成人18禁三级| 久久久久亚洲AV无码专区首JN | 精品无码成人久久久久久| 99久久人妻无码精品系列| 无码国内精品人妻少妇蜜桃视频| 亚洲精品高清无码视频| 亚洲日韩精品一区二区三区无码 | 无码精品蜜桃一区二区三区WW| 无码精品A∨在线观看无广告| 日韩免费a级毛片无码a∨| 精品久久久久久久无码久中文字幕|