Socket IO与NIO(三)
Socket-TCP快速入門:
TCP是什么?
英語:Transmission Control Protocal,縮寫為TCP [tr?ns'm???n]n. 傳動裝置,[機] 變速器;傳遞;傳送;播送
TCP是傳輸控制協議;是一種面向連接的 可靠的 基于字節流的傳輸層通信協議,有IETF的RFC 793定義。
與UDP一樣完成第四層傳輸層所指定的功能與職責。
TCP的機制:
三次握手,四次揮手。
具有校驗機制、可靠、數據傳輸穩定。校驗機制保證了數據傳輸的可靠性以及數據傳輸的穩定性,那么數據傳輸的可靠性呢,在于我們的客戶端往服務器端發送數據的時候,數據會經過層層的校驗,以便后面的數據是否確定可以進行發送,當然服務器端給客戶端回送消息的時候也會經過這樣的一個校驗機制,所以說可以保證咋們的數據可靠性。
那么數據傳輸的穩定性呢,這在于我們數據傳輸的一個速率的窗口大小的隨機調整。原理大概是:客戶端給服務器端發送數據的時候,由于受限于我們客戶端或者服務器端的帶寬壓力,或者是客戶端和服務器端處理能力的壓力,那么當客戶端有100個字節發送到服務器端的時候,如果發現服務器端的數據接收過于緩慢,那么服務器端會在接收過程當中定期的回送他自己的一個狀態給客戶端,那么客戶端也會根據這樣的狀態進行自己的一個速度調整,以便自己客戶端的速度適應服務器端的一個接收速度,從而滿足咋們的一個數據傳輸穩定性。
TCP連接、傳輸流程:
首先客戶端的連接經過了3次握手連接到服務器端,然后服務器端在經過這3次握手連接成功之后,才會進入到我們后續的一個傳輸的建立,后面的傳輸建立后就好發送一系列的數據過去同時又回送數據,這是客戶端和服務器端簡單交互的流程介紹。TCP當中會有一些額外的數據進行發送,例如校驗數據,如果你發送的數據為100K,那么可能發送完成之后,你的數據字節會大于100K,這是正常的,因為在整個過程當中TCP和服務器端會不斷的進行一個數據的校驗,以及咋們的一個連接建立,這些都是需要消耗額外的一些流量,這樣咋們額外的一些數據的消耗。
TCP能做什么?
聊天消息傳輸、推送。
單人語言、視頻聊天等。這個點如果用UDP來做的話會更加優秀一點,他在傳輸上的一些限制會更小,當然如果你想要保證單人之間傳輸的一個優越性、數據的一個健壯性、以及保證視頻數據的一個精準送達,那么可以用TCP來做。
幾乎UDP能做的都能做,但需要考慮復雜性、性能問題。
限制:無法進行廣播,多播等操作。這是UDP所獨有的。也無法做搜索,搜索只能由UDP來做。
TCP核心 API講解:
socket(): 創建一個Socket。在于創建一個客戶端的Socket連接,客戶端自己new Socket的時候,其實他通過構造函數創建一個無連接的無綁定的一個客戶端Socket的空狀態。一般而言是創建一個空狀態的Socket,后面在調用方法設置參數建立連接。 bind(): 綁定一個Socket到一個本地地址和端口上。對于客戶端而言就在于綁定到你自己客戶端的端口上。而對于服務器端而言bind這個方法呢在于綁定在自己服務器端的一個接口上之后,那么他還監聽這個地址和端口上所到來的其他的客戶端的一些套接字。 connect(): 客戶端連接到遠程套接字。 accept(): 接受一個新的連接。這是服務器端的一個ServerSocket所獨有的一個方法。這是一個阻塞方法,等待客戶端連接。可是設置超時時間。阻塞狀態直到有一個新的客戶端連接到達,才會進入到后面的Socket創建。那么服務器端的一個簡單流程就是一個ServerSocket綁定到一個自己的端口之后,會進入到一個accept的等待狀態,如果此時有新的客戶端的Socket到達,那么這個時候服務器會創建一個對應的相匹配的Socket,進入到后續的一個發送和接受的流程。那么服務器端還可以自己選擇是否再去accept下一個Socket或者是直接關閉掉,不再進行后續的一個套接字的等待。 write(): 把數據寫入到Socket輸出流。 read(): 從Socket輸入流讀取數據。 復制代碼客戶端流程:
創建Socket-->bind本地套接字(占用一個本地端口)-->connect遠程套接字-->如果連接成功,客戶端就可以和遠程套接字服務器進行數據收發。
服務端流程:
創建ServerSocket-->bind本地套接字-->accept客戶端套接字-->如果此時客戶端的連接到達了,那么遠程套接字服務器就可以和客戶端之間 進行數據的收發。
擴展-Socket與進程關系:
默認的每一個進程都可以創建一個Socket連接,甚至你的一個進程可以創建多個Socket連接。可以借助于Socket來進行進程之間的數據交互。
TCP的buffer以及TCP底層的變量其實是由操作系統所維護的,那么TCP在進行這些變量以及buffer的一些維護得到信息之后,他會信息拋給上層的進程這一層,所以咋們進程這一層其實是無法管理咋們TCP底層的一些東西的,比如咋們前面說的TCP三次握手,四次揮手,以及TCP的數據校驗機制這些東西,是由TCP系統所完成的,系統其實是實現了TCP的協議的,這些東西無序我們進程去關心,我們進程僅僅只是使用TCP,從而實現了進程之間的一個交互。當然進程之間的交互并不僅僅只是你自己電腦上的進程交互,其實是你電腦的某個進程和別人電腦上的某個進程之間的一個交互,這也是屬于進程之間的一個交互。
TCP連接可靠性:
-
三次握手: 首先客戶端TCP會發送一條叫做SYN命令(這是一個連接請求命令),這個命令會攜帶一個參數,暫且叫x=rand()隨機值,隨機值在發送給服務器之后,服務器這個時候還沒有進行一個連接的建立,僅僅只是受到了這樣一次命令,收到這個命令之后他會做一個操作,校驗收到的這個SYN命令,如果這個命令是完整的,那么他會開始回送一條命令,回送的命令叫SYNACK,這么命令當中攜帶了兩個指令,一個是SYN指令,一個是ACK指令,ACK指令是對客戶端發送過來的SYN指令的一次回送,這個回送代表著服務器端自己已經收到了客戶端的一個信息,同理他在回送這個命令的時候也會攜帶一條命令SYN命令過去到客戶端,在這個過程當中首先他會做一件事情,在回送咋們的ACK命令的時候他會攜帶我們客戶端所帶過來的這個x,客戶端在發送SYN命令的時候會把x發送到服務器端,那么服務器端在回送這條ACK命令的時候,其實他會回送這個x回去,那x這個時候會做一個操作,把這個x+1代表著他已經收到并且經過了一次處理,所以他把x+1,然后回送到客戶端,當然同時在回送的時候,他會自己攜帶一條他自己的SYN命令,這條命令會攜帶第二個參數y=rand()隨機值,生成方式和客戶端發送過來的x生成方式一模一樣,這個時候回去的有兩個命令,一起到達客戶端,在客戶端收到的時候,他會先進行一次校驗,如果說認為自己發送的SYN這個命令,收到的這個ACK命令呢是等于x+1的,那么就認為這一次已經收到服務器的一個回送消息,同時他會給服務器回送一次說我已經收到了,那么他給服務器回送消息的時候,他會把y+1同時包括剛剛收到的x值發送給服務器端,服務器端在收到這個ACK命令之后,確保這個連接已經完全的建立,之后就可以進行咋們數據發送上的一些準備和操作。那么在這里要說一下為什么要有這樣的一個3次命令?首先客戶端跟服務器說我要連接你,服務器端給客戶端說你可以來連接我,當然服務器也給客戶端同時說我也要連接你,那么這個時候客戶端給服務器回送說,你可以連接我。這個地方可以確定兩個事情,客戶端可以連接服務器,那么服務器也可以連接客戶端。其中的隨機數是用于確定對應的客戶端的,因為服務器端是要接受無數的客戶端連接。
-
四次揮手: 客戶端也可以是服務器端,這里在于這里是發送方,對面是接受方,其實無論是客戶端還是服務器端都可以進行這樣的一個流程,都可以提出自己想要關閉的一個操作。首先他們會從一個已連接的狀態先發送一個FIN命令FIN=1(1也是隨機值),seq=u(隨機值)。當接受方收到這條消息的時候,他會回送你一條ACK命令,ACK命令都是一個回送命令,ACK=1,seq=v,ack=u+1,當發送方一旦收到這個ack=u+1的時候,那么你其實就已經可以把你自己的連接斷開了。這個時候服務器端有可能還有一些數據沒有給你完全送達,這個時間之內,服務器端會把他沒有完全送達的消息一一給你送達,知道他送達 完成之后,他才給你發送一個FIN=1,ACK=1,seq=w,ack=u+1命令過來說他想要關閉了,就是服務器端跟你說他想要關閉這個連接,他一旦給你發送了這個消息之后,你收到這個消息之后,你會給他回一個ACK=1,seq=u+1,ack=w+1命令,一旦這個ACK命令你確保他已經收到了,那么之后就可以進行一個連接斷開了,但這里其實就只有一個來回來回,你怎么確定一個數據被他收到了呢,其實是這樣的,當咋們的服務器端他發送FIN命令之后,他會進行一個等待狀態,在這個等待狀態當中,他會持續的發送FIN命令,間隔一段時間之后會發送一次FIN命令,這個間隔時間呢一般是一次數據片所送達的完整的一個送達時間 這個簡稱叫SML,在于說一次數據片所能夠達到對面的一次最長時間,那默認這個時間定義為2分鐘,當然在Linux系統當中已經把這個值改為了30秒,30秒認為已經是一個現在網絡非常可靠的一個時間,如果說30秒之內對方沒有收到你的消息,那么就可以認為這條消息可能被網絡斷開丟掉了,現在的網絡當中基本上可以保證在30秒之內把一個數據片送到對方。他發送一個FIN命令之后他會進行一個等待狀態,在等待狀態當中如果超過30秒,他會沒有收到ACK命令,那還會繼續發FIN命令給你,持續的發送,直到說你給他回送一次ACK命令,當然在這個過程當中有可能遇到說我網絡出問題異常斷開了,那么也是有可能會出現IO異常,這也是允許的,在這個過程當中,他給你發送,你給他回送的ACk命令之后,也是一樣的,如果說這個連接還沒有進行一個完整的斷開,那么你會不斷的收到FIN命令,因為對方在沒有收到ACK命令的情況下,他間隔一段時間又會給你發送一個FIN命令,如果說你持續的收到FIN命令,你就要持續的向對方發送ACK表示你已經收到這個命令了,直到對方已經真實的收到了ACK命令之后,對方就把連接斷開,你也把連接斷開。四次揮手為什么要有四次揮手,四次揮手其實是保證了前面我們所說的全雙工連接的一個斷開,什么是全雙工?就是說你可以向對面發送一條消息也可以接受來之對面的消息,對面可以接受你的消息,也可以向你發送消息。全雙工在于你們之間斷開連接,首先你向對面說可以關閉連接嗎,對面一旦向你回復了可以允許斷開連接,那么這個時候你就已經斷開了你的一個輸出流,你的輸出流如果還在進行一個輸出的話,那么會直接觸發一個連接異常。當然這個時候你的輸入還是保留的,因為你還可以接受一個來之服務器端的數據,服務器端這個時候還在向你發送數據,那么這個時候你自己其實是處于一個半雙工的狀態,你僅僅只能夠接受消息了,不能再發送消息。那么服務器端也是一樣,他把自己的消息發送完成之后,他向你回送了一條消息說,他的消息發送完了他也想要關閉連接,那么這個時候一旦你回送了一條消息確認,那么它也會把自己的一條連接斷開,之前你們已經斷開了一次你的發送信息的一個流,之后他斷開了他向你發送信息的流,那么你不向說話了,他也不向你說話了,你們兩者之間就可以確定已經斷開了,所以這就是四次揮手所具備的一個功能。
TCP傳輸可靠性:
- 排序、順序發送、順序組裝。當你進行一條數據發送的時候,首先TCP會將這個數據拆分成不同的片段,然后把片段進行一個排序,之后把剛剛 排序的片段 順序的組裝之后進行發送,這就保證了數據傳輸的有序性。
- 丟棄、超時。一旦在真個發送過程當中,你的一個數據片沒有到達或者是數據片到達的時候超時,那么在TCP的客戶端和服務器端當中,在客戶端 其實是能夠收到一個數據被丟棄或者說數據超時的一個消息的。那么一旦收到這樣的消息,那么客戶端要做的事情是他自己把這條信息重新 的進行一次發送,這就是TCP的一個發送流程,他首先把大的數據拆分成不同的數據片,然后把數據片排序,排序好之后一片一片的進行發送 當然這個一片一片的數據發送,為什么要做這個操作?首先為什么要拆分一個大的數據包,比如說客戶端要發送一個大的數據包到服務器端, 一旦數據出現了一個傳輸上的問題,那么你這一整個數據包都被浪費丟棄掉了,而我們前面傳輸了20%的數據,其實都是無效的。你要重新 傳輸要從0開始傳輸,那么無疑是浪費比較多的流量的,那么為了盡可能的減少這樣的一個流量消耗,所以他要把一個大的數據包拆分成很多 很多的小的數據包,當然拆分之后再進行發送其實也是增大了他的流量消耗,但是從整個體系上來講,他是減少了一個瀏覽傳輸的一個消耗。 這是為了保證他的傳輸可靠性的一個流量消耗,這是允許的。 排序之后發送這個數據,發送數據有可能出現丟失,那么丟失的,我們認為僅僅是需要把剛剛丟失的部分數據進行重新發送即可,所以就需要 你一個順序發送以及一個順序組裝,所以也就涉及到了咋們的一個數據丟棄和數據超時,那么這個一整套流程是TCP底層為我們完成的。
- 重發機制-定時器。如果說在服務器端收到這個信息的時候,他會進行一個定時器會定時的給我們回送一些已經收到的數據片,那么客戶端也是一樣 當我們在一定的定時器范圍之內沒有收到服務器回送的消息的話,那么我們就認為沒有被服務器端送達,我們會重新把這個數據在發送一遍, 一遍我們的數據一定是運行被發送到服務器端的,這樣就保證了傳輸的可靠性。
TCP數據傳輸的一個流程:
左邊是接收方,右邊是發送方,發送方緩沖區有1 2 3 4 5,5個數據片。
首先他會給接受方發送第1個數據包{序列號=1,數據:1460byte},當接受方接受到的時候,他會回送一條{序列號=1,確認號=1461,數據:0byte},這時候緩沖區的指針會移動到2。
發送第2個數據包{序列號=1461,數據:1460byte}給接受方,發送過去之后,這個時候如果說發送方沒有收到接收方的回送,那么這個回送被中斷了,這個時候,發送方緩沖區的指針移動到了3位置,然后他把第3個數據包{序列號=2921,數據:1460byte}發送給接收方,這個時候發送失敗,接受 方沒有收到第3個數據包,發送方這時候緩沖區的指針會移動到4。
給接受方發送第4個數據包{序列號=4381,數據:1460byte},發送過去之后,這個時候如果說發送方沒有收到接收方的回送,那么這個回送被中斷了,這時候緩沖區的指針會移動到5。
首先他會給接受方發送第5個數據包{序列號=5841,數據:1460byte},當接受方接受到的時候,他會回送一條{序列號=1,確認號=2921,數據:0byte}然后接收方1 2 4 5都接受到了,但這個時候有一點是丟掉了一條數據,那么你丟掉的這條數據,如果說在發送方認為時間Timeout了,還沒有收到你的回送,就認為這條數據真的是被丟掉了,那么他會把第三條數據重新發送。
發送方給接受方重發第3個數據包{序列號=2921,數據:1460byte},當接受方接受到的時候,他會回送一條{序列號=1,確認號=7301,數據:0byte}。
一條Socket連接視為一個通道。
TCP基礎類型數據傳輸:
byte(8bit)、char(8big)、short(16bit)
char和byte是可以直接復用的,short要轉換成2個byte數組。
boolean(8bit)、int(32bit)、long(64bit)
float(32bit)、double(64bit)、string(可變的) 中文轉換成byte是3個byte。
UDP輔助TCP實現點對點傳輸案例: 如果說你知道你的服務器地址以及端口,那么往往你可以直接用TCP跟你的服務器進行一個連接。但是假如說在一個局域網當中,不知道你服務器 的一個IP地址,你僅僅知道的是你服務器公共的UDP的端口,那么在這樣的情況下,你如何實現TCP的一個連接呢? TCP連接必須要知道IP地址和端口,那么要怎么要知道IP地址和端口呢?我們可以通過UDP的一個搜索,當我們的服務器與我們的所有的客戶端之間 約定了搜索的一個格式之后,我們可以在客戶端發起一個UDP廣播,在廣播的一個接受者,也就是服務器收到這個廣播之后,然后判斷一下我們的 這個收到的廣播是否是需要處理的,如果說是,那么服務器會回送這個廣播對應的端口和IP地址上面去,當這個回送的時候,客戶端就能收到咋們 服務器回送過來的這個UDP的包,當收到UDP包的時候,UDP的包就包含了IP地址和端口號,當然還可以在服務器回送信息的時候攜帶一些信息,所以 我就可以通過UDP的一個搜索得到我們TCP需要的點對點的IP地址和端口,然后再根據這些信息建立TCP連接。
UDP搜索IP和端口:
構建基礎的口令消息。如果說我們沒有這個口令的一個頭字節的話,那別人發的任何消息只要到達咋們的端口,咋們就會去回送,這是會暴露咋們自己的信息,比如最基本的信息IP地址和端口號。 局域網廣播口令消息(指定端口)。
接受指定端口回送消息(得到客戶端IP、port)。UDP沒有標準的客戶端和服務端,這里客戶端指的就是對面的這一端就是server端。
UDP搜索取消實現:
異步線程接受回送消息。
異步線程等待完成(定時)。
關閉等待-終止線程等待。 一旦超時等待到達,我們就要進行一個線程的關閉,并且終止線程等待。
總結
以上是生活随笔為你收集整理的Socket IO与NIO(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScipt中的Math.ceil
- 下一篇: 【黑金原创教程】【FPGA那些事儿-驱动