- net常用API解析以及应用
- 手动解析HTTP请求头
- 基于网络模块net与文件模块fs搭建简易的node服务
- net模块部分API参数详细解析
一、net常用API解析以及简单的应用
net模块的组成部分:
net.connect()实际上是net.createConnection()的别名,还有一个基于net模块子类的new net.socket()构造方法也具备相同的功能,都是用来创建一个新的客户端连接对象。
net.createServer()同样有一个基于net模块子类的new net.server()构造方法与其功能相同,都是用来创建一个新的服务器端网络连接的对象。
net.server类:
net.server事件:
- close事件:当 server 关闭的时候触发。 如果有连接存在,直到所有的连接结束才会触发这个事件。
- listening事件:启动这个服务监听时触发。
- error事件:当错误出现的时候触发。 不同于 net.Socket,‘close‘ 事件不会在这个事件触发后继续触发,除非 server.close() 是手动调用。
- connection事件:当服务被客户端成功连接时触发,该事件函数可以接受一个服务产生的socket对象作为参数,用于与客户端连接通讯。
net.server属性:
- server.listening:<boolean:布尔类型>表明 server 是否正在监听连接。
- server.maxConnections:<integer:整数类型>设置该属性使得当 server 连接数过多时拒绝连接。
net.server方法:
- new net.server():net.server对象构造方法,net.createServer()。
- server.address():返回当前连接服务的客户端地址信息(返回值为一个对象,address:IP地址;familey:协议族;port:端口号)
- server.close():当 server 被关闭时调用,并且返回当前net.server对象。
- server.getConnections(callback):异步获取服务器的当前并发连接数,当 socket 被传递给子进程时工作。回调函数callback的两个参数是
err
和count
。 - server.listen():启动一个服务来监听连接。该方法有多种参数形式,后面单独解析。
- server.ref():这个方法是用来解除server.unref()方法的关闭server的操作,并且server.ref()被调用后再调用server.unref()方法不生效。
- server.unref():在server(服务)事件系统中,如果当前server是唯一开启中的服务,如果这个server调用过server.unref()方法,该server在事件系统中仅存最后一个事件执行完以后,当前server会被关闭。
net.socket类:
net.socket事件:
- close事件:当socket.end()方法被调用后并且正确的关闭了连接,会触发这个关闭连接的监听事件。这个事件发生在end事件之后。socket.end()方法正确关闭了连接后连接的两端close监听事件都会被触发。
- connect事件:当通过socket.connect()方法连接上服务后,触发这个连接成功监听事件。
- data事件:用来接收消息,并且会将接收到的消息数据data传入回调函数。该事件与发送消息的write方法相对应,data接收的就是write发送的消息。
- end事件:当socket.end()方法被调用表示执行关闭连接,当前end监听事件会被触发。这个事件发生在close事件之前。socket.end()方法会触发当前端的end事件,并且连接的另一端接收到FIN请求关闭连接包后也会触发这个事件。
- error事件
- lookup事件:在找到主机之后创建连接之前触发。不可用于 Unix socket。
- ready事件:套接字准备好使用时触发,即socket.
connect()
后立即触发。 - timeout事件:当socket.setTimeout()启动了一个超时监听,如果在指定的事件内没有收到连接对面的回复,会触发这个事件,但不会关闭连接,关闭连接需要手动调用end或者destroy方法。
net.socket方法:
- socket.address():返回(操作系统报告)当前连接到的服务器地址信息(返回值为一个对象,address:IP地址;familey:协议族;port:端口号)
- socket.connect():用来与服务建立连接,该方法异步执行,连接建立成功后触发connect事件,这个方法有三种参数形式,后面单独拿出来解析。
- socket.destroy():在发生错误时一般使用在try.catch捕获错误中,通过该方法强制关闭该连接。该方法会强制清除socket上的所有io操作,并关闭该socket链接,但不会关闭server服务。该方法调用前socket.destroyed属性值为false,调用后为true。相比end关闭连接只会关闭连接,而不会清除socket正在执行的的io操作。调用该方法是可以将try.catch捕获到的错误对象传入该方法,然后会触发socket的error事件,如果不传入error对象则不会触发error事件。
- socket.end():关闭当前连接,当连接的另一端调用了socket.end()方法后会向服务端发送一个FIN包,这个方法会先触发end事件,连接关闭后会触发close事件。
- socket.pause():暂停读写数据,比如通过socket.write()来发送一个大文件时,计算机的处理速度或网络速度可能跟不上socket的读写速度,可以通过socket.pause()中途暂停,暂停后不会触发data事件,并且可以通过socket.resume()重新开始数据的读写操作。
- socket.ref():这个方法是用来解除socket.unref()方法的关闭socket的操作,并且socket.ref()被调用后再调用server.unref()方法不生效。
- socket.resume():恢复数据的读写操作,这个方法与socket.pause()配合使用。
- socket.setEncoding():设置作为可读流的编码。在 readable.setEncoding() 查看更多详情。默认情况下将字符串转换成‘utf-8’格式,这个方法可以设置一个参数来指定socket通讯的数据编码,比如:‘hex’。
- socket.setKeepAlive(enable[,initialDelay]):禁止或者启用长连接,enable默认为false表示禁止启用长连接,initialDelay用来设置接收最后一个数据包与发送一个长连接探针之间的延迟事件。
- socket.setNoDelay([noDelay]):禁止 Nagle 算法。默认情况下 TCP 连接使用 Nagle 算法,在发送之前缓冲数据。当调用socketl.setNoDelay()方法就会禁止Nagle算法,发送数据之前不会缓冲数据,noDelay默认值为true也就是禁止Nagle算法的参数设置。
- socket.setTimeout():启动连接超时监听事件,该方法第一个参数为超时时间(毫秒)必须传值,第二个是回调函数可选,这个回调函数是在超时时间回调函数之后被调用。
- socket.unref():当前socket是程序中最后一个活跃的服务(连接),执行到最后一个socket事件后关闭连接。该方法只关闭socket的连接,并不会关闭server服务,server服务有对应的server.unref()方法来管理此类操作。
- socket.write(data [,encoding] [,callback]):向服务发送一条数据(data:<string> | <Buffer> | <Uint8Array>),encoding用来设置数据编码格式(默认utf-8),当数据成功发送出去以后调用callback。
net.socket属性:
- socket.bufferSize:设置连接通讯的缓冲字符数,配合socket.write使用,这个属性值关系到服务的网络性能,详细参考:http://blog.chinaunix.net/uid-20726500-id-4949695.html
- socket.bytesRead:记录接收字节数量。
- socket.bytesWritten:记录发送字节数量。
- socket.connecting:<boolean>当socket.connect()被调用并正确建立连接之前为false,未建立连接或连接建立成功之前为true。
- socket.destroyed:<boolean>该属性配合socket.destroy()方法使用,默认参数为false,当socket.destroy()调用并成功关闭了soket上所有io操作后,该属性值被修改为true。
- socket.localAddress:本地的地址
- socket.localPort:本地的端口
- socket.pending:如果 socket 尚未连接,则为
true
,因为尚未调用.connect()
或者因为它仍处于连接过程中。用来判断连接是否连接成功。 - socket.remoteAddress:服务端的地址
- socket.remoteFamily:服务的协议族
- socket.remotePort:服务的端口
net模块API测试代码一:(基本应用)
1 //server.js 2 let net = require("net"); 3 // var server = net.createServer(); 4 let server = new net.Server(); //创建一个net服务对象 5 server.listen(12306, "127.0.0.1"); //serever.listen()启动net服务监听 6 server.on("listening", function() { //listening事件,启动net服务监听时触发 7 console.log("服务已启动"); 8 }); 9 server.on("connection", function(sockte){ //connection事件,当服务器被客户端成功连接时触发,并且会将连接到的(sockte)客户端对象传入事件回调函数 10 server.unref(); 11 // server.ref(); 12 let address = server.address(); 13 //server.address()返回当前连接服务的客户端地址信息(返回值为一个对象,address:IP地址;familey:协议族;port:端口号) 14 console.log("有新的连接,当前连接服务器客户端:\n" + 15 "IP地址:" + address.address + 16 "; \n协议族:" + address.family + 17 "; \n端口:" + address.port +";"); 18 19 server.getConnections(function(err,conten){ //获取当前并发连接数,也就是用来查看当前这个服务端口被多少个客户端连接 20 try{ 21 console.log(conten); 22 }catch (e) { 23 console.log(err); 24 } 25 }); 26 sockte.on("data",function (data) { //data事件,用来接收消息,并且会将接收到的消息数据data传入回调函数 27 console.log("client:" + data.toString()); 28 sockte.write("hello,client,我已经收到了你的消息了。"); //向服务发送会话,详细sockte参考方法解析 29 }); 30 sockte.on("end",function () { //当连接的另一端调用了sockte.end()方法后会向服务端发送一个FIN包,表示客户端正在关闭连接 31 console.log("接收到FIN,客户端请求关闭了连接。"); 32 }); 33 sockte.on("close",function () { //当客户端的sockte.end()方法触发关闭后,服务端的close事件也能监听到并触发回调函数。 34 console.log("客户端已关闭。"); 35 }); 36 }); 37 //client.js 38 var net = require("net"); 39 var socket = net.connect(12306, "127.0.0.1");//启动客户端网络连接请求 40 socket.on("connect",function(){ 41 console.log("连接的服务地址:"+ socket.remoteAddress); 42 console.log("连接的服务协议族:" + socket.remoteFamily); 43 console.log("连接的服务端口:" + socket.remotePort); 44 console.log("本地的IP地址:" + socket.localAddress); 45 console.log("本地的端口:" + socket.localPort); 46 }); 47 socket.write("hello,server")//向服务发送会话,详细sockte参考方法解析 48 socket.on("data",function (data) {//data事件,用来接收消息,并且会将接收到的消息数据data传入回调函数 49 console.log("server:" + data.toString()); 50 socket.end();//关闭连接 51 }); 52 socket.on("end",function () { 53 console.log("客户端调用了end方法,已经向服务端发送了FIN报文"); 54 }) 55 socket.on("close",function () { //当socket.end()触发连接关闭后,触发close事件 56 console.log("连接以关闭"); 57 });
net模块API测试代码二:(测试:server.unref()、server.ref()、socket.unref()、socket.ref())
1 //server.js 2 let net = require("net"); 3 let server1 = net.createServer(); 4 let server2 = new net.Server(); 5 6 server1.listen(12306, "127.0.0.1"); //serever.listen()启动net服务监听 7 // server2.listen(12307, "127.0.0.1"); //serever.listen()启动net服务监听 8 9 server1.on("listening",function () { 10 console.log("服务server1已启动"); 11 }); 12 // server2.on("listening",function () { 13 // console.log("服务server2已启动"); 14 // }); 15 16 server1.on("connection",function (socket) { 17 server1.unref(); 18 server1.ref(); 19 server1.unref();//这个无效了 20 console.log("服务server1有新的连接。"); 21 socket.on("data",function (data) { 22 console.log("client1:" + data.toString()); 23 socket.write("hello,client1,我已经收到你的消息了"); 24 }); 25 socket.on("end",function () { //当连接的另一端调用了sockte.end()方法后会向服务端发送一个FIN包,表示客户端正在关闭连接 26 console.log("接收到FIN,客户端请求关闭了连接。"); 27 }); 28 socket.on("close",function () { //当客户端的sockte.end()方法触发关闭后,服务端的close事件也能监听到并触发回调函数。 29 console.log("客户端已关闭。"); 30 }); 31 }); 32 // server2.on("connection",function (socket) { 33 // console.log("服务server2有新的连接。"); 34 // }); 35 36 //client.js 37 var net = require("net"); 38 var socket1 = net.connect(12306, "127.0.0.1");//启动客户端网络连接请求 39 // var socket2 = net.connect(12307, "127.0.0.1"); 40 41 socket1.write("hello,server1"); 42 // socket2.write("hello,server2"); 43 // 44 socket1.on("data",function (data) { 45 console.log("server1:" + data.toString()); 46 // socket1.end(); 47 socket1.unref(); 48 }); 49 socket1.on("end",function () { 50 console.log("客户端调用了end方法,已经向服务端发送了FIN报文"); 51 }) 52 socket1.on("close",function () { //当socket.end()触发连接关闭后,触发close事件 53 console.log("连接以关闭"); 54 });
net模块API测试代码三:(测试socket.end()、socket.destroy()、socket.destroyed)
1 //server.js 2 let net = require("net"); 3 let server = net.createServer(); 4 server.listen(12306, "127.0.0.1"); 5 server.on("listening",function () { 6 console.log("服务已启动"); 7 }); 8 server.on("connection",function (socket) { 9 socket.on("data",function(data){ 10 try{ 11 console.log("client:" + data.toString()); 12 throw new Error("测试server的Error事件"); 13 }catch (e) { 14 console.log("错误处理后关闭客户端的连接"); 15 console.log(socket.destroyed);//false 16 socket.destroy();//这里可以关闭客户端的连接,但不会关闭server服务。相比end,destroy会清除该socket上的所有io操作。 17 console.log(socket.destroyed);//true 18 // socket.end();//end也可以关闭客户端连接,且不会关闭server服务。 19 //try、catch处理错误,但不会关闭客户端连接,在服务端使用 20 // console.log(socket.) 21 } 22 }); 23 }); 24 //client.js 25 let net = require("net"); 26 let socket = net.connect(12306, "127.0.0.1"); 27 socket.on("connect",function () { 28 socket.write("hello,server!"); 29 }); 30 socket.on("end",function () { 31 console.log("end与destroy方法调用都会触发这个方法。"); 32 }) 33 socket.on("close",function () { 34 console.log("连接以关闭"); 35 })
net模块API测试代码四:(测试setTimeout()、timeout事件)
1 //server.js 2 let net = require("net"); 3 let server = net.createServer(); 4 server.listen(12306, "127.0.0.1"); 5 server.on("listening",function () { 6 console.log("服务已启动"); 7 }); 8 server.on("connection",function (socket) { 9 socket.on("data",function(data){ 10 console.log("client:" + data.toString()); 11 }); 12 }); 13 14 //client.js 15 let net = require("net"); 16 let socket = net.connect(12306, "127.0.0.1"); 17 socket.on("connect",function () { 18 socket.write("hello,server!");//向服务器发送消息 19 socket.setTimeout(5000,function () { 20 console.log("我在什么时候调用呢?") 21 });//发送消息后启动超时监听事件,如果在指定的事件内没有收到服务端的消息,就会触发回调函数及超时事件。(这里没有使用超时回调函数) 22 }); 23 socket.on("timeout",function () { 24 console.log("连接超时,执行end关闭连接"); 25 socket.end();//socket 将会收到一个 ‘timeout‘ 事件,但连接不会被断开。用户必须手动调用 socket.end() 或 socket.destroy() 来断开连接。 26 }) 27 socket.on("end",function () { 28 console.log("end与destroy方法调用都会触发这个方法。"); 29 }) 30 socket.on("close",function () { 31 console.log("连接以关闭"); 32 })
二、手动解析HTTP请求头
前面只针对net的API做了解析,并未就net的实现基础做任何阐述,net作为nodejs中重要的网络应用底层模块实际是基于TCP/IP协议的API。所以如果net.server如果接收到一个http请求的话,他会解析出一个http报文。这是因为net模块底层只负责解析TCP/IP协议的套字节,并不解析http的套字节,比如启动下面这个服务程序,然后通过浏览器访问这个服务,服务接收到请求后会打印出一个完整的http请求报文头:
1 let net = require("net"); 2 let server = net.createServer(); 3 server.listen(12306, "127.0.0.1"); 4 server.on("listening",function () { 5 console.log("服务已启动"); 6 }); 7 server.on("connection",function (socket) { 8 socket.on("data",function(data){ 9 console.log(data.toString()); 10 }); 11 });
在浏览器通过http协议发起请求:
http://127.0.0.1:12306
然后控制台会打印出一个完整的http请求报文:
net启动的网络服务能解析出http报文,也就意味着可以通过net服务向浏览器响应一个http报文,并且可以被浏览器解析:
1 let net = require("net"); 2 let server = net.createServer(); 3 server.listen(12306, "127.0.0.1"); 4 server.on("listening",function () { 5 console.log("服务已启动"); 6 }); 7 server.on("connection",function (socket) { 8 socket.on("data",function(data){ 9 console.log(data.toString()); 10 //手写一个http响应报文,报文内容为一个html页面 11 socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>hello, browser!</h1></html>") 12 }); 13 });
通过浏览器可以通过请求http://127.0.0.1:12306可以获得响应,服务还可以获得完整的http报文也就意味着可以手动解析报文,来分析http请求的内容是什么,沿着这样的思路就可以通过解析http请求来给浏览器响应对应的请求资源,用下面这个请求index.html包含一个图片的页面作为示例:
//服务端文件 index.html server.js image.jpg
html代码:
<h1>
hello,browser!
</h1>
<img src="./image.jpg"/>
server.js
1 let net = require("net"); 2 let fs = require("fs"); 3 4 let server = net.createServer(); 5 server.listen(12306, "127.0.0.1"); 6 server.on("listening",function () { 7 console.log("服务已启动"); 8 }); 9 10 server.on("connection",function (socket) { 11 try{ 12 socket.on("data",function(data){ 13 let request = data.toString().split("\r\n"); 14 let url = request[0].split(" ")[1]; 15 let fileData = fs.readFileSync(__dirname + url); 16 //手写一个http响应报文,报文内容为一个html页面 17 // let writeState = socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>hello, browser!</h1></html>"); 18 // let writeState = socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n" + fileData.toString()); 19 //当需要发送文件时,特别是html文件,其内部会包含很多其他文件的引用,如果采用拼接到报文头部后面无法被socket正常解析内部文件引用 20 //可是依靠TCP协议传输特性,每次先将独立的HTTP头部发送出去,然后再通过socket.write发送文件,当socket.write发送文件时会自动解析文件内容 21 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 22 let writeState = socket.write(fileData); 23 if(writeState){ 24 socket.end();//当成功发送数据以后关闭连接 25 }else{ 26 socket.write("HTTP/1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>404</h1></html>"); 27 socket.end(); 28 } 29 }); 30 }catch(e){ 31 socket.destroy(e);//强制关闭socket所有io操作,并将传入的e:Error对象传递给socket的Error监听事件 32 } 33 socket.on("error",function (e) { 34 console.log(e); 35 }) 36 });
三、基于网络模块net与文件模块fs搭建简易的node服务
通常情况下我们不会把前端代码与后端代码放到一起,特别在开发的时候,为了更方便前端代码连接服务测试,这时候我们可以在服务端配置前端代码的路径以及测试端口:
//假设将前端文件放到桌面 web //根目录 html //html文件 index.html css //css文件 js //js文件 image //图片文件 image.jpg json //json文件
1 <!DOCTYPE html> 2 <head> 3 <title>Node-Net-Fs-Server</title> 4 <link rel="stylesheet" href=""> 5 </head> 6 <body> 7 <h3>hello,browser!</h3> 8 <img src="./image/image.jpg"/> 9 </body> 10 </html>
服务端:
server.config
server.js
serverConfig.js
可以通过server.config配置不同的端口与前端文件路径:
port=12306
path= 桌面的路径 + \web
1 //serverConfig.js 用来解析配置文件 2 let fs = require("fs"); 3 4 function analysisConfig(configFile){ 5 let obj = {}; 6 let arr = configFile.toString().split("\r\n"); 7 for(let i = 0; i < arr.length; i++){ 8 let item = arr[i].split("="); 9 obj[item[0]] = item[1]; 10 } 11 return obj; 12 } 13 // 文件模型, 文件数据对象 14 let configFile, configData; 15 try{ 16 configFile = fs.readFileSync(__dirname + "/server.config"); 17 configData = analysisConfig(configFile); 18 }catch (e) { 19 console.log("解析server.config文件出错!",e); 20 } 21 22 module.exports = configData;
1 //srever.js 2 let net = require("net"); 3 let fs = require("fs"); 4 let config = require("./serverConfig.js"); 5 let server = net.createServer(); 6 server.listen(parseInt(config["port"]),"127.0.0.1"); 7 server.on("listening",function () { 8 console.log("服务已启动"); 9 }); 10 server.on("connection",function (socket) { 11 socket.on("data",function (data) { 12 try{ 13 let request = data.toString().split("\r\n"); 14 let url = request[0].split(" ")[1]; 15 let urlType = url.split("/")[1]; 16 if(url === "/" || url === "/index.html"){//这里响应首页 17 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 18 socket.write(fs.readFileSync(config["HTMLPath"] + "/index.html")); 19 }else if(urlType === ‘image‘ || urlType === ‘html‘ || urlType === ‘css‘ || urlType === ‘js‘ || urlType===‘json‘){ 20 //这里主要用来响应html、css、js内部的引用文件 21 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 22 socket.write(fs.readFileSync(config["path"] + url)); 23 }else{ //如果有请求除html、css、js、image以外的其他文件就在服务器相对路径下查找 24 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n"); 25 socket.write(fs.readFileSync(__dirname + url)); 26 } 27 socket.end(); 28 }catch(e){//如果出现请求不存在的数据fs在解析文件时会抛出错误,这里返回404页面,然后关闭当前会话。 29 //这里你可能会想到如果请求页面内部引用文件不存在呢? 30 //这种情况可以在响应页面内部文件之前先判断,如果文件存在再读写文件,然而这种情况HTTP模块已经提供了非常完善的机制。 31 socket.write("HTTP1.1 200OK\r\nContent-Type: text/html\r\n\r\n<html><h1>404!</h1></html>"); 32 socket.destroy(e); 33 } 34 }); 35 socket.on("error",function (e) { 36 console.log(e); 37 }) 38 });
四、net模块部分API参数详细解析