46 udp网络程序

查询网络服务的命令

netstat -nlup
n: 显示数字
a:显示所有
u:udp服务
p:显示pid

在这里插入图片描述
Recv-Q收到的数量,本地ip和远端ip,00表示可以收到任何地址

网络聊天

服务端

定义一个server类,成员保存ip地址,端口号,文件描述符,是否运行
在这里插入图片描述
ip和端口号构造时设置默认值,“0.0.0.0”和“8000”
在这里插入图片描述

  • init函数内初始化套接字,返回文件描述符,然后绑定到os。
    在这里插入图片描述
    socket的三个参数,协议族IPv4,套接字类型面向字节流,协议0自动选择,面向字节流默认匹配udp
    返回值描述符,是后续功能传入的第一个参数

  • bind函数建立本地捆绑
    在这里插入图片描述
    第二个参数需要sockaddr类型,这个通用类型需要用sockaddr_in类型强转
    在这里插入图片描述

设置sockaddr_in的结构内容:
初始化为0
设置协议族
ip地址,需要将点分十进制转换为32位整数类型uint32_t,然后转为网络字节序
端口转为网络字节序

ip地址转化方法
在这里插入图片描述
先定义ip结构体,每个成员8位,总共4字节
字符串转整数:
1.先去掉.符号,切割为四个整数
2.然后定义一个uint32变量,强转为ip结构体
3.每个部分对应分割的字符串,将字符串转整数

整数转字符串:
1.强制转换为ip结构
2.将每部分转为字符串加上.拼接为点分

  • run函数内接收和发送消息

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

rev第一个参数标识符,第二个接收存放的缓冲区,缓冲区大小,调用方式,0表示阻塞直到有数据或错误,对方的协议结构,协议结构的长度(socklen_t类型)
send参数,标识符,发送内容,内容的大小,调用方式0,不使用特殊行为,对方的协议结构,协议的长度

启动服务端查看有没有服务
在这里插入图片描述

ip和port说明
ip可以固定设置,如果是本机ip,虚拟机是可以的,云服务器禁止绑定公网ip。ip填0的意思是,凡是发给我主机的数据,都根据端口号向上交付,如果有两张网卡,固定设置,只会收到发往一个的,0可以收到任一个网卡的,可以提前设置ip,也可以在绑定时设置

addrin.sin_addr.s_addr = INADDR_ANY;

【0,1023】是系统内定的端口号,有固定的应用层协议使用,http:80 https:443 mysql:3306。。也有几个这种特殊的,所以设置端口号时尽量往大一点设置

#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"enum
{SOCK_ERR = 1
};uint16_t defaultport = 8000;
std::string defaultip = "0.0.0.0";Log log;
class server
{ 
public:server(const std::string ip = defaultip, const uint16_t port = defaultport){_ip = ip;_port = port;_sockfd = 0;}void init(){//1.创建套接字int _sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){log.logmessage(fatal, "socket create error:%d", _sockfd);exit(SOCK_ERR);}log.logmessage(info, "socket create success:%d", _sockfd);//绑定sockaddr_in addrin;bzero(&addrin, sizeof(addrin));addrin.sin_family = AF_INET;addrin.sin_addr.s_addr = htonl(inet_addr(_ip.c_str()));addrin.sin_port = htons(_port);addrin.sin_addr.s_addr = INADDR_ANY;int ret = bind(_sockfd, (const sockaddr*)&addrin, sizeof(addrin));if (ret < 0){log.logmessage(fatal, "bind error:%s", strerror(errno));}log.logmessage(info, "bind success:%d", ret);}void run(){_isrunning = true;char buff[1024];while (_isrunning){sockaddr client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, &client, &len);if (n < 0){//log.logmessage(warning, "recv error:%s", strerror(errno));}buff[1024] = 0;std::string echo_string = buff;echo_string = "client#" + echo_string;sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, &client, len);}}~server(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;      // 网路文件描述符uint16_t _port;   // 任意地址bind 0std::string _ip;  // 表明服务器进程的端口号bool _isrunning;
};

客户端

先初始化端口和ip,这个安装软件使用的时候已经默认设好了。这里定为服务器的ip和刚刚启动服务的端口

设置目标协议addr结构,内容为上面初始化
在这里插入图片描述

打开套接字
在这里插入图片描述

客户端也是需要绑定的,只不过不是用户显示绑定,由os随机选择,一个端口号只能被一个进程绑定,对服务端也是如此。客户端的port只需要保证主机上的唯一性,在首次发送数据的时候会自动绑定

和服务端一样,发送和接受消息,只不过客户端先发送再接收
在这里插入图片描述

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>using namespace std;
int main()
{uint16_t port = 8000;string ip = "ip";sockaddr_in in;bzero(&in, sizeof(in));in.sin_family = AF_INET;in.sin_addr.s_addr = inet_addr(ip.c_str());in.sin_port = htons(port);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket error" << endl;}//client 需要绑定,但不显示绑定,由os自由选择string message;char buff[1024];while (true){cout << "please enter: ";getline(cin , message);sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr*)&in, sizeof(in));sockaddr_in rec;socklen_t len = sizeof(rec);ssize_t s = recvfrom(sockfd, buff, sizeof(buff) - 1, 0, (sockaddr*)&rec, &len);if (s > 0){buff[s] = 0;cout << buff << endl;}}close(sockfd);
}

注意
hton转字节序的函数是根据本机字节序情况转换,如果本机是小端调用几次会转几次

打开云主机端口
如果程序没问题,但收发不到消息,可以试着打开云主机的防火墙端口,以腾讯云为例:
登录控制台,查看详情,防火墙添加规则
在这里插入图片描述

服务端自定义数据处理
使用自定义函数类型,run调用实例出的函数,实现数据处理方法的自定义

using func_t = std::function<std::string(const std::string &)>; //定义函数类型

std::string handler(const std::string& str)
{std::string s = "server get message: ";s += str;std::cout << s << std::endl;return s;
}

命令bash

服务端可以收到信息了,也可以将收到的内容当做其他形式。如果将收到的内容当做xshell命令,就会执行命令
run执行的函数替换。popen函数可以创建子进程通信管道,将传入的命令创建子进程执行。将命令执行的结果添加到字符串内,回显发送给客户端

std::string execute(const std::string& cmd)
{//safecheck 不想执行的命令安全检查FILE* fp = popen(cmd.c_str(), "r");if (fp == nullptr){perror("popen");return "error";}string ret;char buff[4096];while (true){char *ok = fgets(buff, sizeof(buff), fp);if (ok == nullptr){break;}ret += buff;}pclose(fp);return ret;
}

在这里插入图片描述

本地环回地址

只会在本地协议走一遍,不会发向网络,通常用来测试

xshell

这就是为什么要用xshell登录,就相当于客户端,将命令发到了远端的服务器,接受到字符串执行命令,然后将结果发送回xshell显示

后台里有一个ssh的tcp服务,端口号22号接收发送的命令
在这里插入图片描述

win客户端

实现让win客户端和linux服务端网络通讯
win需要包含头文件WinSock2.h头文件和ws2_32.lib网络库
上面的头文件要在windows.h这个头文件之前,不然会报错

在这里插入图片描述

首先初始化网络版本信息,addr协议内容
在这里插入图片描述
打开套接字
在这里插入图片描述

收发功能和前面一样
在这里插入图片描述
清理数据
在这里插入图片描述
在这里插入图片描述
两边汉字编码不一样,可能会显示有问题

#include <iostream>
#include <string>
#include <WinSock2.h>
//#include <Windows.h>//关闭安全警告
#pragma warning(disable:4996)
//包含网络库
#pragma comment(lib, "ws2_32.lib")using namespace std;
string ip = "ip";
uint16_t port = 8000;int main()
{//初始化socket数据WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.S_un.S_addr = inet_addr(ip.c_str());server.sin_port = htons(port);//套接字SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == SOCKET_ERROR){cout << "sock error" << endl;return 1;}string message;char buff[1024];while (true){cout << "please enter: ";getline(cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr*) & server, sizeof(server));sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buff, 1023, 0, (sockaddr*)&temp, &len);if (s > 0){buff[s] = 0;cout << buff << endl;}}closesocket(sockfd);WSACleanup();return 0;
}

网络聊天室 (多线程)

聊天室功能需要用户名标识每一个主机,这里用对方的ip和port作为区分,发言的前缀
用一个无序map容器,string作为索引,值存addr结构
在这里插入图片描述

将收到的addr结构转为主机序列加入到显示屏幕的内容

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

检查如果是新的主机就加入到_online结构,并显示新主机加入
在这里插入图片描述
遍历容器内容,将最新消息发送给所有用户
在这里插入图片描述
sendto可以自动转网络序列,所以容器内容不需要转换

上面可以实现接收到不同用户的消息,但有个问题,客户端的getline没有内容时是一直阻塞住的,不输入内容的时候收不到其他消息。所以用多线程,一个线程收消息,一个线程发消息

两个线程都需要文件描述符和addr结构
定义一个结构体,将初始化的内容赋值给结构体在这里插入图片描述

在这里插入图片描述
这时可以显示其他人的消息,但自己因为发送和接收的顺序无法正常接收。需要将输入和输出的终端分开

在这里插入图片描述

/dev/pts 这个文件里保存了所有打开的终端

在这里插入图片描述

0号终端重定向到6,显示到了1号页面中,所以文件6代表了下面这个终端,5是上面的终端。在接收消息后想显示到上面的终端,下面的用来发送,由于线程的文件描述共享,所以将标准错误重定向到5,在下面打开服务端

在这里插入图片描述

上面的内容可以简化,打开两个端口,提前确定哪个用来显示,另一个启动客户端,将标准错误重定向到显示端
在这里插入图片描述

服务端

#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "log.hpp"enum
{SOCK_ERR = 1
};uint16_t defaultport = 8000;
std::string defaultip = "0.0.0.0";
using func_t = std::function<std::string(const std::string &)>; //定义函数类型
//typedef std::function<std::string(const std::string &)> func_tLog log;
class server
{ 
public:server(const std::string ip = defaultip, const uint16_t port = defaultport){_ip = ip;_port = port;_sockfd = 0;}void init(){//1.创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){log.logmessage(fatal, "socket create error:%d", _sockfd);exit(SOCK_ERR);}log.logmessage(info, "socket create success:%d", _sockfd);//绑定struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = inet_addr(_ip.c_str());local.sin_port = htons(_port);//local.sin_addr.s_addr = INADDR_ANY;int ret = bind(_sockfd, (const sockaddr*)&local, sizeof(local));if (ret < 0){log.logmessage(fatal, "bind error:%s", strerror(errno));}log.logmessage(info, "bind success:%d", ret);}void checkuser(const struct sockaddr_in& sock, const string ip, const uint16_t port){auto it = _online.find(ip);if (it == _online.end()){_online.insert({ip, sock});cout << "[" << ip << ":" << port << "] add new user" << endl;}}void broadcast(const string& message, string ip, uint16_t port){for (const auto user : _online){socklen_t len = sizeof(user.second);sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);}}void run(func_t func){_isrunning = true;char buff[1024];while (_isrunning){sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (sockaddr*)&client, &len);if (n < 0){//log.logmessage(warning, "recv error:%s", strerror(errno));continue;}buff[n] = 0;//获取端口号和ipstring ip = inet_ntoa(client.sin_addr);// 网络字节序转主机uint16_t port = ntohs(client.sin_port);checkuser(client, ip, port);std::string echo_string = "[" + ip + ":" + to_string(port) + "]: " + buff;broadcast(echo_string, ip, port);// std::cout << echo_string << endl;//  echo_string = func(echo_string);//sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);}}~server(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;      // 网路文件描述符uint16_t _port;   // 任意地址bind 0std::string _ip;  // 表明服务器进程的端口号bool _isrunning;//用ip检索unordered_map<string, struct sockaddr_in> _online;  //注册主机
};

客户端

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>using namespace std;string path = "/dev/pts/5";
struct thread_data
{int _sockfd;struct sockaddr_in _server;string _ip;
};void *send_message(void * temp)
{thread_data *td = (struct thread_data*)temp;string message;cout << "welcome " << td->_ip << endl;while (true){cout << "please enter: ";getline(cin, message);sendto(td->_sockfd, message.c_str(), message.size(), 0, (const sockaddr *)&td->_server, sizeof(td->_server));}}void* recv_message(void* temp)
{// int fd = open(path.c_str(), O_WRONLY);// if (fd < 0)// {//     perror("open");// }// dup2(fd, 2);thread_data *td = (struct thread_data *)temp;char buff[1024];sockaddr_in rec;socklen_t len = sizeof(rec);while (true){ssize_t s = recvfrom(td->_sockfd, buff, sizeof(buff) - 1, 0, (sockaddr *)&rec, &len);if (s > 0){buff[s] = 0;cerr << buff << endl;}}//close(fd);
}int main()
{thread_data td;uint16_t port = 8000;string ip = "ip";sockaddr_in in;bzero(&in, sizeof(in));td._server.sin_family = AF_INET;td._server.sin_addr.s_addr = inet_addr(ip.c_str());td._ip = ip;td._server.sin_port = htons(port);td._sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td._sockfd < 0){cout << "socket error" << endl;}//client 需要绑定,但不显示绑定,由os自由选择pthread_t tid_send;pthread_t tid_recv;pthread_create(&tid_send, nullptr, send_message, &td);pthread_create(&tid_recv, nullptr, recv_message, &td);  pthread_join(tid_send, nullptr);pthread_join(tid_recv, nullptr);close(td._sockfd);
}

地址转换函数

介绍基于ipv4的socket网络编程,struct in_addr sin_addr表示32位的ip地址,但是我们通常用点分十进制字符串表示ip地址,一下函数可以在字符串表示和in_addr表示之间转换

其中inet_pton和inet_ntop不仅可以转换ipv4的in_addr,还可以转换ipv6的in6_addr,因此接口是void* addrptr

字符串转in_addr函数:
在这里插入图片描述在这里插入图片描述
in_addr转字符串函数:
在这里插入图片描述在这里插入图片描述

关于inet_ntoa

这个函数返回一个char*,自己内部申请了一块内存保存ip的结果,那么需不需要手动释放呢?

在这里插入图片描述
man手册说返回结果放在了静态存储区,不需要手动释放

当多次调用时,两个不同ip都会转换为相同的结果,以最后一个为准
因为inet_ntoa把结果放到内部的静态缓存区,第二次调用的时候会覆盖上一次结果。APUE中,明确提出inet_ntoa不是线程安全的函数,但是centos7上测试,没有出现问题,可能是内部实现加了互斥锁,在多线程环境下,推荐使用inet_ntop,这个函数由用户提供缓冲区结果,可以规避线程安全的问题

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

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

相关文章

龟兔赛跑(基于GUI与多线程实现)

直击龟兔赛跑现场 下面这张图是我们设计龟兔赛跑界面的初始效果与基本组成结构&#xff1a; 接下来是我仅代表我个人提出的一些疑问与解答&#xff1a; 1、俩动物以图片的形式显示&#xff1f; 其实在这里两个动物类就像标签一样 标签组件是什么&#xff1f;用于短文本字符串…

从loss角度理解LLM涌现能力

如今的很多研究都表明小模型也能出现涌现能力&#xff0c;本文的作者团队通过大量实验发现模型的涌现能力与模型大小、训练计算量无关&#xff0c;只与预训练loss相关。 作者团队惊奇地发现&#xff0c;不管任何下游任务&#xff0c;不管模型大小&#xff0c;模型出现涌现能力…

云贝教育 |【直播课】5月19日Oracle 19c OCM认证大师课 即将上课了!(附课件预览)

贝教育独家认证课OCM全网价格最低&#xff0c;性价比最高&#xff01;&#xff01;&#xff01; Oracle 19c OCM认证大师培训 - 课程体系 - 云贝教育 (yunbee.net) OCM部分课件预览 Oracle Database 19c Certified Master Exam (OCM) 认证大师 25 天 / 150课时 什么是Oracle 1…

0X JavaSE-- UML、

# Unified Modeling Language UML 统一建模语言 UML 是一种图形化的语言。 UML 不是专门为 Java 准备的。 只要是面向对象的编程语言&#xff0c;开发前的设计&#xff0c;都需要画 UML 图进行系统设计。 最常用的四个 UML 图是 类图&#xff08;Class Diagram&#xff09;&…

启明云端ESP32 C3 模组WT32C3通过 MQTT 连接 AWS

ESP32-C3因为其性价比高&#xff0c;价格便宜性能又好一直是量产的首选芯片&#xff0c;随着量产项目扩大&#xff0c;对接云服务器的情况也增加了&#xff0c;接下来小启给大家分享下启明云端ESP32-C3模组——WT32C3系列模组/开发板连接AWS亚马逊云方法。 WT32C3系列模组 WT3…

笨方法自学python(一)

我觉得python和c语言有很多相似之处&#xff0c;如果有c语言基础的话学习python也不是很难。这一系列主要是学习例题来学习python&#xff1b;我用的python版本是3.12 代码编辑器我用的是notepad&#xff0c;运行py程序用cmd 现在开始写第一个程序&#xff1a; print ("…

ViLT 浅析

ViLT 浅析 论文链接&#xff1a;ViLT 文章目录 ViLT 浅析创新点网络结构总结 创新点 本文先分析了4种不同类型的Vision-and-Language Pretraining(VLP) 其中每个矩形的高表示相对计算量大小&#xff0c;VE、TE和MI分别是visual embedding、text embedding和modality interact…

Excel 根据分类及组内序号进行编码

例题描述和简单分析 Excel 记录课程数据&#xff0c;未排序&#xff0c;部分如下&#xff1a; ABC1CourseDateTime2Word1-Sep-209:003Word1-Sep-209:004PowerPoint1-Sep-209:005Word1-Sep-2012:006PowerPoint1-Sep-2012:007Excel1-Sep-2012:008Word1-Sep-2012:00 现在要新增…

【CAD建模号】学习笔记(四):工作平面

工作平面介绍 CAD建模号右侧导航栏提供了很多便捷的工具&#xff0c;有测量工具、坐标系、模型和图层切换、视图切换等。 1. 测量工具组 测量工具可以测量图形的几何体积&#xff0c;长度&#xff0c;角度等。工具组包含如下&#xff1a; 测量几何&#xff1a;可以测量图形的面…

笨方法自学python(二)-注释

注释和#号 程序里的注释是很重要的。它们可以用自然语言告诉你某段代码的功能是什么。在你想要临时移除一段代码时&#xff0c;你还可以用注解的方式将这段代码临时禁用。 # A comment, this is so you can read your program later. # Anything after the # is ignored by py…

【Python】IPython 魔法命令使用指南

依然记得从你口中 说出再见坚决如铁 昏暗中有种烈日灼身的错觉 黄昏的地平线 划出一句离别 爱情进入永夜 依然记得从你眼中 滑落的泪伤心欲绝 混乱中有种热泪烧伤的错觉 黄昏的地平线 割断幸福喜悦 相爱已经幻灭 &#x1f3b5; 周传雄《黄昏》 %run - 运行…

面向对象的三大特性:封装、继承、多态

一、封装 封装是面向对象的核心思想。是以类为载体&#xff0c;将对象的属性和行为封装起来&#xff0c;对外隐藏其实现细节。 封装保证了类内部数据结构的完整性&#xff0c;使得外部&#xff08;使用该类的用户&#xff09;不能轻易地直接操作此数据结构&#xff0c;只能执…

考了PMP后,NPDP到底还有没有必要考?NPDP通关宝典来啦!

NPDP和PMP相比&#xff0c;两者的相同点都是由美国发起的&#xff0c;都是管理行业的证书。区别也很大&#xff0c;PMP是项目经理国际认证证书&#xff0c;NPDP是产品经理认证证书&#xff0c;不过PMP已经由外专局引入国内二十多年了&#xff0c;在市面上知名度更高&#xff0c…

svg 元素 getBoundingClientRect() 数值为 0

问题 在页面在刷新时&#xff0c;想要立即获取页面中 svg 元素的宽高&#xff0c;做进一步的计算。发现通过 getBoundingClientRect 获取会有一定几率获取值为 0。 解决方案 监听 svg 元素的 load 事件&#xff0c;在回调中再获取。 svgElem.addEventListener("load&…

Vite创建Vue3项目识别 ../ 与 @/ 引入路径

在使用vite脚手架生成项目时,会出现一些引入路径失败的错误 例子:router中用 component引入路径时 引入 ../ 路径失败 找不到模块“../views/login/index.vue”或其相应的类型声明 {path: "/login",name: "login",component: () > import("../v…

鸿蒙开发接口Ability框架:【(窗口扩展能力)】

窗口扩展能力 WindowExtensionAbility基于ExtensionAbility&#xff0c;WindowExtensionAbility中展示的内容作为一个控件(AbilityComponent)内容展示在其他应用窗口中&#xff0c;实现在一个窗口中展示多个应用程序内容的功能。 说明&#xff1a; 本模块首批接口从API versio…

互斥锁和自旋锁的实现机制

本文介绍互斥锁和自旋锁的实现原理和工作过程 一、互斥锁 1.内存标记——线程id 互斥锁会记录下访问锁的线程的id&#xff0c;用于进行线程切换、组织阻塞队列等操作 2.阻塞队列 当多个线程试图获取同一把互斥锁&#xff0c;没有获取的锁的线程会被组织到阻塞队列中&#…

Keysight 是德 N1077B 光/电时钟恢复设备,收藏保存

Keysight N1077B是一款光/电时钟恢复设备&#xff0c;支持115 MBd至24 GBd的数据速率范围&#xff0c;适用于多模和单模光信号以及电信号。该设备能够处理PAM4和NRZ两种类型的数据信号&#xff0c;并提供符合标准的时钟恢复功能。 N1077B具备可调峰值和环路带宽&#xff08;高…

leetcode206-Reverse Linked List

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 分析 用一个指针记录当前位置&#xff0c;另外一个指针记录当前位置的前一个位置&#xff0c…

【经验分享】图片自适应窗口大小css;CSS实现背景图片全屏铺满自适应的方式

目录 设置背景颜色和边距 设置背景图片 调整背景图片尺寸和位置 完整代码 使用效果如下&#xff08;展示&#xff09; 网页版图片效果展示 手机版图片效果展示 如何使用 CSS 创建网页背景效果 在网页设计中&#xff0c;背景是一个重要的视觉元素&#xff0c;它可以为网…