TCP/UDP编程中的问题汇总
TCP/UDP編程中的問(wèn)題匯總
?
?
?
TCP和UDP發(fā)送大文件的問(wèn)題。
答:
發(fā)送端:
發(fā)送時(shí),先發(fā)送文件的名稱及大小等信息。
然后,設(shè)置一個(gè)緩沖區(qū)的大小,假設(shè)為4K。
再循環(huán)讀4K的文件內(nèi)容,并發(fā)送,直到發(fā)送完成。
最后,再發(fā)送完成標(biāo)記。
?
接收端:
接收到第一個(gè)包時(shí),得到文件的大小等信息。
計(jì)算出要接收多少個(gè)包。
然后,循環(huán)接收包,并將接收到的數(shù)據(jù)寫入到文件中。
直到,接收到的數(shù)據(jù)長(zhǎng)度等于文件的大小。
?
struct package
{
文件標(biāo)識(shí) //GUID
偏移量 //001-
數(shù)據(jù)段 //Byte[]
};
?
?
如何用UDP實(shí)現(xiàn),返回?cái)?shù)據(jù)。
?
UDP服務(wù)端和客戶端,同時(shí)實(shí)現(xiàn)發(fā)送和接收數(shù)據(jù)。
答:
1,客戶端與服務(wù)端在同一局域網(wǎng),或客戶端有公網(wǎng)IP。
?
?
?
?
2,客戶端與服務(wù)端不在同一局域網(wǎng),且客戶端無(wú)公網(wǎng)IP。
NAT技術(shù)。
?
?
?
UDP報(bào)文丟失的問(wèn)題。
答:
如果C給S發(fā)報(bào)文。則S收到報(bào)文后,給C發(fā)一個(gè)收到報(bào)文的響應(yīng)。C對(duì)此進(jìn)行記錄。
如果C在某段時(shí)間內(nèi)未收到響應(yīng),則重發(fā)此報(bào)文。
?
?
UDP報(bào)文的順序問(wèn)題。
答:
發(fā)送端在報(bào)文中加入發(fā)送序號(hào)。接收端就可以按照發(fā)送序號(hào)進(jìn)行重新排列。
?
UDP缺乏流量控制。
?
1.UDP缺乏流量控制的概念
UDP協(xié)議沒(méi)有TCP協(xié)議所具有的滑動(dòng)窗口概念,接收數(shù)據(jù)的時(shí)候直接將數(shù)據(jù)放到緩沖區(qū)中。如果用戶不有及時(shí)地從緩沖區(qū)中將數(shù)據(jù)復(fù)制出來(lái)
,后面到來(lái)的數(shù)據(jù)會(huì)接著向緩沖區(qū)中放入。當(dāng)緩沖區(qū)滿的時(shí)候,后面到來(lái)的數(shù)據(jù)會(huì)覆蓋之前的數(shù)據(jù)造成數(shù)據(jù)的丟失。
2.緩沖區(qū)溢出對(duì)策
解決UDP接收緩沖區(qū)溢出的現(xiàn)象需要根據(jù)實(shí)際情況確定,一般可以用增大接收數(shù)據(jù)緩沖區(qū)和接收方接收單獨(dú)處理的方法來(lái)解決局部的UDP數(shù)
據(jù)接收緩沖區(qū)溢出問(wèn)題。
?
UDP協(xié)議中的數(shù)據(jù)報(bào)文截?cái)?/h2>
?
?
?
?
UDP一次發(fā)送多少bytes好?
當(dāng)然,這個(gè)沒(méi)有唯一答案,相對(duì)于不同的系統(tǒng),不同的要求,其得到的答案是不一樣的,我這里僅對(duì)像ICQ一類的發(fā)送聊天消息的情況作分析,對(duì)于其他情況,你或許也能得到一點(diǎn)幫助:
首先,我們知道,TCP/IP通常被認(rèn)為是一個(gè)四層協(xié)議系統(tǒng),包括鏈路層,網(wǎng)絡(luò)層,運(yùn)輸層,應(yīng)用層.
UDP屬于運(yùn)輸層,下面我們由下至上一步一步來(lái)看:
以太網(wǎng)(Ethernet)數(shù)據(jù)幀的長(zhǎng)度必須在46-1500字節(jié)之間,這是由以太網(wǎng)的物理特性決定的. 這個(gè)1500字節(jié)被稱為鏈路層的MTU(最大傳輸單元).
但這并不是指鏈路層的長(zhǎng)度被限制在1500字節(jié),其實(shí)這這個(gè)MTU指的是鏈路層的數(shù)據(jù)區(qū). 并不包括鏈路層的首部和尾部的18個(gè)字節(jié).
所以,事實(shí)上,這個(gè)1500字節(jié)就是網(wǎng)絡(luò)層IP數(shù)據(jù)報(bào)的長(zhǎng)度限制.
因?yàn)镮P數(shù)據(jù)報(bào)的首部為20字節(jié),所以IP數(shù)據(jù)報(bào)的數(shù)據(jù)區(qū)長(zhǎng)度最大為1480字節(jié).
而這個(gè)1480字節(jié)就是用來(lái)放TCP傳來(lái)的TCP報(bào)文段或UDP傳來(lái)的UDP數(shù)據(jù)報(bào)的.
又因?yàn)閁DP數(shù)據(jù)報(bào)的首部8字節(jié),所以UDP數(shù)據(jù)報(bào)的數(shù)據(jù)區(qū)最大長(zhǎng)度為1472字節(jié).
這個(gè)1472字節(jié)就是我們可以使用的字節(jié)數(shù)。:)
當(dāng)我們發(fā)送的UDP數(shù)據(jù)大于1472的時(shí)候會(huì)怎樣呢?
這也就是說(shuō)IP數(shù)據(jù)報(bào)大于1500字節(jié),大于MTU.這個(gè)時(shí)候發(fā)送方IP層就需要分片(fragmentation).
把數(shù)據(jù)報(bào)分成若干片,使每一片都小于MTU.而接收方IP層則需要進(jìn)行數(shù)據(jù)報(bào)的重組.
這樣就會(huì)多做許多事情,而更嚴(yán)重的是,由于UDP的特性,當(dāng)某一片數(shù)據(jù)傳送中丟失時(shí),接收方便無(wú)法重組數(shù)據(jù)報(bào).將導(dǎo)致丟棄整個(gè)UDP數(shù)據(jù)報(bào)。
因此,在普通的局域網(wǎng)環(huán)境下,我建議將UDP的數(shù)據(jù)控制在1472字節(jié)以下為好.
進(jìn)行Internet編程時(shí)則不同,因?yàn)镮nternet上的路由器可能會(huì)將MTU設(shè)為不同的值.
如果我們假定MTU為1500來(lái)發(fā)送數(shù)據(jù)的,而途經(jīng)的某個(gè)網(wǎng)絡(luò)的MTU值小于1500字節(jié),那么系統(tǒng)將會(huì)使用一系列的機(jī)制來(lái)調(diào)整MTU值,使數(shù)據(jù)報(bào)能夠順利到達(dá)目的地,這樣就會(huì)做許多不必要的操作.鑒于Internet上的標(biāo)準(zhǔn)MTU值為576字節(jié),所以我建議在進(jìn)行Internet的UDP編程時(shí). 最好將UDP的數(shù)據(jù)長(zhǎng)度控件在548字節(jié)(576-8-20)以內(nèi).
?
理論上,IP數(shù)據(jù)報(bào)的最大長(zhǎng)度是65535字節(jié),這是由IP首部16比特總長(zhǎng)度字段所限制的。去除20字節(jié)的IP首部和8個(gè)字節(jié)的UDP首部,UDP數(shù)據(jù)報(bào)中用戶數(shù)據(jù)的最長(zhǎng)長(zhǎng)度為65507字節(jié)。但是,大多數(shù)實(shí)現(xiàn)所提供的長(zhǎng)度比這個(gè)最大值小。
?
我們將遇到兩個(gè)限制因素。第一,應(yīng)用程序可能會(huì)受到其程序接口的限制。socket API提供了一個(gè)可供應(yīng)用程序調(diào)用的函數(shù),以設(shè)置接收和發(fā)送緩存的長(zhǎng)度。對(duì)于UDP socket,這個(gè)長(zhǎng)度與應(yīng)用程序可以讀寫的最大UDP數(shù)據(jù)報(bào)的長(zhǎng)度直接相關(guān)。現(xiàn)在的大部分系統(tǒng)都默認(rèn)提供了可讀寫大于8192字節(jié)的UDP數(shù)據(jù)報(bào)(使用這個(gè)默認(rèn)值是因?yàn)?192是NFS讀寫用戶數(shù)據(jù)數(shù)的默認(rèn)值)。
?
第二個(gè)限制來(lái)自于TCP/IP的內(nèi)核實(shí)現(xiàn)。可能存在一些實(shí)現(xiàn)特性(或差錯(cuò)),使IP數(shù)據(jù)報(bào)長(zhǎng)度小于65535字節(jié)。
在SunOS 4.1.3下使用環(huán)回接口的最大IP數(shù)據(jù)報(bào)長(zhǎng)度是32767字節(jié)。比它大的值都會(huì)發(fā)生差錯(cuò)。
?
但是從BSD/386到SunOS 4.1.3的情況下,Sun所能接收到最大IP數(shù)據(jù)報(bào)長(zhǎng)度為32786字節(jié)(即32758字節(jié)用戶數(shù)據(jù))。
?
在Solaris 2.2下使用環(huán)回接口,最大可收發(fā)IP數(shù)據(jù)報(bào)長(zhǎng)度為65535字節(jié)。
?
從Solaris 2.2到AIX 3.2.2,發(fā)送的最大IP數(shù)據(jù)報(bào)長(zhǎng)度可以是65535字節(jié)。很顯然,這個(gè)限制與源端和目的端的實(shí)現(xiàn)有關(guān)。
?
主機(jī)必須能夠接收最短為576字節(jié)的IP數(shù)據(jù)報(bào)。在許多UDP應(yīng)用程序的設(shè)計(jì)中,其應(yīng)用程序數(shù)據(jù)被限制成512字節(jié)或更小,因此比這個(gè)限制值小。
?
由于IP能夠發(fā)送或接收特定長(zhǎng)度的數(shù)據(jù)報(bào)并不意味著接收應(yīng)用程序可以讀取該長(zhǎng)度的數(shù)據(jù)。因此,UDP編程接口允許應(yīng)用程序指定每次返回的最大字節(jié)數(shù)。如果接收到的數(shù)據(jù)報(bào)長(zhǎng)度大于應(yīng)用程序所能處理的長(zhǎng)度,那么會(huì)發(fā)生什么情況呢?不幸的是,該問(wèn)題的答案取決于編程接口和實(shí)現(xiàn)。
?
典型的Berkeley版socket API對(duì)數(shù)據(jù)報(bào)進(jìn)行截?cái)?#xff0c;并丟棄任何多余的數(shù)據(jù)。應(yīng)用程序何時(shí)能夠知道,則與版本有關(guān)(4.3BSD Reno及其后的版本可以通知應(yīng)用程序數(shù)據(jù)報(bào)被截?cái)?#xff09;。
?
SVR4下的socket API(包括Solaris 2.x) 并不截?cái)鄶?shù)據(jù)報(bào)。超出部分?jǐn)?shù)據(jù)在后面的讀取中返回。它也不通知應(yīng)用程序從單個(gè)UDP數(shù)據(jù)報(bào)中多次進(jìn)行讀取操作。TLI API不丟棄數(shù)據(jù)。相反,它返回一個(gè)標(biāo)志表明可以獲得更多的數(shù)據(jù),而應(yīng)用程序后面的讀操作將返回?cái)?shù)據(jù)報(bào)的其余部分。在討論TCP時(shí),我們發(fā)現(xiàn)它為應(yīng)用程序提供連續(xù)的字節(jié)流,而沒(méi)有任何信息邊界。TCP以應(yīng)用程序讀操作時(shí)所要求的長(zhǎng)度來(lái)傳送數(shù)據(jù),因此,在這個(gè)接口下,不會(huì)發(fā)生數(shù)據(jù)丟失。
?
?
TCP消息邊界的問(wèn)題
通過(guò)套接字或其助手類來(lái)接收信息時(shí),是從緩存區(qū)里一次性把全部的緣存都讀取出來(lái),只要你設(shè)置的緩存夠大,它就能讀取這么多,這樣就會(huì)導(dǎo)致這樣的情況出現(xiàn)。如果服務(wù)端連續(xù)發(fā)送信息到客戶端,如我連續(xù)發(fā)送字符串"message 1"、"message 2"、"message 3"、"message 4"、"message 5",我預(yù)想的是在客戶端也是能夠收到這樣的五個(gè)完整的字符串,如果用前二篇中講的方法,在同臺(tái)機(jī)子上測(cè)試的話,是正常的,因?yàn)橥_(tái)機(jī)子上網(wǎng)絡(luò)信息傳送出現(xiàn)的異常會(huì)比較少,但如果把客戶端與服務(wù)端部署在不同的機(jī)器上,則會(huì)出現(xiàn)一些異想不到的現(xiàn)象。你會(huì)發(fā)現(xiàn)接收到的字符都被打亂了,會(huì)出現(xiàn)如"3message 4"的字符串,這樣的話,我們就不能把服務(wù)端發(fā)送的信息正常的還原。這個(gè)就是消息的邊界問(wèn)題,要解決這個(gè)問(wèn)題,方法有很多,現(xiàn)抽取其中幾個(gè)來(lái)講一下:
?
1、固定尺寸的消息
?
這是最簡(jiǎn)單但也是最昂貴的解決TCP消息問(wèn)題的方案。就是要設(shè)計(jì)一種協(xié)議,永遠(yuǎn)以固定的長(zhǎng)度傳遞消息,通過(guò)將所有的消息都設(shè)置為固定的尺寸,在從遠(yuǎn)程設(shè)備中接收到完整的消息時(shí),TCP接收程序就能夠了解發(fā)送的情況了。用這各地意味著必須將短消息加長(zhǎng),造成網(wǎng)絡(luò)帶寬資源的浪費(fèi)。
?
2、使用消息尺寸信息
?
這個(gè)方案允許使用可變長(zhǎng)度的消息,惟一的不足就是接收端的遠(yuǎn)程設(shè)置必須了解每一個(gè)變長(zhǎng)消息的確切長(zhǎng)度。具體的方法是,在發(fā)送消息的時(shí)候,一起發(fā)送該消息的長(zhǎng)度。那么在客戶端接收的時(shí)候就能知道該消息的長(zhǎng)度是多少,再來(lái)讀取消息。
?
3、使用消息標(biāo)記
?
該方案使用預(yù)先確定的一個(gè)字符(或多個(gè)字符)來(lái)指定消息的結(jié)束,通過(guò)這種方式來(lái)分隔不同的消息。但用這種方法必須對(duì)所接收到的每一個(gè)字符進(jìn)行檢查以便確定為結(jié)束標(biāo)記,這對(duì)于大型消息來(lái)說(shuō),可能導(dǎo)致系統(tǒng)性能的下降,不過(guò)對(duì)于C#語(yǔ)言來(lái)說(shuō),提供了一些類,能夠用于簡(jiǎn)化這個(gè)過(guò)程,那就是System.IO命名空間流類,下面我們也著重來(lái)講一下這各方法。至于第二種方法,將在下一篇中與在消息中傳送實(shí)體類信息相結(jié)合來(lái)講述。
?
在上一篇中,我們已經(jīng)提到NetworkStream類,利用該類來(lái)傳送和接收消息。在這里,再提一下另外的二個(gè)流類,那就是StreamReader和StreamWriter,這二個(gè)類也可用于TCP協(xié)議發(fā)送和接收文本消息。
?
當(dāng)我們得到Socket連接的一個(gè)NetworkStream對(duì)象時(shí),可以通過(guò)下面的方法得到StreamWriter和StreamReader對(duì)象。
?
1NetworkStream ns = s.GetStream();
2 StreamReader sr = new StreamReader(ns);
3 StreamWriter sw = new StreamWriter(ns);
?
這樣我們就可以通過(guò)StreamWriter來(lái)發(fā)送消息,通過(guò)StreamReader來(lái)接收消息:
?
1//發(fā)送消息
2string welcome = "Welcome to my test sever ";
3
4 sw.WriteLine(welcome);
5 sw.Flush();
?
接收消息:
?
1//接收消息
2string data = "";
3data = sr.ReadLine();
?
這樣是不是比以前的做法更簡(jiǎn)單了,而且同時(shí)也解決了TCP消息邊界問(wèn)題了。
?
但是用這各方法必須得注意以下二點(diǎn):
?
1、這種方法其實(shí)就是利用消息標(biāo)記來(lái)解決邊界問(wèn)題的,這里的標(biāo)記就是換行符,也就是說(shuō),StreamWriter中的WriteLine()和StreamReader中的ReadLine()一定要成對(duì)使用,不然如果發(fā)送的信息中沒(méi)有換行符,則客戶機(jī)中用ReadLine()讀取信息時(shí),將無(wú)法結(jié)束,將堵塞程序的執(zhí)行,一直等待換行符。
?
2、另外還要保證在發(fā)送的消息本身不應(yīng)該帶有換行符,如果消息本身帶有換行符,則這些換行符將被ReadLine()方法錯(cuò)誤地作為標(biāo)記,影響數(shù)據(jù)的完整性。
?
?
TCP長(zhǎng)連接和短連接。
2個(gè)小時(shí)。
?
?
斷點(diǎn)續(xù)傳的問(wèn)題。
如何實(shí)現(xiàn)在傳文件時(shí)的斷點(diǎn)續(xù)傳。
答案:
?
? 其實(shí)在tcp/ip協(xié)議中傳輸文件可以保證傳輸?shù)挠行?#xff0c;但有一個(gè)問(wèn)題文件傳了一部分連接意外斷開(kāi)了怎樣;那這種情況只能在重新連接后繼續(xù)傳輸,由于文件那部分已經(jīng)傳了那部分沒(méi)有完成并不是tcp/ip的范圍,所以需要自己來(lái)制定協(xié)議達(dá)到到這個(gè)目的。實(shí)現(xiàn)這個(gè)續(xù)傳的協(xié)議制定其實(shí)也是非常簡(jiǎn)單,通過(guò)協(xié)議把文件按塊來(lái)劃分,每完成一個(gè)塊就打上一個(gè)標(biāo)記;即使是連接斷了通過(guò)標(biāo)記狀態(tài)就知道還需要傳那些內(nèi)容。下面通過(guò)beetle來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單斷點(diǎn)續(xù)傳的程序(包括服務(wù)端和客戶端)。
? ? ? 在實(shí)現(xiàn)之前先整理一下流程思路,首先提交一個(gè)發(fā)送請(qǐng)求信息包括(文件名,塊大小,塊的數(shù)量等),等對(duì)方確認(rèn)后就進(jìn)行文件塊發(fā)送,對(duì)方接收塊寫入后返回一個(gè)標(biāo)記,然后再繼續(xù)發(fā)直到所有發(fā)送完成。思路明確后就制定協(xié)了:
文件傳輸申請(qǐng)信息
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public?class?Post:MessageBase {?? ????public?string?FileName; ????public?long?Size; ????public?int?PackageSize; ????public?int?Packages; ????public?Post() ????{ ????????FileID = Guid.NewGuid().ToString("N"); ????} } public?class?PostResponse : MessageBase { ????public?string?Status; } |
FileID這個(gè)值是用來(lái)協(xié)同工作的,兩端根據(jù)這個(gè)ID來(lái)找到具體操作的文件和相關(guān)信息;Response提供了一個(gè)Status屬性,可以用來(lái)提供一個(gè)錯(cuò)誤的描述,如果無(wú)有任何值的情況說(shuō)明對(duì)方允許這個(gè)行為.
文件塊傳輸信息
| 1 2 3 4 5 6 7 8 9 10 | public?class?PostPackage:MessageBase { ????public?byte[] Data; ????public?int?Index; } public?class?PostPackageResponse : MessageBase { ????public?int?Index; ????public?string?Status; } |
文件塊傳輸也是一個(gè)請(qǐng)求,一個(gè)應(yīng)答;分別帶的信息就是塊數(shù)據(jù)信息和塊的位置,同樣也是根據(jù)Status信息來(lái)標(biāo)記塊的處理是否成功。
? ? ? 結(jié)構(gòu)定義完成了,那就進(jìn)行邏輯處理部分;不過(guò)為了調(diào)用更方便還需要封裝一些東西,如根據(jù)塊大小來(lái)劃分文件塊的數(shù)目,獲取某一文件塊的內(nèi)容和寫入文件某一些的內(nèi)容等功能。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public?static?int?GetFilePackages(long?filesize) { ????int?count; ????if?(filesize % PackageSize > 0) ????{ ????????count = Convert.ToInt32(filesize / PackageSize) + 1; ????} ????else ????{ ????????count = Convert.ToInt32(filesize / PackageSize); ????} ???? ? ????return?count; } public?static?byte[] FileRead(string?filename, int?index, int?size) { ????using?(Smark.Core.ObjectEnter oe = new?Smark.Core.ObjectEnter(filename)) ????{ ????????byte[] resutl = null; ????????long?length = (long)index * (long)size + size; ????????using?(System.IO.FileStream stream = System.IO.File.OpenRead(filename)) ????????{ ????????????if?(length > stream.Length) ????????????{ ????????????????resutl = new?byte[stream.Length - ((long)index * (long)size)]; ????????????} ????????????else ????????????{ ????????????????resutl = new?byte[size]; ????????????} ????????????stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin); ????????????stream.Read(resutl, 0, resutl.Length); ????????} ????????return?resutl; ????} } public?static?void?FileWrite(string?filename, int?index, int?size, byte[] data) { ????using?(Smark.Core.ObjectEnter oe = new?Smark.Core.ObjectEnter(filename)) ????{ ????????using?(System.IO.FileStream stream = System.IO.File.OpenWrite(filename)) ????????{ ????????????stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin); ????????????stream.Write(data, 0, data.Length); ????????????stream.Flush(); ????????} ????} ? ? } |
? ? ? 準(zhǔn)備工作完成了,就開(kāi)始寫接收端的代碼了。之前的文章已經(jīng)介紹了Beetle如果創(chuàng)建一個(gè)服務(wù)和綁定分包機(jī)制,在這里就不多說(shuō)了;看下接收的邏輯是怎樣處理了.
接收傳文件請(qǐng)求
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public?void?Post(ChannelAdapter adapter, Beetle.FileTransfer.Post e) { ????string?file = txtFolder.Text + e.FileName; ????PostResponse response = new?PostResponse(); ????response.FileID = e.FileID; ????response.ID = e.ID; ????try ????{ ????????if?(FileTransferUtils.CreateFile(file, e.Size)) ????????{ ????????????Logics.FileItem item = new?Logics.FileItem(); ????????????item.FileID = e.FileID; ????????????item.FileName = file; ????????????item.Packages = e.Packages; ????????????item.PackageSize = e.PackageSize; ????????????item.Completed = 0; ????????????item.Size = e.Size; ????????????Logics.Access.Update(item); ????????????AddItem(item); ????????} ????????else ????????{ ????????????response.Status = "不能創(chuàng)建文件!"; ????????} ????} ????catch?(Exception e_) ????{ ????????response.Status = e_.Message; ????} ????adapter.Send(response); ????? ? } |
接收請(qǐng)求后根據(jù)信息創(chuàng)建臨時(shí)文件,創(chuàng)建成功就把文件相關(guān)信息保存到數(shù)據(jù)庫(kù)中,如果失敗或處理異常就設(shè)置相關(guān)Status信息返回.
接收文件塊請(qǐng)求
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public?void?PostPackage(ChannelAdapter adapter, Beetle.FileTransfer.PostPackage e) { ????PostPackageResponse response = new?PostPackageResponse(); ????response.FileID = e.FileID; ????response.ID = e.ID; ????try ????{ ????????Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID); ????????if?(item != null) ????????{ ????????????FileTransferUtils.FileWrite( ????????????????item.Item.FileName + ".up", e.Index, item.Item.PackageSize, e.Data); ????????????item.Completed(e.Index); ????????????response.Index = e.Index; ????????????if?(item.Status == Logics.FileItemStatus.Completed) ????????????????FileTransferUtils.Rename(item.Item.FileName); ????????} ????????else ????????{ ????????????response.Status = "不存在上傳信息!"; ????????} ????} ????catch?(Exception e_) ????{ ????????response.Status = e_.Message; ????} ????adapter.Send(response); } |
接收塊請(qǐng)求后處理也很簡(jiǎn)單,根據(jù)FileID獲取相關(guān)信息,然后把數(shù)據(jù)寫入到文件對(duì)應(yīng)的位置中;當(dāng)所有塊都已經(jīng)完成后把臨時(shí)文件名改會(huì)來(lái)就行了。如果處理異常很簡(jiǎn)單通過(guò)設(shè)置到Status成員中告訴請(qǐng)求方。
以下就是請(qǐng)求端的代碼了,其代碼比接收端更加簡(jiǎn)單了
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public?void?PostResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostResponse e) { ????mResponse = e; ????mResetEvent.Set(); } public?void?PostPackageResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostPackageResponse e) { ????Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID); ????if?(item != null) ????{ ????????if?(string.IsNullOrEmpty(e.Status)) ????????{ ????????????item.Completed(e.Index); ????????????PostPacakge(item); ????????} ????????else ????????????item.Status = Logics.FileItemStatus.Default; ????} } private?void?PostPacakge(Logics.FileListItem item) { ????if?(mChannel != null?&& mChannel.Socket != null?&& item.Status == Logics.FileItemStatus.Working ????????&& item.Item.Completed != item.Item.Packages) ????{ ????????PostPackage post = new?PostPackage(); ????????post.FileID = item.Item.FileID; ????????post.Index = item.Item.Completed; ????????post.Data = FileTransferUtils.FileRead(item.Item.FileName, ????????????item.Item.Completed, item.Item.PackageSize); ????????mAdapter.Send(post); ????} } |
請(qǐng)求端要做的工作就是發(fā)送文件傳輸請(qǐng)求,等回應(yīng)后就處理PostPacakge進(jìn)行文件塊發(fā)送,接收到當(dāng)前文件塊處理成功后就發(fā)送下一塊直接完成。
? ? ? 到這里斷點(diǎn)續(xù)傳的功能代碼就已經(jīng)完成,兩邊的程序已經(jīng)可以工作。不過(guò)對(duì)于一些使用者來(lái)說(shuō)希望程序更友好的表現(xiàn)工作情況,這個(gè)時(shí)候還得對(duì)UI下一點(diǎn)功夫,如看到當(dāng)前傳輸?shù)臓顟B(tài)和每個(gè)文件進(jìn)度情況等。
? ? ? 以上效果看起來(lái)很不錯(cuò),那接下來(lái)就把它實(shí)現(xiàn)吧,程序使用ListBox來(lái)顯示傳輸文件信息,要達(dá)到以上效果需要簡(jiǎn)單地重寫一下OnDrawItem達(dá)到我們需要的。在講述代碼之前介紹一個(gè)圖標(biāo)網(wǎng)站http://www.iconfinder.com/,畢竟好的圖標(biāo)可以讓程序生色不少。下面看下這個(gè)重寫的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | protected?override?void?OnDrawItem(DrawItemEventArgs e) { ????base.OnDrawItem(e); ????StringFormat ListSF; ????Point imgpoint = new?Point(e.Bounds.X + 2, e.Bounds.Y + 1); ????ListSF = StringFormat.GenericDefault; ????ListSF.LineAlignment = StringAlignment.Center; ????ListSF.FormatFlags = StringFormatFlags.LineLimit | StringFormatFlags.NoWrap; ????ListSF.Trimming = StringTrimming.EllipsisCharacter; ????Rectangle labelrect = new?Rectangle(e.Bounds.X + 44, e.Bounds.Y, e.Bounds.Width - 44, e.Bounds.Height); ????if?(Site == null?|| Site.DesignMode == false) ????{ ????????if?(e.Index >= 0) ????????{ ????????????FileListItem item = (FileListItem)Items[e.Index]; ????????????LinearGradientBrush brush; ????????????brush = ??????????????new?LinearGradientBrush(e.Bounds, Color.FromArgb(208, 231, 253), ??????????????Color.FromArgb(10, 94, 177), LinearGradientMode.Horizontal); ????????????double?pent = (double)item.Item.Completed / (double)item.Item.Packages; ????????????using?(brush) ????????????{ ????????????????e.Graphics.FillRectangle(brush, e.Bounds.X + 40, e.Bounds.Y + 2, Convert.ToInt32((e.Bounds.Width - 40) * pent), e.Bounds.Height - 4); ????????????} ????????????if?(item.Status == FileItemStatus.Working) ????????????{ ????????????????mImgList.Draw(e.Graphics, imgpoint, 1); ????????????} ????????????else?if?(item.Status == FileItemStatus.Completed) ????????????{ ????????????????mImgList.Draw(e.Graphics, imgpoint, 2); ????????????} ????????????else ????????????{ ????????????????mImgList.Draw(e.Graphics, imgpoint, 0); ????????????} ????????????e.Graphics.DrawString(item.ToString(),new?Font("Ariel", 9), new?SolidBrush(Color.Black),labelrect, ListSF); ????????} ????} } |
重繪代碼就是根據(jù)當(dāng)前文件的進(jìn)度內(nèi)容來(lái)計(jì)算出填沖的寬度,還有根據(jù)當(dāng)前文件狀態(tài)繪制不同的圖標(biāo),是不是比較簡(jiǎn)單:)
整個(gè)功能完成了看下總體的效果怎樣:
下載完整代碼
FileTransfer.rar (649.79 kb)?
如果需要Smark名稱空間的代碼可以到?http://smark.codeplex.com/
?
多線程下載的原理:
各個(gè)線程任務(wù)分配是這樣實(shí)現(xiàn)的。在開(kāi)始下載時(shí),文件平均分成若干塊進(jìn)行下載。如第一個(gè)線程一開(kāi)始的任務(wù)是從文件的0位置開(kāi)始下載一直到72908位置處。 線程1每次下載一塊數(shù)據(jù)后就要調(diào)整任務(wù),如第一次下載了20800字節(jié)的數(shù)據(jù),那么線程1的任務(wù)將改為:20800-72908。如此下去,直到任務(wù)為 72908-72908時(shí)表示線程1完成了當(dāng)前的下載任務(wù)。此時(shí),線程1就分析各個(gè)線程的任務(wù),找出任務(wù)最為繁忙的一個(gè)線程:如線程 3:14816-218724。那么線程1就自動(dòng)去調(diào)整任務(wù),拿50%的任務(wù)來(lái)再次下載。周而復(fù)始直到各個(gè)線程都完成任務(wù)。
不過(guò)這里有一點(diǎn)需要注意:為了避免重復(fù)下載部分?jǐn)?shù)據(jù),在調(diào)整任務(wù)的時(shí)候,起始的文件偏移量必須加上接受緩沖器的字節(jié)數(shù),因?yàn)槿缜懊嫠e的例子來(lái)看。線程1和線程3在平衡負(fù)載的時(shí)候,線程正在下載數(shù)據(jù),如果所剩的數(shù)據(jù)比接受緩沖器的大小還小,線程1和線程3的部分下載數(shù)據(jù)將會(huì)重復(fù)。
?
?
UDP協(xié)議基礎(chǔ)知識(shí)
?
UDP協(xié)議在IP協(xié)議上增加了復(fù)用、分用和差錯(cuò)檢測(cè)功能。UDP的特點(diǎn):
?????? A)是無(wú)連接的。相比于TCP協(xié)議,UDP協(xié)議在傳送數(shù)據(jù)前不需要建立連接,當(dāng)然也就沒(méi)有釋放連接。
?????? B)是盡最大努力交付的。也就是說(shuō)UDP協(xié)議無(wú)法保證數(shù)據(jù)能夠準(zhǔn)確的交付到目的主機(jī)。也不需要對(duì)接收到的UDP報(bào)文進(jìn)行確認(rèn)。
?????? C)是面向報(bào)文的。也就是說(shuō)UDP協(xié)議將應(yīng)用層傳輸下來(lái)的數(shù)據(jù)封裝在一個(gè)UDP包中,不進(jìn)行拆分或合并。因此,運(yùn)輸層在收到對(duì)方的UDP包后,會(huì)去掉首部后,將數(shù)據(jù)原封不動(dòng)的交給應(yīng)用進(jìn)程。
???????D)沒(méi)有擁塞控制。因此UDP協(xié)議的發(fā)送速率不送網(wǎng)絡(luò)的擁塞度影響。
?????? E)UDP支持一對(duì)一、一對(duì)多、多對(duì)一和多對(duì)多的交互通信。
???????F)UDP的頭部占用較小,只占用8個(gè)字節(jié)。
UDP報(bào)文格式
?????? ?UDP協(xié)議分為首部字段和數(shù)據(jù)字段,其中首部字段只占用8個(gè)字節(jié),分別是個(gè)占用兩個(gè)字節(jié)的源端口、目的端口、長(zhǎng)度和檢驗(yàn)和。
??????? 長(zhǎng)度:UDP報(bào)文的整個(gè)大小,最小為8個(gè)字節(jié)(僅為首部)。
??????? 檢驗(yàn)和:在進(jìn)行檢驗(yàn)和計(jì)算時(shí),會(huì)添加一個(gè)偽首部一起進(jìn)行運(yùn)算。偽首部(占用12個(gè)字節(jié))為:4個(gè)字節(jié)的源IP地址、4個(gè)字節(jié)的目的IP地址、1個(gè)字節(jié)的0、一個(gè)字節(jié)的數(shù)字17、以及占用2個(gè)字節(jié)UDP長(zhǎng)度。這個(gè)偽首部不是報(bào)文的真正首部,只是引入為了計(jì)算校驗(yàn)和。相對(duì)于IP協(xié)議的只計(jì)算首部,UDP檢驗(yàn)和會(huì)把首部和數(shù)據(jù)一起進(jìn)行校驗(yàn)。接收端進(jìn)行的校驗(yàn)和與UDP報(bào)文中的校驗(yàn)和相與,如果無(wú)差錯(cuò)應(yīng)該全為1。如果有誤,則將報(bào)文丟棄或者發(fā)給應(yīng)用層、并附上差錯(cuò)警告。
?
TCP協(xié)議基礎(chǔ)知識(shí)
?
TCP協(xié)議的特點(diǎn):
??????? A)是面向連接的。應(yīng)用程序在使用TCP協(xié)議時(shí),必須進(jìn)行連接;當(dāng)然,數(shù)據(jù)傳輸結(jié)束后,要斷開(kāi)TCP連接。
??????? B)TCP連接是點(diǎn)對(duì)點(diǎn)的。
??????? C)TCP連接時(shí)可靠的。也就是說(shuō)傳輸?shù)臄?shù)據(jù)時(shí)無(wú)差錯(cuò)的、不丟失、不重復(fù)、有序到達(dá)的。
??????? D)是全雙工的。即TCP連接的兩端都設(shè)有發(fā)送緩存和接收緩存,用來(lái)存放雙向通信的數(shù)據(jù)。
??????? E)是面向字節(jié)流的。也就是說(shuō)TCP將應(yīng)用程序交下來(lái)的數(shù)據(jù)看成僅僅是一連串的無(wú)結(jié)構(gòu)的字節(jié)流,其不知道這些字節(jié)流的具體含義。TCP協(xié)議無(wú)法保證發(fā)送的數(shù)據(jù)塊的具體大小,因?yàn)?/strong>TCP協(xié)議的發(fā)送的數(shù)據(jù)大小收到對(duì)方給出的窗口值和當(dāng)前的網(wǎng)絡(luò)擁塞度的影響。
TCP的連接端點(diǎn)是套接字。套接字是IP地址拼接上端口號(hào)組成的,即點(diǎn)分方式的十進(jìn)制后面是端口號(hào),中間用逗號(hào)或冒號(hào)隔開(kāi)。如下方式:
?????????????????? 套接字Socket=(IP地址:端口號(hào))
TCP報(bào)文段的首部
TCP協(xié)議的首部有20字節(jié)的固定長(zhǎng)度,以及4N字節(jié)的變長(zhǎng)段,因此TCP報(bào)文段首部最小為20字節(jié)。其格式如下:
??? A)分別占用兩個(gè)字節(jié)的源端口和目的端口。
????B)序號(hào)。占用4個(gè)字節(jié)。序號(hào)是循環(huán)的,當(dāng)增加到最大值后又回到0。TCP是面向字節(jié)流的,這樣會(huì)個(gè)發(fā)送的數(shù)據(jù)按順序給每個(gè)字節(jié)編上號(hào)。在建立連接時(shí),會(huì)指出發(fā)送的字節(jié)流的起始序號(hào)。首部中的序號(hào)表示發(fā)送的報(bào)文段的數(shù)據(jù)的第一個(gè)字節(jié)的序號(hào),而報(bào)文的給字節(jié)的序號(hào)是順序的。如第一個(gè)字節(jié)為401,報(bào)文的數(shù)據(jù)長(zhǎng)度為100,則首部的序號(hào)為401,最后一個(gè)字節(jié)的序號(hào)為500,也就是說(shuō)下一個(gè)報(bào)文的首部序號(hào)為501。
??? C)確認(rèn)號(hào)。占用4個(gè)字節(jié)。表示期望收到下一個(gè)報(bào)文段的第一個(gè)數(shù)據(jù)字節(jié)的序號(hào),也是下一個(gè)報(bào)文段的首部序號(hào)。如:B收到了A的200個(gè)字節(jié)數(shù)據(jù)的TCP報(bào)文段,而這個(gè)TCP報(bào)文段的首部序號(hào)為601,則B希望收到的下一個(gè)報(bào)文段確認(rèn)號(hào)為801。也可以說(shuō)如果確認(rèn)號(hào)為N,則前面N-1為止的數(shù)據(jù)已經(jīng)收到。
????D)數(shù)據(jù)偏移。占用4位。其表示數(shù)據(jù)起始位置相對(duì)于報(bào)文段起始的位置的偏移量,也就是報(bào)文段首部的長(zhǎng)度。其單位為32位字,也就是說(shuō)其表示的值應(yīng)該乘上4個(gè)字節(jié)。如:該字段的值為4,則其報(bào)文段首部長(zhǎng)度為16個(gè)字節(jié)。其最大值只能表示15,也就是說(shuō)報(bào)文段的首部最大只能為60個(gè)字節(jié)(即變長(zhǎng)只能為20個(gè)字節(jié))。其最小值為5,因?yàn)槭撞孔钚≈禐?/span>20。
???? E)保留。占用6位。其值設(shè)為0。
???? F)緊急URG。其占用1位。如果URG設(shè)為1,表示首部中的緊急指針有效。表示其發(fā)送的報(bào)文段數(shù)據(jù)有緊急數(shù)據(jù),其需要馬上發(fā)送出去(也就是取得最高優(yōu)先權(quán)),TCP會(huì)將緊急數(shù)據(jù)插入到該報(bào)文的最前端,后面任然為普通數(shù)據(jù)。
???? G)確認(rèn)ACK。占用1位。當(dāng)ACK=1時(shí),表示首部中確認(rèn)號(hào)字段有效,為0時(shí),確認(rèn)號(hào)字段無(wú)效。TCP規(guī)定,在連接建立后ACK字段必須為1。
???? H)推送PSH。占用1位。兩個(gè)應(yīng)用程序通信,有時(shí)應(yīng)用程序希望鍵入一個(gè)命令馬上能夠得到對(duì)方回應(yīng),這是就可以使用這個(gè)字段。將PSH設(shè)為1,TCP會(huì)馬上建立一個(gè)報(bào)文將其發(fā)送出去。接收端在收到該報(bào)文后,會(huì)盡快的將其交給應(yīng)用程序,不用得到緩沖區(qū)滿。
????? I)復(fù)位RST。占用1位。當(dāng)RST=1時(shí),表示連接出現(xiàn)嚴(yán)重錯(cuò)誤,需要釋放連接,然后重新進(jìn)行連接。其還用于拒絕打開(kāi)一個(gè)連接或拒絕非法報(bào)文。
????? J)同步SYN。占用1位。用于在連接建立時(shí)同步序號(hào)。當(dāng)SYN=而ACK=0時(shí),表示這是一個(gè)連接請(qǐng)求報(bào)文段。當(dāng)SYN=1且ACK=1時(shí),表示對(duì)方接收建立連接報(bào)文段。因此SYN=1,表示這是一個(gè)連接請(qǐng)求或連接接受報(bào)文段。
????? K)終止FIN。占用1位。當(dāng)FIN=1時(shí),表示發(fā)送方的數(shù)據(jù)發(fā)送完畢,并要求釋放連接。
????? L)窗口。占用2個(gè)字節(jié)。表示發(fā)送該報(bào)文段的一方的接收窗口,表示允許對(duì)方發(fā)送的數(shù)據(jù)量。窗口值告訴對(duì)方:從報(bào)文段首部中的確認(rèn)好算起,接收方目前允許對(duì)方發(fā)送的數(shù)據(jù)量。如:確認(rèn)為801,窗口值為1000,則表示其還有接收1000個(gè)字節(jié)數(shù)據(jù)(801-1800)的接收緩存空間。
????? M)檢驗(yàn)和。占2個(gè)字節(jié)。其也和UDP一樣需要加上偽首部,但是其中的17會(huì)變?yōu)?/span>6。
????? N)緊急指針。占用2個(gè)字節(jié)。當(dāng)URG=1時(shí)有效,其指出了緊急數(shù)據(jù)在報(bào)文端中的末尾位置。緊急數(shù)據(jù)在報(bào)文數(shù)據(jù)段的開(kāi)始。
????? O)選項(xiàng)。可選,最大為40個(gè)字節(jié)。包括MSS、窗口擴(kuò)大、時(shí)間戳、選擇確認(rèn)等。
?
TCP的連接與斷開(kāi)
?
TCP連接的建立采用客戶服務(wù)器方式。TCP連接可分為通信雙方一方發(fā)起連接和雙方同時(shí)發(fā)起連接。
一方發(fā)起連接
????? ? 假設(shè)A方為客服端,B為服務(wù)器端,由A向B發(fā)起建立連接請(qǐng)求。
????? ? B的服務(wù)器進(jìn)程創(chuàng)建傳輸控制塊TCB,準(zhǔn)備接受客戶進(jìn)程的連接請(qǐng)求。然后服務(wù)器進(jìn)程處于LISTEN狀態(tài),等待客服端的連接請(qǐng)求。
?????? ?A打的TCP客服端進(jìn)程創(chuàng)建控制塊TCB,然后向B發(fā)送連接請(qǐng)求報(bào)文段。該報(bào)文的首部SYN=1,并且假設(shè)序號(hào)seq=x。TCP規(guī)定,該連接請(qǐng)求報(bào)文段不攜帶任何數(shù)據(jù),但需要占用一個(gè)序號(hào)。A發(fā)送了連接請(qǐng)求報(bào)文后進(jìn)入SYN-SENT狀態(tài)。
??????? B收到A的連接請(qǐng)求報(bào)文后,會(huì)向A返回連接接受報(bào)文。該報(bào)文的首部SYN=1,ACK=1,seq=y,ack=x+1。TCP也規(guī)定,連接接受報(bào)文段也不攜帶任何數(shù)據(jù),但也需要占用一個(gè)序號(hào)。B發(fā)送該報(bào)文后立即進(jìn)入SYN-RECD狀態(tài)。
??????? A收到B的連接接受報(bào)文后,還需要向B發(fā)送一個(gè)確認(rèn)報(bào)文段。在該報(bào)文中ACK=1,seq=x+1,ack=y+1。TCP規(guī)定,在該報(bào)文段中可以攜帶數(shù)據(jù),也可不攜帶數(shù)據(jù)。但,不攜帶數(shù)據(jù)時(shí),不會(huì)消耗一個(gè)序號(hào),也就會(huì)說(shuō)下一個(gè)發(fā)送的報(bào)文段的序號(hào)仍為x+1。該報(bào)文段發(fā)送成功后,進(jìn)入ESTAB-LISHED狀態(tài),。
?????? B收到A的確認(rèn)后,也進(jìn)入ESTAB-ISHED狀態(tài)。
?同時(shí)發(fā)起連接
?????? 出現(xiàn)這種同時(shí)發(fā)起連接的情況可能性極小,但TCP仍然支持這種方式,這種方式?jīng)]有客戶端和服務(wù)器區(qū)分,一個(gè)既是客戶端又是服務(wù)器。這種方式,雙方同時(shí)發(fā)起連接請(qǐng)求報(bào)文,并進(jìn)入SYN-SENT狀態(tài)。當(dāng)收到對(duì)方的連接請(qǐng)求報(bào)文后,馬上發(fā)送一個(gè)連接接受報(bào)文,并馬上進(jìn)入SYN-REVD狀態(tài)。當(dāng)收到對(duì)方的連接接受報(bào)文后,進(jìn)入ESTABISHED狀態(tài)。
斷開(kāi)連接?
斷開(kāi)連接也可以分為由連接一方發(fā)起斷開(kāi)連接請(qǐng)求和同時(shí)發(fā)起斷開(kāi)連接請(qǐng)求。
一方發(fā)起斷開(kāi)連接
????????A向B發(fā)起斷開(kāi)連接請(qǐng)求報(bào)文。該報(bào)文的首部中FIN=1,假設(shè)序列號(hào)seq=u,其為前面發(fā)送的報(bào)文段的數(shù)據(jù)的最后一個(gè)序列號(hào)加一。這是A進(jìn)入FIN-WAI-1狀態(tài)。TCP規(guī)定,斷開(kāi)連接請(qǐng)求報(bào)文不攜帶任何數(shù)據(jù),但要消耗一個(gè)序列號(hào)。發(fā)送該報(bào)文之前會(huì)將緩沖區(qū)中的數(shù)據(jù)全部發(fā)送出去,該報(bào)文可以附加數(shù)據(jù)。
?????? B在收到A的斷開(kāi)連接請(qǐng)求報(bào)文后,會(huì)發(fā)回一個(gè)確認(rèn)報(bào)文。該報(bào)文的首部中ACK=1,ack=u+1,假設(shè)seq=v。B進(jìn)入了CLOSE-WAIT狀態(tài)。這時(shí)B會(huì)通知上層應(yīng)用進(jìn)程,這是A到B方向的連接釋放了,表明A沒(méi)有數(shù)據(jù)發(fā)給B了。這是可能會(huì)在該狀態(tài)持續(xù)一段時(shí)間,等待B將沒(méi)發(fā)送完的數(shù)據(jù)發(fā)送給A。這時(shí)A不能夠發(fā)送數(shù)據(jù)給B,但A還可以接收數(shù)據(jù)。
????? A在收到B的確認(rèn)報(bào)文后,會(huì)進(jìn)入FIN-WAIT-2狀態(tài),等待B向A發(fā)送斷開(kāi)連接請(qǐng)求報(bào)文。
????? 當(dāng)B沒(méi)有數(shù)據(jù)需要發(fā)送給A時(shí),其會(huì)釋放TCP連接。B向A發(fā)送斷開(kāi)連接請(qǐng)求報(bào)文。該報(bào)文的首部中FIN=1,ACK=1,ack=u+1,假設(shè)序列號(hào)為seq=w(因?yàn)榭赡茉诘却倪@段時(shí)間里,B向A發(fā)送了一些數(shù)據(jù))。這是B進(jìn)入LAST-ACK狀態(tài)。該報(bào)文也可以附加數(shù)據(jù)。
????? A收到B的斷開(kāi)連接請(qǐng)求報(bào)恩后,也會(huì)發(fā)回個(gè)確認(rèn)報(bào)文。該報(bào)文中ACK=1,ack=w+1,seq=u+1。這時(shí)A進(jìn)入TIME-WAIT狀態(tài)。
???? B在收到A餓確認(rèn)報(bào)文后,就徹底的斷開(kāi)了連接,并且會(huì)撤去相應(yīng)的傳輸控制塊。
? ?
????? 快速斷開(kāi)連接:這種情況出現(xiàn)B發(fā)送給A斷開(kāi)連接確認(rèn)報(bào)文,不僅為ACK而且FIN=1,也就是說(shuō)在該確認(rèn)報(bào)文中還包括了B的斷開(kāi)連接請(qǐng)求。這是A在收到該報(bào)文后,會(huì)給B發(fā)送一個(gè)確認(rèn)報(bào)文,并進(jìn)入TIME-WAIT狀態(tài)。(相對(duì)于正常斷開(kāi)的4次握手,快速斷開(kāi)連接方式將第二次和第三次握手合成了一次)。
同時(shí)斷開(kāi)連接
?????????兩端應(yīng)用層同時(shí)發(fā)出關(guān)閉命令時(shí),兩端均從 ESTABLISHED 變?yōu)?/span> FIN_WAIT_1 。這將導(dǎo)致雙方各發(fā)送一個(gè) FIN ,兩個(gè) FIN 經(jīng)過(guò)網(wǎng)絡(luò)傳送后分別到達(dá)另一端。收到 FIN 后,狀態(tài)由 FIN_WAIT_1 變遷到 CLOSING ,并發(fā)送最后的 ACK 。當(dāng)收到最后的 ACK 時(shí),狀態(tài)變化為 TIME_WAIT 狀態(tài)。
?
?
在同一個(gè)進(jìn)程中,UDP和TCP可以共用同一個(gè)端口嗎?
答:
可以。不同的協(xié)議沒(méi)有沖突。相同的協(xié)議有沖突。
?
?
?
數(shù)據(jù)的加密
SSL
?
轉(zhuǎn)載于:https://www.cnblogs.com/heavyhe/p/4547065.html
總結(jié)
以上是生活随笔為你收集整理的TCP/UDP编程中的问题汇总的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 答辩完了该思考些什么
- 下一篇: 数据结构题目