udp编程-TFTP、广播、多播

TFTP简介及通信过程

TFTP简单文件传送协议
最初用于引导无盘系统,被设计用来传输小文件

特点

1、基于UDP协议实现
2、不进行用户有效性认证

数据传输模式

1、octet:二进制模式
2、netascii:文本模式
3、mail:已经不再支持

在这里插入图片描述

TFTP通信过程总结

在这里插入图片描述

TFTP协议分析

在这里插入图片描述

在这里插入图片描述

tftp ERROR错误码

在这里插入图片描述
在这里插入图片描述
TFTP客户端

使用TFTP协议,下载server上的文件到本地

实现思路

1、构造请求报文,送至服务器69号端口
2、等待服务器回应
3、分析服务器回应
4、接收数据,直到接收的数据包小于规定的数据长度

TFTP流程图

1、开始
2、创建UDP套接字
3、构造读请求数据包并发送到69端口
4、创建下载文件(最好在接收到服务端应答后再创建文件)
5、接收数据
6、判断数据的操作码

  1. CMD = 3(数据包) => 保存数据到文件 => 构造ACK包并发送到对方临时端口 => 数据长度小于512则关闭文件
  2. CMD = 5(错误包) => 删除文件
  3. CMD = 6(Oack相关包) => 发送ACK确认包

7、结束


tftp客户端下载文件举例

1、服务端使用tftp软件
在这里插入图片描述

2、客户端下载文件代码示例

#include <stdio.h>   // printf
#include <sys/types.h>   //
#include <sys/socket.h>  // socket
#include<stdlib.h>   // exit
#include<netinet/in.h>  // sockaddr_in
#include<arpa/inet.h>   // htons  inet_addr
#include<unistd.h>   // close
#include<string.h>
#include <sys/stat.h>
#include<fcntl.h>

void do_download(int sockfd, struct sockaddr_in serveraddr)
{
    char filename[128] = "";
    printf("请输入要下载的文件名:");
    scanf("%s",filename);

    // 给服务器发送消息,告知服务器执行下载操作
    unsigned char text[1024] = "";
    int text_len;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    int fd;
    int flags = 0;
    int num = 0;
    ssize_t bytes;

    // 构建给服务器发送的tftp指令并发送给服务器    sprintf指的是字符串格式化命令  例如: 01test.txt0octet0
    text_len = sprintf(text, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0);
    if(sendto(sockfd, text, text_len, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
    {
        perror("fail to sendto");;
        exit(1);
    }

    while (1)
    {
        // 接收服务器发送过来的数据并处理
        if((bytes = recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&serveraddr, &addrlen)) < 0)
        {
            perror("fail to recvfrom");
            exit(1);
        }

        // 数据包:操作码(2Bytes) + 块编号(2Bytes) + 数据(512Bytes Data)

        printf("操作码:%d, 块编号:%u\n", text[1], ntohs(*(unsigned short *)(text+2)));
        // printf("数据:%s\n", text+4);

        // 判断操作码执行相应的处理
        if(text[1] == 5)
        {
            // printf("error: %s\n", text+4);
            printf("差错码是:%d ,差错信息error: %s\n", ntohs(*(unsigned short *)(text+2)), text+4);
            return ;
        }
        else if(text[1] == 3)
        {
            if(flags == 0)
            {
                // 创建文件
                if((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0)
                {
                    perror("fail to open");
                    exit(1);
                }
                flags = 1;
            }

            // 对比块编号和接收的数据大小并将文件内容写入文件
            if((num+1 == ntohs(*(unsigned short *)(text+2))) && (bytes == 516))
            {
                num = ntohs(*(unsigned short *)(text+2));
                if(write(fd, text+4, bytes - 4) < 0)
                {
                    perror("fail to write");
                    exit(1);
                }

                // 当文件写入完毕后,给服务器发送ACK
                text[1] = 4;
                if(sendto(sockfd, text, 4, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
                {
                    perror("fail to sendto");;
                    exit(1);
                }
            }
            // 当最后一个数据接收完毕后,写入文件后退出函数
            else if((num+1 == ntohs(*(unsigned short *)(text+2))) && (bytes < 516))
            {
                if(write(fd, text + 4, bytes - 4) < 0)
                {
                    perror("fail to write");
                    exit(1);
                }

                text[1] = 4;
                if(sendto(sockfd, text, 4, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
                {
                    perror("fail to sendto");;
                    exit(1);
                }

                printf("文件下载完毕\n");
                return ;
            }
        }
    }
}

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr, "Usage: %s <server_ip>\n", argv[0]);
        exit(1);
    }


    int sockfd;   // 文件描述符
    struct sockaddr_in serveraddr;  // 服务器网络信息结构体


    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    // 填充服务器网络信息结构体
    serveraddr.sin_family = AF_INET;  // 协议族,AF_INET:ipv4网络协议
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);  // tftp服务端的ip地址
    serveraddr.sin_port = htons(69);       // 端口号

    do_download(sockfd, serveraddr);  // 下载操作

    return 0;
}

在这里插入图片描述


UDP广播

广播:由一台主机向该主机所在的子网内的所有主机发送数据的方式

广播只能用UDP或原始IP实现,不能用TCP

广播的用途

单个服务与多个客户主机通信时减少分组流通

以下几个协议都用到广播

1、地址解析协议ARP
2、动态主机配置协议DHCP
3、网络时间协议NTP

UDP广播的特点

1、处于同一子网的所有主机都必须处理数据
2、UDP数据包会沿协议栈向上一直到UDP层
3、运行音视频等较高速率工作的应用,会带来大负荷
4、局限于局域网内使用

广播地址
在这里插入图片描述

广播与单播
在这里插入图片描述
在这里插入图片描述

广播流程

  • 发送者:

    • 第一步:创建套接字socket()
    • 第二步:设置为允许发送广播权限setsockopt()
    • 第三步:向广播地址发送sendto()
  • 接收者:

    • 第一步:创建套接字socket()
    • 第二步:将套接字与广播的信息结构体绑定bind()
    • 第三步:接收数据recvfrom()

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

   int getsockopt(int sockfd, int level, int optname,
                  void *optval, socklen_t *optlen);
   int setsockopt(int sockfd, int level, int optname,
                  const void *optval, socklen_t optlen);

setsockopt函数=>允许发送广播数据

setsockopt

在这里插入图片描述
在这里插入图片描述

广播发送者

广播发送者

#include <stdio.h>   // printf
#include <sys/types.h>   //
#include <sys/socket.h>  // socket
#include<stdlib.h>   // exit
#include<netinet/in.h>  // sockaddr_in
#include<arpa/inet.h>   // htons  inet_addr
#include<unistd.h>   // close
#include<string.h>
#include <sys/stat.h>
#include<fcntl.h>


int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;   // 文件描述符
    struct sockaddr_in broadcataddr;  // 服务器网络信息结构体
    socklen_t addrlen = sizeof(broadcataddr);

    // 创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    // 设置为允许发送广播权限
    int on = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0)
    {
        perror("fail to setsockopt");
        exit(1);
    }

    // 填充广播信息结构体
    broadcataddr.sin_family = AF_INET;  // 协议族,AF_INET:ipv4网络协议
    broadcataddr.sin_addr.s_addr = inet_addr(argv[1]);  //   173.0.255.255或255.255.255.255
    broadcataddr.sin_port = htons(atoi(argv[2]));       //   设置端口

    // 发送数据
    char buf[128] ="";
    while (1)
    {
        fgets(buf, 128, stdin);
        buf[strlen(buf) - 1] = '\0';  // 把buf字符串中的\n转化为\0

        if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&broadcataddr, addrlen) == -1)
        {
            perror("fail to sendto");
            exit(1);
        }
    }


    return 0;
}

广播接收者

广播接收者

#include <stdio.h>   // printf
#include <sys/types.h>   //
#include <sys/socket.h>  // socket
#include<stdlib.h>   // exit
#include<netinet/in.h>  // sockaddr_in
#include<arpa/inet.h>   // htons  inet_addr
#include<unistd.h>   // close
#include<string.h>
#include <sys/stat.h>
#include<fcntl.h>


int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;   // 文件描述符
    struct sockaddr_in broadcataddr;  // 服务器网络信息结构体
    socklen_t addrlen = sizeof(broadcataddr);

    // 创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    // 填充广播信息结构体
    broadcataddr.sin_family = AF_INET;  // 协议族,AF_INET:ipv4网络协议
    broadcataddr.sin_addr.s_addr = inet_addr(argv[1]);  //   173.0.255.255或255.255.255.255
    broadcataddr.sin_port = htons(atoi(argv[2]));       //   设置端口

    // 将套接字与广播信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&broadcataddr, addrlen) < 0)
    {
        perror("fail to bind");
        exit(1);
    }

    // 第四步:进行通信
    char text[32] = "";
    struct sockaddr_in sendaddr;
    // socklen_t addrlen = sizeof(struct sockaddr_in);

    while (1)
    {
        if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
        {
            perror("fail to recvfrom");
            exit(1);
        }

        // 打印接收到的数据 打印客户端的ip地址和port
        printf("[%s - %d]: %s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), text);
    }

    return 0;
}


执行结果
在这里插入图片描述


多播概述

多播:数据的收发仅仅在同一分组中进行
多播的特点

1、多播地址标示一组接口
2、多播可以用于广域网使用
3、在IPv4中,多播是可选的

在这里插入图片描述
在这里插入图片描述

比起广播,多播具有可控性
只有加入多播组的接收者才可以接收到数据,否则接收不到

多播工作过程

在这里插入图片描述

多播流程

  • 发送者
    • 第一步:创建套接字socket()
    • 第二步:向多播地址发送数据sendto()
  • 接收者
    • 第一步:创建套接字socket()
    • 第二步:设置为加入多播组setsockopt()
    • 第三步:将套接字与多播信息结构体绑定bind()
    • 第四步:接收数据

多播地址结构体

在IPv4因特网(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示

在这里插入图片描述

setsockopt函数=>设置加入多播组

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

   int getsockopt(int sockfd, int level, int optname,
                  void *optval, socklen_t *optlen);
   int setsockopt(int sockfd, int level, int optname,
                  const void *optval, socklen_t optlen);

在这里插入图片描述
在这里插入图片描述

加入多播组示例

在这里插入图片描述

多播发送者

多播发送者

#include <stdio.h>   // printf
#include <sys/types.h>   //
#include <sys/socket.h>  // socket
#include<stdlib.h>   // exit
#include<netinet/in.h>  // sockaddr_in
#include<arpa/inet.h>   // htons  inet_addr
#include<unistd.h>   // close
#include<string.h>
#include <sys/stat.h>
#include<fcntl.h>


int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;   // 文件描述符
    struct sockaddr_in groupcastaddr;  // 服务器网络信息结构体
    socklen_t addrlen = sizeof(groupcastaddr);

    // 创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    // 填充组播信息结构体
    groupcastaddr.sin_family = AF_INET;  // 协议族,AF_INET:ipv4网络协议
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]);  //   224.x.x.x - 239.x.x.x
    groupcastaddr.sin_port = htons(atoi(argv[2]));       //   设置端口

    // 进行通信
    char buf[128] ="";
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';  // 把buf字符串中的\n转化为\0

        if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
        {
            perror("fail to sendto");
            exit(1);
        }
    }

    return 0;
}

多播接收者

多播接收者

#include <stdio.h>   // printf
#include <sys/types.h>   //
#include <sys/socket.h>  // socket
#include<stdlib.h>   // exit
#include<netinet/in.h>  // sockaddr_in
#include<arpa/inet.h>   // htons  inet_addr
#include<unistd.h>   // close
#include<string.h>
#include <sys/stat.h>
#include<fcntl.h>


int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;   // 文件描述符
    struct sockaddr_in groupcastaddr;  // 服务器网络信息结构体
    socklen_t addrlen = sizeof(groupcastaddr);

    // 创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    // 设置为加入多播组
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
    mreq.imr_interface.s_addr = INADDR_ANY;
    if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
    {
        perror("fail to setsockopt");
        exit(1);
    }

    // 填充多播信息结构体
    groupcastaddr.sin_family = AF_INET;  // 协议族,AF_INET:ipv4网络协议
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]);  //   224.x.x.x - 239.x.x.x
    groupcastaddr.sin_port = htons(atoi(argv[2]));       //   设置端口

    // 将套接字与广播信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&groupcastaddr, addrlen) < 0)
    {
        perror("fail to bind");
        exit(1);
    }

    // 第四步 进行通信
    char text[32] = "";
    struct sockaddr_in sendaddr;
    // socklen_t addrlen = sizeof(struct sockaddr_in);

    while (1)
    {
        if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
        {
            perror("fail to recvfrom");
            exit(1);
        }

        // 打印接收到的数据 打印客户端的ip地址和port
        printf("[%s - %d]: %s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), text);
    }

    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C#中,你可以使用UDP协议进行单、多广播通信。下面是一些基本概念和代码示例: 1. 单(Unicast):将数据从一个发送方发送到一个接收方。 ```csharp // 发送方 using (UdpClient udpClient = new UdpClient()) { string message = "Hello, Unicast!"; byte[] bytes = Encoding.ASCII.GetBytes(message); udpClient.Send(bytes, bytes.Length, "接收方IP地址", 接收方端口号); } // 接收方 using (UdpClient udpClient = new UdpClient(接收方端口号)) { IPEndPoint senderEndpoint = new IPEndPoint(IPAddress.Any, 0); byte[] bytes = udpClient.Receive(ref senderEndpoint); string message = Encoding.ASCII.GetString(bytes); Console.WriteLine("接收到消息: " + message); } ``` 2. 多(Multicast):将数据从一个发送方发送到一组接收方。 ```csharp // 发送方 using (UdpClient udpClient = new UdpClient()) { udpClient.JoinMulticastGroup(组组IP地址); string message = "Hello, Multicast!"; byte[] bytes = Encoding.ASCII.GetBytes(message); udpClient.Send(bytes, bytes.Length, 组组IP地址, 接收方端口号); } // 接收方 using (UdpClient udpClient = new UdpClient(接收方端口号)) { udpClient.JoinMulticastGroup(组组IP地址); IPEndPoint senderEndpoint = new IPEndPoint(IPAddress.Any, 0); byte[] bytes = udpClient.Receive(ref senderEndpoint); string message = Encoding.ASCII.GetString(bytes); Console.WriteLine("接收到消息: " + message); } ``` 3. 广播(Broadcast):将数据从一个发送方发送到同一子网中的所有接收方。 ```csharp // 发送方 using (UdpClient udpClient = new UdpClient()) { udpClient.EnableBroadcast = true; string message = "Hello, Broadcast!"; byte[] bytes = Encoding.ASCII.GetBytes(message); udpClient.Send(bytes, bytes.Length, "255.255.255.255", 接收方端口号); } // 接收方 using (UdpClient udpClient = new UdpClient(接收方端口号)) { IPEndPoint senderEndpoint = new IPEndPoint(IPAddress.Any, 0); byte[] bytes = udpClient.Receive(ref senderEndpoint); string message = Encoding.ASCII.GetString(bytes); Console.WriteLine("接收到消息: " + message); } ``` 请注意,示例中的IP地址和端口号需要根据实际情况进行替换。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值