【Linux网络编程三】Udp套接字编程(简易版服务器)

【Linux网络编程三】Udp套接字编程(简易版服务器)

  • 一.创建套接字
  • 二.绑定网络信息
    • 1.构建通信类型
    • 2.填充网络信息
      • ①网络字节序的port
      • ②string类型的ip地址
    • 3.最终绑定
  • 三.读收消息
    • 1.服务器端接收消息recvfrom
    • 2.服务器端发送消息sendto
    • 3.客户端端发送消息sendto
    • 4.客户端端接收消息recvfrom
  • 四.关于绑定ip与port细节
  • 五.客户端不需要主动绑定
  • 六.客户端/服务器端代码

UDP套接字编程:
网络通信本质是进程之间通信,所以我们需要两个进程来网络通信,假设一个为服务器进程,一个为客户端进程演示。

一.创建套接字

在这里插入图片描述
socket()接口可以用来创建套接字,它总共有三个参数。

第一个参数domain,表示通信的类型,是使用网络通信还是本地通信由用户选择,当传入AF_INET/AF_INET6时表示使用网络通信。
第二个参数type,表示套接字的类型,是TCP呢还是UDP呢。如果传递SOCK_DGRAM表示是UDP,如果传递的SOCK_STREAM表示是TCP.
第三个参数protocol,表示协议,默认为0.

创建套接字成功后会返回一个文件描述符sockfd,也就是创建套接字的本质就是打开一个文件!
在这里插入图片描述

服务器端进程在创建完套接字后,该做什么呢?该套接字(文件)是属于你服务器进程的,然后呢?假设客户端也打开一个套接字(文件),这两个套接字文件都是属于同一个,也就是满足了进程间通信的前提条件:能看到一个共享资源。
而两个进程看到同一份资源后,那么该如何准确的发送给对方呢?因为可能打开这个套接字文件的进程有很多。所以接下来就是两个进程需要知道各自对方能够唯一标识自己的ip地址和端口号等网络信息。这样才能够准确的将数据从客户端进程发送给服务器进程。

 // 1.创建udp套接字,本质就是打开网络套接字文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 表示创建失败{lg(Fatal, "socket create error,socket: %d", _sockfd);exit(SOCKET_ERR); // 创建失败之间退出}

二.绑定网络信息

套接字创建成功后,就相当于打开了一个文件,该文件是就两个进程通信的共享资源,不过要想准确通信,还需要知道各自进程的ip地址和端口号,这样往文件里传输数据时,对端才能准确接收到,也就是这个文件需要绑定一些各自进程的网络信息才能准确的传递到对端。

进程之间网络通信需要先绑定端口号,这里创建完套接字后,就需要让该进程的端口号与套接字绑定。这样对端的进程才能找到这个进程
在这里插入图片描述
第一个参数就是创建的套接字,也就是文件对象
第二个参数就是要绑定的该进程的网络信息,包括端口号,ip地址等
第三个参数是网络信息结构体对象的大小
这个网络信息结构体对象要求传的是统一的接口,但是你实际使用什么类型的网络通信,你就定义什么类型,然后传递时强转即可。

 // 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addrstruct sockaddr_in local;                       // 网络通信类型bzero(&local, sizeof(local));                   // 将内容置为0local.sin_family = AF_INET;                     // 网络通信类型local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求socklen_t len = sizeof(local);// 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息

1.构建通信类型

比如如果我要网络通信,那么就需要定义一个sockaddr_in结构体对象。
在这里插入图片描述
该结构体对象里有三个需要初始化的参数:
1.sin_family:要使用的通信类型
2.sin_port:该进程的端口号
3.sin_addr:该进程的ip地址

sin_addr这个结构体对象里面只有一个参数,s_addr这个也就是真正的ip地址。

2.填充网络信息

①网络字节序的port

在给套接字绑定网络信息之前,需要将网络信息给构建好,就比如端口号,我们需要将当前进程的端口号填充到sockaddr_in这个结构体对象里。
不过端口号在网络通信中,是要不但的来回发送的,不管是客户端,还是服务器端,两个进程通信就必须知道对方的端口号。
所以端口号是需要发送到网络里的,所以在填充时,必须是网络字节序。
在这里插入图片描述
也就是主机转网络字节序

②string类型的ip地址

用户一般喜欢用string类型的ip地址类型,这样比较好显示。
但是系统里的ip是uint16_t类型的,所以我们在填充初始化时.
【要求1】首先需要将string类型的参数转换成uint16_t类型。
在这里插入图片描述

【要求2】其次ip地址也需要发送到网络里的,所以也必须是网络字节序。
在这里插入图片描述
系统里给了我们相关的接口:inet_addr(char*cp)
在这里插入图片描述

它就是可以将string类型的数据转换成uint16_t类型,并且将主机字节序转换成网络字节序。

3.最终绑定

在这里插入图片描述

  if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败{lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功

三.读收消息

1.服务器端接收消息recvfrom

一般客户端进程对服务器进程发送消息,服务器进程接收客户端发送的消息。那么服务器进程如何接收客户端发送的消息呢?
服务器进程是从套接字接收消息,也就是该进程创建的文件里接收。
在这里插入图片描述
不过服务器除了能够接收到消息,还需要知道是谁给它发送的消息,这样它才可以将消息再发送回去。
所以就需要一个sockaddr_in结构体对象,作为输出型参数,将发送端的网络信息存储下来。也就是将客户端的网络信息带出来。

所以recvfrom的功能
1.除了接收到对端发送的消息内容
2.还可以知道对端的网络消息。知道是谁发送过来的。

            struct sockaddr_in client;socklen_t len = sizeof(client);// 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去// 利用一个输出型参数,将对方的网络信息填充到里面ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));continue;}// 读取成功,将网络文件里的消息读取到buffer里buffer[n] = 0; // 字符串形式

2.服务器端发送消息sendto

服务器一般只要用来接收其他客户端的消息,然后加工处理,再将数据发送回去,所以服务器将数据再发送回客户端,也就是往套接字里发送消息,而要发送的客户端网络信息,刚好被存储在输出型参数里。因为客户但是主动发送消息的,服务器一定是先收到客户端发送的消息,然后会将客户端的网络消息存储起来,再根据客户端网络信息发送回去。
在这里插入图片描述

 // 3.将应答发送回去
// 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);

因为服务器端在接收客户端发送的消息时,还会保存客户端的网络信息,所以如果服务器想发送消息给客户端是很容易的。

3.客户端端发送消息sendto

客户端将消息发送给服务器端,该怎么发送呢?通过套接字(文件)发送给服务器,发送时需要服务器端的网络信息,比如ip地址端口号等,这样客户端才能准确的发送给该服务器端。
在这里插入图片描述
在这里插入图片描述

//构建服务器端的网络信息std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序server.sin_port = htons(serverport);socklen_t len = sizeof(server);// 1.创建套接字---本质就是打开网络文件int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket create err" << endl;return 1;}// 创建成功// 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定char outbuffer[1024];string message;getline(cin, message);//1.发送给服务器sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);

4.客户端端接收消息recvfrom

客户端要想接收服务器发送回来的消息,只需要读取套接字里的消息即可。利用recvfrom接口读取客户端的套接字。
不过还需要定义一个结构体对象用来保存服务器端的网络信息,虽然客户端已经知道,但是接口要求,所以必须定义。

        struct sockaddr_in temp;socklen_t l=sizeof(temp); //2.接收服务器的应答ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);

四.关于绑定ip与port细节

【细节1】当服务器端绑定ip时,如果ip地址是’0.0.0.0"则表示任意ip地址绑定。
就表示不管客户端发送时目的ip是多少,只要消息发送到服务器的主机上,那么都可以接收,并将端口号往上发送。

也就是只要是发送到我这台主机上的报文,那么就会忽略到该报文的目的ip地址是多少,只看端口号。
这就表示任意ip地址绑定。相当于一种动态绑定。可以接收到所有发送到我这台主机上的报文。并往上传递。
在这里插入图片描述
还要注意在云服务器上,公用ip是不能随意绑定的,无法直接绑定,不管是TCP还是UDP。除非是127.0.0.1这个ip地址是专门用来检测服务器和客户端的。其他的不要瞎绑定。但是在虚拟机上可以。
【细节2】端口号不是随意绑定的,有些是已经被固定使用的,【0,1023】是属于系统内定的端口号,一般要有固定的应用层协议使用。
所以我们最好使用1023后面的。

【总结】
如果服务器端的ip地址默认是0的话,那么我们只需要知道服务器端的端口号即可。在这里插入图片描述

五.客户端不需要主动绑定

在这里插入图片描述
所以客户端是不需要显示的绑定相关的端口号和ip地址的。操作系统会帮它自动绑定。
本质原因是用户不关心客户端的端口号和ip地址等网络信息,所以不需要显示绑定。
关键是要唯一。由操作系统随机选择。

但是服务器端必须主动绑定端口号!为什么呢?
因为用户关心服务器的端口号,必须知道服务器端的端口号。不然无法找到服务器端。
在这里插入图片描述

六.客户端/服务器端代码

【客户端】

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;#include <iostream>
#include <strings.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
// 客户端
void Usage(std::string proc)
{std::cout << "\n\r./Usage: " << proc << " serverip serverport\n"<< endl;
}
// 启动客户端时要求是: ./Client 服务器ip 服务器port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序server.sin_port = htons(serverport);socklen_t len = sizeof(server);// 1.创建套接字---本质就是打开网络文件int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket create err" << endl;return 1;}// 创建成功// 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定// 3.往服务器的套接字里发送消息--需要知道服务器的ip和端口号,目的ip和目的port,将ip和port填入结构体对象里char outbuffer[1024];string message;while (true){cout<<"Please enter@ ";getline(cin, message);//1.发送给服务器sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t l=sizeof(temp); //2.接收服务器的应答ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);if(s>0){//接收成功outbuffer[s]=0;cout<<outbuffer<<endl;}}close(sockfd);return 0;
}

【服务器端】

@ -0,0 +1,103 @@
#pragma once#include "Log.hpp"
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8080;
Log lg; // 日志,默认往显示屏打印
typedef std::function<std::string(const std::string&)> func_t;//相当于定义了一个函数指针
//返回值是string类型,函数参数也是string类型,利用函数回调的方法,将服务器端对数据的处理操作进行分离,由上层传递的函数来决定如何处理
enum
{SOCKET_ERR = 1,BIND_ERR
};
class Udpserver
{
public:Udpserver(const uint16_t &port = defaultport, std::string &ip = defaultip) : _sockfd(0), _port(port), _ip(ip){}void Init(){// 1.创建udp套接字,本质就是打开网络套接字文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 表示创建失败{lg(Fatal, "socket create error,socket: %d", _sockfd);exit(SOCKET_ERR); // 创建失败之间退出}// 创建成功lg(Info, "socket create success,socket: %d", _sockfd);// 2.绑定服务器的套接信息,比如ip和端口号// 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addrstruct sockaddr_in local;                       // 网络通信类型bzero(&local, sizeof(local));                   // 将内容置为0local.sin_family = AF_INET;                     // 网络通信类型local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求socklen_t len = sizeof(local);// 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败{lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功}void Run(func_t func) // 服务器是一旦启动不会退出,服务器接收消息,并发送答应{// 1.接收信息char buffer[SIZE];while (true){struct sockaddr_in client;socklen_t len = sizeof(client);// 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去// 利用一个输出型参数,将对方的网络信息填充到里面ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));continue;}// 读取成功,将网络文件里的消息读取到buffer里buffer[n] = 0; // 字符串形式// 2.加工处理// std::string info = buffer;// std::string echo_string  = "server echo# " + info;std::string info=buffer;std::string echo_string=func(info);//将接收的信息由外层函数进行处理// 3.将应答发送回去// 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);}}~Udpserver(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;     // 套接字文件描述符std::string _ip; // 我们习惯用string类型的ip地址uint16_t _port;  // 服务器进程的端口号
};

#include "Udpserver.hpp"
#include <memory>
#include <cstdio>
#include <stdlib.h>
// "120.78.126.148" 点分十进制字符串风格的IP地址
std::string handler(const std::string &info)
{ std::string res="get a message: ";res+=info;std::cout<<res<<std::endl;return res;//最后将处理的数据返回回去
}#include "Udpserver.hpp"
#include <memory>
#include <cstdio>void Usage(std::string proc)
{std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}
//服务器进程,启动时,按照./Udpserver+port的形式传递
int main(int args,char* argv[])
{if(args!=2){Usage(argv[0]);exit(0);}uint16_t port=std::stoi(argv[1]);std::unique_ptr<Udpserver> svr(new Udpserver(port));//首先创建一个服务器对象指针//智能指针,用一个UdpServer指针来管理类对象svr->Init();//初始化服务器svr->Run(handler);//启动服务器return 0;
} 

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

海康威视球机摄像头运动目标检测、跟踪与轨迹预测

一、总体方案设计 运动目标检测与跟踪方案设计涉及视频流的实时拍摄、目标检测、轨迹预测以及云台控制。以下是四个步骤的详细设计&#xff1a; 1.室内场景视频流拍摄 使用海康威视球机摄像头进行室内视频流的实时拍摄。确保摄像头能覆盖整个室内空间&#xff0c;以便捕捉所…

如何修改远程端服务器密钥

前言 一段时间没改密码后&#xff0c;远程就会自动提示CtrlAltEnd键修改密码。但我电脑是笔记本&#xff0c;没有end键。打开屏幕键盘按这三个键也没用。 解决方法 打开远程 1、远程端WINC 输入osk 可以发现打开了屏幕键盘 2、电脑键盘同时按住CtrlAlt&#xff08;若自身电…

【iOS ARKit】人形提取

为解决人形分离和深度估计问题&#xff0c;ARKit 新增加了 Segmentation Buffer&#xff08;人体分隔缓冲区&#xff09;和Estimated Depth Data Buffer&#xff08;深度估计缓冲区&#xff09;两个缓冲区。人体分隔缓冲区作用类似于图形渲染管线中的 Stencil Buffer&#xff0…

机器学习--K近邻算法,以及python中通过Scikit-learn库实现K近邻算法API使用技巧

文章目录 1.K-近邻算法思想2.K-近邻算法(KNN)概念3.电影类型分析4.KNN算法流程总结5.k近邻算法api初步使用机器学习库scikit-learn1 Scikit-learn工具介绍2.安装3.Scikit-learn包含的内容4.K-近邻算法API5.案例5.1 步骤分析5.2 代码过程 1.K-近邻算法思想 假如你有一天来到北京…

2月6日作业

1.现有无序序列数组为23,24,12,5,33,5347&#xff0c;请使用以下排序实现编程 函数1:请使用冒泡排序实现升序排序 函数2:请使用简单选择排序实现升序排序 函数3:请使用快速排序实现升序排序 函数4:请使用插入排序实现升序排序 #include<stdio.h> #include<string.h&…

嵌入式软件bug分析基本要求

摘要&#xff1a;软件从来不是一次就能完美的&#xff0c;需要以包容的眼光看待它的残缺。那问题究竟为何产生&#xff0c;如何去除呢&#xff1f; 1、软件问题从哪来 软件缺陷问题千千万万&#xff0c;主要是需求、实现、和运行环境三方面。 1.1 需求描述偏差 客户角度的描…

十分钟GIS——geoserver+postgis+udig从零开始发布地图服务

1数据库部署 1.1PostgreSql安装 下载到安装文件后&#xff08;postgresql-9.2.19-1-windows-x64.exe&#xff09;&#xff0c;双击安装。 指定安装目录&#xff0c;如下图所示 指定数据库文件存放目录位置&#xff0c;如下图所示 指定数据库访问管理员密码&#xff0c;如下图所…

正点原子--STM32通用定时器学习笔记(2)

1. 通用定时器输入捕获部分框图介绍 捕获/比较通道的输入部分&#xff08;通道1&#xff09; 输入通道映射CC1S[1:0]→采样频率CKD[1:0]→滤波方式IC1F[3:0]→边沿检测方式CC1P→捕获分频ICPS[1:0]→使能捕获CC1E 输入部分对相应的TIx输入信号采样&#xff0c;并产生一个滤波后…

【Linux取经路】探寻shell的实现原理

文章目录 一、打印命令行提示符二、读取键盘输入的指令三、指令切割四、普通命令的执行五、内建指令执行5.1 cd指令5.2 export指令5.3 echo指令 六、结语 一、打印命令行提示符 const char* getusername() // 获取用户名 {return getenv("USER"); }const char* geth…

【教程】Linux使用git自动备份和使用支持文件恢复的rm命令

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 背景介绍 首先非常不幸地告诉你&#xff1a;Linux 系统的标准 rm 命令不支持文件恢复功能。一旦使用 rm 删除了文件或目录&#xff0c;它们就会从文件系统中永久删除&#xff0c;除非你使用专门的文件恢复工具尝试…

【Spring基础】从0开始学习Spring(2)

前言 在上篇文章&#xff0c;我已经讲了Spring中最核心的知识点&#xff1a;IoC&#xff08;控制反转&#xff09;以及DI&#xff08;依赖注入&#xff09;。这篇文章&#xff0c;我将讲一下关于Spring框架中的其它比较琐碎但是又还是挺重要的知识点&#xff0c;因此&#xff…

灵敏可靠的缓激肽(Bradykinin)ELISA检测试剂盒

灵敏可靠的ELISA试剂盒&#xff0c;用于检测血浆、血清和尿液样本中的缓激肽 缓激肽&#xff08;Bradykinin&#xff09;于1949年被发现&#xff0c;由血浆中的球蛋白前体在蛋白酶的作用下生成。它的名字表明它会促使肠道缓慢运动。早在1909年&#xff0c;人们就注意到在尿液中…

动态内存管理(2)

文章目录 4. 几个经典的笔试题4.1 题目14.2 题目24.3 题目34.4 题目4 5. C/C程序的内存开辟6. 动态通讯录7. 柔性数组7.1 柔性数组的特点7.2 柔性数组的使用7.3 柔性数组的优势 4. 几个经典的笔试题 4.1 题目1 #include <stdio.h> #include <stdlib.h> #include …

【极数系列】Flink集成KafkaSink 实时输出数据(11)

文章目录 01 引言02 连接器依赖2.1 kafka连接器依赖2.2 base基础依赖 03 使用方法04 序列化器05 指标监控06 项目源码实战6.1 包结构6.2 pom.xml依赖6.3 配置文件6.4 创建sink作业 01 引言 KafkaSink 可将数据流写入一个或多个 Kafka topic 实战源码地址,一键下载可用&#xf…

零代码3D可视化快速开发平台

老子云平台 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。此技术已经在全球申请了专利…

李宏毅LLM——生成式学习的两种策略

文章目录 生成式学习的两种策略&#xff1a;各个击破和一次到位成为专才&#xff1a;成为通才神秘的 In-context Learning 能力Instruction LearningChain of Thought Prompting 生成式学习的两种策略&#xff1a;各个击破和一次到位 对应视频的 P7-P11 生成有结构的复杂物件也…

(2)(2.14) SPL Satellite Telemetry

文章目录 前言 1 本地 Wi-Fi&#xff08;费用&#xff1a;30 美元以上&#xff0c;范围&#xff1a;室内&#xff09; 2 蜂窝电话&#xff08;费用&#xff1a;100 美元以上&#xff0c;范围&#xff1a;蜂窝电话覆盖区域&#xff09; 3 手机卫星&#xff08;费用&#xff…

React+Echarts实现数据排名+自动滚动+Y轴自定义toolTip文字提示

1、效果 2、环境准备 1、react18 2、antd 4 3、代码实现 原理&#xff1a;自动滚动通过创建定时器动态更新echar的dataZoom属性startValue、endValue&#xff0c;自定义tooltip通过监听echar的鼠标移入移出事件&#xff0c;判断tooltTip元素的显隐以及位置。 1、导入所需组…

CSS太极动态图

CSS太极动态图 1. 案例效果 我们今天学习用HTML和CSS实现动态的太极&#xff0c;看一下效果。 2. 分析思路 太极图是由两个旋转的圆组成&#xff0c;一个是黑圆&#xff0c;一个是白圆。实现现原理是使用CSS的动画和渐变背景属性。 首先&#xff0c;为所有元素设置默认值为0…

uniapp 本地存储的方式

1. uniapp 本地存储的方式 在uniapp开发中&#xff0c;本地存储是一个常见的需求。本地存储可以帮助我们在客户端保存和管理数据&#xff0c;以便在应用程序中进行持久化存储。本文将介绍uniapp中本地存储的几种方式&#xff0c;以及相关的代码示例。 1.1. 介绍 在移动应用开发…