1.UDP特点
```c
/*
udp 传输层协议, 和tcp是一样的
特点:
面向无连接的, 不安全的, 报式传输协议
1. 无连接: udp通信的时候不需要connect
1). 通信不需要建立连接
2). 如果想给对方发送数据, 只需要指定对方的IP和端口
2. udp会丢包
1). 数据丢失了就没有了, 没有数据校验机制
2). udp不会丢失一部分数据, 丢就是全丢, 不丢就是一点不不丢
3. 报式:
发送端发送多少数据, 接收端接收多少数据
*/
```
2.UDP通信流程
3.sendto和recvfrom函数
- 服务器端
```c
// 1. 创建一个通信的套接字
int cfd = socket(AF_INET, SOCK_DGRAM, 0); // 通信使用udp
// 2. 通信的套接字和本地的IP和端口绑定
// 绑定的目的: 程序启动之后不主动发送数据, 先接收数据, 就需要绑定端口
// 如果不手动绑定端口, 就会自动绑定端口, 主动发送数据, 可以自动绑定端口
struct sockaddr_in addr;
bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
// 3. 通信
接收数据: recvfrom();
发送数据: sendto();
// 4. 关闭通信的文件描述符
close();
4.知识点概述
- 客户端
```c
// 1. 创建一个通信的套接字
int cfd = socket(AF_INET, SOCK_DGRAM, 0); // 通信使用udp
// 2. 通信的套接字和本地的IP和端口绑定
// 绑定的目的: 程序启动之后不主动发送数据, 先接收数据, 就需要绑定端口
// 如果不手动绑定端口, 就会自动绑定端口
struct sockaddr_in addr;
bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
// 3. 通信
接收数据: recvfrom();
发送数据: sendto();
// 4. 关闭通信的文件描述符
close();
```
5.全局变量errno的值对应的头文件定义
- 操作函数
```c
// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
- sockfd: 通信的文件描述符
- buf: 指向一块有效内存地址, 存储接收的数据
- len: 参数buf指向的内存大小
- flags: 使用默认属性, 指定为0即可
- src_addr: 传出参数, 保存发送端的地址信息(IP和端口) -> 大端(网络字节序)
- 对发送端的地址不感兴趣, 可以指定为NULL
- addrlen: 传入传出参数, 类似于accept()最后一个参数
- src_addr为NULL, 该参数也指定为NULL即可
6.udp通信流程
// 发送数据函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
- sockfd: 通信的文件描述符
- buf: 指向一块有效内存地址, 内存中存储了待发送的数据
- len: 参数buf指向的内存中待发送的数据长度
- flags: 使用默认属性, 指定为0即可
- dest_addr: 传入参数, 保存接收端的地址信息(IP和端口) -> 大端(网络字节序)
- addrlen: 传入参数, dest_addr参数指向的内存大小
返回值:
>0: 发送的字节数
-1: 失败
7.udp服务器程序代码
> UDP: 面向无连接的, 不安全(会丢失数据)的, 报文传输
>
> 应用场景:
>
> - 数据容易丢, 用在对数据没有那么敏感的场景下
> - 用户名密码 -> 不行
> - 文件传输 -> 不行
> - 语言通信 -> 可以
> - 视频聊天, 视频会议 -> 可以
> - 屏幕共享 -> 可以
> - 效率高
> - 不需要建立连接
> - 数据校验机制
> - 一般用于局域网, 数据丢包比较少, 广域网, 网络环境不好容易丢包
> - qq处理文件传输使用的是tcp, 其他都是udp
> - 比较大的公司, 有一个人力和财力, 会在应用层对udp通信的数据包进行校验
> - 应用层 -> 检验udp数据, 让udp类似于tcp, 但是效率高
> - 传输层 -> udp
> - 网络层
> - 网络接口层
>
> udp的两个特性: (tcp没有)
>
> - 广播
> - 组播(多播)
8.udp客户端代码
操作函数
```c
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 接收和发送数据的函数默认是阻塞的
// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
- sockfd: 通信的文件描述符
- buf: 接收数据的缓冲区(数组地址)
- len: buf的容量
- flags: 写0, 使用默认属性
- src_addr: 发送数据的那一方地址信息: IP, Port, 地址族协议 -> 传出参数
- addrlen: 传入传出参数, src_addr 对应的内存大小
// 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
9.udp通信测试
参数:
- sockfd: 通信的文件描述符
- buf: 存储了要发送到数据
- len: 要发送的数据的长度 -> 通过strlen() 计算
- flags: 写0, 使用默认属性
- dest_addr: 接收数据的一方的地址信息
- addrlen: 传入, dest_addr对应的内存大小
```
- 通信流程
- 服务器
```c
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_dgram, 0);
// 1.5 如果服务器只接受数据, 需要绑定端口
bind();
// 2. 通信
recvfrom();
sendto();
// 3. 关闭套接字
close();
```
10.udp的应用场景
- 客户端
```c
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_dgram, 0);
// 1.5 如果客户端是主动发送发送数据, 绑定端口可以不写(随机绑定)
// bind();
// 2. 通信
sendto();
recvfrom();
// 3. 关闭套接字
close();
```
11.广播的特点
> 向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1 (最后一部分是255)。
>
> - 由一个udp的程序, 通过这个程序发送消息, 可以有多个接收端同时收到消息
> - 广播是一对多, 1:N
> - 广播的数据要发送到广播地址上
> - 如果是1对1发送, 使用的地址是这个接收端使用的实际IP地址
> - 广播需要使用一个特殊的广播地址
> - 192.168.x.255
> - x -> 代表的是一个网段
> - 点分十进制的最后一部分(最后一个字节)
> - 取值范围: 0-255, 代表这个网段最多支持多少台计算机
> - 只能在局域网里边使用, 广域网是不支持的
> - 只要在局域网访问内, 并且这个终端绑定了广播端口, 那么就一定能收到广播消息
> - 无法拒绝, 除非把接收端关闭了
>
12.广播的流程
13.设置广播属性试用setsockopt
设置广播属性
```c
// 广播属性设置
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数:
- sockfd: 通信的套接字
- level: SOL_SOCKET
- optname: SO_BROADCAST
- optval: int
- 1: 允许广播
- 0: 不允许广播
- optlen: sizeof(int);
```
14.广播代码修改
```c
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_dgram, 0);
// 2, 被动接收数据, 需要手动绑定端口 (广播数据发送到哪个端口, 就绑定哪个端口)
bind();
// 3. 接收数据
recvfrom();
// 4. 关闭套接字
close();
```
15.组播特点
- 广播特点:
```c
1. 广播发送端的开销很小, 只是使用了广播地址, 数据就可以发送到多个接收终端上
2. 只能在局域网范围内使用
3. 发送广播的一端必须要设置广播属性, 广播的消息发送到广播地址上
```
- 代码
- 发送端
```c
```
- 接收端
```c
```
16.上午广播bug的修改
- 广播特点:
```c
1. 广播发送端的开销很小, 只是使用了广播地址, 数据就可以发送到多个接收终端上
2. 只能在局域网范围内使用
3. 发送广播的一端必须要设置广播属性, 广播的消息发送到广播地址上
```
- 代码
- 发送端
```c
```
- 接收端
```c
```
17.广播特点总结
18.组播地址
> Server给局域网的交换机发送数据,无论连接到局域网的客户端想不想接收该数据,Server都会给客户端发送该数据。—>进而造成客户端上数据的拥塞—>因此引出了多播(组播):Server可以将数据包只发送给指定组内的客户端,而不发送给指定组外的客户端。
>
> - 可以在广域网中使用
> - 使用组播发送数据也是 1:N
> - 数据发送端 1个
> - 数据不是一次发送到接收端对应的地址上, 而是发送到组播地址上
> - 数据接收端 N 个
> - 加入到组播地址, 就可以接收消息
> - 广播消息在局域网中是没有办法被拒绝的, 如果不想收到发送的消息, 可以使用组播
> - 原理:
> - 需要在发送端创建一个群, 如果想要接收发送的消息, 需要加入到这个群中
> - 如果不加入这个群就无法收到发送的消息了
> - 基于这个原理可以实现的功能: 远程会议(数据需要在Internet中传输)
> - 组播需要使用组播地址
> - 发送消息的人, 需要将消息发送到组播地址上
> - 接收消息的人, 需要加入到组播地址
> - 组播属性需要手动设置
> - setsockopt
> - 加入到组播地址
> - setsockopt
19.组播的通信流程
20.通过setsockupt设置组播属性和加入到多播组
21.组播代码修改
组播地址
> IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它的范围从 `224.0.0.0` 到 `239.255.255.255`,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
| IP地址 | 说明 |
| :-----------------------: | :----------------------------------------------------------: |
| 224.0.0.0~224.0.0.255 | 局部链接多播地址:是为路由协议和其它用途保留的地址,只能用于局域网中,路由器是不会转发的地址 224.0.0.0不能用,是保留地址 |
| 224.0.1.0~224.0.1.255 | 为用户可用的组播地址(临时组地址),可以用于 Internet 上的。 |
| 224.0.2.0~238.255.255.255 | 用户可用的组播地址(临时组地址),全网范围内有效 |
| 239.0.0.0~239.255.255.255 | 为本地管理组播地址,仅在特定的本地范围内有效 |
22.组播代码的测试
```c
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_dgram, 0);
// 2. 因为是主动发送数据, 因此不需要手动绑定端口(随机自动绑定即可)
// 需要设置组播的属性, 通过setsockopt() 函数 -> 不设置不能组播
setsockopt();
// 3. 初始化接收端的地址信息
// IP地址: 组播地址 -> 239.0.0.10 (可以用于局域网组播)
// port: 接收组播的一端绑定的固定端口(被动接收数据的一方需要手动绑定端口)
sendto();
// 4. 关闭套接字
close();
```
- 设置组播属性
```c
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数:
- sockfd: 通信的文件描述符
- level: IPPROTO_IP
- optname: IP_MULTICAST_IF
- optval: struct in_addr{}; 里边记录是组播地址
- optlen: sizeof(struct in_addr) ==> 4字节
23.组播特点总结
```c
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_dgram, 0);
// 2, 被动接收数据, 需要手动绑定端口 (组播数据发送到哪个端口, 就绑定哪个端口)
bind();
// 3. 加入到组播地址(群中) -> 加入到多播组
setsockopt();
// 4. 接收数据
recvfrom();
// 5. 关闭套接字
close();
```
- 加入到组播地址
```c
typedef unsigned int uint32_t;
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
struct ip_mreqn
{
struct in_addr imr_multiaddr; // 组播地址/多播地址
struct in_addr imr_address; // 本地地址
int imr_ifindex; // 网卡的编号, 每个网卡都有一个编号
};
// 通过网卡的名字得到网卡的编号:
#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);
- 通过 ifconfig 命令可以看到网卡的名字, 在能查看IP地址的数据块前的名字就网卡名字