解析C語言基于UDP協(xié)議進(jìn)行Socket編程的要點(diǎn)
兩種協(xié)議 TCP 和 UDP
前者可以理解為有保證的連接,后者是追求快速的連接。
當(dāng)然最后一點(diǎn)有些 太過絕對(duì) ,但是現(xiàn)在不需熬考慮太多,因?yàn)槌跞胩捉幼志幊?,一切從簡?br />
稍微試想便能夠大致理解, TCP 追求的是可靠的傳輸數(shù)據(jù), UDP 追求的則是快速的傳輸數(shù)據(jù)。
前者有繁瑣的連接過程,后者則是根本不建立可靠連接(不是絕對(duì)),只是將數(shù)據(jù)發(fā)送而不考慮是否到達(dá)。
以下例子以 *nix 平臺(tái)的便準(zhǔn)為例,因?yàn)?Windows平臺(tái)需要考慮額外的加載問題,稍作添加就能在 Windows 平臺(tái)上運(yùn)行UDP。
UDP
這是一個(gè)十分簡潔的連接方式,假設(shè)有兩臺(tái)主機(jī)進(jìn)行通信,一臺(tái)只發(fā)送,一臺(tái)只接收。
接收端:
int sock; /* 套接字 */ socklen_t addr_len; /* 發(fā)送端的地址長度,用于 recvfrom */ char mess[15]; char get_mess[GET_MAX]; /* 后續(xù)版本使用 */ struct sockaddr_in recv_host, send_host; /* 創(chuàng)建套接字 */ sock = socket(PF_INET, SOCK_DGRAM, 0); /* 把IP 和 端口號(hào)信息綁定在套接字上 */ memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 接收任意的IP */ recv_host.sin_port = htons(6000); /* 使用6000 端口號(hào) */ bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host)); /* 進(jìn)入接收信息的狀態(tài) */ recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len); /* 接收完成,關(guān)閉套接字 */ close(sock);
上述代碼省略了許多必要的 錯(cuò)誤檢查 ,在實(shí)際編寫時(shí)要添加
代碼解釋:
PF_INET 代表協(xié)議的類型,此處代表 IPv4 網(wǎng)絡(luò)協(xié)議族, 同樣 PF_INET6 代表 IPv6 網(wǎng)絡(luò)協(xié)議族,這個(gè)范圍在后方單獨(dú)記錄,不與IPv4混在一起(并不意味著更復(fù)雜,實(shí)際上更簡便)。
AF_INET 代表地址的類型,此處代表 IPv4 網(wǎng)絡(luò)協(xié)議使用的地址族, 同樣有 AF_INET6 (在操作系統(tǒng)實(shí)現(xiàn)中 PF_INET 和 AF_INET 的值一樣,但是還是要寫宏更好,而不應(yīng)該直接用數(shù)字或者,混淆使用)
htonl 和 htons 兩個(gè)函數(shù)的使用涉及到 大端小端問題, 這里不敘述,需要記住的是在網(wǎng)絡(luò)編程時(shí)一定要使用這種函數(shù)將必要信息轉(zhuǎn)為 大端表示法 。
(struct sockaddr *) 這個(gè)強(qiáng)制轉(zhuǎn)換是為了參數(shù)的必須,但不會(huì)出錯(cuò),因?yàn)?sizeof(struct sockaddr_in) == sizeof(struct sockaddr) 具體可以查詢相關(guān)信息,之所以這么做是為了方便編寫套接字程序的程序員。
發(fā)送端:
int sock; const char* mess = "Hello Server!"; char get_mess[GET_MAX]; /* 后續(xù)版本使用 */ struct sockaddr_in recv_host; socklen_t addr_len; /* 創(chuàng)建套接字 */ sock = socket(PF_INET, SOCK_DGRAM, 0); /* 綁定 */ memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); recv_host.sin_port = htons(6000); /* 發(fā)送信息 */ /* 在此處,發(fā)送端的IP地址和端口號(hào)等各類信息,隨著這個(gè)函數(shù)的調(diào)用,自動(dòng)綁定在了套接字上 */ sendto(sock, mess, strlen(mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host)); /* 完成,關(guān)閉 */ close(sock);
上述代碼是發(fā)送端。
代碼解釋:
inet_addr 函數(shù)是用于將字符串格式的 IP地址 轉(zhuǎn)換為 大端表示法的 地址類型,即 s_addr 的類型 in_addr_t
與之相反,同樣也有功能相反的函數(shù) inet_ntoa 用于將 in_addr_t 類型轉(zhuǎn)為字符串,但是使用時(shí)一定要記住及時(shí)拷貝返回值 char addr[16]; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); strcpy(addr, inet_ntoa(recv_host.sin_addr.s_addr));
從上述代碼看出, UDP 協(xié)議的使用十分簡潔,幾乎就是 創(chuàng)建套接字->準(zhǔn)備數(shù)據(jù)->裝備套接字->發(fā)送/接收->結(jié)束
其中,都沒有連接的操作,但是實(shí)際上這是為了方便 UDP 隨時(shí)和 不同的主機(jī) 進(jìn)行通信所默認(rèn)的設(shè)置,如果需要和相同主機(jī)一直通信呢?
此中的原由暫時(shí)不需要知道,記錄方法,即長時(shí)間使用 UDP 和同一主機(jī)通信時(shí),可以使用 connect 函數(shù)來進(jìn)行優(yōu)化自身。此時(shí) 假設(shè)兩臺(tái)主機(jī)的實(shí)際功能一致,既接收也發(fā)送
發(fā)送端:
/* 前方高度一致,將 bind函數(shù)替換為 */ connect(sock, (struct sockaddr *)&recv_host, sizeof(recv_host); // 將對(duì)方的 IP地址和 端口號(hào)信息 注冊(cè)進(jìn)UDP的套接字中) while(1) /* 循環(huán)的發(fā)送和接收信息 */ { size_t read_len = 0; /* 原先使用的 sendto 函數(shù),先擇改為使用 write 函數(shù), Windows平臺(tái)為 send 函數(shù) */ write(sock, mess, strlen(mess)); /* send(sock, mess, strlen(mess), 0) FOR Windows Platform */ read_len = read(sock, get_mess, GET_MAX-1); /* recv(sock, mess, strlen(mess)-1, 0) FOR Windows Platform */ get_mess[read_len-1] = '\0'; printf("In Client like Host Recvive From Other Host : %s\n", get_mess); } /* 后方高度一致 */
接收端:
/* 前方一致, 添加額外的 struct sockaddr_in send_host; 并添加循環(huán),構(gòu)造收發(fā)的現(xiàn)象*/ while(1) { size_t read_len = 0; char sent_mess[15] = "Hello Sender!"; /* 用于發(fā)送的信息 */ sendto(sock, mess, strlen(sent_mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host)); read_len = recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len) mess[read_len-1] = '\0'; printf("In Sever like Host Recvive From other Host : %s\n", mess); } /* 后方高度一致 */ /* * 之所以只在接收端使用 connect 的原因,便在于我們模擬的是 客戶端-服務(wù)器 的模型,而服務(wù)器的各項(xiàng)信息是不會(huì)隨意變更的 * 但是 客戶端就不同了,可能由于 ISP(Internet Server Provider) 的原因,你的IP地址不可能總是固定的,所以只能 * 保證 在客戶端 部分注冊(cè)了 服務(wù)器 的各類信息,而不能在 服務(wù)器端 注冊(cè) 客戶端 的信息。 * 當(dāng)然也有例外,例如你就想這個(gè)軟件作為私密軟件,僅供兩個(gè)人使用, 且你有固定的 IP地址,那么你可以兩邊都connect,但是 * 一定要注意,只要有一點(diǎn)信息變動(dòng),這個(gè)軟件就可能無法正常的收發(fā)信息了。 */
代碼解釋
故而實(shí)際上,雖然前方的表格顯示,UDP 似乎并沒有 connect 的使用必要,但是實(shí)際上還是有用到的地方。
就 *nix 的 API 來說,sendto 和 write 的區(qū)別十分明顯,便是一個(gè)需要在參數(shù)中提供目標(biāo)主機(jī)的各類信息,而后者則不需要提供。同樣的道理recvfrom和read也是如此。
這個(gè)代碼只是做演示而已,所以將代碼置于無限循環(huán)當(dāng)中,現(xiàn)實(shí)中可以自行定義出口條件。
以上是 UDP 的一些簡單說明,入門足矣,并未詳細(xì)敘述某些 函數(shù) 的具體用法,而是用實(shí)際例子來體現(xiàn)。 在 記錄 TCP 之前,還是需要講一個(gè)函數(shù) shutdown
shutdown 與 close(closesocket)
首先要知道,網(wǎng)絡(luò)通信一般而言是雙方的共同進(jìn)行的,換而言之就是雙向的,一個(gè)方向只用來發(fā)送消息,一個(gè)方向只用來讀取消息。
這就導(dǎo)致了,在結(jié)束套接字通信的時(shí)候,需要關(guān)閉兩個(gè)方向的通道(暫時(shí)叫它們通道),那同時(shí)關(guān)閉不行嗎?可以啊
close(sock); // closesocket(sock); FOR Windows PlatForm 就是這么干的,同時(shí)斷開兩個(gè)方向的連接。
簡單的通信程序或者單向通信程序這么做的確無甚大礙,但是萬一在結(jié)束通信的時(shí)候需要接收最后一個(gè)信息那該怎么辦?
假設(shè)通信結(jié)束,客戶端向服務(wù)器發(fā)送 "Thank you"
服務(wù)器需要接收這個(gè)信息,之后才能關(guān)閉通信
問題就在這之間,服務(wù)器并不知道客戶端會(huì)在通信結(jié)束后的什么時(shí)刻傳來信息
所以我們選擇在通信完成后先關(guān)閉 服務(wù)器的 發(fā)送通道(寫流),等待客戶端發(fā)來消息后,關(guān)閉剩下的 接收通道(讀流)
發(fā)送端:
/* 假設(shè)有一個(gè) TCP 的連接,此為客戶端 */ write(sock, "Thank you", 10); close(sock); // 寫完直接關(guān)閉通信
接收端:
/* 此為服務(wù)器 */ /* 首先關(guān)閉寫流 */ shutdown(sock_c, SHUT_WR); read(sock_c, get_mess, GET_MAX); printf("Message : %s\n", get_mess); close(sock_c); close(sock_s); // 關(guān)閉兩個(gè)套接字是因?yàn)?TCP 服務(wù)器端的需要,后續(xù)會(huì)記錄
代碼解釋
shutdown 函數(shù)的作用就是 可選擇的關(guān)閉那個(gè)方向的輸出
int shutdown(int sock, int howto);
sock 代表要操作的套接字
howto有幾個(gè)選擇
- * nix ** : SHUT_RD SHUT_WR SHUT_RDWR
- Windows : SD_RECEIVE SD_SEND SD_BOTH
下面,有幾個(gè)結(jié)構(gòu)體,以及一個(gè)接口十分重要及常用:
- struct sockaddr_in6 : 代表的是 IPv6 的地址信息
- struct addrinfo : 這是一個(gè)通用的結(jié)構(gòu)體,里面可以存儲(chǔ) IPv4 或 IPv6 類型地址的信息
- getaddrinfo : 這是一個(gè)十分方便的接口,在上述 UDP 程序中許多手動(dòng)填寫的部分,都能夠省去,有該函數(shù)替我們完成
改寫一下上方的例子:
接收端:
int sock; /* 套接字 */ socklen_t addr_len; /* 發(fā)送端的地址長度,用于 recvfrom */ char mess[15]; char get_mess[GET_MAX]; /* 后續(xù)版本使用 */ struct sockaddr_in host_v4; /* IPv4 地址 */ struct sockaddr_in6 host_v6; /* IPv6 地址 */ struct addrinfo easy_to_use; /* 用于設(shè)定要獲取的信息以及如何獲取信息 */ struct addrinfo *result; /* 用于存儲(chǔ)得到的信息(需要注意內(nèi)存泄露) */ struct addrinfo * p; /* 準(zhǔn)備信息 */ memset(&easy_to_use, 0, sizeof easy_to_use); easy_to_use.ai_family = AF_UNSPEC; /* 告訴接口,我現(xiàn)在還不知道地址類型 */ easy_to_use.ai_flags = AI_PASSIVE; /* 告訴接口,稍后“你”幫我填寫我沒明確指定的信息 */ easy_to_use.ai_socktype = SOCK_DGRAM; /* UDP 的套接字 */ /* 其余位都為 0 */ /* 使用 getaddrinfo 接口 */ getaddrinfo(NULL, argv[1], &easy_to_use, &result); /* argv[1] 中存放字符串形式的 端口號(hào) */ /* 創(chuàng)建套接字,此處會(huì)產(chǎn)生兩種寫法,但更保險(xiǎn),可靠的寫法是如此 */ /* 舊式方法 * sock = socket(PF_INET, SOCK_DGRAM, 0); */ /* 把IP 和 端口號(hào)信息綁定在套接字上 */ /* 舊式方法 * memset(&recv_host, 0, sizeof(recv_host)); * recv_host.sin_family = AF_INET; * recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 接收任意的IP */ * recv_host.sin_port = htons(6000); /* 使用6000 端口號(hào) */ * bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host)); */ for(p = result; p != NULL; p = p->ai_next) /* 該語法需要開啟 -std=gnu99 標(biāo)準(zhǔn)*/ { sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if(sock == -1) continue; if(bind(sock, p->ai_addr, p->ai_addrlen) == -1) { close(sock); continue; } break; /* 如果能執(zhí)行到此,證明建立套接字成功,套接字綁定成功,故不必再嘗試。 */ } /* 進(jìn)入接收信息的狀態(tài) */ //recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len); switch(p->ai_socktype) { case AF_INET : addr_len = sizeof host_v4; recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v4, &addr_len); break; case AF_INET6: addr_len = sizeof host_v6 recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v6, &addr_len); break; default: break; } freeaddrinfo(result); /* 釋放這個(gè)空間,由getaddrinfo分配的 */ /* 接收完成,關(guān)閉套接字 */ close(sock);
代碼解釋:
首先解釋幾個(gè)新的結(jié)構(gòu)體
struct addrinfo 這個(gè)結(jié)構(gòu)體的內(nèi)部順序?qū)τ?*nix 和 Windows 稍有不同,以 *nix 為例
struct addrinfo{ int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr * ai_addr; /* 存放結(jié)果地址的地方 */ char * ai_canonname; /* 忽略它吧,很長一段時(shí)間你無須關(guān)注它 */ struct addrinfo * ai_next; /* 一個(gè)域名/IP地址可能解析出多個(gè)不同的 IP */ };
ai_family 如果設(shè)定為 AF_UNSPEC 那么在調(diào)用 getaddrinfo 時(shí),會(huì)自動(dòng)幫你確定,傳入的地址是什么類型的
ai_flags 如果設(shè)定為 AI_PASSIVE 那么調(diào)用 getaddrinfo 且向其第一個(gè)參數(shù)傳入 NULL 時(shí)會(huì)自動(dòng)綁定自身 IP,相當(dāng)于設(shè)定 INADDR_ANY
- ai_socktype 就是要?jiǎng)?chuàng)建的套接字類型,這個(gè)必須明確聲明,系統(tǒng)沒法預(yù)判(日后人工智能說不定呢?)
- ai_protocol 一般情況下我們?cè)O(shè)置為 0,含義可以自行查找,例如 MSDN 或者 UNP
- ai_addr 這里保存著結(jié)果,可以通過 調(diào)用getaddrinfo之后 的第四個(gè)參數(shù)獲得。
- ai_addrlen 同上
- ai_next 同上
getaddrinfo 強(qiáng)大的接口函數(shù)
int getaddrinfo(const char * node, const char * service,
const struct addrinfo * hints, struct addrinfo ** res);
通俗的說這幾個(gè)參數(shù)的作用
node 便是待獲取或者待綁定的 域名 或是 IP,也就是說,這里可以直接填寫域名,由操作系統(tǒng)來轉(zhuǎn)換成 IP 信息,或者直接填寫IP亦可,是以字符串的形式
service 便是端口號(hào)的意思,也是字符串形式
hints 通俗的來說就是告訴接口,我需要你反饋哪些信息給我(第四個(gè)參數(shù)),并將這些信息填寫到第四個(gè)參數(shù)里。
res 便是保存結(jié)果的地方,需要注意的是,這個(gè)結(jié)果在API內(nèi)部是動(dòng)態(tài)分配內(nèi)存了,所以使用完之后需要調(diào)用另一個(gè)接口(freeaddrinfo)將其釋放
實(shí)際上對(duì)于現(xiàn)代的 套接字編程 而言,多了幾個(gè)新的存儲(chǔ) IP 信息的結(jié)構(gòu)體,例如 struct sockaddr_in6 和 struct sockaddr_storage 等。
其中,前者是后者的大小上的子集,即一個(gè) struct storage 一定能夠裝下一個(gè) struct sockaddr_in6,具體(實(shí)際上根本看不到有意義的實(shí)現(xiàn))
struct sockaddr_in6{ u_int16_t sin6_family; u_int16_t sin6_port; u_int32_t sin6_flowinfo; /* 暫時(shí)忽略它 */ struct in6_addr sin6_addr; /* IPv6 的地址存放在此結(jié)構(gòu)體中 */ u_int32_t sin_scope_id; /* 暫時(shí)忽略它 */ }; struct in6_addr{ unsigned char s6_addr[16]; } ------------------------------------------------------------ struct sockaddr_storage{ sa_family_t ss_family; /* 地址的種類 */ char __ss_pad1[_SS_PAD1SIZE]; /* 從此處開始,不是實(shí)現(xiàn)者幾乎是沒辦法理解 */ int64_t __ss_align; /* 從名字上可以看出大概是為了兼容兩個(gè)不同 IP 類型而做出的妥協(xié) */ char __ss_pad2[_SS_PAD2SIZE]; /* 隱藏了實(shí)際內(nèi)容,除了 IP 的種類以外,無法直接獲取其他的任何信息。 */ /* 在各個(gè)*nix 的具體實(shí)現(xiàn)中, 可能有不同的實(shí)現(xiàn),例如 `__ss_pad1` , `__ss_pad2` , 可能合并成一個(gè) `pad` 。 */ };
在實(shí)際中,我們往往不需要為不同的IP類型聲明不同的存儲(chǔ)類型,直接使用 struct sockaddr_storage 就可以,使用時(shí)直接強(qiáng)制轉(zhuǎn)換類型即可
改寫上方 接收端 例子中,進(jìn)入接收信息的狀態(tài)部分
/* 首先將多于的變量化簡 */ // - struct sockaddr_in host_v4; /* IPv4 地址 */ // - struct sockaddr_in6 host_v6; /* IPv6 地址 struct sockaddr_storage host_ver_any; /* + 任意類型的 IP 地址 */ ... /* 進(jìn)入接收信息的狀態(tài)部分 */ recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_ver_any, &addr_len); /* 像是又回到了只有 IPv4 的年代*/
補(bǔ)充完整上方對(duì)應(yīng)的 發(fā)送端 代碼
int sock; const char* mess = "Hello Server!"; char get_mess[GET_MAX]; /* 后續(xù)版本使用 */ struct sockaddr_storage recv_host; /* - struct sockaddr_in recv_host; */ struct addrinfo tmp, *result; struct addrinfo *p; socklen_t addr_len; /* 獲取對(duì)端的信息 */ memset(&tmp, 0, sizeof tmp); tmp.ai_family = AF_UNSPEC; tmp.ai_flags = AI_PASSIVE; tmp.ai_socktype = SOCK_DGRAM; getaddrinfo(argv[1], argv[2], &tmp, &result); /* argv[1] 代表對(duì)端的 IP地址, argv[2] 代表對(duì)端的 端口號(hào) */ /* 創(chuàng)建套接字 */ for(p = result; p != NULL; p = p->ai_next) { sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); /* - sock = socket(PF_INET, SOCK_DGRAM, 0); */ if(sock == -1) continue; /* 此處少了綁定 bind 函數(shù),因?yàn)樽鳛榘l(fā)送端不需要講對(duì)端的信息 綁定 到創(chuàng)建的套接字上。 */ break; /* 找到就可以退出了,當(dāng)然也有可能沒找到,那么此時(shí) p 的值一定是 NULL */ } if(p == NULL) { /* 錯(cuò)誤處理 */ } /* -// 設(shè)定對(duì)端信息 memset(&recv_host, 0, sizeof(recv_host)); recv_host.sin_family = AF_INET; recv_host.sin_addr.s_addr = inet_addr("127.0.0.1"); recv_host.sin_port = htons(6000); */ /* 發(fā)送信息 */ /* 在此處,發(fā)送端的IP地址和端口號(hào)等各類信息,隨著這個(gè)函數(shù)的調(diào)用,自動(dòng)綁定在了套接字上 */ sendto(sock, mess, strlen(mess), 0, p->ai_addr, p->ai_addrlen); /* 完成,關(guān)閉 */ freeaddrinfo(result); /* 實(shí)際上這個(gè)函數(shù)應(yīng)該在使用完 result 的地方就予以調(diào)用 */ close(sock);
到了此處,實(shí)際上是開了網(wǎng)絡(luò)編程的一個(gè)初始,解除了現(xiàn)代的 UDP 最簡單的用法(甚至還算不上完整的使用),但是確實(shí)是進(jìn)行了交互。
介紹 UDP 并不是因?yàn)樗唵?,而是因?yàn)樗啙?,也不是因?yàn)樗恢匾喾此鋵?shí)很強(qiáng)大。
永遠(yuǎn)不要小看一個(gè)簡潔的東西,就像 C語言
ARP 協(xié)議
最簡便的方法就是找一個(gè)有 WireShark 軟件或者 tcpdump 的 *nix 平臺(tái),前者你可以選擇隨意監(jiān)聽一個(gè)機(jī)器,不多時(shí)就能看見 ARP 協(xié)議的使用,因?yàn)樗褂玫奶l繁了。
對(duì)于 ARP 協(xié)議而言,首先對(duì)于一臺(tái)機(jī)器 A,想與 機(jī)器B 通信,(假設(shè)此時(shí) 機(jī)器A 的高速緩存區(qū)(操作系統(tǒng)一定時(shí)間更新一次)中 沒有 機(jī)器B的緩存),
那么機(jī)器A就向廣播地址發(fā)出 ARP請(qǐng)求,如果 機(jī)器B 收到了這個(gè)請(qǐng)求,就將自己的信息(IP地址,MAC地址)填入 ARP應(yīng)答 中,再發(fā)送回去就行。
上述中, ARP請(qǐng)求 和 ARP應(yīng)答 是一種報(bào)文形式的信息,是 ARP協(xié)議 所附帶的實(shí)現(xiàn)產(chǎn)品,也是用于兩臺(tái)主機(jī)之間進(jìn)行通信。
這是當(dāng) 機(jī)器A 和 機(jī)器B 同處于一個(gè)網(wǎng)絡(luò)的情況下,可以借由本網(wǎng)絡(luò)段的廣播地址 發(fā)送請(qǐng)求報(bào)文。
對(duì)于不同網(wǎng)絡(luò)段的 機(jī)器A 與 機(jī)器B 而言,想要通過 ARP協(xié)議 獲取 MAC地址 ,就需要借助路由器的幫助了,可以想象一下,路由器(可以不止一個(gè))在中間,機(jī)器A 和 機(jī)器B 分別在這些路由器的兩邊(即在不同子網(wǎng))
由于 A 和 B 不在同一個(gè)子網(wǎng)內(nèi),所以沒辦法通過通過直接通過廣播到達(dá),但是有了路由器,就能進(jìn)行 ARP代理 的操作,大概就是將路由器當(dāng)成機(jī)器B, A向自己的本地路由器發(fā)送 ARP請(qǐng)求
之后路由器判斷出是發(fā)送給B的ARP請(qǐng)求,又正好 B 在自己的管轄范圍之內(nèi),就把自己的硬件地址 寫入 ARP應(yīng)答 中發(fā)回去,之后再有A向B 的數(shù)據(jù),就都是A先發(fā)送給路由器,再經(jīng)由路由器發(fā)往B了
ICMP協(xié)議
這個(gè)協(xié)議比較重要。
請(qǐng)求應(yīng)答報(bào)文 和 差錯(cuò)報(bào)文 ,重點(diǎn)在于差錯(cuò)報(bào)文。
請(qǐng)求應(yīng)答報(bào)文在 ICMP 的應(yīng)用中可以拿來查詢本機(jī)的子網(wǎng)掩碼之類的信息,大致通過向本子網(wǎng)內(nèi)的所有主機(jī)發(fā)送該請(qǐng)求報(bào)文(包括自己,實(shí)際上就是廣播),后接收應(yīng)答,得到信息
差錯(cuò)報(bào)文在后續(xù)中會(huì)有提到,這里需要科普一二。
首先對(duì)于差錯(cuò)報(bào)文的一大部分是關(guān)于 xxx不可達(dá) 的類型,例如主機(jī)不可達(dá),端口不可達(dá)等等,每次出現(xiàn)錯(cuò)誤的時(shí)候,ICMP報(bào)文總是第一時(shí)間返回給對(duì)端,(它一次只會(huì)出現(xiàn)一份,否則會(huì)造成網(wǎng)絡(luò)風(fēng)暴),但是對(duì)端是否能夠接收到,就不是發(fā)送端的問題了。
這點(diǎn)上 套接字的類型 有著一定的聯(lián)系,例如 UDP 在 unconnected 狀態(tài)下是會(huì)忽略 ICMP報(bào)文的。而 TCP 因?yàn)榭偸?connected 的,所以對(duì)于 ICMP報(bào)文能很好的捕捉。
ICMP差錯(cuò)報(bào)文中總是帶著 出錯(cuò)數(shù)據(jù)報(bào)中的一部分真實(shí)數(shù)據(jù),用于配對(duì)。
相關(guān)文章
通過stringstream實(shí)現(xiàn)常用的類型轉(zhuǎn)換實(shí)例代碼
在本篇文章里小編給大家分享了關(guān)于通過stringstream實(shí)現(xiàn)常用的類型轉(zhuǎn)換實(shí)例代碼內(nèi)容,需要的朋友們可以參考下。2020-04-04c++ 隊(duì)列相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了c++ 隊(duì)列相關(guān)知識(shí)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下2021-03-03C語言修煉之路初識(shí)分支句?循環(huán)助本心上篇
現(xiàn)實(shí)生活中我們經(jīng)常需要根據(jù)不同的條件做出不同的選擇。程序設(shè)計(jì)中也需要根據(jù)條件來選擇不同的程序進(jìn)行處理,這稱之為分支結(jié)構(gòu),當(dāng)條件表達(dá)式不存在時(shí),它被假設(shè)為真。您也可以設(shè)置一個(gè)初始值和增量表達(dá)式,一般情況下,C?程序員偏向于使用?for(;;)?結(jié)構(gòu)來表示一個(gè)無限循環(huán)2022-03-03C++實(shí)現(xiàn)LeetCode(64.最小路徑和)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(64.最小路徑和),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語言字符串函數(shù)介紹與模擬實(shí)現(xiàn)詳解
字符串函數(shù)(String?processing?function)也叫字符串處理函數(shù),指的是編程語言中用來進(jìn)行字符串處理的函數(shù),如C,pascal,Visual以及LotusScript中進(jìn)行字符串拷貝,計(jì)算長度,字符查找等的函數(shù)2022-02-02C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作的相關(guān)資料,需要的朋友可以參考下2017-05-05C++編程中的或||、與&&、非!邏輯運(yùn)算符基本用法整理
這篇文章主要介紹了C++中的或||、與&&、非!邏輯運(yùn)算符基本用法整理,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01c++ priority_queue用法入門超詳細(xì)教程
priority_queue即優(yōu)先級(jí)隊(duì)列,它的使用場景很多,它底層是用大小根堆實(shí)現(xiàn)的,可以用log(n)的時(shí)間動(dòng)態(tài)地維護(hù)數(shù)據(jù)的有序性,這篇文章主要介紹了c++ priority_queue用法入門超詳細(xì)教程,需要的朋友可以參考下2023-12-12