起初我覺(jué)得學(xué)習(xí)套接字并不需要知道內(nèi)部的通信原理,因?yàn)檫@些都是由系統(tǒng)來(lái)處理骡显,但是隨著后來(lái)的深入我發(fā)現(xiàn)了這個(gè)錯(cuò)誤的想法施籍,后面有關(guān)套接字的相關(guān)設(shè)置都與內(nèi)部通信原理息息相關(guān)(包括后來(lái)的設(shè)置套接字可選項(xiàng)兢哭、套接字I/O緩存)诅挑,今天一起來(lái)探究一下套接字的內(nèi)部的通信原理
上一篇我們聊了Socket連接創(chuàng)建及通信流程的一些基本函數(shù) 《套接字(Socket)編程(一) 函數(shù)概念篇》,這篇我們將來(lái)聊一聊TCP的連接和斷開(kāi)甲棍、TCP套接字I/O緩沖區(qū)简识、UDP套接字I/O緩沖區(qū)、UDP套接字的無(wú)連接和有連接模式
關(guān)于下面說(shuō)到的TCP套接字連接握手感猛、斷開(kāi)握手以及中間的數(shù)據(jù)交互涉及到的 SYN七扰、ACK、FIN陪白、SEQ等關(guān)鍵詞請(qǐng)參考我后面一片文章《TCP/IP 協(xié)議及數(shù)據(jù)格式》颈走,文章 “3.2 TCP協(xié)議數(shù)據(jù)報(bào)的頭”部分,里面有詳細(xì)的講解咱士;關(guān)于連接立由、斷開(kāi)和通信過(guò)程中ACK和SEQ的數(shù)值變化以及交互的細(xì)節(jié)可以參考這篇文章里面的 “3.3 TCP通信數(shù)據(jù)交互細(xì)節(jié)和實(shí)踐”部分轧钓,里面有詳細(xì)的數(shù)據(jù)抓取和講解。
一锐膜、TCP連接的三次握手和斷開(kāi)的四次握手
在講到下面函數(shù)之前毕箍,我們不得不先說(shuō)下連接過(guò)程和端口的過(guò)程,雖然我們用C代碼寫(xiě)連接的時(shí)候看不到握手的過(guò)程道盏,但是里面的參數(shù)我們是可以通過(guò)相關(guān)函數(shù)調(diào)用進(jìn)行設(shè)置而柑,后面講的函數(shù)很多關(guān)乎到這里面的參數(shù)設(shè)置
首先來(lái)說(shuō)下TCP套接字連接進(jìn)程中的10種狀態(tài)
狀態(tài) | 描述 |
---|---|
LISTEN | 偵聽(tīng)來(lái)自遠(yuǎn)端TCP協(xié)議端口的連接請(qǐng)求 |
SYN-SENT | 發(fā)送連接請(qǐng)求后,等待匹配的連接請(qǐng)求 |
SYN-RECEIVED | 收到和發(fā)送一個(gè)連接請(qǐng)求后荷逞,等待確認(rèn) |
ESTABLISHED | 連接已經(jīng)打開(kāi)媒咳,可以發(fā)送或接收數(shù)據(jù) |
FIN-WAIT-1 | 發(fā)送了中斷連接請(qǐng)求,等待對(duì)方確認(rèn) |
FIN-WAIT-2 | 收到對(duì)方對(duì)于中斷請(qǐng)求的確認(rèn)种远,等待對(duì)方的中斷請(qǐng)求 |
TIME-WAIT | 等待足夠的時(shí)間涩澡,以保證遠(yuǎn)端收到連接中斷請(qǐng)求的確認(rèn) |
CLOSE-WAIT | 等待本地應(yīng)用層發(fā)來(lái)中斷請(qǐng)求 |
LAST-ACK | 等待遠(yuǎn)端TCP協(xié)議對(duì)連接中斷的確認(rèn) |
CLOSED | 沒(méi)有任何連接 |
1.1 TCP連接時(shí)的三次握手
步驟:
①第一條消息為SYN消息Synchronization
同步消息,表示收發(fā)數(shù)據(jù)前傳輸?shù)耐较?br>
SEQ = x
:表示現(xiàn)在傳遞的數(shù)據(jù)包的序號(hào)為x院促,如果接收無(wú)誤筏养,請(qǐng)通知我向你傳遞x+1號(hào)數(shù)據(jù)包
②接著服務(wù)器會(huì)回復(fù)SYN+ACK類(lèi)型數(shù)據(jù)消息,服務(wù)器對(duì)客戶(hù)端首次傳輸?shù)臄?shù)據(jù)包確認(rèn)(ACK)和服務(wù)器傳輸數(shù)據(jù)做準(zhǔn)備的同步消息(SYN)捆綁發(fā)送
ACK = x+1
:表示剛才客戶(hù)端的序號(hào)為x的數(shù)據(jù)包接收無(wú)誤常拓,接下來(lái)請(qǐng)傳輸序號(hào)為x+1的數(shù)據(jù)包
SEQ = y
:表示現(xiàn)傳遞的數(shù)據(jù)包序號(hào)為y,如果接收無(wú)誤辉浦,請(qǐng)通知我向你傳輸y+1號(hào)數(shù)據(jù)包
③客戶(hù)端回復(fù)服務(wù)器ACK消息
SEQ = x+1
:表示向服務(wù)器傳遞序號(hào)為x+1數(shù)據(jù)包
ACK = y+1
:表示接受服務(wù)器端的序號(hào)為y的數(shù)據(jù)包接收無(wú)誤弄抬,接下來(lái)可以傳輸序號(hào)為y+1的數(shù)據(jù)包
連接三次握手為了防止已失效的連接請(qǐng)求報(bào)文段突然又傳到了服務(wù)器,導(dǎo)致服務(wù)器誤認(rèn)為客戶(hù)端想請(qǐng)求連接而發(fā)生連接的錯(cuò)誤
舉個(gè)例子:在兩次握手的前提下宪郊,Client發(fā)出連接請(qǐng)求掂恕,但因?yàn)閬G失了,故而不能收到Server的確認(rèn)弛槐。于是Client重新發(fā)出請(qǐng)求懊亡,然后收到確認(rèn),建立連接乎串,數(shù)據(jù)傳輸完畢后店枣,釋放連接,Client發(fā)了2個(gè)叹誉,但是鸯两,某種情況下,Client發(fā)出的第一個(gè)連接請(qǐng)求在某個(gè)節(jié)點(diǎn)滯留了长豁,延誤到達(dá)Server钧唐。假設(shè)此時(shí)Server已經(jīng)釋放連接,那么Server在收到此實(shí)現(xiàn)的連接請(qǐng)求后匠襟,就誤認(rèn)為Client又發(fā)出一次連接請(qǐng)求钝侠,在兩次握手的情況下(Client發(fā)生請(qǐng)求该园,Server接受請(qǐng)求并確認(rèn)),Server就認(rèn)為Client又發(fā)出一次新連接請(qǐng)求帅韧。此時(shí)Server就又給Client發(fā)生一個(gè)確認(rèn)爬范,表示同意建立連接。因?yàn)槭莾纱挝帐秩醴耍珻lient收到后青瀑,也不再次發(fā)出確認(rèn)連接。此時(shí)Server會(huì)等待Client發(fā)送的數(shù)據(jù)萧诫,而Client本來(lái)就沒(méi)有要求發(fā)送數(shù)據(jù)斥难,肯定也無(wú)動(dòng)于衷。此時(shí)Server的資源就被浪費(fèi)了帘饶。
1.2 TCP數(shù)據(jù)交換
通過(guò)第一步的三次握手過(guò)程完成數(shù)據(jù)交換哑诊,下面就正式開(kāi)始收發(fā)數(shù)據(jù),其默認(rèn)方式如下圖
如圖:客戶(hù)端分兩次向服務(wù)器傳遞了200字節(jié)的過(guò)程及刻,首先客戶(hù)端通過(guò)一個(gè)數(shù)據(jù)包發(fā)送100個(gè)字節(jié)镀裤,數(shù)據(jù)包的SEQ為1200,服務(wù)器為了確認(rèn)這一點(diǎn)缴饭,向客戶(hù)端發(fā)送ACK為1300消息暑劝。
此時(shí)ACK號(hào)為1300而非1200,也不是1301颗搂,原因在于ACK號(hào)的增量為傳遞的數(shù)據(jù)字節(jié)數(shù)担猛,假設(shè)每次ACK號(hào)不加傳輸?shù)淖止?jié),這樣雖然可以確認(rèn)數(shù)據(jù)包的傳輸丢氢,但無(wú)法確認(rèn)100字節(jié)全部正確傳遞還是丟失了一部分傅联,比如只傳遞了80個(gè)字節(jié),按公式計(jì)算傳遞ACK消息ACK號(hào) = SEQ號(hào) + 傳遞的字節(jié)數(shù)
疚察。
網(wǎng)上和書(shū)上很多說(shuō)法說(shuō)這里ACK值的計(jì)算應(yīng)該是ACK號(hào) = SEQ號(hào) + 傳遞的字節(jié)數(shù) + 1
蒸走,實(shí)際這種說(shuō)法是不準(zhǔn)確的,在連接的三次握手和斷開(kāi)的4次握手里面貌嫡,每次傳輸數(shù)據(jù)長(zhǎng)度為0(這些數(shù)據(jù)報(bào)只有報(bào)頭)比驻,回復(fù)確認(rèn)都是ACK號(hào) = 接收到數(shù)據(jù)報(bào)的SEQ號(hào) + 1
,但是除了這特別的情況外(連接完成衅枫,正常通信過(guò)程中)嫁艇,每個(gè)數(shù)據(jù)報(bào)的SEQ都是這段TCP數(shù)據(jù)首個(gè)字節(jié)的序號(hào),按上面的圖來(lái)說(shuō)弦撩,第一次傳輸時(shí)SEQ = 1200步咪,其實(shí)序列號(hào)1200也是該段數(shù)據(jù)第一個(gè)字節(jié)的序號(hào),那么第二個(gè)字節(jié)的序號(hào)就是1201益楼,傳輸了100個(gè)字節(jié)猾漫,該數(shù)據(jù)段的最后一個(gè)字節(jié)的序號(hào)應(yīng)該是1299点晴,下次需要傳輸?shù)臄?shù)據(jù)要從1300開(kāi)始,所以服務(wù)器回復(fù)的確認(rèn)號(hào)是1300悯周,而不是1301粒督,這里需要注意下。
客戶(hù)端在規(guī)定時(shí)間沒(méi)有收到服務(wù)器的確認(rèn)消息禽翼,則認(rèn)為丟失屠橄,然后重傳
2. 斷開(kāi)時(shí)的四次握手
對(duì)于TCP的斷開(kāi)也是非常優(yōu)雅的,如下圖
大概流程簡(jiǎn)單翻譯:
客戶(hù)端:“我希望斷開(kāi)連接”
服務(wù)器:“哦闰挡,是嗎锐墙?請(qǐng)稍等”
服務(wù)器:“我也準(zhǔn)備就緒,可以斷開(kāi)連接”
客戶(hù)端:“好的长酗,謝謝合作”
如上圖所示溪北,數(shù)據(jù)包內(nèi)的FIN表示斷開(kāi)連接,也就是說(shuō)雙方各發(fā)送1次FIN消息后斷開(kāi)連接夺脾,此過(guò)程經(jīng)理4個(gè)階段之拨,因此又稱(chēng)四次握手,SEQ和ACK前面已經(jīng)做過(guò)解釋?zhuān)适÷浴?/p>
注意:
服務(wù)器收到客戶(hù)端連FIN報(bào)文段后就立即發(fā)送確認(rèn)咧叭,然后就進(jìn)入close-wait狀態(tài)蚀乔,此時(shí)TCP服務(wù)器進(jìn)程就通知高層應(yīng)用進(jìn)程,此時(shí)是“半關(guān)閉”狀態(tài)佳簸。即客戶(hù)端不可以發(fā)送數(shù)據(jù)到服務(wù)器乙墙,但是服務(wù)器可以發(fā)送數(shù)據(jù)給客戶(hù)端。
此時(shí)生均,若服務(wù)器沒(méi)有數(shù)據(jù)報(bào)要發(fā)送給客戶(hù)端了,其應(yīng)用進(jìn)程就通知TCP釋放連接腥刹,然后發(fā)送給客戶(hù)端FIN報(bào)文段马胧,并等待確認(rèn)。
客戶(hù)端發(fā)送確認(rèn)后衔峰,進(jìn)入time-wait佩脊,注意,此時(shí)TCP連接還沒(méi)有釋放掉垫卤,然后經(jīng)過(guò)時(shí)間等待計(jì)時(shí)器設(shè)置的2MSL后威彰,客戶(hù)端才進(jìn)入到close狀態(tài)。
為什么要等待呢?
①穴肘、為了保證客戶(hù)端發(fā)送的最后一個(gè)ACK報(bào)文段能夠到達(dá)服務(wù)器歇盼。即最后這個(gè)確認(rèn)報(bào)文段很有可能丟失,那么服務(wù)器會(huì)超時(shí)重傳评抚,然后客戶(hù)端再一次確認(rèn)豹缀,同時(shí)啟動(dòng)2MSL計(jì)時(shí)器氛悬,如此下去眉枕。如果沒(méi)有等待時(shí)間,發(fā)送完確認(rèn)報(bào)文段就立即釋放連接的話(huà),服務(wù)器就無(wú)法重傳了(連接已被釋放谎僻,任何數(shù)據(jù)都不能出傳了),因而也就收不到確認(rèn)绍刮,就無(wú)法按照步驟進(jìn)入CLOSE狀態(tài)赦役,即必須收到確認(rèn)才能close,流程看下圖
②妇汗、防止“已失效的連接請(qǐng)求報(bào)文段”出現(xiàn)在連接中帘不。經(jīng)過(guò)2MSL,那些在這個(gè)連接持續(xù)的時(shí)間內(nèi)铛纬,產(chǎn)生的所有報(bào)文段就可以都從網(wǎng)絡(luò)中消失厌均。即在這個(gè)連接釋放的過(guò)程中會(huì)有一些無(wú)效的報(bào)文段滯留在樓閣結(jié)點(diǎn),但是呢告唆,經(jīng)過(guò)2MSL這些無(wú)效報(bào)文段就肯定可以發(fā)送到目的地棺弊,不會(huì)滯留在網(wǎng)絡(luò)中。這樣的話(huà)擒悬,在下一個(gè)連接中就不會(huì)出現(xiàn)上一個(gè)連接遺留下來(lái)的請(qǐng)求報(bào)文段了模她。
可以看出:服務(wù)器結(jié)束TCP連接的時(shí)間比客戶(hù)端早一點(diǎn),因?yàn)榉?wù)器收到確認(rèn)就斷開(kāi)連接了懂牧,而客戶(hù)端還得等待Time-Wait侈净,雖然這個(gè)Time-Wait看似重要,但是在實(shí)際開(kāi)發(fā)中并不那么討人喜歡僧凤,后面的一片文章里面有介紹怎么去掉這個(gè)Time-Wait的等待(套接字(Socket)編程(三) 套接字可選項(xiàng))畜侦。
上面經(jīng)過(guò)四次握手?jǐn)嚅_(kāi)的屬于正常斷開(kāi),經(jīng)過(guò)四次握手雙方都知道連接斷開(kāi)了躯保,但是平時(shí)通信的過(guò)程中有各種原因會(huì)造成異常斷開(kāi)旋膳,比方說(shuō)服務(wù)器斷電或者客戶(hù)端斷電...一般的TCP通信中有兩種方式來(lái)解決這個(gè)異常:①、自己在應(yīng)用層定時(shí)發(fā)送心跳包來(lái)判斷連接是否正常途事,此方法比較通用验懊,靈活可控,但改變了現(xiàn)有的協(xié)議尸变;②义图、使用TCP的keepalive機(jī)制,TCP協(xié)議自帶的闭倮茫活功能碱工,使用起來(lái)簡(jiǎn)單,減少了應(yīng)用層代碼的復(fù)雜度, 推測(cè)也會(huì)更節(jié)省流量痛垛,因?yàn)橐话銇?lái)說(shuō)應(yīng)用層的數(shù)據(jù)傳輸?shù)絽f(xié)議層時(shí)都會(huì)被加上額外的包頭包尾草慧,由TCP協(xié)議提供的檢活,其發(fā)的探測(cè)包匙头,理論上實(shí)現(xiàn)的會(huì)更精妙(用更少的字節(jié)完成更多的目標(biāo))漫谷,耗費(fèi)更少的流量;具體請(qǐng)看后面文章實(shí)現(xiàn)蹂析,這里先不做深聊
二舔示、TCP套接字中的I/O緩沖區(qū)
如當(dāng)前所述,TCP套接字收發(fā)無(wú)邊界电抚,服務(wù)器端調(diào)用1次send函數(shù)傳輸40個(gè)字節(jié)惕稻,客戶(hù)端也能通過(guò)4次調(diào)用recv函數(shù)每次讀10個(gè)字節(jié),那么這個(gè)時(shí)候問(wèn)題就來(lái)了蝙叛,服務(wù)器一次發(fā)送了40個(gè)字節(jié)的數(shù)據(jù)俺祠,而客戶(hù)端則可以緩慢分批讀取,客戶(hù)端讀取了10個(gè)字節(jié)后剩余的30個(gè)字節(jié)的數(shù)據(jù)在什么地方呢借帘?
實(shí)際上調(diào)用send函數(shù)不是立即發(fā)送數(shù)據(jù)蜘渣,調(diào)用recv函數(shù)也并非立馬接收數(shù)據(jù),更精確的講肺然,send函數(shù)調(diào)用瞬間蔫缸,將數(shù)據(jù)移至輸出緩沖區(qū),recv函數(shù)調(diào)用瞬間际起,從輸入緩沖區(qū)讀取數(shù)據(jù)拾碌,入下圖所示
如圖所示,調(diào)用send函數(shù)將數(shù)據(jù)移至緩沖區(qū)街望,在適當(dāng)?shù)臅r(shí)候(不管是分批傳送還是一次性傳送)傳向?qū)Ψ降妮斎刖彌_區(qū)校翔,這個(gè)時(shí)對(duì)方將調(diào)用recv函數(shù)從自己的輸入緩沖隊(duì)列里面讀取對(duì)方發(fā)送過(guò)來(lái)的數(shù)據(jù)。
特性
- I/O緩沖區(qū)在每個(gè)套接字中單獨(dú)存在
- I/O緩沖區(qū)在創(chuàng)建套接字時(shí)自動(dòng)生成
- 即使關(guān)閉套接字灾前,也可以繼續(xù)傳送輸入緩沖區(qū)里面的數(shù)據(jù)
- 關(guān)閉套接字將丟失輸入緩沖區(qū)里面的數(shù)據(jù)
三展融、UDP套接字中的I/O緩沖區(qū)
前面說(shuō)過(guò)TCP數(shù)據(jù)傳輸中不存在邊界,這表示數(shù)據(jù)傳輸過(guò)程中調(diào)用I/O函數(shù)的次數(shù)不具有任何意義豫柬,相反UDP是具有數(shù)據(jù)邊界的協(xié)議,傳輸中調(diào)用I/O函數(shù)的次數(shù)非常重要扑浸,因?yàn)檩斎牒瘮?shù)的調(diào)用次數(shù)應(yīng)和輸出函數(shù)的調(diào)用次數(shù)完全一致烧给,這樣才能保證接收全部已發(fā)送數(shù)據(jù),例如調(diào)用了3次輸出函數(shù)發(fā)送數(shù)據(jù)喝噪,就必須調(diào)用3次輸入函數(shù)才能完成接收础嫡,下面通過(guò)簡(jiǎn)單的例子來(lái)印證下
發(fā)送UDP數(shù)據(jù)端代碼
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
int sendPacket(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int send_result = sendPacket();
if (send_result == 0) printf("開(kāi)啟發(fā)送失敗\n");
}
return 0;
}
#pragma mark ---廣播
int sendPacket()
{
printf("請(qǐng)輸入U(xiǎn)DP數(shù)據(jù)傳送IP地址:");
char ip[INET_ADDRSTRLEN];
scanf("%s",ip);
int send_sock;
send_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (send_sock < 0) return 0;
struct sockaddr_in addr;
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(2001);
inet_aton(ip, &addr.sin_addr);
printf("開(kāi)始連續(xù)三次發(fā)送數(shù)據(jù)\n");
char msg1[] = "Hi";
char msg2[] = "Hello";
char msg3[] = "Nice to meet you";
sendto(send_sock, msg1, sizeof(msg1), 0, (struct sockaddr*)&addr, addr.sin_len);
sendto(send_sock, msg2, sizeof(msg2), 0, (struct sockaddr*)&addr, addr.sin_len);
sendto(send_sock, msg3, sizeof(msg3), 0, (struct sockaddr*)&addr, addr.sin_len);
printf("關(guān)閉UDP套接字\n");
close(send_sock);
return 1;
}
接收UDP數(shù)據(jù)端代碼
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
int recvPacket(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int recv_result = recvPacket();
if (recv_result == 0) printf("開(kāi)啟接收失敗\n");
}
return 0;
}
#pragma mark ---UDP數(shù)據(jù)接收端
int recvPacket()
{
int recv_sock;
recv_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (recv_sock < 0) return 0;
struct sockaddr_in addr,recv_addr;
socklen_t len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_port = htons(2001);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(recv_sock, (struct sockaddr*)&addr, addr.sin_len) < 0) {
printf("%s\n",strerror(errno));
return 0;
}
printf("開(kāi)始接收UDP數(shù)據(jù)\n");
char recv_buffer[512];
for (int i = 0; i<3; i++) {
sleep(5);
memset(recv_buffer, 0, 512);
ssize_t recv_len = recvfrom(recv_sock,
recv_buffer,
sizeof(recv_buffer),
0,
(struct sockaddr*)&recv_addr,
&len);
if (recv_len <= 0) {
printf("接收數(shù)據(jù)失敗:%s\n",strerror(errno));
break;
}
printf("recv: %s\n",recv_buffer);
}
printf("關(guān)閉UDP套接字\n");
close(recv_sock);
return 1;
}
UDP發(fā)送端打印
/**
* 請(qǐng)輸入U(xiǎn)DP數(shù)據(jù)傳送IP地址:10.22.70.99
* 開(kāi)始連續(xù)三次發(fā)送數(shù)據(jù)
* 關(guān)閉UDP套接字
*/
UDP接收端打印
/**
* 開(kāi)始接收UDP數(shù)據(jù)
* recv: Hi
* recv: Hello
* recv: Nice to meet you
* 關(guān)閉UDP套接字
*/
結(jié)論
從上面的例子可以看出,發(fā)送端連續(xù)發(fā)送了三個(gè)UDP數(shù)據(jù)包,而接收端輪循了三次去接收榴鼎,每次接收到都等待5s再進(jìn)行下次數(shù)據(jù)讀取伯诬,也就是說(shuō)接收端在讀第一次數(shù)據(jù)的時(shí)候,其實(shí)輸入緩沖區(qū)里面已經(jīng)有三組數(shù)據(jù)巫财,但是他只讀取了最前面的一組盗似,這正好說(shuō)明了發(fā)送端和接收端在傳輸數(shù)據(jù)的過(guò)程中調(diào)用I/O數(shù)據(jù)次數(shù)要一致才能將全部數(shù)據(jù)讀取完
解釋
UDP套接字傳輸?shù)臄?shù)據(jù)包又稱(chēng)數(shù)據(jù)報(bào),實(shí)際上數(shù)據(jù)報(bào)也屬于數(shù)據(jù)包的一種平项,只是與TCP包不同赫舒,其本身可以成為一個(gè)完整數(shù)據(jù),這與UDP數(shù)據(jù)傳輸特性有關(guān)闽瓢,UDP中存在數(shù)據(jù)邊界接癌,1個(gè)數(shù)據(jù)包即可以成為1個(gè)完整數(shù)據(jù),因此稱(chēng)為數(shù)據(jù)報(bào)扣讼。
四缺猛、UDP套接字的連接(connected)和非連接(unconnected)
TCP套接字傳輸數(shù)據(jù)需要注冊(cè)目的地址的IP和端口號(hào)信息,而UDP中則無(wú)需注冊(cè)椭符,于是通過(guò)sendto()
函數(shù)發(fā)發(fā)送數(shù)據(jù)的流程大概如下
① 向UDP套接字中注冊(cè)目標(biāo)IP和端口號(hào)
② 傳輸數(shù)據(jù)
③ 刪除套接字中注冊(cè)的IP和端口號(hào)
每次調(diào)用sendto()
函數(shù)荔燎,每次都重復(fù)上面步驟變更目標(biāo)地址,因此可以利用同一個(gè)UDP套接字向不同的地址發(fā)送數(shù)據(jù)艰山,這種未注冊(cè)目標(biāo)地址信息的套接字稱(chēng)為未連接套接字湖雹,相反注冊(cè)過(guò)地址的套接字稱(chēng)為連接套接字,顯然UDP默認(rèn)套接字為未連接套接字曙搬。
在平時(shí)開(kāi)發(fā)中有這么一種情況摔吏,需要向同一個(gè)地址連續(xù)發(fā)送UDP數(shù)據(jù),比方說(shuō)前面的《TFTP服務(wù)器和TFTP客戶(hù)端》纵装,需要向同一個(gè)客戶(hù)端地址連續(xù)發(fā)送數(shù)據(jù)包和接收該地址發(fā)送過(guò)來(lái)的確認(rèn)包征讲,這個(gè)時(shí)候如果還用無(wú)連接模式發(fā)送數(shù)據(jù),上述的第一個(gè)階段和第三個(gè)階段占整個(gè)通信過(guò)程近1/3的時(shí)間橡娄。
創(chuàng)建已連接UDP套接字
創(chuàng)建已連接UDP套接字诗箍,跟前面創(chuàng)建未連接UDP套接字步驟一樣,只不過(guò)多調(diào)用了connect函數(shù)挽唉,創(chuàng)建步驟如下:
int sock = socket(AF_INET, SOCK_DGRAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = ....
addr.sin_addr.s_addr = ...
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
針對(duì)UDP套接字調(diào)用connect()
函數(shù)并不意味著要與對(duì)方套接字連接滤祖,只是向UDP套接字注冊(cè)目標(biāo)IP和端口信息,之后就與TCP套接字一樣瓶籽,每次調(diào)用sendto()
函數(shù)時(shí)只需傳輸數(shù)據(jù)匠童,因?yàn)橐呀?jīng)指定了接收對(duì)象,所以不僅可以使用sendto()
塑顺、recvfrom
函數(shù)汤求,還可以使用send()
俏险、recv()
函數(shù)進(jìn)行數(shù)據(jù)傳送和讀取。
五扬绪、尾聲
接下來(lái)的文章會(huì)繼續(xù)更新有關(guān)套接字的詳解竖独,這是我起初學(xué)習(xí)的一個(gè)流程,希望也對(duì)大家有幫助!