TCP與UDP的異同(服務端接收數據,客戶端發送數據)


面向TCP連接的socket通信程序
服務端:創建套接字,指定協議族(sockaddr_in),綁定,監聽(listen),接受鏈接(accept),發送或接收數據;客戶端:創建套接字,指定協議族,連接,發送或接收數據
這幾個步驟都是必須的。
補充:在發送和接受數據時:write/send/sendto,read/recv/recvfrom都可以用,通常會用:send,recv;但需要注意的是:在面向UDP的socket程序中,發送數據時,如果用sendto的話,就不用connect了;但是,在面向TCP的程序中,在發送數據時,即使sendto,也必須connect,也就是說connect這一步是必不可少的。

 

面向UDP連接的socket通信程序:
服務端:創建套接字,指定協議族(sockaddr_in),綁定(不需要listen和accept),發送或接收數據;客戶端:創建套接字,指定協議族,連接(和TCP的客戶端步驟一樣),發送或接收數據。
補充:在發送和接收數據時,和TCP大同小異,write/send/sendto,read/recv/recvfrom都可以用,但UDP通常會用sendto,recvfrom;需要注意的是:當用sendto發送數據的時候,就不用connect了(用了也沒事),其他的(write,send)必須connect。

 

補充:無論是TCP還是UDP,默認情況下創建的都是阻塞模式(blocking)的套接字,執行到accept,connect,write/send/sendto,read/recv/recvfrom等語句時,會一直等待(connect有點例外,它連接一段時間,如果連接不成功,會以錯誤形式返回,不會一直等待)。
可以把socket設置成非阻塞模式,linux下用fcntl函數,windows下用的是ioctlsocket函數。(TCP和UDP設置成非阻塞模式以后,效果是一樣的,都不再等待,而是立即返回。只是sendto和send一次發送的最大數據量可能不同,兩種模式下返回的錯誤代碼應該也是相同的)
設置成非阻塞模式以后,這些函數不再等待會立即返回(這和windows下是相同的),至於錯誤時返回的值應該也是和windows下相同的(具體沒試,send和recv在windows錯誤時返回的值,請看2011-4-27的博客:“套接字的同步阻塞(blocking)與異步非阻塞(no blocking)”)。


TCP面向連接,UDP面向無連接(在默認的阻塞模式下):
read/recv/recvfrom:當客戶端退出程序或斷開連接時,TCP的這個函數會立即返回不再阻塞(因為服務端自己知道客戶端已經退出或斷開連接,證明它是面向連接的),而UDP的這個函數將會始終保持阻塞(因為服務端自己不知道客戶端已經退出或斷開連接,證明它是面向無連接的)。
TCP無邊界,UDP有邊界(在默認的阻塞模式下):
read/recv/recvfrom:TCP,客戶端連續發送數據,只要服務端的這個函數的緩沖區足夠大,會一次性接收過來(客戶端是分好幾次發過來,是有邊界的,而服務端卻一次性接收過來,所以證明是無邊界的);UDP:客戶端連續發送數據,即使服務端的這個函數的緩沖區足夠大,也只會一次一次的接收,發送多少次接收多少次(客戶端分幾次發送過來,服務端就必須按幾次接收,從而證明,這種UDP的通訊模式是有邊界的)。


補充(來自網絡):
1.socket()的參數不同
2.UDP Server不需要調用listen和accept
3.UDP收發數據用sendto/recvfrom函數
4.UDP:shutdown函數無效
5.TCP:地址信息在connect/accept時確定
  UDP:在sendto/recvfrom函數中每次均需指定地址信息


Sendto()和recvfrom()用於在無連接的數據報socket方式下進行數據傳輸。由於本地socket並沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值為sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
  Recvfrom()函數原型為:
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
  from是一個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。

 

如果你對UDP模式的socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源地址信息。(這一點正說明了我在“面向UDP連接的socket通信程序”中所補充的內容)

 

總結:從sendto和recvfrom的后兩個參數想到的:當客戶端向服務端發送數據時,客戶端必須知道服務端的IP地址和端口號,而結構體sockaddr_in正是完成了這項工作。所以在客戶端程序中,一定要指定需要連接的服務端的IP地址和端口號(TCP中必須指定,因為它是面向連接的,交換數據前必須connect,而connect時就必須用到那個結構體;UDP可以不指定,那就需要讓服務端先發送數據到客戶端,客戶端通過recvfrom函數接收,從而通過參數from得到了存放服務端IP地址和端口號的那個結構體,然后就可以通過它來交換數據了,后來一想這樣不行,因為服務端向客戶端發送數據時,在sendto中要指定客戶端的IP地址和端口號,這個就又需要客戶端也創建一個套接字打開一個端口並把它們綁定在一起接受連接。從而,得出的結論是:UDP中:無論服務端還是客戶端,開始時,發送數據方一定要在程序中指定接收數據方的IP和端口,接收方通過recvfrom得到數據以后也就得到了發送方的IP和端口就可以通過這個結構體發送數據了實現了數據的雙向傳遞(好像不對,UDP面向無連接,這個連接有可能過一會就斷掉了,更重要的是,發送方根本就沒有和socket綁定,也就是說那個端口是臨時分配的,所以我認為即使成功也是偶然,有待證明);TCP不同:它是必須在客戶端指定服務端的IP和端口,建立連接,然后兩者可以任意接收和發送數據,實現雙向數據傳遞)。

 

另外

TCP在運行客戶端connect之前必須先運行服務端,不然的話connect的連接會出錯;UDP不一樣(客戶端發送信息,服務端接收信息,服務端需要綁定,客戶端就需要指定服務端的IP和端口),先運行客戶端和后運行客戶端一樣通信,只是當先運行客戶端后運行服務端時,從客戶端起來到服務端起來的這段時間內發送的數據服務端就收不到了(這也從另個方面體現了TCP的面向連接和UDP的面向無連接)。還有一點需要注意:UDP中,客戶端先運行,用connect+write/send/sendto:在服務端起來之前不發送數據過去,起來之后,和后運行客戶端收發數據一樣,在服務端起來之前發送數據了,那么服務端起來之后,客戶端第一次發送的數據,服務端會收不到,再發就一樣了;用sendto(不用connect)的話,則不管服務端起來之前發沒發數據,服務端起來之后都照常收發數據,從客戶端起來到服務端起來的這段時間內發送的數據服務端就收不到了(這一點,或許是和上面所說的“如果你對UDP模式的socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源地址信息”有關,有待進一步證明)。

最后再補充一個小知識點:sizeof()這個函數,參數是一個變量時根本不用括號(用的話也行),只要用空格隔開即可;但是如果是一個數據類型的話,則必須用括號。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM