C++学习之udp通信

发布于:2025-04-08 ⋅ 阅读:(55) ⋅ 点赞:(0)

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地址的数据块前的名字就网卡名字


网站公告

今日签到

点亮在社区的每一天
去签到