emmmmmmm...
tcp
我们应该都知道,tcp是一种网络协议。
说起网络,在大学学计算机网络的时候,记得老师讲过网络一共分7层,这7层从上到下依次是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 我们来看张图:
OSI是Open System Interconnection的缩写,意为开放式系统互联。国际标准化组织(ISO)制定了OSI模型,该模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层
上面是基本网络模型的组成。我们再来看一下tcp/ip的参考模型
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...然后我们两个智障就愉快的建立了链接。。做一些偷偷摸摸传东西的事情。。
为了更形象的来解读一下三次握手,我们来看张图:
我们可以很清楚的看到。
第一次握手: 建立连接。客户端发送连接请求,发送SYN报文,将seq设置为0。然后,客户端进入SYN_SEND状态,等待服务器的确认。
第二次握手: 服务器收到客户端的SYN报文段。需要对这个SYN报文段进行确认,发送ACK报文,将ack设置为1。同时,自己还要发送SYN请求信息,将seq为0。服务器端将上述所有信息一并发送给客户端,此时服务器进入SYN_RECV状态。
第三次握手: 客户端收到服务器的ACK和SYN报文后,进行确认,然后将ack设置为1,seq设置为1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成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); //接受服务端数据
});
});
复制代码