問題: UDP 是否還有其他一對多的資料傳送方式?
UDP 通訊中的多播
多播是向特定組中的所有主機傳輸資料的方法
,多播也稱之為組播
多播資料傳輸的特點:
- 多播傳送者針對特定的多播組,只傳送 1 次資料,組內主機均可接收到資料
- 主機加入特定組,即可接收該組中的多播資料
- 多播組可在 IP 地址範圍內任意新增
關鍵問題:如何收發多播資料
多播組是一個 D
類地址 (224.0.0.0 - 239.255.255.255)
"加入多播組"可理解為 UDP 網路程式進行的申請
- 如:申請接收發往 239.234.111.222 的多播資料
- 即:設定屬性 (
IPPROTO_IP, IP_ADD_MEMBERSHP
)
傳送多播資料的方式,與傳送普通 UDP 資料的方式相同
- 預備操作:設定屬性,如:(
IPPROTO_IP, IP_MULTICAST_TTL
)
注意事項
加入同一個多播組的主機不一定在同一個網路中
因此,必須設定多播資料的最多轉發次數(TTL)
- TTL(即:Time To Live) 是決定資料傳輸距離的主要因素
- TTL 用整數表示,並且每經過 1 個路由就減少 1
- 當 TTL 變為 0 時,資料無法繼續傳遞,只能銷燬
多播程式設計:傳送端
IP_MULTCAST_TTL: 用於設定多播資料的”最遠傳輸距離“,預設 1
IP_MULTICAST_IF: 用於設定多播資料從哪一個網路介面(網路卡)傳送出去,預設:0.0.0.0
預設 0.0.0.0 情況下作業系統會自主選擇使用哪個網路介面,但在[多網路卡主機]的[實際工程應用]中,最好手工指定!!
IP_MULTCAST_LOOP: 用於設定多播資料是否傳送回本機,預設1 (1,傳送回本機)
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("224.1.1.168");
remote.sin_port = htons(8888);
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
do {
len = sizeof(remote);
r = sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len);
sleep(1);
}while(r > 0);
多播程式設計:接收端
IP_ADD_MEMBRESHIP: 用於申請加入多播組,引數為:多播組和本機地址
struct ip_mreq {
// group address
struct in_addr inmr_multiaddr;
// local host address
struct in_addr imr_interface
}
?
struct ip_mreq group = {0};
group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
group.imr_interface.s_addr = htonl(INADDR_ANY); // 監聽所有網路卡
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
do {
len = sizeof(remote);
r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
buf[r] = 0;
printf("r = %d\n", r);
printf("msg = %s\n", buf);
}while (r > 0)
退出多播組
quit = setsockopt(sock, IP_PROTO_IP, IP_DROP_MEMBERSHIP, &group, sizeof(group));
if (quit == 0) {
break;
}
程式設計實驗:UDP 多播程式設計
mul_tx.c
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int sock = 0;
struct sockaddr_in remote = {0};
socklen_t len = 0;
char buf[128] = "D.T.Software";
struct in_addr addr = {0};
int ttl = 0;
int loop = 0;
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock == -1) {
printf("socket error\n");
return -1;
}
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("224.1.1.168");
remote.sin_port = htons(8888);
//-------------------------
ttl = 0;
len = sizeof(ttl);
getsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, &len); // 可以不設定,ttl 預設為 1
printf("default ttl = %d\n", ttl);
ttl = 32;
len = sizeof(ttl);
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, len);
printf("current ttl = %d\n", ttl);
loop = 0;
len = sizeof(loop);
getsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &len); // 可以不設定,ttl 預設為 1 ,即本機也會收到
printf("default loop = %d\n", loop);
// addr.s_addr = inet_addr("192.168.3.221"); // 具體指定使用哪一塊網路卡進行資料廣播
addr.s_addr = htonl(INADDR_ANY); // 交由作業系統進行選擇
len = sizeof(addr);
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, len); // 可以不設定,預設為 INADDR_ANY 即 "0.0.0.0", 交由作業系統選擇使用哪個網路卡
printf("current if = %s\n", inet_ntoa(addr)); // 當主機有多個網路卡,在實際工程使用時,最好手工指定使用那塊網路卡廣播資料
//-------------------------
while (1) {
len = sizeof(remote);
sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len);
sleep(1);
}
close(sock);
return 0;
}
mul_rx.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int server = 0;
struct sockaddr_in saddr = {0};
struct sockaddr_in remote = {0};
struct ip_mreq group = {0};
int len = 0;
char buf[32] = {0};
int r = 0;
server = socket(PF_INET, SOCK_DGRAM, 0);
if (server == -1) {
printf("server socket error");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(8888);
if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
printf("udp server bind error\n");
return -1;
}
printf("udp server start sucess\n");
group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
group.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
while (1) {
len = sizeof(remote);
r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
if (r > 0) {
buf[r] = 0;
printf("Recvive: %s\n", buf);
} else {
break;
}
}
close(server);
return 0;
}
小結
單播:一對一資料傳送,即:指定目標主機傳送資料
廣播 (必須同一區域網)
- 本地廣播:本地區域網廣播資料,所有主機均可接收資料
- 直接廣播:指定網路廣播資料,目標網路中的主機均可接收資料
多播(組播)(可不同一區域網)
- 向指定的多播地址傳送資料,”訂閱“該地址的主機均可接收資料