1. 基于UDP的TFTP文件传输上传下载完整版本

news2024/11/26 8:50:42

1)tftp协议概述

简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输

特点:

是应用层协议

基于UDP协议实现

数据传输模式

octet:二进制模式(常用)

mail:已经不再支持

2)tftp下载模型

TFTP通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。

3)tftp协议分析

差错码:

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 Unsupported option(s) requested.

#include <head.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 69               //1024~49151
#define IP "192.168.122.49"    //
 
int do_download(int sfd, struct sockaddr_in sin);
int do_upload(int sfd, struct sockaddr_in sin);
 
int main(int argc, const char *argv[])
{
    //创建报式套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("socket create success  sfd=%d __%d__\n", sfd, __LINE__);
 
    //绑定--->非必须绑定
    //如果不绑定,操作系统会自动绑定本机IP,从49152~65535范围内随机一个未被使用的端口号
    
    //填充服务器的地址信息结构体
    //供给下面的sendto使用,
    struct sockaddr_in sin;
    sin.sin_family         = AF_INET;     //必须填AF_INET;
    sin.sin_port         = htons(PORT);     //服务器绑定的端口,网络字节序
    sin.sin_addr.s_addr = inet_addr(IP);     //服务器绑定的IP,本机IP ifconfig
    
    char choose = 0;
    while(1)
    {
        printf("----------------------------\n");
        printf("----------1. 下载-----------\n");
        printf("----------2. 上传-----------\n");
        printf("----------3. 退出-----------\n");
        printf("----------------------------\n");
        printf("请输入>>> ");
        choose = getchar();
        while(getchar()!=10);
 
        switch(choose)
        {
        case '1':
            do_download(sfd, sin);
            break;
        case '2':
            do_upload(sfd, sin);
            break;
        case '3':
            goto END;
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    }
END:
    //关闭所有文件描述符
    close(sfd);
    return 0;
}
 
int do_upload(int sfd, struct sockaddr_in sin)
{
    int ret_val = 0;
    char filename[20] = "";
    printf("请输入要上传的文件名>>> ");
    scanf("%s", filename);
    while(getchar()!=10);
 
    //判断文件存不存在
    int fd = open(filename, O_RDONLY);
    if(fd < 0)
    {
        ERR_MSG("open");
        return -1;
    }
 
 
    //发送上传请求
    //组上传请求包
    char buf[516] = "";
    char *ptr = buf;
	unsigned short *p1 = (unsigned short*)buf;
	*p1 = htons(2);
	char *p2 = buf + 2;
	strcpy(p2,filename);
	char *p3 = p2 + strlen(p2)+1;
	strcpy(p3,"octet");
	//计算大小
	int size =2+strlen(filename)+7;
    //sendto 
    if(sendto(sfd, buf, size, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("sendto");
        return -1;
    }
 
    socklen_t addrlen = sizeof(sin);
    ssize_t res = 0;
    unsigned short num = 0;      //本地记录的快编号
 
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收服务器的应答
        res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &addrlen);
        if(res < 0)
        {
            ERR_MSG("recvfrom");
            ret_val = -1;
            break;
        }
        //printf("res = %ld   __%d__\n", res, __LINE__);
        //printf("0:%d 1:%d 2:%d 3:%d\n", buf[0], buf[1], buf[2], buf[3]);
    
        if(4 == buf[1])
        {
            if(htons(num) == *(unsigned short*)(buf+2))
            {
                //组数据包给服务器
                num++;
                *(unsigned short*)buf = htons(3);
                *(unsigned short*)(buf+2) = htons(num);
 
                res = read(fd, buf+4, 512);
                if(res < 0)
                {
                    ret_val = -1;
                    break;
                }
                else if(0 == res)
                {
                    printf("文件:%s 上传完毕\n", filename);
                    break;
                }
 
                //发送数据包
                if(sendto(sfd, buf, res+4, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
                {
                    ERR_MSG("sendto");
                    return -1;
                }
            }
        }
        else if(5 == buf[1])//错误包
        {
            printf("MSG_ERR: code[%d] msg[%s] __%d__\n", \
                    ntohs(*(unsigned short*)(buf+2)), buf+4, __LINE__);
            ret_val = -1;
            break;
        }
 
    }
 
    close(fd);
    return ret_val;
}
 
 
 
 
int do_download(int sfd, struct sockaddr_in sin)
{
    int ret_val = 0;
    char buf[516] = "";
 
    char filename[20] = "";
    printf("请输入要下载的文件名>>> ");
    scanf("%s", filename);
    while(getchar()!=10);
 
    //发送下载请求
    //组协议包
    char *ptr = buf;
	unsigned short *p1 = (unsigned short*)buf;
	*p1 = htons(1);
	char *p2 = buf + 2;
	strcpy(p2,filename);
	char *p3 = p2 + strlen(p2)+1;
	strcpy(p3,"octet");
	//计算大小
	int size =2+strlen(filename)+7;
    //sendto 
    if(sendto(sfd, buf, size, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("sendto");
        return -1;
    }
 
    //本地创建并打开要下载的文件
    int fd = -1;     //必须初始化一个无意义的文件描述符,否则下面的close
 
    socklen_t addrlen = sizeof(sin);
    ssize_t res = 0;
    unsigned short num = 0;      //本地记录的快编号
 
    //循环接收数据包,回复ACk
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收数据包
        res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &addrlen);
        if(res < 0)
        {
            ERR_MSG("recvfrom");
            ret_val = -1;
            break;
        }
        //printf("res = %ld   __%d__\n", res, __LINE__);
 
        //printf("0:%d 1:%d 2:%d 3:%d\n", buf[0], buf[1], buf[2], buf[3]);
 
        //if(ntohs(*(unsigned short*)buf) == 3)
        //由于操作码占两个字节的无符号整数,所以传输的时候以大端传输
        //所有有效差错码,会存储在高地址上,即存储在buf[1]位置,buf[0]中存储的是0
 
        if(3 == buf[1])     //数据包
        {
            //UDP可能会数据重复,为了防止重复收包。
            //则在本地记录了一下服务器回过来的快编号,每次处理前,先判断快编号是否正确
            if(htons(num+1) == *(unsigned short*)(buf+2))
            {
                num++;
 
                if(-1 == fd)
                {
                    fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664);
                    if(fd < 0)
                    {
                        ERR_MSG("open");
                        ret_val = -3;
                        break;
                    }
                }
 
                //将获取到的数据,写入到文件中
                if(write(fd, buf+4, res-4) < 0)
                {
                    ERR_MSG("write");
                    ret_val = -3;
                    break;
                }
 
                //回复ACK,数据包前四个字节与ACK包基本一致
                //只有操作码不一致,有效操作码在buf[1]的位置,直接将buf[1]从3,修改成4即可。
                buf[1] = 4;
                if(sendto(sfd, buf, 4, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
                {
                    ERR_MSG("sendto");
                    ret_val = -1;
                    break;
                }
 
                if(res-4 < 512)
                {
                    printf("======= 文件下载完毕 =======\n");
                    break;
                }
            }
        }
        else if(5 == buf[1])//错误包
        {
            printf("MSG_ERR: code[%d] msg[%s] __%d__\n", \
                    ntohs(*(unsigned short*)(buf+2)), buf+4, __LINE__);
            ret_val = -1;
            break;
        }
 
    }
 
    close(fd);
    return ret_val;
}

head.h文件

#ifndef __HEAD_H__
#define __HEAD_H__


#define ERR_MSG(msg) do{\
	fprintf(stderr,"__%d__:",__LINE__);\
	perror(msg);\
}while(0)
#include <dirent.h>           // 目录项  
#include <fcntl.h>            // 文件控制  
#include <fnmatch.h>          // 文件名匹配类型  
#include <glob.h>             // 路径名模式匹配类型  
#include <grp.h>              // 组文件  
#include <netdb.h>            // 网络数据库操作  
#include <pwd.h>              // 口令文件  
#include <regex.h>            // 正则表达式  
#include <tar.h>              // TAR归档值  
#include <termios.h>          // 终端I/O  
#include <unistd.h>           // 符号常量  
#include <utime.h>            // 文件时间  
#include <wordexp.h>          // 字符扩展类型   
#include <arpa/inet.h>        // INTERNET定义  
#include <net/if.h>   	 	  // 套接字本地接口  
#include <netinet/in.h> 	  // INTERNET地址族  
#include <netinet/tcp.h>  	  // 传输控制协议定义   
#include <sys/mman.h> 	      // 内存管理声明  
#include <sys/select.h>   	  // Select函数  
#include <sys/socket.h>    	  // 套接字借口  
#include <sys/stat.h>   	  // 文件状态  
#include <sys/times.h>    	  // 进程时间  
#include <sys/types.h>   	  // 基本系统数据类型  
#include <sys/un.h>    		  // UNIX域套接字定义  
#include <sys/utsname.h>  	  // 系统名  
#include <sys/wait.h>   	  // 进程控制  

#include <cpio.h>   		  // cpio归档值  
#include <dlfcn.h>   		  // 动态链接  
#include <fmtmsg.h>   		  // 消息显示结构  
#include <ftw.h>         	  // 文件树漫游  
#include <iconv.h>   		  // 代码集转换使用程序  
#include <langinfo.h>  		  // 语言信息常量  
#include <libgen.h>    		  // 模式匹配函数定义  
#include <monetary.h>    	  // 货币类型  
//#include <ndbm.h>    		  // 数据库操作  
#include <nl_types.h> 		  // 消息类别  
#include <poll.h>   		  // 轮询函数  
#include <search.h>  		  // 搜索表  
#include <strings.h>   		  // 字符串操作  
#include <syslog.h>   		  // 系统出错日志记录  
#include <ucontext.h>   	  // 用户上下文  
#include <ulimit.h>   		  // 用户限制  
#include <utmpx.h>   		  // 用户帐户数据库  
#include <sys/ipc.h>          // IPC(命名管道)  
#include <sys/msg.h>   		  // 消息队列  
#include <sys/resource.h> 	  // 资源操作  
#include <sys/sem.h>    	  // 信号量  
#include <sys/shm.h>   		  // 共享存储  
#include <sys/statvfs.h>  	  // 文件系统信息  
#include <sys/time.h>      	  // 时间类型  
#include <sys/timeb.h>  	  // 附加的日期和时间定义  
#include <sys/uio.h>    	  // 矢量I/O操作 

#include <aio.h>        	  // 异步I/O  
#include <mqueue.h>   		  // 消息队列  
#include <pthread.h>   		  // 线程  
#include <sched.h>    		  // 执行调度  
#include <semaphore.h>  	  // 信号量  
#include <spawn.h>     		  // 实时spawn接口  
#include <stropts.h>    	  // XSI STREAMS接口  
//#include <trace.h>    		  // 事件跟踪

#include <assert.h>    //设定插入点  
#include <ctype.h>     //字符处理  
#include <errno.h>     //定义错误码  
#include <float.h>     //浮点数处理  
#include <iso646.h>        //对应各种运算符的宏  
#include <limits.h>    //定义各种数据类型最值的常量  
#include <locale.h>    //定义本地化C函数  
#include <math.h>     //定义数学函数  
#include <setjmp.h>        //异常处理支持  
#include <signal.h>        //信号机制支持  
#include <stdarg.h>        //不定参数列表支持  
#include <stddef.h>        //常用常量  
#include <stdio.h>     //定义输入/输出函数  
#include <stdlib.h>    //定义杂项函数及内存分配函数  
#include <string.h>    //字符串处理  
#include <time.h>     //定义关于时间的函数  
#include <wchar.h>     //宽字符处理及输入/输出  
#include <wctype.h>    //宽字符分类
#endif
#include <head.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#define PORT 6666               //1024~49151
#define IP  "192.168.122.15"     //IP地址,本机IP ifconfig

struct cli_msg
{
    int newfd;
    struct sockaddr_in cin;
};

void* deal_cli_msg(void* arg);

int main(int argc, const char *argv[])
{
    //创建流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(sfd < 0)
    {   
        ERR_MSG("socket");
        return -1; 
    }   
    printf("socket create success  sfd = %d\n", sfd);

    //设置允许端口快速被重用
    int resue = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0)
    {   
        ERR_MSG("setsockopt");
        return -1; 
    }   

    //填充服务器的地址信息结构体
    //真实的地址信息结构体根据地址族执行,AF_INET: man 7 ip
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;      //必须填AF_INET;
    sin.sin_port        = htons(PORT);  //端口号的网络字节序,1024~49151
    sin.sin_addr.s_addr = inet_addr(IP);    //IP地址的网络字节序,ifconfig查看

    //绑定---必须绑定
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {   
        ERR_MSG("bind");
        return -1; 
    }   
    printf("bind success ");

    //将套接字设置为被动监听状态
    if(listen(sfd, 128) < 0)
    {   
        ERR_MSG("listen");
        return -1; 
    }   
    printf("listen success ");


    //功能:阻塞函数,阻塞等待客户端连接成功。
    //当客户端连接成功后,会从已完成连接的队列头中获取一个客户端信息,
    //并生成一个新的文件描述符;新的文件描述符才是与客户端通信的文件描述符
    struct sockaddr_in cin;     //存储连接成功的客户端的地址信息
    socklen_t addrlen = sizeof(cin);
    int newfd = -1; 

    pthread_t tid;
    struct cli_msg info;

    while(1)
    {   
        //主线程负责连接
        //accept函数阻塞之前,会先预选一个没有被使用过的文件描述符
        //当解除阻塞后,会判断预选的文件描述符是否被使用
        //如果被使用了,则重新遍历一个没有被用过的
        //如果没有被使用,则直接返回预先的文件描述符;
        newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd < 0)
        {
            ERR_MSG("accept");
            return -1; 
        }
        printf("[%s : %d] newfd=%d 客户端连接成功\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);

        info.newfd = newfd;
        info.cin = cin;

        //能运行到当前位置,则代表有客户端连接成功
        //则需要创建一个分支线程用来,与客户端交互
        if(pthread_create(&tid, NULL, deal_cli_msg, &info) != 0)
        {
            fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
            return -1; 
        }
    
        pthread_detach(tid);        //分离线程
    }   


    //关闭所有套接字文件描述符
    close(sfd);
    return 0;
}

//线程执行体
void* deal_cli_msg(void* arg)   //void* arg = (void*)&info
{

    //---------问题处------
    int newfd = ((struct cli_msg*)arg)->newfd;
    struct sockaddr_in cin = ((struct cli_msg*)arg)->cin;

    char buf[128] = ""; 
    ssize_t res = -1; 
    while(1)
    {   
        bzero(buf, sizeof(buf));
        //接收
        res = recv(newfd, buf, sizeof(buf), 0); 
        if(res < 0)
        {
            ERR_MSG("recv");
            break;
        }
        else if(0 == res)
        {
            fprintf(stderr, "[%s : %d] newfd=%d 客户端下线\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);
            break;
        }
        printf("[%s : %d] newfd=%d : %s\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf);

        //发送 -- 将数据拼接一个 *_* 发送回去
        strcat(buf, "*_*");
        if(send(newfd, buf, sizeof(buf), 0) < 0)
        {
            ERR_MSG("send");
            break;
        }
        printf("send success\n");
    }   
    close(newfd);

    pthread_exit(NULL);
}

上述程序中

1. 多线程中的newfd,能否修改成全局,不行,为什么?
2. 多线程中分支线程的newfd能否不另存,直接用指针间接访问主线程中的newfd,不行,为什么?

    //必须要另存,因为同一个进程下的线程共享其附属进程的所有资源
    //如果使用全局,则会导致每次连接客户端后, newfd和cin会被覆盖
    //如果使用指针间接访问外部成员变量,也会导致,成员变量被覆盖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/877964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jenkins 安装nodejs 14

参考&#xff1a; jenkins容器安装nodejs-前端问答-PHP中文网

微服务08-多级缓存

1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分…

VB6查表法编解Modbus RTU协议CRC16校验码

Modbus RTU协议CRC16编解码用VB6写起来比较啰嗦&#xff0c;需要做一些简单处理。下面就查表法&#xff0c;贴上源代码&#xff0c;并做一些简要说明。 源程序&#xff0c;对照上面的图看更方便。 Private Sub Command2_Click() Dim I As Integer, J As Integer Dim CRCHi As …

部署lawyer-llama

Git - Downloading PackageGit - Downloading PackageGit - Downloading Package 下载git&#xff0c;wget需要下载一下 &#xff08;GNU Wget 1.21.4 for Windows&#xff09;&#xff0c; Windows中git bash完全可以替代原生的cmd&#xff0c;但是对于git bash会有一些Linu…

HTTP之cookie基础学习

目录 Cookie 什么是Cookie Cookie分类 Cookie版本 Cookie工作原理 Cookie详解 创建cookie cookie编码 cookie过期时间选项 Cookie流程 Cookie使用 会话管理 个性化信息 记录用户的行为 Cookie属性 domain选项 path选项 secure选项 cookie…

【日常积累】RPM包依赖下载及私有yum仓库搭建

概述 某些时候&#xff0c;我们需要下载某个RPM包依赖的依赖。如某些内网环境&#xff0c;就需要自行准备rpm包。可以通过能上互联网的服务器进行相应的rpm包下载&#xff0c;然后在拷贝到相应的服务器安装&#xff0c;或者搭建自己的内容rpm包仓库。 查看*.rpm 包依赖&#…

修改el-tooltip组件的背景色

修改el-tooltip组件的背景色 // 提示气泡的背景色 .el-tooltip__popper{background-color: pink !important; } .popper__arrow {border-top-color: pink !important; } .popper__arrow:after {border-top-color: pink !important; }

基于通达信量化接口会实现自动交易吗?(股票自动下单接口)

通常情况下&#xff0c;在开发股票交易接口时&#xff0c;会包含多个接口功能的研发&#xff0c;因此通达信量化接口可以实现自动化交易。即通过通达信的API接口&#xff08;股票自动下单接口&#xff09;&#xff0c;可以实现与交易所的连接和交互&#xff0c;包括下单、撤单、…

Vue3 引用第三方Swiper内容触摸滑动简单应用

去官网查看更多教程→&#xff1a;Swiper官网 → 点击教程在vue中使用Swiper→ 在Vue中使用Swiper cd 到项目 安装Swiper&#xff1a; cnpm install --save swiper 安装指定版本 cnpm install --save swiper8.1.6 9.4.1 10.1.0…

【CI/CD】Rancher K8s

Rancher & K8s Rancher 和 K8s 的关系是什么&#xff1f;K8s 全称为 Kubernetes&#xff0c;它是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用。而 Rancher 是一个完全开源的企业级多集群 Kubernetes 管理平台&#xff0c;实现了 Kubernetes 集群在混合…

OpenCV图像处理——边缘检测

目录 原理Sobel检测算子方法应用 Laplacian算子Canny边缘检测原理 原理 Sobel检测算子 方法 应用 sobel_x_or_ycv.Sobel(src,ddepth,dx,dy,dst,ksize,scale,delta,borderType)import numpy as np import cv2 as cv import matplotlib.pyplot as pltimgcv.imread(./汪学长的随堂…

Python学习 -- 常用函数与实例详解

在Python编程中&#xff0c;数据转换是一项关键任务&#xff0c;它允许我们在不同数据类型之间自由流动&#xff0c;从而提高代码的灵活性和效率。本篇博客将深入探讨常用的数据转换函数&#xff0c;并通过实际案例为你展示如何巧妙地在不同数据类型之间转换。 数据类型转换函…

红日ATT&CK VulnStack靶场(三)

网络拓扑 web阶段 1.扫描DMZ机器端口 2.进行ssh和3306爆破无果后访问web服务 3.已知目标是Joomla&#xff0c;扫描目录 4.有用的目录分别为1.php 5.configuration.php~中泄露了数据库密码 6.administrator为后台登录地址 7.直接连接mysql 8.找到管理员表&#xff0c;密码加密了…

日常BUG—— SpringBoot项目DEBUG模式启动慢、卡死。

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 我们调试程序时&#xff0c;需要使用DEBUG模式启动SpringBoot项目&#xff0c; 有时候会发…

【C++入门到精通】C++入门 —— list (STL)

阅读导航 前言一、list简介1.概念2.特点 二、list的使用1.list的构造2.常见的操作⭕std::list类型的增、删、查、改 三、list与vector的对比温馨提示 前言 文章绑定了VS平台下std::list的源码&#xff0c;大家可以下载了解一下&#x1f60d; 前面我们讲了C语言的基础知识&…

如何做好一名网络工程师?具体实践?

预防问题 – 资格与认证 在安装线缆或升级网络时测试线缆是预防问题的有效方式。对已安装布线进行测试的方法有两种。 资格测试确定布线是否有资格执行某些操作 — 换言之&#xff0c;支持特定网络速度或应用。尽管“通过”认证测试也表明按标准支持某一网络速度或应用的能力…

国企的大数据岗位方向的分析

现如今大数据已无所不在&#xff0c;并且正被越来越广泛的被应用到历史、政治、科学、经济、商业甚至渗透到我们生活的方方面面中&#xff0c;获取的渠道也越来越便利。 今天我们就来聊一聊“大屏应用”&#xff0c;说到大屏就一定要聊到数据可视化&#xff0c;现如今&#xf…

vue自定义穿梭框支持远程滚动加载

分享-2023年资深前端进阶&#xff1a;前端登顶之巅-最全面的前端知识点梳理总结&#xff0c;前端之巅 *分享一个使用比较久的&#x1fa9c; 技术框架公司的选型(老项目)&#xff1a;vue2 iview-ui 方案的实现思路是共性的&#xff0c;展现UI样式需要你们自定义进行更改&#…

大数据课程J1——Scala的概述

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解Scala的特点&#xff1b; ⚪ 了解Scala的开发环境搭建&#xff1b; ⚪ 了解Scala的开发工具&#xff1b; 一、概述 1.简介 Scala既是面向对象的语言&#xff0c;…

大数据大屏的分析

今天又进行了大屏的训练&#xff0c;就是很多的报表头是最难的&#xff0c;因为确定了头&#xff0c;就确定了大屏的风格了。 今天的还是有点丑但是也是学习了。报班报班~~~~