UDP 协议的特点
- UDP 不要求保持一个连接
- UDP 没有因接收方检查数据包 (或当数据包没有正确抵达而自动重传) 而带来的开销
- 设计UDP的目的是用于短应用和控制消息
- UDP要求的网络带宽比 TCP 更小
使用 UDP 协议的场合
- 传送的数据对顺序和可靠性要求不高
- 简单的 发送 — 接收 一次通信形式
- 传输的数据量较少
- 发送广播或多播信息
UDP 和 TCP 的区别
- TCP 面向连接, 完整的 “流”
- UDP 无连接, 独立的报文
UDP-socket 编程模型1
特点
- 应用程序双方是对等的, 通信时都经过 6 个阶段
- 双方必须确切地知道对方的网络地址, 且将约定好的自己的网络地址绑定到自己的 socket 上
- 每次发送或接受数据报时, 所使用的 sendto( 和 recvfrom( 中要包括对方的网络地址信息
- recvfrom( 在没有收到数据时, 默认情况下会阻塞, 程序不向下执行
UDP-socket 编程模型2
特点
- 应用程序程序双方不对等, 服务器要先行启动, 处于被动地等待访问状态, 客户端则可随时主动请求访问服务器
- 客户端不需要绑定 socket
- 服务器将 socket 绑定到周知的端口或指定的端口, 且客户端必须确切地知道服务器 socket 使用的网络地址
- 客户端 socket 使用动态分配的自由端口, 不需要进行绑定, 服务器事先不必知道客户端 socket 的网络地址
- 客户端必须先发送数据报, 服务器收到后才能知道客户端的地址, 才能给客户端会送数据报
sendto(
int
WSAAPI
sendto(
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags,
_In_reads_bytes_(tolen) const struct sockaddr FAR * to,
_In_ int tolen
);
- to 指定对端的地址结构
- tolen 指定对端的地址结构长度
- 返回值
- 成功: 返回实际发送的字节数
- 失败: 返回 SOCKET_ERROR
recvfrom(
int
WSAAPI
recvfrom(
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen
);
- from 接收发过来的地址结构
- 返回值
- 成功: 返回实际接收的字节数
- 连接已终止: 0
- 失败: SOCK_ERROR
发送和接收 0 字节
- TCP
- 可以发送 0 字节报文
- 若接收到 0 字节, 意味着连接被优雅关闭
- UDP
- 可以发送 0 字节报文
- 若接收到 0 字节, 意味着报文长度为 0 并关闭连接
- recvfrom( 和 sendto( 也可以用在 TCP 中
- 此时, 接收到 0 字节意味着连接被优雅关闭
- 一般不这样用
示例
sendto(
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in localaddr;
memset((void *)&localaddr, 0, sizeof(localaddr));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
localaddr.sin_port = htons(0);
bind(s, (struct sockaddr *)&localaddr, sizeof(localaddr));
struct sockaddr_in peeraddr;
memset((void *)&peeraddr, 0, sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
peeraddr.sin_port = htons(8888);
sendto(s, "Hello", 6, 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr));
recvfrom(
s = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in localaddr;
memset((void *)&localaddr, 0, sizeof(localaddr));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
localaddr.sin_port = htons(8888);
bind(s, (struct sockaddr *)&localaddr, sizeof(localaddr));
char buf[32];
recvfrom(s, buf, 32, sizeof(buf), NULL, 0);
setsockopt( 设置 socket 选项
int
WSAAPI
setsockopt(
_In_ SOCKET s,
_In_ int level,
_In_ int optname,
_In_reads_bytes_opt_(optlen) const char FAR * optval,
_In_ int optlen
);
- level 是被设置的选项的级别, 可选 SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP, IPPROTO_IPv6
- optname 是准备设定的选项, 其取值取决于 level
- optval 指向存放选项值的缓冲区
- optlen optval 缓冲区长度
- 在 SOL_SOCKET level 上 optname 取值
- SO_DEBUG, 打开或关闭调试信息
- SO_REUSEADDR, 打开或关闭地址复用功能
- SO_KEEPALIVE, socket 保活
- SO_DONTROUTE, 打开或关闭路由查找功能
- SO_BROADCAST, 允许或禁止发送广播数据
- SO_LINGER, 如果选择此选项, close 或 shutdown 将 等到所有 socket 队列里的消息成功发送或到达延时时间后才会返回, 否则, 调用将立刻返回
- SO_DONTLINGER
- SO_OOBINLINE, 紧急数据放入普通数据流
利用 socket 实现 UDP协议的广播通信
- 创建UDP- socket, 只有 UDP-socket支持广播通信
- 绑定 socket 于指定的地址和端口
- 通过 socket 选项设置广播属性
- 通过 sendto( 发送广播信息, 发送地址为 INADDR_BROADCASE
- 通过 recvfrom( 接收广播消息
让 UDP 广播数据
BOOL optval = true;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&optval, sizeof(BOOL));
利用 socket 实现 UDP协议的组播通信
- 创建 UDP-socket
- 绑定 socket 于指定的地址和端口
- 通过 socket 选项设置组播属性
- 通过 sendto( 发送组播信息
- 通过 recvfrom( 接收组播消息
- 组播地址
- 224.0.0.0—224.0.0.255 224.0.0.0 — 224.0.0.255 被 IANA 保留为网络协议使用
- 244.0.0.1 244.0.0.1 全主机组 244.0.0.2 244.0.0.2 全多播路由器组 244.0.0.5 244.0.0.5 全OSPF路由器组
- 224.0.1.0—238.255.255.255 224.0.1.0 — 238.255.255.255 是公用的组播地址, 可以用于Internet上
- 239.0.0.0—239.255.255.255 239.0.0.0 — 239.255.255.255 是私有地址, 供各个内部网在内部使用, 这个地址的组播不能上公网
IP 多播组的加入和离开用 setsockopt( 完成
- IP_ADD_MEMBERSHIP 加入组
- IP_DROP_MEMBERSHIP 脱离组
使用时要传递一个 ip_mreq 结构
struct ip_mreq { struct in_addr imr_multiaddr; struct in_addr imr_interface; }
setsocket(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&ipmr, &len); setsocket(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char*)&ipmr, &len);
多播的 TTL (生存时间) 设置
int optval = 3;
setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char *)optval, sizeof(int));
多播的 禁止回环设置
int optval = 0;
setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)optval, sizeof(int));