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()這個函式,引數是一個變數時根本不用括號(用的話也行),只要用空格隔開即可;但是如果是一個數據型別的話,則必須用括號。