LinuxUDP协议编程(下)

简介: LinuxUDP协议编程(下)

4.服务端完整代码


#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 9999
#define IP "192.168.1.8"
#define SIZE 1024
int main(){
    int sockfd = -1;
    int len = sizeof(struct sockaddr);
    struct sockaddr_in serveraddr = {0};
    char buf[SIZE];
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -1;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(IP);
    if (bind(sockfd, (struct sockaddr*)&serveraddr), sizeof(struct sockaddr)){
        perror("fail not bind");
        close(sockfd);   // 这里要关闭一下套接字,因为已经失败了
        return -2;
    }
    // 循环读取数据
    while(1){
        recvfrom(sockfd, buf, SIZE, 0, (struct sockaddr*)&serveraddr, len);
        printf("[%s %d]:%s\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port), &buf);
    }
    return 0;
}


5.sendto发送函数


学习了接收函数后我们就可以来学习一下发送函数了,函数也是比较简单,函数原型:


#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:
    sockfd:文件描述符,socket的返回值
    buf:要发送的数据
    len:buf的长度
    flags:标志位
    0 阻塞
    MSG_DONTWAIT 非阻塞
    dest_addr:目的网络信息结构体(需要自己指定要给谁发送)
    addrlen:dest_addr的长度
返回值:
    成功:发送的字节数
    失败:‐1


使用的代码如下:


printf("请输入你要发送的信息:");
scanf("%s", buf);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&serveraddr, len);


这样我们客户端的代码也写完了。


6.客户端完整代码


#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 9999
#define IP "192.168.1.8"
#define SIZE 1024
int main(){
    int sockfd = -1;
    int len = sizeof(struct sockaddr);
    struct sockaddr_in serveraddr = {0};
    char buf[SIZE];
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -1;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(IP);
    // 循环读取数据
    while(1){
        printf("请输入你要发送的信息:");
        scanf("%s", buf);
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&serveraddr, len);
        printf("发送成功\n");
    }
    return 0;
}


这里有一个很重要的问题,就是在客户端里不用进行绑定,那如果不进行绑定怎么让程序知道发送给哪个服务端呢?


这里我们不是使用了一个sendto,而这个函数携带了目标机器的配置结构体,它会通过这个结构体去找到目标。


三、TFTP文件接收程序


1.TFTP概述


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

这个协议是基于UDP协议实现的,不用进行用户有效性认证。

传输模式分为三种:

  • octet : 二进制模式
  • netascii : 文本模式
  • mail : 已经不再支持


2.TFTP通讯过程

79401429fbb9814dc90845a9d0a81bcb.png


实现客户端发送一个请求连接的数据包给服务端,服务端收到后验证一下请求包中的内容:

387dc7acf55f45fc2710e60b16dbb585.png



然后服务端收到请求后会发送请求的内容组成的数据包:

9c28b35bd6e7f71c48c18e57b0f2f404.png



当客户端收到数据包后需要向服务端发送ACK响应包:


af1e3e7a079f805710e2ff5b17519139.png


如果在这个过程中出现了问题,服务端会发送一个错误的数据包:

826c53c661492c2454a56a257903cd61.png


这里面的错误码有下面的几种:


错误码:


0 未定义,参见错误信息


1 File not found


2 Access violation


3 Disk full or allocation exceeded


4.illegal TFTP operation


5.Unknown transfer ID


6.File already exists


7.No such user


8.Unsuppored option(s) requested


3.TFTP客户端


现在我们来编写一个简单的TFTP客户端来接收服务端的文件,这里的服务端用软件来进行开启,当然你也可以主机写一个,都是使用UDP的,很简单。


流程如下:

908800750d485a69085e285328ce2a57.png


代码如下:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 1024
#define DATASIZE 516
void getFile(int sockfd, struct sockaddr_in serveraddr, char* filename){
    char buf[SIZE];
    int fileflag = 0;   // 用来判断文件是否被创建
    int structlen = sizeof(struct sockaddr);
    int len = 0;  // 数据包长度
    int fd = -1;
    int num = 1;  // 包序号
    int n = 0;    // 接收长度
    len = sprintf(buf, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0);  // 拼接请求数据包
    // 发送数据包
    sendto(sockfd, buf, len, 0, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr));
    // 接收数据包
    n = recvfrom(sockfd, buf, DATASIZE, 0, (struct sockaddr*)&serveraddr, &structlen);
    // 判断数据包
    if (buf[1] == 5){
        // 这个是获取到错误包
        printf("error:%s\n", buf+4);  // 打印错误信息
        return ;
    }
    else if (buf[1] == 3){
        // 获取数据包
        if (fileflag == 0){
            // 文件没有被创建
            fd = open(filename, O_WRONLY | O_CREAT, 0664);
            if (fd == -1){
                perror("fail not open");
                return;
            }
        }
        while(1){
            // 将读取的内容写入文件中
            if (n == DATASIZE && (ntohs(*(unsigned short*)(buf + 2)) == num)){    // 判断数据长度和包序号
                write(fd, buf + 4, n - 4); // 去掉数据头和数据包数
                // 发送ACK响应数据包
                sendto(sockfd, buf, 4, 0, (struct sockaddr*)&serveraddr, len);
                num++;
            }
            else if (n < DATASIZE && (ntohs(*(unsigned short*)(buf + 2)) == num)){   // 数据包长度小于516,接收结束
               write(fd, buf + 4, n - 4);
               sendto(sockfd, buf, 4, 0, (struct sockaddr*)&serveraddr, len);
               break;
            }
        }
        close(fd);   // 关闭文件
        printf("传输完成\n");
    }
    return ;
}
int main(int argv, char* argc[]){
    int sockfd = -1;
    struct sockaddr_in serveraddr = {0}; // TFTP服务端配置结构体
    if (argv != 3){
        printf("./TFTP <IP> <Filename>");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -2;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(69);   // TFTP默认开启69号端口
    serveraddr.sin_addr.s_addr = inet_addr(argc[1]);
    getFile(sockfd, serveraddr, argc[2]);  // 获取文件的函数
    close(sockfd);
    return 0;
}


这里使用服务端的软件是:TFTPd32,下载的链接:tftpd32

上面写的只是下载的代码,当然也可以写一个上传的功能,其实就是将write改变为read,然后通过数据包发送过去即可。


四、UDP广播


1.什么是广播


广播是由一台主机向该主机所在子网内的所有主机发送数据的方式,例如192.168.1.1主机发送广播消息,192.168.1.1~192.168.1.254都能收到数据。


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


2.广播特点和用途


用途主要是单个服务器与多个客户主机通信时减少分组流通,下面的几个协议都用到广播:

1、地址解析协议(ARP)

2、动态主机配置协议(DHCP)

3、网络时间协议(NTP)

广播局限于在局域网内使用,离开了局域网后就没有办法进行使用了。


3.广播地址


广播地址分为两种,一种是定向广播地址,另一种是受限广播地址。


定向广播地址是主机ID全为1,比如一个局域网内,IP地址是192.168.1.0,子网掩码是:255.255.255.0,那这个网段中的定向广播地址为:192.168.1.255。


而受限广播地址是:255.255.255.255,路由器从来不转发该广播。


4.广播、单播和多播


4.1 单播


单播是什么?我们可以拿一张图来解释一下:


31514aebe7acad7221b880130d8c8bc6.png


这个过程和之前的UDP通讯一样,这个单播的MAC地址必须要是对方的MAC地址,如果MAC地址不是对方的MAC地址那对方就不会再继续解析。


4.2 广播


2609a5487aa9d7ba06a568a280dee29b.png


在广播中,MAC地址是全为1的,对方接收到这个数据包中如果看到MAC地址全为1,不是和自己一样的也会继续解析,相当于就忽略了检查MAC地址的这一步,然后继续解析。


编写一个广播的代码是很简单的,实现我们要了解一下编写的流程:


发送者:


1、创建套接字


2、设置为允许发送广播权限


3、发送内容


接收者:


1、创建套接字


2、将套接字与广播的消息结构体绑定到一起


3.接收消息


4.2.1 设置为允许发送广播权限


这个需要使用到一个函数setsockopt,这个函数在设置广播和多播的时候会使用到,所以我们先了解一下这个函数,函数的原型如下:


#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项(属性)
参数:
    socket:文件描述符
    level:协议层次
        SOL_SOCKET 套接字层次
        IPPROTO_TCP tcp层次
        IPPROTO_IP IP层次
    option_name:选项的名称
        SO_BROADCAST 允许发送广播数据(SOL_SOCKET层次的)
        option_value:设置的选项的值
    int类型的值,存储的是bool的数据(1和0)
        0 不允许
        1 允许
    option_len:option_value的长度
返回值:
    成功:0
    失败:‐1

比如说我们要设置一个套接字为发送广播权限,代码可以这样写:

int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
• 1
• 2

这样就可以设置好发送广播了。


4.2.2 发送端代码


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SIZE 1024
int main(int argv, char* argc[]){
    int sockfd = -1;
    int on = 1;
    int len;
    int ret = -1;
    char buf[SIZE];
    struct sockaddr_in serveraddr = {0};
    if (argv != 3){
        printf("./Send <IP> <PORT>");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("sockfd not ok");
        return -2;
    }
    // 设置发送广播权限
    ret = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
    if (ret == -1){
        perror("fail not setsockopt");
        close(sockfd);
        return -3;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argc[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argc[1]);
    len = sizeof(struct sockaddr);
    while(1){
        // 发送数据
        printf("you talk:");
        scanf("%s", buf);
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&serveraddr, len);
    }
    close(sockfd);
    return 0;
}


4.2.3 接收端完整代码


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define SIZE 1024
int main(int argv, char* argc[]){
    int sockfd = -1;
    int len;
    int n;
    char buf[SIZE];
    struct sockaddr_in serveraddr = {0}; // 广播的结构体
    if (argv != 3){
        printf("./recv <IP> <PORT>\n");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -2;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argc[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argc[1]);
    if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){
        perror("fail not bind");
        close(sockfd);
        return -3;
    }
    len = sizeof(struct sockaddr);
    while(1){
        // 接收信息
        n = recvfrom(sockfd, buf, SIZE, 0, (struct sockaddr*)&serveraddr, &len);
        if (n <= 0){
            printf("no");
            break;
        }
        printf("he talk:%s\n", buf);
    }
    close(sockfd);
    return 0;
}


4.3 多播


多播其实就算一个发送端给多个接收端发送消息,但是这些接收端必须是在一个组内才能接收这个消息。

而多播发送端的地址需要从224.0.0开始到239.255.255.254结束,也就是你在设置发送端的时候需要将发送端的IP设置为这个范围内才可以。

多播工作流程如下:


4ebeb4ef555639527ed81191a6935521.png

4.3.1 多播编写流程


发送者:

1、创建套接字

2、发送数据


接收者:

1、创建套接字

2、设置为加入多播组

3、将套接字和多播消息绑定到一起

4、接收数据

这里看起来很麻烦,特别是设置为加入多播组该如何设置,其实还是用到上面使用的setsockopt函数来进行设置,那如何设置呢?


#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项(属性)
参数:
socket:文件描述符
level:协议层次
    IPPROTO_IP IP层次
option_name:选项的名称
    IP_ADD_MEMBERSHIP 加入多播组
option_value:设置的选项的值
    struct ip_mreq
    {
        struct in_addr imr_multiaddr; //组播ip地址
        struct in_addr imr_interface; //主机地址
        INADDR_ANY 任意主机地址(自动获取你的主机地址)
    };
    option_len:option_value的长度
返回值:
    成功:0
    失败:‐1

比如说我将创建好的套接字加入到多播组中,代码就如下:


struct ip_mreq mreq = {0};
mreq.imr_multiaddr.s_addr = inet_addr("224.1.1.1");   // 组播的IP地址
mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq));


这样就将接收端加入进多播组中了。

4.3.1 发送端完整代码


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#define SIZE 1024
int main(int argv, char* argc[]){
    int sockfd = -1;
    char buf[SIZE];
    struct sockaddr_in serveraddr = {0};
    if (argv != 3){
        printf("./group_send <IP> <PORT>\n");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -2;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argc[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argc[1]);
    while(1){
            printf("you talk:");
            scanf("%s", buf);
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
    }
    return 0;
}



4.3.2 接收端完整代码


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define SIZE 1024
int main(int argv, char* argc[]){
    int sockfd = -1;
    int len;
    char buf[SIZE];
    struct ip_mreq mreq = {0};
    struct sockaddr_in serveraddr = {0};
    if (argv != 3){
        printf("./group_recv <IP> <PORT>\n");
        return -1;
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1){
        perror("fail not socket");
        return -2;
    }
    // 添加一个多播组IP
    mreq.imr_multiaddr.s_addr = inet_addr(argc[1]);
    // 添加一个将要添加到多播组的IP
    mreq.imr_interface.s_addr = INADDR_ANY;
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))){
        perror("fail not setsockopt");
        close(sockfd);
        return -3;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argc[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argc[1]);
    if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){
        perror("fail not bind");
        close(sockfd);
        return -4;
    }
    len = sizeof(struct sockaddr);
    while(1){
        recvfrom(sockfd, buf, SIZE, 0, (struct sockaddr*)&serveraddr, &len);
        printf("[IP:%s PORT:%d]:%s\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port), buf);
    }
    return 0;
}


总结


UDP通信就介绍完了,其实UDP通信是很简单的,相比于TCP,UDP的客户端不需要进行连接,只需要使用sendto函数就可以将数据发送给客户端了,大家只需要多多练习就好了,再过几天我给大家介绍一下TCP的通讯,这样就能对UDP有更深的理解了。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
2月前
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。
|
6月前
|
存储 网络协议 Linux
|
6月前
|
网络协议 Java 网络安全
【计算机网络】—— Socket通信编程与传输协议分析
【计算机网络】—— Socket通信编程与传输协议分析
|
6月前
|
网络协议 Linux 数据处理
Linux网络编程(各种协议模型的基础知识)
Linux网络编程(各种协议模型的基础知识)
75 0
|
存储 域名解析 安全
计算机网络面试专题:HTTP协议基本概念以及通信过程
计算机网络面试专题:HTTP协议基本概念以及通信过程、HTTPS基本概念、SSL加密原理、通信过程、中间人攻击问题、HTTP协议和HTTPS协议区别
111 1
|
存储 域名解析 网络协议
LinuxUDP协议编程(上)
LinuxUDP协议编程
101 0
|
网络协议 Java 测试技术
协议的学习技巧
协议的学习技巧
128 0
协议的学习技巧
|
域名解析 网络协议 算法
Linux网络原理与编程(4)——第十四节 传输层协议
客户端认为连接已经建立成功了,所以就正常发数据。但是这个时候服务器并未建立连接,在收到数据之后,会向客户端发送一个含有RST的报文(reset),即希望客户端重新建立连接。
248 0
Linux网络原理与编程(4)——第十四节 传输层协议

热门文章

最新文章