使用net模块实现基于TCP的数据通信

在Node.js中,提供了一个net模块,专用于实现TCP服务器与TCP客户端之间的通信。


1、创建TCP服务器

const server = net.createServer([options], [connectionListener]);

createServer方法返回被创建的TCP服务器,其中使用两个可选参数:

options:其值为一个对象,可以在该对象中使用一个布尔值类型的allowHalfOpen属性,当该属性值被指定为false时,当TCP服务器接收到客户端发送的一个FIN包时将会回发一个FIN包,当该属性值被设定为true时,当TCP服务器接收到客户端发送的一个FIN包时不回发FIN包,这使得TCP服务器可以继续向客户端发送数据,但是不会继续接收客户端发送的数据。开发者必须调用end方法来关闭socket连接。该属性值默认为false。
connectionListener:用于指定当客户端与服务器端建立连接时所要调用的回调函数,该回调函数使用一个参数,参数值为该TCP服务器监听的socket端口对象。

function(socket) {
}

我们也可以不在createServer方法中使用connectionListener参数,而是通过对connection事件进行监听,并且指定该事件的回调函数的方法来指定当客户端与服务器建立连接时需要执行的处理方法如下所示:

server.on('connection', function(socket) {
})

创建TCP服务器之后,可以使用listen方法通知服务器开始监听客户端连接。该方法具有如下三种指定方法。
方法一:指定方法如下所示(代码中server代表一个TCP服务器)

server.listen(port, [host], [backlog], [callback])

port:必须,用于指定需要监听的端口号,参数值为0时TCP服务器分配一个随机端口号。
host:可选,用于指定需要监听的IP地址或主机名,如果省略该参数,服务器将监听来自于任何IPv4地址的客户端连接。
backlog:可选,参数值为一个整数值,用于指定位于等待队列中的客户端连接的最大数量,一旦超越这个长度,TCP服务器将开始拒绝来自于新的客户端的连接请求,改参数的默认值为511。
callback:回调函数,可选。该回调函数不使用任何参数。

方法二:指定方法如下所示(代码中server代表一个使用Unix端口的服务器)

server.listen(path, [callback])

path:指定需要监听的路径。
callback:回调函数,可选。该回调函数不使用任何参数。
这种形式的listen方法用于通知一个使用Unix端口的服务器开始监听来自于指定路径的客户端连接。当对使用Unix端口的服务器指定了需要监听的路径后,服务器将立即开始监听来自于该路径的客户端连接,这时触发该服务器的listening事件,可使用listen方法的callback参数来指定listening事件触发时调用的回调函数,该回调函数不使用任何参数。

方法三:指定方法如下所示(代码中server代表一个TCP服务器)

server.listen(handle, [callback])

handle:指定需要监听的socket句柄。
callback:回调函数,可选。该回调函数不使用任何参数。
这种形式的listen方法用于通知一个TCP服务器开始监听来自于指定socket句柄(该句柄可以为一个TCP服务器对象,可以为一个socket端口对象,也可以为一个文件描述符,在Windows操作系统中不支持对文件描述符的监听)的客户端连接。当对TCP服务器指定了需要监听的socket句柄后,服务器端将立即开始监听来自于该socket句柄的客户端连接,这时触发该服务器的listening事件,可使用listen方法的callback参数来指定listening事件触发时调用的回调函数,该回调函数不使用任何参数。

如果不在上述三种形式的listen方法中使用callback参数,我们也可以通过监听TCP服务器对象的listening事件,并指定该事件触发时调用的回调函数方法来指定TCP服务器开始监听时所需执行的处理,方法如下所示:

server.on('listening', function(){
// 回调函数代码略
})

在对TCP服务器指定需要监听的地址及端口时,如果该地址及端口已被占用,将产生一个错误代码为“EADDRINUSE”的错误(表示用于监听的地址及端口已被占用),同时将触发TCP服务器的error事件。

server.on('error', function(e) {
    if(e.code == 'EADDRINUSE') {
        // 回调函数代码略
    }
})

2、TCP服务器拥有的属性和方法

1)、使用TCP服务器的address方法,来查看服务器所监听的地址信息

let address = server.address()

该方法返回一个对象具有如下属性:

  • port:属性值为TCP服务器监听的socket端口号,如8080。
  • address:属性值为TCP服务器监听的地址,如127.0.0.1。
  • family:属性值为一个标识了TCP服务器所监听的地址是IPv4地址还是IPv6地址的字符串,如“IPv4”。

2)、使用TCP服务器的getConnections方法,来查看当前与TCP服务器建立连接的客户端连接数量。

server.getConnections(callback)

该方法是一个异步方法,可以指定一个callback作为参数,该回调函数使用两个参数:

err:参数值为获取客户端连接数时所触发的错误对象。
count:参数值为获取到的客户端连接数。

3)、TCP服务器的maxConnections属性,用于指定TCP服务器可以接收的客户端连接的最大数量。

server.maxConnections = 2; //设置TCP服务器最大连接数为2

4)、使用TCP服务器的close方法,显式指定服务器拒绝所有新的客户端连接。

server.close(callback)

在使用close方法时,并不会断开所有现存的客户端连接。当这些客户端连接被关闭时,TCP服务器将被自动关闭,同时触发TCP服务器的close事件

server.on('close', function() {
}

3、socket端口对象

在Node.js中,使用net.Socket代表一个socket端口对象。该对象具有以下属性和方法:

1)、使用socket端口对象的address方法,获取该socket端口对象相关的地址信息

let address = server.address()

该方法返回一个对象具有如下属性:

  • port:属性值为TCP服务器监听的socket端口号,如8080。
  • address:属性值为TCP服务器监听的地址,如127.0.0.1。
  • family:属性值为一个标识了TCP服务器所监听的地址是IPv4地址还是IPv6地址的字符串,如“IPv4”。

socket端口对象可用来读取客户端发送的流数据,每次接收到客户端发送的流数据时触发data事件。

socket.on('data', function(data){
})

在该回调函数中,使用一个参数,参数值为一个Buffer对象(在未使用socket端口对象的setEncoding方法指定编码方式时)或一个方法字符串对象(在使用socket端口对象的setEncoding方法指定编码方式后)。我们可以使用如下所示方法设置读取到数据的编码格式:
方法一:使用socket端口对象的setEncoding方法。

socket.setEncoding('utf8');
socket.on('data', function(data) {
  console.log(data)
}

方法二:使用Buffer对象的toString方法。

socket.on('data', function(data) {
  console.log(data.toString())
}

2)、使用socket端口对象具有一个bytesRead属性,属性值为该socket端口对象接收到的客户端发送数据的字节数。
当客户端连接被关闭时触发socket端口对象的end事件。可以通过对该事件进行监听并且指定事件处理函数来指定当客户端连接被关闭时所需执行的处理,该回调函数不使用任何参数。

const net = require('net');
let server = net.createServer();
server.on('connection', function(socket) {
    socket.setEncoding('utf8');
    socket.on('data', function(data) {
        console.log(data);
        console.log('已接收的字节数据', socket.bytesRead);
    });
    socket.on('end', function() {
        console.log('客户端连接被关闭');
    });
})
server.listen(8080, 'locahost');

3)、可以使用socket对象的pipe方法,将客户端发送的流数据书写到文件等其他目标对象中。

socket.pipe(destination, [options])

该方法使用以下两个参数:

destination:参数值必须为一个可用于写入流数据的对象。
options:可选,参数值为一个对象,可以在该对象中使用一个布尔类型的end属性,如果该属性值为true,则当数据被全部读取完毕时立即结束写操作;如果该属性值为false,则并不结束写操作,目标对象中可以被继续写入新的数据,该属性的默认属性值为true。

const fs = require('fs');
const net = require('net');
let file = fs.createWriteStream('./message.txt');
let server = net.createServer();
server.on('connection', function(socket) {
    socket.pipe(file)
})
server.listen(8080, 'localhost')

4)、当使用了pipe方法指定要写入的目标对象后,可以使用socket端口对象的unpipe方法取消目标对象的写入操作。

socket.unpipe([destination])

该方法使用以下参数:

destination:可选,参数值为pipe方法中指定的被写入的目标对象。如果不使用该参数,则取消所有对在pipe方法中指定的目标对象的写入操作。

5)、使用socket端口对象的pause方法,可以暂停data事件的触发,这时服务器端将把每一个客户端发送的数据暂存在一个单独的缓存区中。

socket.pause();

6)、在使用了pause方法暂停data事件的触发之后,可以使用socket端口对象的resume方法恢复data事件的触发,这时将读取被缓存的该客户端数据。

socket.resume()

7)、在Node.js中,未对客户端连接指定默认超时时间,可以使用socket端口对象的setTimeout方法指定与该端口相连接的超时时间。

socket.setTimeou(timeout, [callback])

该方法使用以下参数:

timeout:参数值为一个整数数值,用于指定客户端连接的超时时间,单位为毫秒。
callback:可选,当客户端连接超时时使用的回调函数,该回调函数不使用任何参数。当客户端连接超时时,触发socket端口对象的timeout事件。我们也可以对timeout事件进行监听:

const fs = require('fs');
const net = require('net');
let file = fs.createWriteStream('./message.txt');
let server = net.createServer();
server.on('connection', function(socket) {
    socket.setTimeout(10*1000);
    socket.pause();
    socket.on('timeout', function() {
        socket.resume();
        socket.pipe(file);
    });
})
server.listen(8080, 'localhost')

在使用了一次setTimeout方法指定客户端连接的超时时间后,可以通过再使用一次setTimeout方法并将timeout参数值指定为0的方法来取消对客户端连接超时时间的指定。

4、创建TCP客户端

在Node.js中,创建TCP客户端只需要创建一个用于连接TCP服务器的socket端口对象即可。

const net = new net.socket([options])

在net.socket对象的构造函数中,可以使用一个可选的options对象,其中可以使用如下属性:

fd:用于指定一个现存的socket的文件描述符,TCP客户端将使用这个现存的socket端口与服务器相连接。
type:用于指定客户端所使用的协议,可指定值为“tcp4”、“tcp6”或者“unix”。
allowHalfOpen:该属性值被指定为false时,当TCP服务器接收到客户端发送的一个FIN包时将会回发一个FIN包,当该属性值被设定为true时,当TCP服务器接收到客户端发送的一个FIN包时不回发FIN包,这使得TCP服务器可以继续向客户端发送数据,但是不会继续接收客户端发送的数据。开发者必须调用end方法来关闭socket连接。该属性值默认为false。
创建了socket端口对象之后,即可使用socket端口对象的如下所示的两种connect方法连接TCP服务器。

方法一:指定方法如下所示(代码中socket代表一个socket端口对象)

socket.connect(port, [host], [connectListener])

port:必须,用于指定需要连接的TCP服务器端口。
host:可选,用于指定需要连接的TCP服务器地址,该地址可以是一个IP地址,也可以为一个主机名,如果不指定host参数,将默认使用本地主机名localhost。
connectListener:可选,该参数用于指定一个当客户端与TCP服务器成功连接时调用的回调函数,该回调函数不使用任何参数。
当客户端与TCP服务器建立连接后,触发socket端口对象的connect事件,我们也可以通过对connect事件的监听来执行相关处理:

socket.on('connect', function(){
})

方法二:指定方法如下所示(代码中socket代表一个socket端口对象)

socket.connect(patch,  [connectListener])

这种形式的connect方法用于与一个使用unix端口的服务器进行连接,方法中使用以下参数:

path:必须,用于指定服务器所使用的unix端口的路径。
connectListener:可选,该参数用于指定一个当客户端与TCP服务器成功连接时调用的回调函数,该回调函数不使用任何参数。

当TCP客户端与TCP服务器建立连接后,TCP客户端用于建立连接的socket端口对象与TCP服务器端用于监听客户端连接的socket端口对象都具有如下所示的属性:

  • remoteAddress:连接的另一端所使用的远程地址,例如74.125.127.X。
  • remotePort:连接的另一端所使用的端口号,例如80。
  • localAddress:本地用于建立连接的地址,例如192.168.1.1。
  • localPort:本地用于建立连接的端口号,例如80。

socket端口对象也可被用来写入向客户端或服务器端发送的流数据,当流数据被写入后将立即被发送到客户端或服务器端。当需要写入流数据时,可以使用write方法。

socket.write(data,  [encoding],  [callback])

该方法使用以下参数:

data:参数值可以为一个Buffer对象或一个字符串,用于指定需要写入的数据。
encoding:可选,用于指定当data参数为字符串时以什么编码方式写入。
callback:可选,用来指定当数据被写入完毕时所要调用的回调函数,该回调函数不使用任何参数。

在一个快速的网络中,当数据量较少的时候,Node.js总是将数据直接发送到操作系统专用于发送数据的TCP缓存区中,然后从该TCP缓存区中取出数据发送给对方。在一个慢速的网络中或需要发送大量数据时,TCP客户端或服务器端所发送的数据并不一定会立即被对方接收,在这种情况下,Node.js会将这些数据缓存在缓存队列中,在对方可以接收数据的情况下将缓存队列中的数据通过TCP缓存区发送给对方。socket端口对象的write方法返回一个布尔类型的返回值,当数据直接被发送的TCP缓存区中时,改返回值为true,当数据直接被发送到缓存队列时,该返回值为false。当返回值为false且TCP缓存区中数据已全部被发送出去时,触发drain事件。
可以使用socket端口对象的bufferSize属性值来查看用于缓存队列中当前缓存的字符数。

使用net模块实现基于TCP的数据通信_第1张图片
创建TCP客户端

使用net模块实现基于TCP的数据通信_第2张图片
创建TCP服务器

在TCP服务器与TCP客户端建立连接或进行通信的过程中发生错误时,将触发TCP服务器使用的与该客户端相连接的socket端口对象或TCP客户端使用的与服务器端相连接的socket端口对象的error事件。

socket.on('error', function(err){
})

在捕捉到错误之后,我们应该使用触发触发错误的socket端口对象的destroy方法销毁该socket端口对象,以确保该socket端口对象不会再被利用。

socket.destroy()

当TCP服务器与TCP客户端建立连接后,TCP服务器可以使用与该客户端相连接的socket端口对象的end方法关闭该客户端的连接,TCP客户端也可以使用与服务器端相连接的socket端口对象的end方法关闭与服务器端的连接。

socket.end([data], [encoding])

data:可选,其参数值可以为一个Buffer对象或一个字符串,用于指定在关闭连接前需要向另一端(客户端或服务器端)追加发送的数据。
encoding:可选,用于指定当data参数为字符串时以什么编码方式进行发送。

另外可以通过对end事件的监听,来执行断开连接后要执行的操作。

socket.on('end', function() {
})

在默认情况下,运行TCP服务器的应用程序不会自动退出,即使客户端连接已被全部关闭。我们可以使用TCP服务器的unref方法指定当客户端连接被全部关闭时退出应用程序。

server.unref()

在使用了TCP服务器的unref方法之后,可以继续使用TCP服务器的ref方法继续阻止应用程序的退出。

server.ref()

当socket端口彻底关闭时,触发socket端口对象的close事件,可以通过对close事件的监听并且指定事件函数的方法来指定当socket端口关闭时需要执行的处理。

socket.on('close', function(had_error) {
})

had_error:参数值为布尔类型,当参数值为true时,表示该socket端口的关闭是由于一个错误而引起的,当参数值为false时,表示该socket端口被正常关闭。

在TCP客户端与服务器端建立连接后,当一方主机突然出现断电、重启、系统崩溃等意外情况时,将来不及向另一方发送用于关闭连接的FIN包,这样另一方将永远处于连接状态。
在Node.js中,可以通过使用用于建立客户端连接或服务器端连接的socket对象的setKeepAlive方法来解决这一问题。

socket.setKeepAlive([enable], [initialDelay])

enable:可选,参数值为布尔类型,当参数值为true时,启用Keep-alive机制。启用Keep-alive机制后,使用了setKeepAlive方法的这一段会不断的向对方发送一个探测包,如果发送探测包后对方没有发回响应的话,则认为对方已关闭连接,执行对方关闭连接时应该执行的处理,如果该参数为false,则不启用Keep-alive机制,该属性值默认为false,即不采用Keep-alive机制。
initialDelay:可选,指定每个多久发送一次探测包,单位为毫秒,如果将改参数设置为0,则保持系统中的默认设置的Keep-alive探测包的发送间隔时间,或者保持程序中当前已设置的Keep-alive探测包的发送间隔时间,该参数默认为0。

5、 net模块中的类方法

  1. isIP方法
    isIP方法用于判断一个字符串值是否为一个IP地址。
net.isIP(input)

input:该参数为一个字符串,如果字符串值不为IP地址,方法返回0,如果字符串值为IPv4地址,方法返回4;如果字符串值为IPv6地址,方法返回6。

  1. isIPv4方法
    isIPv4方法用于判断一个字符串值是否为一个isIPv4地址。
net.isIPv4(input)

input:该参数为一个字符串,如果字符串值为一个有效的isIPv4地址,则方法返回true,否则返回false。

  1. isIPv6方法
    isIPv6方法用于判断一个字符串值是否为一个isIPv6地址。
net.isIPv6(input)

input:该参数为一个字符串,如果字符串值为一个有效的isIPv6地址,则方法返回true,否则返回false。

你可能感兴趣的:(使用net模块实现基于TCP的数据通信)