浅析TCP和nodejs中TCP的简单应用

emmmmmmm...
tcp
咱们应该都知道,tcp是一种网络协议。
javascript

提及网络,在大学学计算机网络的时候,记得老师讲过网络一共分7层,这7层从上到下依次是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 咱们来看张图:java

OSI是Open System Interconnection的缩写,意为开放式系统互联。国际标准化组织(ISO)制定了OSI模型,该模型定义了不一样计算机互联的标准,是设计和描述计算机网络通讯的基本框架。OSI模型把网络通讯的工做分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层

上面是基本网络模型的组成。咱们再来看一下tcp/ip的参考模型node

TCP/IP是传输控制协议/网络互联协议的简称。早期的TCP/IP模型是一个四层结构,从下往上依次是网络接口层、互联网层、传输层和应用层。后来在使用过程当中,借鉴OSI七层参考模型,将网络接口层划分为了物理层和数据链路层,造成五层结构。缓存

咱们看到了tcp/ip网络模型把osi中的应用层、表示层、会话层合并成了应用层。
Tcp协议其实是在传输层。传输层是面向链接的、可靠的的进程到进程通讯的协议。TCP提供全双工服务,即数据可在同一时间双向传播。TCP将若干个字节构成一个分组,此分组称为报文段(Segment)。提供了一种端到端的链接。 传输层的协议主要是TCP ,TCP(Transimision Control Protocal)是一种可靠的、面向链接的协议,传输效率低。服务器


咱们再来看一下tcp协议的格式

  •   源端口号和目标端口号,计算机经过端口号识别访问哪一个服务,好比http服务或ftp服务,发送方端口号是进行随机端口,目标端口号决定了接收方哪一个程序来接收。
  •   32位序列号 TCP用序列号对数据包进行标记,以便在到达目的地后从新重装,假设当前的序列号为s,发送数据长度为 l,则下次发送数据时的序列号为 s + l。在创建链接时一般由计算机生成一个随机数做为序列号的初始值确认应答号 它等于下一次应该接收到的数据的序列号。假设发送端的序列号为 s,发送数据的长度为 l,那么接收端返回的确认应答号也是 s + l。发送端接收到这个确认应答后,能够认为这个位置之前全部的数据都已被正常接收。
  •   首部长度:TCP 首部的长度,单位为 4 字节。若是没有可选字段,那么这里的值就是 5。表示 TCP 首部的长度为 20 字节。控制位 TCP的链接、传输和断开都受这六个控制位的指挥
  •         PSH(push急迫位) 缓存区将满,马上传输速度
  •         RST(reset重置位) 链接断了从新链接
  •         URG(urgent紧急位) 紧急信号
  •         ACK(acknowledgement 确认)为1表示确认号
  •         SYN(synchronous创建联机) 同步序号位

  •         TCP创建链接时要将这个值设为1
  •         FIN发送端完成位,提出断开链接的一方把FIN置为1表示要断开链接
  •     窗口值说明本地可接收数据段的数目,这个值的大小是可变的。当网络通畅时将这个窗口值变大加快传输速度,当网络不稳定时减小这个值能够保证网络数据的可靠传输。它是来在TCP传输中进行流量控制的
  •   窗口大小:用于表示从应答号开始可以接受多少个 8 位字节。若是窗口大小为 0,能够发送窗口探测。
  •   效验和: 用来作差错控制,TCP校验和的计算包括TCP首部、数据和其它填充字节。在发送TCP数据段时,由发送端计算校验和,当到达目的地时又进行一次检验和计算。若是两次校验 和一致说明数据是正确的,不然 将认为数据被破坏,接收端将丢弃该数据
  •   紧急指针:尽在 URG(urgent紧急) 控制位为 1 时有效。表示紧急数据的末尾在 TCP

  •   数据部分中的位置。一般在暂时中断通讯时使用(好比输入 Ctrl + C)。
  • 三次握手

    想必你们都知道这么个东西。。那么到底什么是三次握手呢。。
    举个🌰
    假如说我去咖啡店买咖啡。这时我要问店员了:请问有某某咖啡吗?而后店员告诉我:有的。你带钱了么。而后我就说:我带钱了,给来一杯把。。而后咱们该给钱给钱该给咖啡给咖啡网络

    咱们来看这个例子。假如说我就是客户端,店员是服务器。
    而后我去问服务器,你能够创建链接嘛(一次握手)?服务器说我能够创建连接,你能够创建链接吗(二次握手)?这时我再回复他说,我能够创建连接(三次握手)...并发

    emmmmm...而后咱们两个智障就愉快的创建了连接。。作一些偷偷摸摸传东西的事情。。框架

    为了更形象的来解读一下三次握手,咱们来看张图:socket

    咱们能够很清楚的看到。
      第一次握手: 创建链接。客户端发送链接请求,发送SYN报文,将seq设置为0。而后,客户端进入SYN_SEND状态,等待服务器的确认。
      第二次握手: 服务器收到客户端的SYN报文段。须要对这个SYN报文段进行确认,发送ACK报文,将ack设置为1。同时,本身还要发送SYN请求信息,将seq为0。服务器端将上述全部信息一并发送给客户端,此时服务器进入SYN_RECV状态。
      第三次握手: 客户端收到服务器的ACK和SYN报文后,进行确认,而后将ack设置为1,seq设置为1,向服务器发送ACK报文段,这个报文段发送完毕之后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
    tcp

    四次挥手

    咱们再来举个🌰

    假如我到饭店去吃饭,我吃完了要买单。这时我会喊服务员买单->服务员说您要买单吗->服务员说一共xxxx元->我给钱以后就能够走人了

    比喻可能不太恰当。。但差很少这个意思,咱们直接看图

      第一次挥手:客户端向服务器发送一个FIN ACK报文段。此时,客户端进入 FIN_WAIT_1状态,这表示客户端没有数据要发送服务器了,请求关闭链接;

      第二次挥手:服务器收到了客户端发送的FIN报文段,向客户端回一个ACK报文段。服务器进入了CLOSE_WAIT状态,客户端收到服务器返回的ACK报文后,进入FIN_WAIT_2状态;

      第三次挥手:服务器会观察本身是否还有数据没有发送给客户端,若是有,先把数据发送给客户端,再发送FIN报文;若是没有,那么服务器直接发送FIN报文给客户端。请求关闭链接,同时服务器进入LAST_ACK状态;

      第四次挥手:客户端收到服务器发送的FIN报文段,向服务器发送ACK报文段。而后客户端进入TIME_WAIT状态;服务器收到客户端的ACK报文段之后,就关闭链接;此时,客户端等待2MSL(大约4分钟?)后依然没有收到回复,则证实Server端已正常关闭,客户端也能够关闭链接了。


    了解完三次握手和四次挥手后咱们可能会有几个问题

    1.为何客户端和服务器须要三次握手

    答:三次握手的主要目的是保证客户端和服务端都是能够正常进行的收发信息。

    2.为何须要四次挥手?

    答:四次挥手的目的是为了确保双方的数据发送完毕均可以断开。

    3.为何握手是三次,挥手倒是4次

    答:server端收到fin报文时可能并不会当即关闭。关于这个咱们想一下,有可能客户端发送断开链接的fin报文,server还有数据须要传输,这时就不该该当即断开连接。


    nodejs中的TCP(net模块)

    在Node.js中,net模块实现了基于TCP的数据通讯

    咱们来看一下建立一个简单的tcp服务端的方法

    建立TCP服务器
    let net  = require('net');
    let server = net.createServer(function(socket){
        console.log('客户端已经连接');
    })
    server.listen('8080',function(){
        console.log('server is run in 8080');
    })
    复制代码

    这样就使用nodejs建立了一个简单的tcp服务器; 注意:createServer的回调中有一个参数socket,指的是TCP服务器监听的socket端口对象。

    设置最大连接数以及监听客户端的连接数量

    server.getConnections((err,count)=>{//得到当前的连接个数
        console.log('已经连接'+count+'个用户')
    });
    server.maxConnections = 2;//限制当前的最大链接个数为2
    复制代码

    socket对象

    net.Socket表明一个socket端口对象,他是一个双工流(可读可写)

    address

    获取客户端地址

    let net = require('net');
    let server = net.createServer(function(socket){
       console.log(socket.address())
    });
    server.listen(8080);
    复制代码

    结果:

    pipe

    let ws = fs.createWriteStream(path.join(__dirname,'./1.txt'));
    
    let server = net.createServer(function(socket){
      socket.pipe(ws,{end:false}); // 第二个参数让文件不自动关闭
      setTimeout(function(){
        ws.end(); //关闭可写流
        socket.unpipe(ws); //取消管道
      },15000);
    });
    复制代码

    能够看到咱们建立了一个可写流,咱们能够把socket(双工流)中的东西pipe到可写流中。

    server和client

    建立server

    let net = require('net');
    
    let server = net.createServer(function(socket){
      socket.setEncoding('utf8');
      socket.on('data',function(data){
        console.log(data); //获取从客户端发来的数据
      })
      socket.end('服务端关闭');
    });
    server.on('connection',function(){
      console.log('客户端连接');
    })
    server.listen(8080);
    复制代码

    建立client

    let net = require('net');
    //createConnection第一个参数是你要链接到服务器的端口号
    let socket = net.createConnection(8080,function(){
      socket.write('hello'); //向服务端发送
      socket.on('data',function(data){
        console.log(data); //接受服务端数据
      });
    });
    复制代码
    相关文章
    相关标签/搜索