Node.js中的TCP通信(Node.js自学第十一天)

2,807 阅读8分钟

TCP/IP协议栈

TCP/IP协议栈,包含了一系列构成互联网服务的网络协议,为计算机间的数据传输定义了从硬件到软件的一系列标准协议。

未命名文件.png

TCP/IP通信

未命名文件 (1).png

TCP协议

TCP(Transmission Control Protocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

应用层向TCP层发送用8位字节表示的数据流,TCP把数据流分区成适当的消息段。之后TCP把结果包传送给IP层,继续向后传输。

TCP为了保证不发生丢包,就给每一个包一个序号,同时序号也保证了传送到接收端的包的按序接收。接收端对已成功接收到的包发回一个相应的确认(ACK);如果发送端在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已经丢失将会被重新重传。TCP用一个校验和函数来检验数据是否有误;在发送和接收时都要计算校验和,从而保证了可靠的数据传输。

TCP通信过程

基于TCP协议的应用程序分为服务端和客户端两部分。二者的工作时序为:

(1)开始监听
服务器端应用绑定在特定主机地址的特定端口上,监听客户端请求;

(2)建立连接
客户端应用主动发起到服务器应用的连接,经过三次握手,建立连接;

(3)数据传输
双方使用创建连接时各自创建的Socket套接字端口进行数据的读写;

(4)断开连接
通信结束,一方发起连接断开消息,经过四次挥手,连接正式断开。

TCP三次握手

TCP协议使用"三次握手"简历客户端到服务器端的连接。具体过程如下:

Azure (1).png

(1)客户端发送SYN(SEO=x)消息给服务器端,进入SYN-SEND状态。
(2)服务器端收到SYN消息,回应一个SYN(SEO=y)ACK(ACK=x+1)消息,进入SYN—RECV状态。
(3)客户端接收到服务端的SYN消息,回应一个ACK(ACK=y+1)消息,进入Established状态。

TCP四次挥手

Azure (2).png

建立连接需要三次握手,而终止一个连接需要四次挥手,这是由TCP的半关闭(half-close)造成的。具体过程如下:

(1)某个应用进程首先调用close,称该端执行"主动关闭"。该端的TCP于是发送一个FIN消息,表示数据完毕。
(2)接收到这个FIN的对端执行"被关闭",这个FIN由TCP确认,表示接收端应用进程在相应连接上再无额外数据可接收。
(3)一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
(4)接收这个最终FIN的原发送端TCP(即执行主动关闭那一端)确认这个FIN。

Socket通信

Socket:套接字,本意插座,TCP应用中的服务器和客户端程序在建立好连接后,创建了一对相互连接的套接字对象,用于向对方写出数据,以及从对方处读取数据。

Azure (3).png

TCP应用中,服务器端应用为了与多个不同的客户端通信,会为每个连接到自己的客户端创建一个独立的Socket对象,用于与对方传输数据。
Azure (4).png

net模块

Node.js提供net模块,实现了面向连接的TCP数据通信,可用于创建TCP服务器端应用及客户端应用。

class:net.Server TCP服务器对象
class:net.Socket 通信套接字对象
net.connect( ) 连接到指定TCP服务器
net.createServer( ) 创建TCP服务器
net.isIP( ) 判断参数字符串是否为IP地址
net.isIPv4( ) 判断参数字符串是否为IPv4地址
net.isIPv6( ) 判断参数字符串是否为IPv6地址

net.Server对象

server.listen( ) 监听指定端口和地址
server.unref( ) 若没有更多客户端连接了则退出服务器
server.ref( ) 取消服务器退出
server.listening 服务器当前是否处于监听状态
server.maxConnections 设置服务器可以保持的最大连接数
server.getConnections( ) 得到当前客户端连接数
server.close( ) 保持现有连接,拒绝新连接
server.address( ) 返回一个服务器端口和地址描述对象

net.Server对象事件

Event:listening 开始监听端口
Event:connection 接收到客户端连接
Event:close 服务器关闭事件;注意:只要还有客户端连接,此事件不会触发
Event:error 服务器运行错误,如监听端口已被占用

net.Socket对象

socket.remoteAddress 远程地址
socket.remotePort 远程端口
socket.localAddress 本地地址
socket.localPort 本地端口
socket.write 写出数据
socket.end() 结束写出,进入"半连接"状态
socket.destroy() 在发生错误时,销毁套接字
socket.pause( ) 暂停数据接收,暂存于缓冲区
socket.resume() 继续数据接收

net.Socket对象事件

Event:connect 连接到服务器
Event:data 读取到数据
Event:drain 输出缓冲区已空
Event:error 读写错误
Event:close 完全关闭
Event:end 对方已发出Fin包

创建TCP服务器

const net=require('net');
var server=net.createServer({
	allowHalfOpen:false,//是否允许半连接,默认为false
  pauseOnConnect:false//一旦建立连接,是否先暂停Socket,默认为false
},(socket)=>{	//连接监听器
  console.log("接收到一个客户端连接!")
  socket.on("data",(data)=>{	//读取到数据
  	console.log("读取到客户端数据:%s",data)
  })
  socket.write('hello\r\n');
  socket.on('end',()=>{
  	console.log("连接已断开!");
  })
});
server.on('error',(err)=>{
	throw err;
})
server.listen('8000',()=>{
	console.log('正在监听8000端口...')
})

处理连接请求,可以使用上述在createServer的回调中处理,也可以给server绑定监听函数,监听connect连接建立了再处理:

const net=require('net');
var server=net.createServer();//假如默认配置参数不需要修改可以省略不写
server.on('connection',(socket)=>{
  server.getConnections((err,count)=>{
        if(err) throw err;
        console.log("服务器正在处理的客户端请求数为:%d!",count)
  })
	console.log("接收到一个客户端连接请求...");
})
server.on('error',(err)=>{
	throw err;
})
server.listen('8000',()=>{
	console.log('正在监听8000端口...')
})
//正在监听8000端口...
//接收到一个客户端连接!
//服务器正在处理的客户端请求数为:1!

image.png

假如我们只允许TCP服务器最多连接2个请求:

const net=require('net');
var server=net.createServer();
server.setMaxListeners=2;		//设置服务器可以保持的最大连接数
server.on('connection',(socket)=>{
    server.getConnections((err,count)=>{
        if(err) throw err;
        console.log("服务器正在处理的客户端请求数为:%d!",count)
    })
    console.log("接收到一个客户端连接请求...");
})
server.on('error',(err)=>{
    throw err;
})
server.listen('8000',()=>{
    console.log('正在监听8000端口...')
})

image.png

第一个连接和第二个连接请求连接成功,第三个连接请求无法连接。

我们也可以判断连接数当连接数达到3个或以上关闭服务器:

const net=require('net');
var server=net.createServer();//假如默认配置参数不需要修改可以省略不写
server.on('connection',(socket)=>{
    server.getConnections((err,count)=>{
        if(err) throw err;
        console.log("服务器正在处理的客户端请求数为:%d!",count);
        if(count>2){
            server.close();
        }
    })
    console.log("接收到一个客户端连接请求...");
})
server.on('error',(err)=>{
    throw err;
})
server.listen('8000',()=>{
    console.log('正在监听8000端口...')
})

image.png

sockte对象属性:

const net=require('net');
var server=net.createServer();//假如默认配置参数不需要修改可以省略不写
server.on('connection',(serverSocket)=>{
    console.log("接收到一个客户端连接请求...");
    console.log("服务器方端口:%s",serverSocket.localPort);
    console.log("服务器方IP地址:%s",serverSocket.localAddress);
    console.log("客户端方端口:%s",serverSocket.remotePort);
    console.log("客户端方IP地址:%s",serverSocket.remoteAddress);
})
server.listen('8000',()=>{
    console.log('正在监听8000端口...')
})
//正在监听8000端口...
//接收到一个客户端连接请求...
//服务器方端口:8000
//服务器方IP地址:::ffff:127.0.0.1
//客户端方端口:49345
//客户端方IP地址:::ffff:127.0.0.1

接收客户端发送的消息和响应客户端消息:

const net=require('net');
var server=net.createServer();//假如默认配置参数不需要修改可以省略不写
server.on('connection',(serverSocket)=>{
    console.log("接收到一个客户端连接请求...");
    console.log("服务器方端口:%s",serverSocket.localPort);
    console.log("服务器方IP地址:%s",serverSocket.localAddress);
    console.log("客户端方端口:%s",serverSocket.remotePort);
    console.log("客户端方IP地址:%s",serverSocket.remoteAddress);
    serverSocket.on("data",(data)=>{//接收到客户端发送给服务器的消息
        console.log("接收到客户端消息:%s",data);
      	serverSocket.write("服务器接收到了数据:"+data);//服务器给客户端发送消息
    })
})
server.listen('8000',()=>{
    console.log('正在监听8000端口...')
})
//正在监听8000端口...
//接收到一个客户端连接请求...
//服务器方端口:8000
//服务器方IP地址:::ffff:127.0.0.1
//客户端方端口:49381
//客户端方IP地址:::ffff:127.0.0.1
//接收到客户端消息:a
//接收到客户端消息:b
//接收到客户端消息:c
//接收到客户端消息:d

创建TCP客户端

const net =require("net");

var clientSocket=net.connect(8000,'127.0.0.1',()=>{		//连接监听器
    clientSocket.write("world!");
    console.log("服务器方端口:%s",clientSocket.remotePort);
    console.log("服务器方IP地址:%s",clientSocket.remoteAddress);
    console.log("客户端方端口:%s",clientSocket.localPort);
    console.log("客户端方IP地址:%s",clientSocket.localAddress);//向服务器写出数据
})
clientSocket.on('data',(data)=>{//读取数据
    console.log(data.toString());
    clientSocket.end();	//主动断开连接
})
clientSocket.on('end',()=>{//发送FIN包完成
    console.log("双方进入半连接状态!")
});
clientSocket.on('close',()=>{//完全断开
    console.log("连接完全关闭!")
});
//服务器方端口:8000
//服务器方IP地址:127.0.0.1
//客户端方端口:49469
//客户端方IP地址:127.0.0.1
//response-world!
//双方进入半连接状态!
//连接已断开!