15、UDP 数据收发实战
① 再论UDP协议
- UDP是无连接的(不可靠的,无应答消息,数据包无序号标识)
- UDP是面向数据包的,对应用层数据既不合并也不拆分(保留数据包边界,不粘包)
- UDP没有拥塞控制,网络出现的拥塞不会使源主机的发送速率降低
- UDP支持一对一、一对多、多对一和多对多的交互通信
- UDP消息头开销小,只有8个字节(TCP消息头共20个字节)
- UDP相比较TCP更高效,牺牲效率
② UDP和IP的区别
- UDP是建立于IP之上的数据传输协议
- IP负责将UDP数据包从源主机传输到目标主机
- UDP则将应用层数据投递到目标socket(端口号)(socket和端口号通过bind函数绑定)
- UDP几乎完整“继承”了IP传输的特性(不能缺少传输层,UDP应运而生)
- 通信两端无交互,无流控,无超时重发,不具备可靠性
- ····应用层(应用数据) ==> 传输层(TCP(深度定制)、UDP(轻度定制)) ==> IP层(IP)
③ UDP数据收发
④ UDP编程模式
⑤ UDP数据收发
⑥ 编程实验:UDP数据收发
client.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 sock = 0;
struct sockaddr_in addr = {0};
struct sockaddr_in remote = {0};
int len = 0;
char buf[128] = {0};
char input[32] = {0};
int r = 0;
sock = socket(PF_INET, SOCK_DGRAM, 0);
if( sock == -1 )
{
printf("socket error\n");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(9999);
if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
{
printf("udp bind error\n");
return -1;
}
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("127.0.0.1");
remote.sin_port = htons(8888);
while( 1 )
{
printf("Input: ");
scanf("%s", input);
len = sizeof(remote);
sendto(sock, input, strlen(input), 0, (struct sockaddr*)&remote, len);
r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
if( r > 0 )
{
buf[r] = 0;
printf("Receive: %s\n", buf);
}
else
{
break;
}
}
close(sock);
return 0;
}
server.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};
int client = 0;
struct sockaddr_in remote = {0};
socklen_t asize = 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\n");
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 success\n");
while( 1 )
{
len = sizeof(remote);
r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
buf[r] = 0;
printf("r = %d\n", r);
printf("buf = %s\n", buf);
printf("remote ip = %s\n", inet_ntoa(remote.sin_addr));
printf("remote port = %d\n", ntohs(remote.sin_port));
sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
}
close(server);
return 0;
}
16、深入UDP 数据收发(上)
① 问题:如何进行一对多的UDP数据发送?
② UDP通信中的广播
- 广播是向同一网络中的所有主机传输数据的方法
- 广播类型
- 直接广播:IP地址中除网络地址外,其余主机地址均设置为1(eg:192.168.1.255【网络地址 主机地址】)
- 本地广播:无需知道网络地址,使用255.255.255.255作为IP地址使用
- 区别:
③ 本地广播应用案例:DHCP
- DHCP(动态主机配置协议)是一个局域网的网络协议(基于UDP协议)
- DHCP采用 客户端/服务器 模型, 地址的动态分配由网络主机驱动
- 工作方式:
- 当DHCP服务器接收到来自网络主机的地址申请时, 会向网络主机发送相关的地址配置信息,以实现网络主机地址信息的动态配置
④ 预备工作 -> socket属性设置(option)
- socket的本质是对本机网络资源的一种标识
- socket本身有各种属性(不同的连接,属性可能不同)
- 通过 setsockopt() /getsockopt() 可存取指定socket的属性值
- socket属性的改变可造成socket数据收发行为的改变
⑤ TCP 编程中涉及的用法
⑥ setsockopt() /getsockopt()属性存取函数
- sock:将要被设置或者获取选项的套接字。
- level:选项所在的协议层。
- optname:需要访问的选项名。
- optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
- optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
⑦ UDP 数据广播
⑦ 实验
client.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 sock = 0;
struct sockaddr_in addr = {0};
struct sockaddr_in remote = {0};
int len = 0;
char buf[128] = {0};
char input[32] = {0};
int r = 0;
sock = socket(PF_INET, SOCK_DGRAM, 0);
if( sock == -1 )
{
printf("socket error\n");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(9000);
if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
{
printf("udp bind error\n");
return -1;
}
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("127.0.0.1");
remote.sin_port = htons(8888);
while( 1 )
{
r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
if( r > 0 )
{
buf[r] = 0;
printf("Receive: %s\n", buf);
}
else
{
break;
}
}
close(sock);
return 0;
}
server.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};
int client = 0;
struct sockaddr_in remote = {0};
socklen_t asize = 0;
int len = 0;
char buf[32] = "D.T.Software";
int r = 0;
int brd = 1;
server = socket(PF_INET, SOCK_DGRAM, 0);
if( server == -1 )
{
printf("server socket error\n");
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 success\n");
remote.sin_family = AF_INET;
// remote.sin_addr.s_addr = inet_addr("192.168.3.255");
remote.sin_addr.s_addr = 0xFFFFFFFF;
remote.sin_port = htons(9000);
// brd = 0;
setsockopt(server, SOL_SOCKET, SO_BROADCAST, &brd, sizeof(brd));
while( 1 )
{
len = sizeof(remote);
r = strlen(buf);
sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
sleep(1);
}
close(server);
return 0;
}
17、深入UDP 数据收发(下)
① 问题:UDP是否还有其他一对多的数据发送方式?
② UDP通信中的多播
- 多播是向特定组中的所有主机传输数据的方法,多播也称之为组播
- 多播数据传输的特点:
- 多播发送者针对特定的多播组,只发送1次数据,组内主机均可收到数据
- 主机加入特定组,即可接收该组中的多播数据
- 多播组可在IP地址范围内任意增加
③ 关键问题:如何收发多播数据?
- 多播组是一个D类地址(224.0.0.0 - 239.255.255.255)
- “加入多播组” 可理解为UDP网络程序进行的申请
- 如:申请接收发往239.234.111.222的多播数据
- 即:设置属性(IP PROTO IP, IP_ADD_MEMBERSHIP)
- 发送多播数据的方式,与发送普通UDP数据的方式相同
- 预备操作:设置属性, 如(IPPROTO_IP,IP_MULTICAST_TTL)
④ 注意事项
- 加入同一个多播组的主机不一定在同一个网络中(可能需要路由器转发)
- 因此,必须设置多播数据的最多转发次数(TTL)(避免资源浪费)
- TTL (即:Time To Live)是决定数据传递距离的主要因素
- TTL 用整数表示,并且每经过1个路由器就减少 1
- TTL 变为0时,数据无法继续被传递,只能销毁
⑤ 多播程序设计:发送端
- IP_MULTICAST_TTL
- IP_MULTICAST_IF
- 用于设置多播数据从哪一个网络接口(网卡)发送出去,默认:0.0.0.0(让操作系统决定用哪个网卡,仅限单网卡)
- IP_MULTICAST_LOOP
⑥ 多播程序设计:接收端
IP_ADD_MEMBERSHIP
- 用于申请加入多播组,参数为:多播组地止 和 本机地址
单网卡中可以使用INADDR_ANY(0.0.0.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 sock = 0;
struct sockaddr_in addr = {0};
struct sockaddr_in remote = {0};
struct ip_mreq group = {0};
int len = 0;
char buf[128] = {0};
char input[32] = {0};
int r = 0;
sock = socket(PF_INET, SOCK_DGRAM, 0);
if( sock == -1 )
{
printf("socket error\n");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // local host
addr.sin_port = htons(9000);
if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
{
printf("udp bind error\n");
return -1;
}
group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
group.imr_interface.s_addr = htonl(INADDR_ANY); // local host
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
while( 1 )
{
len = sizeof(remote);
r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
if( r > 0 )
{
buf[r] = 0;
printf("Receive: %s\n", buf);
}
else
{
break;
}
}
close(sock);
return 0;
}
仅限单网卡
mul_tx.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};
int client = 0;
struct sockaddr_in remote = {0};
socklen_t asize = 0;
int len = 0;
char buf[32] = "D.T.Software";
int r = 0;
int ttl = 0;
int loop = 0;
struct in_addr addr = {0};
server = socket(PF_INET, SOCK_DGRAM, 0);
if( server == -1 )
{
printf("server socket error\n");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY); // local host
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 success\n");
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("224.1.1.168");
remote.sin_port = htons(9000);
ttl = 0;
len = sizeof(ttl);
getsockopt(server, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, &len);
printf("default ttl = %d\n", ttl);
ttl = 32;
len = sizeof(ttl);
setsockopt(server, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, len);
printf("current ttl = %d\n", ttl);
loop = 0;
len = sizeof(loop);
getsockopt(server, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &len);
printf("default loop = %d\n", loop);
addr.s_addr = inet_addr("192.168.3.221");
len = sizeof(addr);
setsockopt(server, IPPROTO_IP, IP_MULTICAST_IF, &addr, len);
printf("current if = %s\n", inet_ntoa(addr));
while( 1 )
{
len = sizeof(remote);
r = strlen(buf);
sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
sleep(1);
}
close(server);
return 0;
}
⑨ 小结
- 单播:一对一数据发送,即:指定目标主机发送数据
- 广播
- 本地广播:本地局域网广播数据,所有主机均可接收数据
- 直接广播:指定网络广播数据,目标网络中的主机均可接收数据
- 多播(组播)
- 向指定的多播地址发送数据,“订阅”该地址的主机均可接收数据
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)