參考與圖片來源緩存
維護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); //讀
});
});
複製代碼