TCP/UDP编程中的问题汇总
TCP/UDP編程中的問題匯總
?
?
?
TCP和UDP發送大文件的問題。
答:
發送端:
發送時,先發送文件的名稱及大小等信息。
然后,設置一個緩沖區的大小,假設為4K。
再循環讀4K的文件內容,并發送,直到發送完成。
最后,再發送完成標記。
?
接收端:
接收到第一個包時,得到文件的大小等信息。
計算出要接收多少個包。
然后,循環接收包,并將接收到的數據寫入到文件中。
直到,接收到的數據長度等于文件的大小。
?
struct package
{
文件標識 //GUID
偏移量 //001-
數據段 //Byte[]
};
?
?
如何用UDP實現,返回數據。
?
UDP服務端和客戶端,同時實現發送和接收數據。
答:
1,客戶端與服務端在同一局域網,或客戶端有公網IP。
?
?
?
?
2,客戶端與服務端不在同一局域網,且客戶端無公網IP。
NAT技術。
?
?
?
UDP報文丟失的問題。
答:
如果C給S發報文。則S收到報文后,給C發一個收到報文的響應。C對此進行記錄。
如果C在某段時間內未收到響應,則重發此報文。
?
?
UDP報文的順序問題。
答:
發送端在報文中加入發送序號。接收端就可以按照發送序號進行重新排列。
?
UDP缺乏流量控制。
?
1.UDP缺乏流量控制的概念
UDP協議沒有TCP協議所具有的滑動窗口概念,接收數據的時候直接將數據放到緩沖區中。如果用戶不有及時地從緩沖區中將數據復制出來
,后面到來的數據會接著向緩沖區中放入。當緩沖區滿的時候,后面到來的數據會覆蓋之前的數據造成數據的丟失。
2.緩沖區溢出對策
解決UDP接收緩沖區溢出的現象需要根據實際情況確定,一般可以用增大接收數據緩沖區和接收方接收單獨處理的方法來解決局部的UDP數
據接收緩沖區溢出問題。
?
UDP協議中的數據報文截斷
?
?
?
?
UDP一次發送多少bytes好?
當然,這個沒有唯一答案,相對于不同的系統,不同的要求,其得到的答案是不一樣的,我這里僅對像ICQ一類的發送聊天消息的情況作分析,對于其他情況,你或許也能得到一點幫助:
首先,我們知道,TCP/IP通常被認為是一個四層協議系統,包括鏈路層,網絡層,運輸層,應用層.
UDP屬于運輸層,下面我們由下至上一步一步來看:
以太網(Ethernet)數據幀的長度必須在46-1500字節之間,這是由以太網的物理特性決定的. 這個1500字節被稱為鏈路層的MTU(最大傳輸單元).
但這并不是指鏈路層的長度被限制在1500字節,其實這這個MTU指的是鏈路層的數據區. 并不包括鏈路層的首部和尾部的18個字節.
所以,事實上,這個1500字節就是網絡層IP數據報的長度限制.
因為IP數據報的首部為20字節,所以IP數據報的數據區長度最大為1480字節.
而這個1480字節就是用來放TCP傳來的TCP報文段或UDP傳來的UDP數據報的.
又因為UDP數據報的首部8字節,所以UDP數據報的數據區最大長度為1472字節.
這個1472字節就是我們可以使用的字節數。:)
當我們發送的UDP數據大于1472的時候會怎樣呢?
這也就是說IP數據報大于1500字節,大于MTU.這個時候發送方IP層就需要分片(fragmentation).
把數據報分成若干片,使每一片都小于MTU.而接收方IP層則需要進行數據報的重組.
這樣就會多做許多事情,而更嚴重的是,由于UDP的特性,當某一片數據傳送中丟失時,接收方便無法重組數據報.將導致丟棄整個UDP數據報。
因此,在普通的局域網環境下,我建議將UDP的數據控制在1472字節以下為好.
進行Internet編程時則不同,因為Internet上的路由器可能會將MTU設為不同的值.
如果我們假定MTU為1500來發送數據的,而途經的某個網絡的MTU值小于1500字節,那么系統將會使用一系列的機制來調整MTU值,使數據報能夠順利到達目的地,這樣就會做許多不必要的操作.鑒于Internet上的標準MTU值為576字節,所以我建議在進行Internet的UDP編程時. 最好將UDP的數據長度控件在548字節(576-8-20)以內.
?
理論上,IP數據報的最大長度是65535字節,這是由IP首部16比特總長度字段所限制的。去除20字節的IP首部和8個字節的UDP首部,UDP數據報中用戶數據的最長長度為65507字節。但是,大多數實現所提供的長度比這個最大值小。
?
我們將遇到兩個限制因素。第一,應用程序可能會受到其程序接口的限制。socket API提供了一個可供應用程序調用的函數,以設置接收和發送緩存的長度。對于UDP socket,這個長度與應用程序可以讀寫的最大UDP數據報的長度直接相關。現在的大部分系統都默認提供了可讀寫大于8192字節的UDP數據報(使用這個默認值是因為8192是NFS讀寫用戶數據數的默認值)。
?
第二個限制來自于TCP/IP的內核實現。可能存在一些實現特性(或差錯),使IP數據報長度小于65535字節。
在SunOS 4.1.3下使用環回接口的最大IP數據報長度是32767字節。比它大的值都會發生差錯。
?
但是從BSD/386到SunOS 4.1.3的情況下,Sun所能接收到最大IP數據報長度為32786字節(即32758字節用戶數據)。
?
在Solaris 2.2下使用環回接口,最大可收發IP數據報長度為65535字節。
?
從Solaris 2.2到AIX 3.2.2,發送的最大IP數據報長度可以是65535字節。很顯然,這個限制與源端和目的端的實現有關。
?
主機必須能夠接收最短為576字節的IP數據報。在許多UDP應用程序的設計中,其應用程序數據被限制成512字節或更小,因此比這個限制值小。
?
由于IP能夠發送或接收特定長度的數據報并不意味著接收應用程序可以讀取該長度的數據。因此,UDP編程接口允許應用程序指定每次返回的最大字節數。如果接收到的數據報長度大于應用程序所能處理的長度,那么會發生什么情況呢?不幸的是,該問題的答案取決于編程接口和實現。
?
典型的Berkeley版socket API對數據報進行截斷,并丟棄任何多余的數據。應用程序何時能夠知道,則與版本有關(4.3BSD Reno及其后的版本可以通知應用程序數據報被截斷)。
?
SVR4下的socket API(包括Solaris 2.x) 并不截斷數據報。超出部分數據在后面的讀取中返回。它也不通知應用程序從單個UDP數據報中多次進行讀取操作。TLI API不丟棄數據。相反,它返回一個標志表明可以獲得更多的數據,而應用程序后面的讀操作將返回數據報的其余部分。在討論TCP時,我們發現它為應用程序提供連續的字節流,而沒有任何信息邊界。TCP以應用程序讀操作時所要求的長度來傳送數據,因此,在這個接口下,不會發生數據丟失。
?
?
TCP消息邊界的問題
通過套接字或其助手類來接收信息時,是從緩存區里一次性把全部的緣存都讀取出來,只要你設置的緩存夠大,它就能讀取這么多,這樣就會導致這樣的情況出現。如果服務端連續發送信息到客戶端,如我連續發送字符串"message 1"、"message 2"、"message 3"、"message 4"、"message 5",我預想的是在客戶端也是能夠收到這樣的五個完整的字符串,如果用前二篇中講的方法,在同臺機子上測試的話,是正常的,因為同臺機子上網絡信息傳送出現的異常會比較少,但如果把客戶端與服務端部署在不同的機器上,則會出現一些異想不到的現象。你會發現接收到的字符都被打亂了,會出現如"3message 4"的字符串,這樣的話,我們就不能把服務端發送的信息正常的還原。這個就是消息的邊界問題,要解決這個問題,方法有很多,現抽取其中幾個來講一下:
?
1、固定尺寸的消息
?
這是最簡單但也是最昂貴的解決TCP消息問題的方案。就是要設計一種協議,永遠以固定的長度傳遞消息,通過將所有的消息都設置為固定的尺寸,在從遠程設備中接收到完整的消息時,TCP接收程序就能夠了解發送的情況了。用這各地意味著必須將短消息加長,造成網絡帶寬資源的浪費。
?
2、使用消息尺寸信息
?
這個方案允許使用可變長度的消息,惟一的不足就是接收端的遠程設置必須了解每一個變長消息的確切長度。具體的方法是,在發送消息的時候,一起發送該消息的長度。那么在客戶端接收的時候就能知道該消息的長度是多少,再來讀取消息。
?
3、使用消息標記
?
該方案使用預先確定的一個字符(或多個字符)來指定消息的結束,通過這種方式來分隔不同的消息。但用這種方法必須對所接收到的每一個字符進行檢查以便確定為結束標記,這對于大型消息來說,可能導致系統性能的下降,不過對于C#語言來說,提供了一些類,能夠用于簡化這個過程,那就是System.IO命名空間流類,下面我們也著重來講一下這各方法。至于第二種方法,將在下一篇中與在消息中傳送實體類信息相結合來講述。
?
在上一篇中,我們已經提到NetworkStream類,利用該類來傳送和接收消息。在這里,再提一下另外的二個流類,那就是StreamReader和StreamWriter,這二個類也可用于TCP協議發送和接收文本消息。
?
當我們得到Socket連接的一個NetworkStream對象時,可以通過下面的方法得到StreamWriter和StreamReader對象。
?
1NetworkStream ns = s.GetStream();
2 StreamReader sr = new StreamReader(ns);
3 StreamWriter sw = new StreamWriter(ns);
?
這樣我們就可以通過StreamWriter來發送消息,通過StreamReader來接收消息:
?
1//發送消息
2string welcome = "Welcome to my test sever ";
3
4 sw.WriteLine(welcome);
5 sw.Flush();
?
接收消息:
?
1//接收消息
2string data = "";
3data = sr.ReadLine();
?
這樣是不是比以前的做法更簡單了,而且同時也解決了TCP消息邊界問題了。
?
但是用這各方法必須得注意以下二點:
?
1、這種方法其實就是利用消息標記來解決邊界問題的,這里的標記就是換行符,也就是說,StreamWriter中的WriteLine()和StreamReader中的ReadLine()一定要成對使用,不然如果發送的信息中沒有換行符,則客戶機中用ReadLine()讀取信息時,將無法結束,將堵塞程序的執行,一直等待換行符。
?
2、另外還要保證在發送的消息本身不應該帶有換行符,如果消息本身帶有換行符,則這些換行符將被ReadLine()方法錯誤地作為標記,影響數據的完整性。
?
?
TCP長連接和短連接。
2個小時。
?
?
斷點續傳的問題。
如何實現在傳文件時的斷點續傳。
答案:
?
? 其實在tcp/ip協議中傳輸文件可以保證傳輸的有效性,但有一個問題文件傳了一部分連接意外斷開了怎樣;那這種情況只能在重新連接后繼續傳輸,由于文件那部分已經傳了那部分沒有完成并不是tcp/ip的范圍,所以需要自己來制定協議達到到這個目的。實現這個續傳的協議制定其實也是非常簡單,通過協議把文件按塊來劃分,每完成一個塊就打上一個標記;即使是連接斷了通過標記狀態就知道還需要傳那些內容。下面通過beetle來實現一個簡單斷點續傳的程序(包括服務端和客戶端)。
? ? ? 在實現之前先整理一下流程思路,首先提交一個發送請求信息包括(文件名,塊大小,塊的數量等),等對方確認后就進行文件塊發送,對方接收塊寫入后返回一個標記,然后再繼續發直到所有發送完成。思路明確后就制定協了:
文件傳輸申請信息
| 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這個值是用來協同工作的,兩端根據這個ID來找到具體操作的文件和相關信息;Response提供了一個Status屬性,可以用來提供一個錯誤的描述,如果無有任何值的情況說明對方允許這個行為.
文件塊傳輸信息
| 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; } |
文件塊傳輸也是一個請求,一個應答;分別帶的信息就是塊數據信息和塊的位置,同樣也是根據Status信息來標記塊的處理是否成功。
? ? ? 結構定義完成了,那就進行邏輯處理部分;不過為了調用更方便還需要封裝一些東西,如根據塊大小來劃分文件塊的數目,獲取某一文件塊的內容和寫入文件某一些的內容等功能。
| 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(); ????????} ????} ? ? } |
? ? ? 準備工作完成了,就開始寫接收端的代碼了。之前的文章已經介紹了Beetle如果創建一個服務和綁定分包機制,在這里就不多說了;看下接收的邏輯是怎樣處理了.
接收傳文件請求
| 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 = "不能創建文件!"; ????????} ????} ????catch?(Exception e_) ????{ ????????response.Status = e_.Message; ????} ????adapter.Send(response); ????? ? } |
接收請求后根據信息創建臨時文件,創建成功就把文件相關信息保存到數據庫中,如果失敗或處理異常就設置相關Status信息返回.
接收文件塊請求
| 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); } |
接收塊請求后處理也很簡單,根據FileID獲取相關信息,然后把數據寫入到文件對應的位置中;當所有塊都已經完成后把臨時文件名改會來就行了。如果處理異常很簡單通過設置到Status成員中告訴請求方。
以下就是請求端的代碼了,其代碼比接收端更加簡單了
| 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); ????} } |
請求端要做的工作就是發送文件傳輸請求,等回應后就處理PostPacakge進行文件塊發送,接收到當前文件塊處理成功后就發送下一塊直接完成。
? ? ? 到這里斷點續傳的功能代碼就已經完成,兩邊的程序已經可以工作。不過對于一些使用者來說希望程序更友好的表現工作情況,這個時候還得對UI下一點功夫,如看到當前傳輸的狀態和每個文件進度情況等。
? ? ? 以上效果看起來很不錯,那接下來就把它實現吧,程序使用ListBox來顯示傳輸文件信息,要達到以上效果需要簡單地重寫一下OnDrawItem達到我們需要的。在講述代碼之前介紹一個圖標網站http://www.iconfinder.com/,畢竟好的圖標可以讓程序生色不少。下面看下這個重寫的代碼:
| 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); ????????} ????} } |
重繪代碼就是根據當前文件的進度內容來計算出填沖的寬度,還有根據當前文件狀態繪制不同的圖標,是不是比較簡單:)
整個功能完成了看下總體的效果怎樣:
下載完整代碼
FileTransfer.rar (649.79 kb)?
如果需要Smark名稱空間的代碼可以到?http://smark.codeplex.com/
?
多線程下載的原理:
各個線程任務分配是這樣實現的。在開始下載時,文件平均分成若干塊進行下載。如第一個線程一開始的任務是從文件的0位置開始下載一直到72908位置處。 線程1每次下載一塊數據后就要調整任務,如第一次下載了20800字節的數據,那么線程1的任務將改為:20800-72908。如此下去,直到任務為 72908-72908時表示線程1完成了當前的下載任務。此時,線程1就分析各個線程的任務,找出任務最為繁忙的一個線程:如線程 3:14816-218724。那么線程1就自動去調整任務,拿50%的任務來再次下載。周而復始直到各個線程都完成任務。
不過這里有一點需要注意:為了避免重復下載部分數據,在調整任務的時候,起始的文件偏移量必須加上接受緩沖器的字節數,因為如前面所舉的例子來看。線程1和線程3在平衡負載的時候,線程正在下載數據,如果所剩的數據比接受緩沖器的大小還小,線程1和線程3的部分下載數據將會重復。
?
?
UDP協議基礎知識
?
UDP協議在IP協議上增加了復用、分用和差錯檢測功能。UDP的特點:
?????? A)是無連接的。相比于TCP協議,UDP協議在傳送數據前不需要建立連接,當然也就沒有釋放連接。
?????? B)是盡最大努力交付的。也就是說UDP協議無法保證數據能夠準確的交付到目的主機。也不需要對接收到的UDP報文進行確認。
?????? C)是面向報文的。也就是說UDP協議將應用層傳輸下來的數據封裝在一個UDP包中,不進行拆分或合并。因此,運輸層在收到對方的UDP包后,會去掉首部后,將數據原封不動的交給應用進程。
???????D)沒有擁塞控制。因此UDP協議的發送速率不送網絡的擁塞度影響。
?????? E)UDP支持一對一、一對多、多對一和多對多的交互通信。
???????F)UDP的頭部占用較小,只占用8個字節。
UDP報文格式
?????? ?UDP協議分為首部字段和數據字段,其中首部字段只占用8個字節,分別是個占用兩個字節的源端口、目的端口、長度和檢驗和。
??????? 長度:UDP報文的整個大小,最小為8個字節(僅為首部)。
??????? 檢驗和:在進行檢驗和計算時,會添加一個偽首部一起進行運算。偽首部(占用12個字節)為:4個字節的源IP地址、4個字節的目的IP地址、1個字節的0、一個字節的數字17、以及占用2個字節UDP長度。這個偽首部不是報文的真正首部,只是引入為了計算校驗和。相對于IP協議的只計算首部,UDP檢驗和會把首部和數據一起進行校驗。接收端進行的校驗和與UDP報文中的校驗和相與,如果無差錯應該全為1。如果有誤,則將報文丟棄或者發給應用層、并附上差錯警告。
?
TCP協議基礎知識
?
TCP協議的特點:
??????? A)是面向連接的。應用程序在使用TCP協議時,必須進行連接;當然,數據傳輸結束后,要斷開TCP連接。
??????? B)TCP連接是點對點的。
??????? C)TCP連接時可靠的。也就是說傳輸的數據時無差錯的、不丟失、不重復、有序到達的。
??????? D)是全雙工的。即TCP連接的兩端都設有發送緩存和接收緩存,用來存放雙向通信的數據。
??????? E)是面向字節流的。也就是說TCP將應用程序交下來的數據看成僅僅是一連串的無結構的字節流,其不知道這些字節流的具體含義。TCP協議無法保證發送的數據塊的具體大小,因為TCP協議的發送的數據大小收到對方給出的窗口值和當前的網絡擁塞度的影響。
TCP的連接端點是套接字。套接字是IP地址拼接上端口號組成的,即點分方式的十進制后面是端口號,中間用逗號或冒號隔開。如下方式:
?????????????????? 套接字Socket=(IP地址:端口號)
TCP報文段的首部
TCP協議的首部有20字節的固定長度,以及4N字節的變長段,因此TCP報文段首部最小為20字節。其格式如下:
??? A)分別占用兩個字節的源端口和目的端口。
????B)序號。占用4個字節。序號是循環的,當增加到最大值后又回到0。TCP是面向字節流的,這樣會個發送的數據按順序給每個字節編上號。在建立連接時,會指出發送的字節流的起始序號。首部中的序號表示發送的報文段的數據的第一個字節的序號,而報文的給字節的序號是順序的。如第一個字節為401,報文的數據長度為100,則首部的序號為401,最后一個字節的序號為500,也就是說下一個報文的首部序號為501。
??? C)確認號。占用4個字節。表示期望收到下一個報文段的第一個數據字節的序號,也是下一個報文段的首部序號。如:B收到了A的200個字節數據的TCP報文段,而這個TCP報文段的首部序號為601,則B希望收到的下一個報文段確認號為801。也可以說如果確認號為N,則前面N-1為止的數據已經收到。
????D)數據偏移。占用4位。其表示數據起始位置相對于報文段起始的位置的偏移量,也就是報文段首部的長度。其單位為32位字,也就是說其表示的值應該乘上4個字節。如:該字段的值為4,則其報文段首部長度為16個字節。其最大值只能表示15,也就是說報文段的首部最大只能為60個字節(即變長只能為20個字節)。其最小值為5,因為首部最小值為20。
???? E)保留。占用6位。其值設為0。
???? F)緊急URG。其占用1位。如果URG設為1,表示首部中的緊急指針有效。表示其發送的報文段數據有緊急數據,其需要馬上發送出去(也就是取得最高優先權),TCP會將緊急數據插入到該報文的最前端,后面任然為普通數據。
???? G)確認ACK。占用1位。當ACK=1時,表示首部中確認號字段有效,為0時,確認號字段無效。TCP規定,在連接建立后ACK字段必須為1。
???? H)推送PSH。占用1位。兩個應用程序通信,有時應用程序希望鍵入一個命令馬上能夠得到對方回應,這是就可以使用這個字段。將PSH設為1,TCP會馬上建立一個報文將其發送出去。接收端在收到該報文后,會盡快的將其交給應用程序,不用得到緩沖區滿。
????? I)復位RST。占用1位。當RST=1時,表示連接出現嚴重錯誤,需要釋放連接,然后重新進行連接。其還用于拒絕打開一個連接或拒絕非法報文。
????? J)同步SYN。占用1位。用于在連接建立時同步序號。當SYN=而ACK=0時,表示這是一個連接請求報文段。當SYN=1且ACK=1時,表示對方接收建立連接報文段。因此SYN=1,表示這是一個連接請求或連接接受報文段。
????? K)終止FIN。占用1位。當FIN=1時,表示發送方的數據發送完畢,并要求釋放連接。
????? L)窗口。占用2個字節。表示發送該報文段的一方的接收窗口,表示允許對方發送的數據量。窗口值告訴對方:從報文段首部中的確認好算起,接收方目前允許對方發送的數據量。如:確認為801,窗口值為1000,則表示其還有接收1000個字節數據(801-1800)的接收緩存空間。
????? M)檢驗和。占2個字節。其也和UDP一樣需要加上偽首部,但是其中的17會變為6。
????? N)緊急指針。占用2個字節。當URG=1時有效,其指出了緊急數據在報文端中的末尾位置。緊急數據在報文數據段的開始。
????? O)選項。可選,最大為40個字節。包括MSS、窗口擴大、時間戳、選擇確認等。
?
TCP的連接與斷開
?
TCP連接的建立采用客戶服務器方式。TCP連接可分為通信雙方一方發起連接和雙方同時發起連接。
一方發起連接
????? ? 假設A方為客服端,B為服務器端,由A向B發起建立連接請求。
????? ? B的服務器進程創建傳輸控制塊TCB,準備接受客戶進程的連接請求。然后服務器進程處于LISTEN狀態,等待客服端的連接請求。
?????? ?A打的TCP客服端進程創建控制塊TCB,然后向B發送連接請求報文段。該報文的首部SYN=1,并且假設序號seq=x。TCP規定,該連接請求報文段不攜帶任何數據,但需要占用一個序號。A發送了連接請求報文后進入SYN-SENT狀態。
??????? B收到A的連接請求報文后,會向A返回連接接受報文。該報文的首部SYN=1,ACK=1,seq=y,ack=x+1。TCP也規定,連接接受報文段也不攜帶任何數據,但也需要占用一個序號。B發送該報文后立即進入SYN-RECD狀態。
??????? A收到B的連接接受報文后,還需要向B發送一個確認報文段。在該報文中ACK=1,seq=x+1,ack=y+1。TCP規定,在該報文段中可以攜帶數據,也可不攜帶數據。但,不攜帶數據時,不會消耗一個序號,也就會說下一個發送的報文段的序號仍為x+1。該報文段發送成功后,進入ESTAB-LISHED狀態,。
?????? B收到A的確認后,也進入ESTAB-ISHED狀態。
?同時發起連接
?????? 出現這種同時發起連接的情況可能性極小,但TCP仍然支持這種方式,這種方式沒有客戶端和服務器區分,一個既是客戶端又是服務器。這種方式,雙方同時發起連接請求報文,并進入SYN-SENT狀態。當收到對方的連接請求報文后,馬上發送一個連接接受報文,并馬上進入SYN-REVD狀態。當收到對方的連接接受報文后,進入ESTABISHED狀態。
斷開連接?
斷開連接也可以分為由連接一方發起斷開連接請求和同時發起斷開連接請求。
一方發起斷開連接
????????A向B發起斷開連接請求報文。該報文的首部中FIN=1,假設序列號seq=u,其為前面發送的報文段的數據的最后一個序列號加一。這是A進入FIN-WAI-1狀態。TCP規定,斷開連接請求報文不攜帶任何數據,但要消耗一個序列號。發送該報文之前會將緩沖區中的數據全部發送出去,該報文可以附加數據。
?????? B在收到A的斷開連接請求報文后,會發回一個確認報文。該報文的首部中ACK=1,ack=u+1,假設seq=v。B進入了CLOSE-WAIT狀態。這時B會通知上層應用進程,這是A到B方向的連接釋放了,表明A沒有數據發給B了。這是可能會在該狀態持續一段時間,等待B將沒發送完的數據發送給A。這時A不能夠發送數據給B,但A還可以接收數據。
????? A在收到B的確認報文后,會進入FIN-WAIT-2狀態,等待B向A發送斷開連接請求報文。
????? 當B沒有數據需要發送給A時,其會釋放TCP連接。B向A發送斷開連接請求報文。該報文的首部中FIN=1,ACK=1,ack=u+1,假設序列號為seq=w(因為可能在等待的這段時間里,B向A發送了一些數據)。這是B進入LAST-ACK狀態。該報文也可以附加數據。
????? A收到B的斷開連接請求報恩后,也會發回個確認報文。該報文中ACK=1,ack=w+1,seq=u+1。這時A進入TIME-WAIT狀態。
???? B在收到A餓確認報文后,就徹底的斷開了連接,并且會撤去相應的傳輸控制塊。
? ?
????? 快速斷開連接:這種情況出現B發送給A斷開連接確認報文,不僅為ACK而且FIN=1,也就是說在該確認報文中還包括了B的斷開連接請求。這是A在收到該報文后,會給B發送一個確認報文,并進入TIME-WAIT狀態。(相對于正常斷開的4次握手,快速斷開連接方式將第二次和第三次握手合成了一次)。
同時斷開連接
?????????兩端應用層同時發出關閉命令時,兩端均從 ESTABLISHED 變為 FIN_WAIT_1 。這將導致雙方各發送一個 FIN ,兩個 FIN 經過網絡傳送后分別到達另一端。收到 FIN 后,狀態由 FIN_WAIT_1 變遷到 CLOSING ,并發送最后的 ACK 。當收到最后的 ACK 時,狀態變化為 TIME_WAIT 狀態。
?
?
在同一個進程中,UDP和TCP可以共用同一個端口嗎?
答:
可以。不同的協議沒有沖突。相同的協議有沖突。
?
?
?
數據的加密
SSL
?
轉載于:https://www.cnblogs.com/heavyhe/p/4547065.html
總結
以上是生活随笔為你收集整理的TCP/UDP编程中的问题汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 答辩完了该思考些什么
- 下一篇: 数据结构题目