廣播(使用 UDP 套接字)
廣播地址:主機號最大的地址。
廣播:給所在局域網的所有主機發送數據報。(之前的數據報發送方式是單播。)
以下情況中使用廣播: 局域網 搜索協議。
比如家中的智能產品, 使用手機可以搜索出附近的智能產品,這就是一個局域網搜索協議。
基于 setsockopt 實現廣播
廣播發送者(客戶端):
1、創建一個數據報套接字;
int sockfd = sock(AF_INET, SOCK_DGRAM, 0);
2、setsockopt(sockfd, 協議層, 選項名, 數據類型, 大小);
int opt = 1; // 非0即可
setsockopt(sockfd, **SOL_SOCKET**, SO_BROADCAST, &opt, sizeof(op));
3、填充結構體;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr,sin_port = htons(atoi(argv[2]));
4、發送數據報;
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, sizeof(addr));
廣播接收者(服務器):
1、創建一個數據報套接字;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
2、綁定廣播IP;
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 廣播地址 或 0.0.0.0
socklen_t length = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){...}
3、等待接收數據;
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &length);
缺點:
廣播發送給所有主機,過多的的網絡會發送大量的網絡帶寬,造成廣播風暴。
廣播風暴: 網絡長時間被大量數據包占用,無法通信,網絡會變得緩慢,甚至崩潰。
機制: 通過向廣播地址發送UDP數據包,將數據包發送給網絡中的所有主機。當一個主機發送廣播消息時,該消息會被路由器轉發到網絡中的所有子網。然后,每個子網上的主機都會接收到該廣播消息,并轉發給它們的相鄰主機。這個過程會一直持續下去,直到廣播消息傳播到整個網絡中的所有主機,如果這個現象一直循環如此,則會造成廣播風暴。
廣播:給發送者設定相應的權限;
組播:接收者要加入到多播組;
組播(使用 UDP 套接字)
組播地址(D類IP):224.0.0.1 ~ 239.255.255.255
基于 setsockopt 實現組播
// 多播結構體
struct ip_mreq{struct in_addr imr_multiaddr; // 指定多播組IP struct in_addr imr_interface; // 本地IP,通常指定為 INADDR_ANY--0.0.0.0
}struct in_addr{_be32 s_addr; // IP地址(大端)
}
組播接收者(服務器):
1、創建數據報套接字;
int sockfd = sock(AF_INET, SOCK_DGRAM, 0);
2、加入多播組(僅限于接收者);
// 核心代碼 ------------------------------------
struct ip_mreq mreq; // 定義組播的結構體變量
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]); // 填充多播組IP
mreq.imr_interface.s_addr = inet_addr("0.0.0.0"); // 自動獲取本機IP// 改變套接字屬性
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
3、填充結構體,綁定 組播IP 和 端口;
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 組IP
socklen_t length = sizeof(caddr);if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){...}
4、等待接收數據;
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &length);
組播發送者(客戶端):
1、創建數據報套接字
2、指定接收方地址為 組播地址,設置端口信息
3、發送數據報
廣播和組播的區別:
● 廣播方式:將數據報發給所有的主機。過多的廣播會占用大量網絡帶寬,造成廣播風暴,影響通信。
● 組播(又稱為多播)是一種折中的方式。只有加入某個多播組的主機才能收到數據。
● 組播方式既可以發給多個主機,又能避免像廣播那樣帶來過多的負載。
本地套接字通信
· unix 網絡編程:最開始都是一臺主機內進程和進程之間的編程。(本地通信)
· socket:可以用于本地間進程通信,創建套接字時使用本地協議 AF_LOCAL 或 AF_UNIX。
特點:
本地通信不需要IP和端口,無法進行兩個主機通信;
分為流式套接字和數據報套接字; // 可以使用 流式套接字 或者 數據包套接字
和其他進程間通信相比,使用方便、效率更高,常用于前、后臺進程通信;
unix 域套接字編程,實現本間進程的通信,依賴的是 s 類型的文件;
核心步驟:
#include <sys/socket.h>
#include <sys/un.h>struct sockaddr_un {sa_family_t sun_family; /* 本地協議 AF_UNIX */char sun_path[UNIX_PATH_MAX]; /* 本地路徑 s類型的套接字文件 */
};unix socket = socket(AF_UNIX, type, 0); // type 可以為流式套接字或數據包套接字// unix 寫為 int 就可以struct sockaddr_un myaddr;
myaddr.sun_family = AF_UNIX; // 填充 UNIX 域套接字
strcpy(saddr.sun_path,"./myunix"); // 創建套接字的路徑
相關代碼
receiver
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>int main(int argc, char const *argv[])
{// 1.創建套接字int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd < 0){perror("sock is err:");return -1;}system("rm ./myunix -f");unlink("./myunix");// 2. 填充結構體struct sockaddr_un saddr;saddr.sun_family = AF_UNIX;strcpy(saddr.sun_path, "./myunix");if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind is err:"); return -1;}if (listen(sockfd,5) < 0){perror("listen is err:"); return -1;}int acceptfd = accept(sockfd, NULL, NULL);if (acceptfd < 0){perror("acceptfd < 0"); return -1;}char buf[128] = "";int recvbyte;while(1){recvbyte = recv(acceptfd, buf, sizeof(buf), 0);if (recvbyte < 0){perror("recv is err:"); return -1;}else if (recvbyte == 0){printf("exit\n");break;}else{printf("buf: %s\n", buf);}}close(sockfd);close(acceptfd);return 0;
}
sender
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>int main(int argc, char const *argv[])
{// 1.創建套接字int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd < 0){perror("sock is err:");return -1;}// 2. 填充結構體struct sockaddr_un saddr;saddr.sun_family = AF_UNIX;strcpy(saddr.sun_path, "./myunix");if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("connect is err:");return -1;}char buf[128] = "";int recvbyte;while(1){fgets(buf,sizeof(buf),stdin);if(buf[strlen(buf) -1] == '\n')buf[strlen(buf) -1] = '\0';send(sockfd, buf, sizeof(buf), 0);}close(sockfd);return 0;
}
網絡頭協議分析
wireshark抓包工具
抓包工具的使用
虛擬機: sudo apt-get install wireshark
windows: 小飛機
(抓包的過程,就是抓取流經網卡的數據。如果不加 sudo 就找不到網卡,沒有辦法抓到數據。)
兩臺不同的主機通信 或 兩臺不同的操作系統(windows、linux)之間 才可以進行抓包。
步驟
1)運行 linux 下的服務器;
2)打開 windows 下的小飛機;
3)打開抓包工具;
4)過濾無關的包;
5)小飛機模擬客戶端, 與 linux 下的服務器通信;
實現:
包頭分析
以太網的完整幀格式
網絡層最大數據幀長度是1500字節。(MTU: 最大傳輸單元)
鏈路層最大數據長度是1518字節。(網絡層 1500 + 以太網 14 + CRC檢錯 4)
TCP 粘包、拆包 與 UDP丟包
● 發生 TCP 粘包或拆包 有很多原因,常見的有以下幾點:
1、待發送數據大于 TCP 發送緩沖區剩余空間大小,將會發生拆包。
2、待發送數據大于 MSS(最大報文長度),TCP 在傳輸前將進行拆包。
3、待發送數據小于 TCP 發送緩沖區的大小,TCP 將多次寫入緩沖區的數據一次發送出去,將會發生粘包。
4、接收數據端的應用層沒有及時讀取接收緩沖區中的數據,將發生粘包。
● TCP 粘包的 解決辦法:
1、發送端給每個數據包添加包首部,首部中應該至少包含數據包的長度,這樣接收端在接收到數據后,通過讀取包首部的長度字段,便知道每一個數據包的實際長度了。
2、發送端將每個數據包封裝為固定長度(不夠的可以通過補 \0填充),這樣接收端每次從接收緩沖區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。
3、可以在數據包之間設置邊界,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數據包拆分開。
以太網頭
以太網中封裝了 目的mac地址 以及 源mac地址、IP 類型,以太網頭又稱為 mac頭。
切換網絡時,IP地址會改變,Mac地址不會改變。
type 類型:
0x0800 ——> 只接收發往 本機MAC 的 IP類型的數據幀;
0x0806 ——> 只接收發往 本機 ARP類型 的數據幀;
0x8035 ——> 只接收發往 本機 RARP類型 的數據幀;
0x0003 ——> 接收發往 本機MAC 的所有類型:IP, ARP, RARP 數據幀,接收從本機發出去的數據幀,
當混雜模式打開的情況下,會接收到非發往本地的 MAC 數據幀。
ARP:ARP協議用于將 IP地址 解析為 MAC地址。當一臺計算機向另一臺計算機發送數據時,它需要知道目標計算機的 MAC地址,而不是 IP地址。
RARP:RARP協議則是與 ARP 相反的過程,它用于將 MAC地址 解析為 IP地址。
IP頭
UDP頭
TCP頭
三次握手
服務器必須準備好接受外來的連接。這通過調用 socket、 bind 和 listen 函數來完成,稱為被動打開(passive open)。
第一次握手:客戶通過調用 connect 進行主動打開(active open)。客戶端發送一個SYN(表示同步)
分節(SYN=J),它告訴服務器客戶將在連接中發送到數據的初始序列號。并進入 SYN_SEND 狀態,
等待服務器的確認。
第二次握手:服務器必須確認客戶的 SYN,同時自己也得發送一個 SYN 分節,它含有服務器將在同一連接
中發送的數據的初始序列號。服務器以單個字節向客戶發送 SYN 和 對客戶 SYN 的 ACK(表示確認)
此時服務器進入 SYN_RECV 狀態。
第三次握手:客戶收到服務器的 SYN+ACK。向服務器發送確認分節,此分節發送完畢,客戶服務器進入
ESTABLISHED(確認)狀態,完成三次握手。
四次揮手
第一次揮手:主動關閉方發送一個 FIN 給被動方,進入 FIN_WAIT 狀態;
第二次揮手:被動方接收到 FIN 包,給主動方發送一個 ACK 包;并進入 CLOSE_WAIT 狀態,主動方接收
到 ACK 包后,如果有數據沒有發送完畢,則繼續發送,一直到發送完畢;
第三次揮手:被動方發送一個 FIN 包,進入 LAST_ACK 狀態。
第四次揮手:主動關閉方收到 FIN 包,回復一個 ACK包。被動關閉方收到主動關閉方的 ACK后關閉連接。