参考与图片来源缓存
维护ing...bash
4位首部长度 TCP首部的长度,单位为4字节,若是没有可选字段,那么这里的值就是5(单位为4字节),表示TCP首部的长度为20字节。【1表明4个字节,4位8个状态能表明32个字节】服务器
6位保留位socket
6位控制位 TCP的链接、传输和断开都接受这个六个控制位的指挥tcp
16位窗口值 客户端和服务端沟通好每次发送多少数据函数
如下引用自 TCP报文段中URG和PSH的区别post
紧急URG(urgent):ui
当URG = 1时代表紧急指针字段有效,他告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送,发送方的TCP就把紧急数据放到本报文段数据的最前面。URG标志位要与首部中的紧急指针字段配合使用,紧急指针指向数据段中的某个字节,(数据从第一个字节到指针所指的字节就是紧急数据)。值得注意的是即便窗口为0时也能够发送紧急数据,紧急数据不进入接收缓冲区直接交给上层进程。this
推送PSH(push):spa
当两个应用进程进行交互式通讯时,有时客户发一个请求给服务器时但愿当即可以收到对方的响应,这种状况下,客户应用程序通知TCP使用推送(push)操做,TCP就把PSH置为1,并当即建立一个报文段发送过去,相似的服务器的TCP收到一个设了PSH标志的报文段时就尽快将全部收到的数据当即提交给服务进程,而不在等到整个缓存都填满了再向上交付。
Q:为何要握手?并且要三次?
答:握手是由于要确保真正开始发送数据以前,彼此(客户端,服务端)收、发数据皆正常,而之因此要三次,嗯。。。请接着往下看
接下来咱们来看详细的过程
注意:[]
中的为1位的信号,后面带=
的是16位的序列号和确认号,是具体的编号。
01:客户端 [SYN]seq=0---> 服务端
******
******
******
02:客户端 <---[SYN,ACK]seq=0,ack=1 服务端
******
******
******
03:客户端 [ACK]seq=1,ack=1---> 服务端
第一次握手,服务端接收到了客户端发来的请求同步的信息,服务端就知道了客户端的发送是正常的。(嘿,我我好喜欢你)
第二次握手,客户端接收到了服务端发来的确认信息和同步信息,客户端就知道了服务端的收发(两样)是正常的。(我也好喜欢你,咱们结婚吧)
第三次握手,服务端接收到了客户端发来的确认信息,服务端就知道了客户端的接收也是正常的。(嗯,咱们结婚)
以上,就确保了彼此的收发消息都是正常的。
Q:为何要挥手?并且要四次? 答:挥手是由于要和平分手,嗯。。。给对方以示意,有什么还没作完的搞快作,作完就了事。至于为何要四次,嗯。。老套路,请看详细过程
首先和同步不同,分手时哪边均可以提出分手
01:A方 [FIN,ACK]seq=xxx,ack=yyy---> B方
******
******
******
02:A方 <---[ACK]seq=yyy,ack=xxx+1 B方
******
******
******
03:A方 <---[FIN,ACK]seq=yyy,ack=xxx+1 B方
******
******
******
04:A方 [ACK]seq=xxx+1,ack=yyy+1---> B方
注意: 若是B方接受到A方的FIN时,恰巧也没数据要发送给A方了,那么02和03会合并为一次
第一次挥手,A方表示本身已经没有什么要发送给B方了,我要断开链接了
第二次挥手,B方表示我已经知道到你(A方)要断开链接了,稍等一下,我把剩下的数据发完
第三次挥手,B方表示我已经没有数据要发送了,你能够断开链接了
第四次挥手,A方表示我已经收到你最后发送的数据了,而且我已真正断开链接,这是个人遗言,此时若B方接受到就会关闭本身的这边
关于第四次挥手,A方挥手完毕后,还会等待2MSL(4min),若是此间又接收到B方发送的FIN
,则表示最后次挥手发送的ACK
对方没有收到,就会从新发送,并刷新等待时间,直到2MSL内再也不收到B放发来的FIN
(表示B放已收到最后的ACK而且关闭),A方完全断开。
在 Node.js
中用内置的 net
模块实现了 TCP
链接
let net = require('net');
let server = net.createServer(function(socket){
...
}).lieten(8080);
复制代码
其中的 socket
俗称为套接字,en...为嘛叫套接字?
咱们经过socket能读取到客户端的输入以及能向客户端写入数据。
注意: 默认连接最大个数(backlog)为511
server.listen(handle[, backlog][, callback])
须要注意的是socket是长链接,这意味着它会一直保持链接直到咱们手动去关闭客户端或则服务端表示要关闭链接。
另外由于是长链接,因此即便你每隔一段时间经过tcp链接向服务端发送信息, createServer
里注册的回调函数也只会执行一次。(不像http,一次请求就会执行一次),因此咱们通常还会在createServer里包一层on('data')
来实时监控客户端的输入以便作出响应。
net.createServer(function(socket){
socket.on('data',function(buffer){
console.log(socket._readableState.length);
})
});
复制代码
由于tcp链接并不像http链接同样会自动中断,So有可能存在一个socket长期不使用却占着位置的状况,通常这种时候咱们就会规定一个超时时间来作出一些操做,好比询问下人在不在啊(防挂机),要不要shuttdown啊什么的。
socket.setTimeout(5000);
socket.on('timeout',function(){
socket.write('喂喂,有人吗?');
});
复制代码
此时就至关于四次挥手中服务端向客户端提出分手[FIN,ACK]seq=xxx,ack=yyy
。
当客户端接到后通常会将第二第三次挥手合并到一块儿,向服务端回复[FIN,ACK]seq=yyy,ack=xxx+1
,而且触发socket.on('end')
注册的事件。
[warning] 注意: 这货并不像ws.end,临死以前还有遗言,会直接关掉socket套接字。
设置一个服务器最大的连接数
server.maxConnections = 111;
复制代码
server.getConnections(function(err,count){ //count为当前链接数
console.log(`当前链接人数${count}人,最大容纳${server.maxConnections}`)
})
复制代码
调用server.close()后,server并不会马上关闭全部链接,close只是表示服务端再也不接受新的请求了,当前的链接(socket)还能继续用。当全部客户端(socket)所有关闭后服务器才会关闭并触发close事件。
经过调用 server.unref()
方法, 当服务器全部链接都关闭后,能让服务器自主关闭。这个方法和server.close
的区别在于unref并不阻止新socket的进驻。
socket继承自 Duplex
(双工流),Duplex是一个可读可写的流
Duplex长这样
let {Duplex} = require('stream');
let d = Duplex({
read(){
this.push('hello'); //不中止会一直以'hello'做为读取值读取
this.push(null); //表示中止读取
}
,write(chunk,encoding,callback){
console.log(chunk);
callback(); //clearBuffer
}
})
复制代码
So,socket能使用一切可写流和可读流的方法进行读取和写入。
咱们经过客户端向服务端发送数据照理说很像写入,但在 socket
看来实际上是读取。(相似于process.stdin.pipe(transform1).pipe(transform2)
,其中stdin也是读取)
咱们能够经过监听 on('data')
事件来读取客户端的输入。
socket.on('data',function(){});
复制代码
也能够经过socket.pause
暂停可读流,以及经过socket.resume
继续读。
socket的可写流层面和通常的可写流通常无二,可写流有的socket都有,write()
、flag
、drain事件
...
有一点要注意的是,socket的end
,上面也说过,它是没有遗言的,便是你end('something'),也不会有输出。
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);
});
复制代码
write()的缓冲区实时大小
let port = 8080;
server.listen(port,'localhost',function(){
console.log(`server is running at ${port}`);
})
server.on('error',function(err){
if(err.code === 'EADDRINUSE'){
server.listen(++port);
}
});
复制代码
建立一个server
let net = require('net');
let server = net.createServer(function(socket){
socket.setEncoding('utf8');
socket.on('data',function(data){
console.log(data); //读
})
socket.write('ok'); //写
socket.end(); //关闭socket
});
server.on('connection',function(){ //注意这个事件和getConnections事件很类似,但getConnections有err和count参数
console.log('客户端连接');
})
server.listen(8080);
复制代码
建立一个server
不一样于建立tcp服务器时socket是做为回调函数中的参数,建立客户端的的时候,createConnection的返回值才是一个socket
let net = require('net');
// port 要链接到host的哪一个端口
let socket = net.createConnection(8080,function(){
socket.write('hello'); //写
socket.on('data',function(data){
console.log(data); //读
});
});
复制代码