有关TCP/UDP
TCP使用窗口機制進行流量控制
什么是窗口?
連接建立時,各端分配一塊緩沖區用來存儲接收的數據,并將緩沖區的尺寸發送給另一端
接收方發送的確認信息中包含了自己剩余的緩沖區尺寸
剩余緩沖區空間的數量叫做窗口
2. TCP的流控過程(滑動窗口)
2.?TCP?與UDP的區別?
很多文章都說TCP協議可靠,UDP協議不可靠!為什么前者可靠,后者不可靠呢?既然UDP協議不可靠,為什么還要使用它呢?所謂的TCP協議是面向連接的協議,面向連接是什么呢????
TCP和UDP都是傳輸層的協議!從編程的角度看,就是兩個模塊(模塊就是代碼的集合,一系列代碼的組合提供相應的功能!模塊化最終目的就是:分工協作!模塊化好處:便于擴展開發以及維護!)。???
先說TCP協議:???
這個協議,是面向的連接!面向連接這個概念,我們要從物理層看起。大家都知道,因為“信道復用技術”的迅猛發展,才促使了計算機網絡的發展!如果沒有“信道復用技術”,那么單條線路上(這里的線路指物理傳輸介質,例如:雙絞線、光纖、電話線)單位時間內只能供一臺計算機使用!還是舉例說明:就拿你自己的計算機來說,你跟同學“小明”聊天的時候,就不能跟另外一位同學“小強”聊天,如果你想同時跟兩位同學聊天,那么你就得裝兩條線路!那么同時與第三位、第四位同學。。。第N位同學聊天的時候,你需要裝幾根線路?全世界人民聊天的時候,又需要裝幾根線路????
“信道復用技術”實現了,在同一條線路上,單位時間內可供X臺計算機同時通信!Toad知道以下幾種復用技術:???
??1、頻分復用????2、時分復用????3、波分復用????4、碼分復用????5、空分復用????6、統計復用????7、極化波復用???
關于“信道復用技術”更深層次的問題,需要你自己去研究!???
上面我們提到了“信道復用技術”!知道了這一點,我們就很容易明白“物理信道”上的“虛擬信道”概念了!不同的信道復用技術,使用不同的復用技術,目的就是創建“虛擬信道”。???
一個TCP協議連接其實就是在物理線路上創建的一條“虛擬信道”。這條“虛擬信道”建立后,在TCP協議發出FIN包之前(兩個終端都會向對方發送一個FIN包),是不會釋放的。正因為這一點,TCP協議被稱為面向連接的協議!???
UDP協議,一樣會在物理線路上創建一條“虛擬信道”,否則UDP協議無法傳輸數據!但是,當UDP協議傳完數據后,這條“虛擬信道”就被立即注銷了!因此,稱UDP是不面向連接的協議!?
TCP協議和UDP協議為什么會共存?
1. 大家要知道,一種物理線路,單位時間內,能夠創建的“虛擬信道”是有限的!
2. 使用TCP協議傳輸數據,當數據從A端傳到B端后,B端會發送一個確認包(ACK包)給A端,告知A端數據我已收到!UDP協議就沒有這種確認機制!這就是為什么說TCP協議可靠,UDP協議不可靠.?
QQ普通會員就是使用的UDP協議進行傳輸數據!既然UDP協議自身沒有確認機制,這個工作可以交給應用層的進程來完成(QQ)!大家使用QQ的時候,感覺出錯的幾率還是非常小吧!當然,把這個確認工作完全交給QQ自身來做,就直接導致了,QQ軟件體積增大! ??
?
有些應用,對數據傳輸可靠性要求非常高,例如大家瀏覽網頁,通過網頁注冊帳號、轉帳等服務,這是不容許出錯的,使用TCP協議能把出錯的可能性降到最低(當然,網絡自身很糟糕,TCP協議也沒辦法)。但是,提供這種可靠服務,會加大網絡帶寬的開銷,因為“虛擬信道”是持續存在的,同時網絡中還會出現大量的ACK和FIN包!??
? 因此,魚和熊掌不可兼得,需根據實際情況選擇傳輸協議.TCP協議提供了可靠的數據傳輸,但是其擁塞控制、數據校驗、重傳機制的網絡開銷很大,不適合實時通信,所以選擇開銷很小的UDP協議來傳輸數據。???
UDP 協議是無連接的數據傳輸協議并且無重傳機制,會發生丟包、收到重復包、亂序等情況。而對于數據精確性要求不高的狀態數據以及視頻數據,丟包的影響不大。因為會不斷收到新的包,丟失的個別包會有新的包來覆蓋,所以只需在遠程控制系統的通信部分自行處理亂序及重復包的問題,而對于丟包的問題一般不作處理。??? 但對于命令包這種需要精確收發的數據, 可在程序的開發中加入丟包重發和超時丟棄的處理。 當然,如果開發的是對于實時性要求不高的事件型控制命令的傳輸,不希望發生指令的丟失也可以直接采用TCP協議。TCP的重傳機制正好適合這種情況。???
?
非面向連接的傳輸協議在數據傳輸之前不建立連接,而是在每個中間節點對非面向連接的包和數據包進行路由。沒有點到點的連接,非面向連接的協議,如UDP,是不可靠的連接。當一個UDP數據包在網絡中移動時,發送過程并不知道它是否到達了目的地,除非應用層已經確認了它已到達的事實。非面向連接的協議也不能探測重復的和亂序的包。標準的專業術語用“不可靠”來描述UDP。在現代網絡中,UDP并不易于導致傳輸失敗,但是你也不能肯定地說它是可靠的
*************************************************************************************************************************************************
*************************************************************************************************************************************************
*************************************************************************************************************************************************
?
TCP/IP雖然叫傳輸控制協議(TCP)和網際協議(IP),但是實際上是一組協議,包含ICMP, RIP, TELENET, FTP, SMTP, ARP, TFTP等。
從協議分層模型方面來講,TCP/IP由四個層次組成:網絡接口層、網絡層、傳輸層、應用層。?
其實自己并沒有深入了解過協議,寫這篇文章的目的也只是自己做下學習筆記初步了解socket。所以關于更多TCP/IP協議,請參考下面的百度鏈接。
http://baike.baidu.com/view/7649.htm
?
TCP與UDP區別
TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之后才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是并不能保證它們能到達目的地。由于UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快
TCP和UDP都是在傳輸層上的。簡單來說,UDP發送 數據的時候是不管數據有沒有真正達到目的地的,所以傳輸起來速度就比較快了。但是同時也容易造成數據丟失。而TCP我們知道有三次握手建立,四次握手釋放,所以傳輸更準確,但是速度可能會相對慢一些。
為確保正確地接收數據,TCP要求在目標計算機成功收到數據時發回一個確認(即ACK)。如果在某個時限內未收到相應的ACK,將重新傳送數據包。如果網絡擁塞,這種重新傳送將導致發送的數據包重復。但是,接收計算機可使用數據包的序號來確定它是否為重復數據包,并在必要時丟棄它(這里讓我想起了linux IPC里可靠信號與不可靠信號的發送也是與之類似的)。
?
socket套接字
套接口可以說是網絡編程中一個非常重要的概念,linux以文件的形式實現套接口,與套接口相應的文件屬于sockfs特殊文件系統,創建一個套接口就是在sockfs中創建一個特殊文件,并建立起為實現套接口功能的相關數據結構。換句話說,對每一個新創建的BSD套接口,linux內核都將在sockfs特殊文件系統中創建一個新的inode。描述套接口的數據結構是socket,將在后面給出。
(一)重要數據結構
下面是在網絡編程中比較重要的幾個數據結構,讀者可以在后面介紹編程API部分再回過頭來了解它們。
(1)表示套接口的數據結構structsocket
套接口是由socket數據結構代表的,形式如下:
| structsocket { socket_state state; /*指明套接口的連接狀態,一個套接口的連接狀態可以有以下幾種 套接口是空閑的,還沒有進行相應的端口及地址的綁定;還沒有連接;正在連接中;已經連接;正在解除連接。*/ unsignedlong flags; structproto_ops ops; /*指明可對套接口進行的各種操作*/ structinode inode; /*指向sockfs文件系統中的相應inode*/ structfasync_struct *fasync_list; /* Asynchronous wake up list */ structfile *file; /*指向sockfs文件系統中的相應文件?*/ structsock sk; /*任何協議族都有其特定的套接口特性,該域就指向特定協議族的套接口對 象。*/ wait_queue_head_t wait; short type; unsignedchar passcred; }; |
?
(2)描述套接口通用地址的數據結構structsockaddr
由于歷史的緣故,在bind、connect等系統調用中,特定于協議的套接口地址結構指針都要強制轉換成該通用的套接口地址結構指針。結構形式如下:
| structsockaddr { sa_family_t sa_family; /*address family, AF_xxx */ char sa_data[14]; /*14 bytes of protocol address */ }; |
?
(3)描述因特網地址結構的數據結構structsockaddr_in(這里局限于IP4):
| structsockaddr_in { __SOCKADDR_COMMON(sin_); /*描述協議族*/ in_port_tsin_port; /*端口號*/ structin_addr sin_addr; /*因特網地址*/ /*Pad to size of `struct sockaddr'. */ unsignedchar sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; }; |
(二)基本的socket接口函數。
?
3.1、socket()函數
int socket(int domain, int type, int protocol);?
socket函數對應于普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用于創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,后續的操作都有用到它,把它作為參數,通過它來進行一些讀寫操作。
正如可以給fopen的傳入不同參數值,以打開不同的文件。創建socket的時候,也可以指定不同的參數創建不同的socket描述符,socket函數的三個參數分別為:
- domain:即協議域,又稱為協議族(family)。常用的協議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須采用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
- type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的類型有哪些?)。
- protocol:故名思意,就是指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。
注意:并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol為0時,會自動選擇type類型對應的默認協議。
當我們調用socket創建一個socket時,返回的socket描述字它存在于協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時系統會自動隨機分配一個端口。
3.2、bind()函數
正如上面所說bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);函數的三個參數分別為:
- sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。
- addr:一個const?struct?sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同,如ipv4對應的是: struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */ };/* Internet address. */ struct in_addr {uint32_t s_addr; /* address in network byte order */ }; ipv6對應的是:? struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ };struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ }; Unix域對應的是:? #define UNIX_PATH_MAX 108struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
- addrlen:對應的是地址的長度。
通常服務器在啟動的時候都會綁定一個眾所周知的地址(如ip地址+端口號),用于提供服務,客戶就可以通過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是為什么通常服務器端在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。
網絡字節序與主機字節序
主機字節序就是我們平常說的大端和小端模式:不同的CPU有不同的字節序類型,這些字節序是指整數在內存中保存的順序,這個叫做主機序。引用標準的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
網絡字節序:4個字節的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。這種傳輸次序稱作大端字節序。由于TCP/IP首部中所有的二進制整數在網絡中傳輸時都要求以這種次序,因此它又稱作網絡字節序。字節序,顧名思義字節的順序,就是大于一個字節類型的數據在內存中的存放順序,一個字節的數據沒有順序的問題了。
所以:在將一個地址綁定到socket的時候,請先將主機字節序轉換成為網絡字節序,而不要假定主機字節序跟網絡字節序一樣使用的是Big-Endian。由于這個問題曾引發過血案!公司項目代碼中由于存在這個問題,導致了很多莫名其妙的問題,所以請謹記對主機字節序不要做任何假定,務必將其轉化為網絡字節序再賦給socket。
3.3、listen()、connect()函數
如果作為一個服務器,在調用socket()、bind()之后就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器端就會接收到這個請求。
int listen(int sockfd, int backlog); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);listen函數的第一個參數即為要監聽的socket描述字,第二個參數為相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。
connect函數的第一個參數即為客戶端的socket描述字,第二參數為服務器的socket地址,第三個參數為socket地址的長度??蛻舳送ㄟ^調用connect函數來建立與TCP服務器的連接。
3.4、accept()函數
TCP服務器端依次調用socket()、bind()、listen()之后,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之后就想TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求之后,就會調用accept()函數取接收請求,這樣連接就建立好了。之后就可以開始網絡I/O操作了,即類同于普通文件的讀寫I/O操作。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);accept函數的第一個參數為服務器的socket描述字,第二個參數為指向struct?sockaddr *的指針,用于返回客戶端的協議地址,第三個參數為協議地址的長度。如果accpet成功,那么其返回值是由內核自動生成的一個全新的描述字,代表與返回客戶的TCP連接。
注意:accept的第一個參數為服務器的socket描述字,是服務器開始調用socket()函數生成的,稱為監聽socket描述字;而accept函數返回的是已連接的socket描述字。一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命周期內一直存在。內核為每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。
3.5、read()、write()等函數
萬事具備只欠東風,至此服務器與客戶已經建立好連接了??梢哉{用網絡I/O進行讀寫操作了,即實現了網咯中不同進程之間的通信!網絡I/O操作有下面幾組:
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
我推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上可以把上面的其它函數都替換成這兩個函數。它們的聲明如下:
?
[cpp]?view plain?copy
?
read函數是負責從fd中讀取內容.當讀成功時,read返回實際所讀的字節數,如果返回的值是0表示已經讀到文件的結束了,小于0表示出現了錯誤。如果錯誤為EINTR說明讀是由中斷引起的,如果是ECONNREST表示網絡連接出了問題。
write函數將buf中的nbytes字節內容寫入文件描述符fd.成功時返回寫的字節數。失敗時返回-1,并設置errno變量。 在網絡程序中,當我們向套接字文件描述符寫時有倆種可能。1)write的返回值大于0,表示寫了部分或者是全部的數據。2)返回的值小于0,此時出現了錯誤。我們要根據錯誤類型來處理。如果錯誤為EINTR表示在寫的時候出現了中斷錯誤。如果為EPIPE表示網絡連接出現了問題(對方已經關閉了連接)。
其它的我就不一一介紹這幾對I/O函數了,具體參見man文檔或者baidu、Google,下面的例子中將使用到send/recv。
3.6、close()函數
在服務器與客戶端建立連接之后,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完打開的文件要調用fclose關閉打開的文件。
#include <unistd.h> int close(int fd);close一個TCP socket的缺省行為時把該socket標記為以關閉,然后立即返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再作為read或write的第一個參數。
注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向服務器發送終止連接請求。
總結
- 上一篇: 8-1日复习 模板函数 模板类
- 下一篇: 昨天电脑问题 补昨日8-3复习内容 异常