TCP流套接字编程

在这里插入图片描述

文章目录

  • 前言
  • TCP 和 UDP 的特点对比
  • TcpEchoServer 服务端实现
    • 1. 创建 ServerSocket 类实现通信双方建立连接
    • 2. 取出建立的连接实现双方通信
    • 3. 服务端业务逻辑实现
    • 关闭资源
    • 服务端整体代码
  • TcpEchoClient 客户端实现
    • 1. 创建出 Socket 对象来与服务端实现通信
    • 2. 实现客户端的主要逻辑
    • 服务端整体代码
    • 功能实现
    • 多个客户端访问服务器
    • 优化后的服务端代码
  • 根据回显服务器实现一个简单的字典功能

前言

前面我们学习了使用 UDP 数据报实现套接字编程,因为 UDP 和 TCP 存在一些差异,所以在实现 TCP 流套接字编程的时候也会出现一些不同的做法,这篇文章我将为大家分享如何使用 TCP 流实现套接字编程。

TCP 和 UDP 的特点对比

要想实现 TCP 流的套接字编程就需要先知道 TCP 和 UDP 的区别。

  1. TCP 是有连接的,UDP 是无连接的。
  2. TCP 是可靠传输,UDP 是不可靠传输。
  3. TCP 是面向字节流,UDP 是面向数据报。
  4. TCP 和 UDP 都是全双工的。

这里实现 TCP 流套接字编程与 UDP 数据报套接字编程的区别则主要体现在 TCP 有连接,UDP 无连接;TCP 面向字节流,UDP 面向数据报。

TcpEchoServer 服务端实现

1. 创建 ServerSocket 类实现通信双方建立连接

要想实现 TCP 通信,首先需要通信双方建立连接。在 Java 中,TCP 双方建立连接依靠 ServerSocket 接口,ServerSocket 底层类似一个阻塞队列,当客户端和服务端建立连接之后,ServerSocket 会通过内核将这些建立好的连接给依次存储下来,当双方需要进行通信的时候服务器就会将这个连接从内核中取出来,然后进行通信。

public class TcpEchoServer {ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {//服务端需要指定端口号serverSocket = new ServerSocket(port);}
}

2. 取出建立的连接实现双方通信

通过 accept() 方法可以取出建立的连接进行双方的通信。

public void start() throws IOException {System.out.println("服务端启动");while (true) {Socket clientSocket = serverSocket.accept();//进行通信processConnection(clientSocket);}
}

3. 服务端业务逻辑实现

TCP 是面向字节流的传输,不像 UDP 依靠数据报传输,所以在这里需要用到前面文件操作的 InputStreamOutputStream 来实现请求的读取和响应的发送了。

public void processConnection(Socket clientSocket) {//提示客户端上线System.out.printf("[%s %d] 客户端线\n",clientSocket.getInetAddress(),clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {} catch (IOException e) {throw new RuntimeException(e);}
}

读取字节数据的时候会显得比较麻烦,所以可以使用 Scanner 来简化读取过程。
scanner.next() 方法读取到空白符(空格、回车、制表符)结束,这正好与我们客户端输入请求的时候以空白符结束对应。

Scanner scanner = new Scanner(inputStream);
while (true) {if (!scanner.hasNext()) {System.out.printf("[%s %d] 客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());break;}String request = scanner.next();
}

process(String request) 方法实现对请求数据的处理。

String response = process(request);public String process(String request) {return request;
}

当服务端接收到 process() 方法返回的数据之后,就需要将处理的数据返回给服务端,因为同样是面向字节流,所以需要依靠 OutputStream 类来将数据传输给服务端,但是字节流操作较麻烦,所以可以使用 PrintWriter 类封装一下 OutputStream 类来简化操作。

PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
//打印出服务端日志
System.out.printf("[%s %d] req=%s res=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);

关闭资源

在 TCP 流实现套接字编程的时候,如果不关闭资源的话就会出现资源泄露的问题,那么为什么会出现资源泄露呢?当服务器和新的客户端实现双方通信的时候就是调用 serverSocket.accept() 方法创建出新的 Socket 资源,而且这个 Socket 资源并不是一直贯穿服务端程序始终的,所以在这里就需要及时关闭资源,防止发生资源泄露的问题。

但是呢,我们不是使用了 try-with-resources 模型吗,当这个模型当中的代码执行完成了之后不是会自动调用里面的对象的 close 方法吗?是的,这里没问题,但是我们在这个模型中创建的是 InputStream 对象和 OutputStream 对象,程序结束的时候关闭的是流对象,而 Socket 对象并没有关闭,所以就还需要我们呢手动关闭这个 Socket 资源。

我们这里使用的是 finally 代码块来关闭资源,防止中间代码出现异常而导致资源没有成功关闭。

finally {clientSocket.close();
}

服务端整体代码

package netWork;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoServer {ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {//服务端需要指定端口号serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务端启动");while (true) {Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}public void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s %d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true) {if (!scanner.hasNext()) {System.out.printf("[%s %d] 客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());break;}String request = scanner.next();String response = process(request);printWriter.println(response);printWriter.flush();System.out.printf("[%s %d] req=%s res=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);} finally {clientSocket.close();}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer( 9906);tcpEchoServer.start();}
}

TcpEchoClient 客户端实现

1. 创建出 Socket 对象来与服务端实现通信

客户端实现与服务端的通信依赖于 Socket 接口。

public class TcpEchoClient {Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {socket = new Socket(serverIp,serverPort);}
}

虽然 TCP 通信,双方会保存对方的信息,但是我们在客户端代码中还是需要指定出 目的 IP 和 目的端口

2. 实现客户端的主要逻辑

提示用户输入请求数据。

public void run() {Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner netWorkScanner = new Scanner(inputStream);while (true) {String request = scanner.next();}} catch (IOException e) {throw new RuntimeException(e);}}

将请求的数据通过 OutputStream 传输。

PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();

使用 InputStream 读取服务端传回的数据。

Scanner netWorkScanner = new Scanner(inputStream);
String response = netWorkScanner.next();
System.out.println(response);

服务端整体代码

package netWork;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 需要在创建 Socket 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~// 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.// 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.socket = new Socket(serverIp, serverPort);}public void start() {// tcp 的客户端行为和 udp 的客户端差不多.// 都是:// 3. 从服务器读取响应.// 4. 把响应显示到界面上.Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {PrintWriter writer = new PrintWriter(outputStream);Scanner scannerNetwork = new Scanner(inputStream);while (true) {// 1. 从控制台读取用户输入的内容System.out.print("-> ");String request = scanner.next();// 2. 把字符串作为请求, 发送给服务器//    这里使用 println, 是为了让请求后面带上换行.//    也就是和服务器读取请求, scanner.next 呼应writer.println(request);writer.flush();// 3. 读取服务器返回的响应.String response = scannerNetwork.next();// 4. 在界面上显示内容了.System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("192.168.144.1", 9090);client.start();}
}

功能实现

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

多个客户端访问服务器

既然是网络编程,那肯定离不开多个客户端同时访问一个服务器的情况,如果我们要想使用多个客户端同时访问同一个服务器能行吗?

在 IDEA 中如何实现同一个代码两个进程呢?

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

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

在这里插入图片描述
当多个客户端同时访问这个服务器的时候,我们可以发现:这个功能并没有真正的实现。那么为什么这个代码不能实现多个客户端同时访问同一个服务器的情况呢?

通过观察服务器端的代码,可以发现:其实这里是有两个 while 循环的,第一个循环是用来取得和多个客户端的连接的,但是为什么这里没有取得和我们的第二个客户端的连接呢?这里第一个客户端在和服务端进行通信的时候,是处在第二个 while 循环当中的,并且这里第一个客户端并没有与服务器断开连接,也就是说,当前服务器还处于第二个 while 循环中等待第一个客户端发送请求,所以当第二个客户端想要和服务器进行通信的时候就不能到达 serverSocket.accept() 方法与服务器进行通信。那么如何解决这个问题呢?

这就需要用到我们前面学习的多线程的知识了,创建多个线程进行 processConnection() 方法,这样就可以使的另一个客户端想要和服务器进行通信的时候就可以在另一个线程中得到和服务器的连接。

public void start() throws IOException {System.out.println("服务端启动");while (true) {Socket clientSocket = serverSocket.accept();Thread t = new Thread(() -> {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});t.start();}
}

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

但是这样频繁的创建和销毁线程也会消耗较多的资源,所以这里可以做出优化:使用线程池,虽然这里线程池可以优化,但是优化的不多。

public void start() throws IOException {System.out.println("服务端启动");ExecutorService service = Executors.newCachedThreadPool();while (true) {Socket clientSocket = serverSocket.accept();service.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}}});}}

其实这里处理多个客户端同一时间访问同一个服务器的最好方法就是 IO 多路复用 / IO 多路转接

IO多路复用是一种同步IO模型,它允许在单个进程/线程内同时处理多个IO请求。具体来说,一个进程/线程可以监视多个文件句柄,一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作。如果没有文件句柄就绪,应用程序将被阻塞,并交出CPU。
这种模型允许多个请求共享同一个进程/线程,使得在处理大量请求时,可以更有效地利用系统资源。如果每个请求都使用独立的进程/线程来处理,那么系统需要创建和管理大量的进程/线程,这将消耗大量的系统资源。而使用IO多路复用技术,可以复用一个或几个线程来处理多个TCP连接,从而大大减少了系统开销。
IO多路复用技术的出现主要是为了解决阻塞IO的问题。在操作系统中,最初只有BIO模式,即阻塞IO。当一个请求被处理时,如果该请求需要等待IO操作完成(例如读写操作),则该进程/线程将被阻塞,直到IO操作完成。这会导致系统资源的浪费,尤其是在需要处理大量请求的情况下。
IO多路复用技术的引入解决了这个问题。通过监视多个文件句柄,一个进程/线程可以在同一时间处理多个IO请求,从而提高了系统的效率。当一个文件句柄就绪时,该进程/线程可以立即进行相应的读写操作,而不需要等待其他请求完成。这种方式可以有效地减少系统资源的浪费,并提高系统的吞吐量。
IO多路复用技术被广泛应用在网络编程中,特别是服务器端编程。由于服务器需要同时处理来自大量客户端的请求,因此使用IO多路复用技术可以提高服务器的性能和响应速度。例如,Nginx服务器就使用了IO多路复用技术来处理大量的客户端连接。

这里具体怎么解决我这里及不过多叙述了,大家有兴趣可以去学习学习。

优化后的服务端代码

package netWork1;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");ExecutorService service = Executors.newCachedThreadPool();while (true) {// 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.// 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.Socket clientSocket = serverSocket.accept();// 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.// 创建新的线程来调用更合理的做法.// 这种做法可行, 不够好
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();// 更好一点的办法, 是使用线程池.service.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}}// 通过这个方法, 来处理当前的连接.public void processConnection(Socket clientSocket) {// 进入方法, 先打印一个日志, 表示当前有客户端连上了.System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());// 接下来进行数据的交互.try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.// 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.while (true) {Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {// 连接断开了. 此时循环就应该结束System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}// 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式. next 的规则是, 读到 "空白符" 就返回.String request = scanner.next();// 2. 根据请求, 计算响应.String response = process(request);// 3. 把响应写回到客户端.//    可以把 String 转成字节数组, 写入到 OutputStream//    也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.PrintWriter printWriter = new PrintWriter(outputStream);//    此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.//    自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)//    此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.printWriter.println(response);//    此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.printWriter.flush();// 4. 打印一下这次请求交互过程的内容System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch (IOException e) {e.printStackTrace();} finally {try {// 在这个地方, 进行 clientSocket 的关闭.// processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}public String process(String request) {// 此处也是写的回显服务器. 响应和请求是一样的.return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9906);server.start();}
}

根据回显服务器实现一个简单的字典功能

这个功能服务器的业务逻辑相比于简单的回显就稍复杂了一点,但是也没有复杂很多,只是需要更改一下服务端的 process 方法,在这个方法中使用 Map 存储几个单词的翻译就好了。

package netWork;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class TcpDictServer extends TcpEchoServer {Map<String, String> dict = new HashMap<>();public TcpDictServer(int port) throws IOException {super(port);dict.put("cat","小猫");dict.put("dog","小狗");dict.put("bird","小鸟");dict.put("pig","小猪");}@Overridepublic String process(String request) {return dict.getOrDefault(request,"您查找的单词在该词典中不存在");}public static void main(String[] args) throws IOException {TcpDictServer tcpDictServer = new TcpDictServer(9906);tcpDictServer.start();}
}

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

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

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

相关文章

OSI网络分层模型

OSI英文全文是Open System Interconnection Reference Model&#xff0c;翻译成中文就是开放式系统互联通信参考模型。 OSI模型分成了七层&#xff0c;部分层次与 TCP/IP 很像&#xff0c;从下到上分别是&#xff1a; 第一层&#xff1a;物理层&#xff0c;网络的物理形式&…

Mac安装nginx(Homebrew)

查看需要安装 nginx 的信息 brew info nginxDocroot 默认为 /usr/local/var/www 在 /opt/homebrew/etc/nginx/nginx.conf 配置文件中默认端口被配置为8080&#xff0c;从而使 nginx 运行时不需要加 sudo nginx将在 /opt/homebrew//etc/nginx/servers/ 目录中加载所有文件 …

10_集成学习方法:随机森林、Boosting

文章目录 1 集成学习&#xff08;Ensemble Learning)1.1 集成学习1.2 Why need Ensemble Learning?1.3 Bagging方法 2 随机森林(Random Forest)2.1 随机森林的优点2.2 随机森林算法案例2.3 随机森林的思考&#xff08;--->提升学习&#xff09; 3 随机森林&#xff08;RF&a…

解决pip安装包后但是Pycharm检测不到

首先要知道python找包的原理&#xff1a;原理 之后把一下代码打印一下&#xff1a; import sys print(sys.executable)# /usr/bin/python2 print(sys.path)# [/usr/lib/python2.7, /usr/lib/python2.7/dist-packages, /usr/local/lib/python2.7/dist-packages] print(sys.prefi…

CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试

CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试 文章目录 CoDeSys系列-2、CoDeSys安装及Windows下创建项目测试一、前言二、下载及安装三、Windows下软PLC项目创建及运行测试1、创建HMI工程1.1、新建标准工程&#xff1a;1.2、添加可视化对象&#xff1a;1.3、拖动添加拨码…

【C语言小游戏--猜数字】

文章目录 前言1.游戏描述2.代码实现2.1打印菜单2.2构建基础框架2.3玩游戏2.3.1生成随机数2.3.1.1rand()2.3.1.2srand()2.3.1.3time() 2.3.2game() 2.4自己设定猜的次数 3.完整代码 前言 猜数字小游戏是我们大多数人学习C语言时都会了解到的一个有趣的C语言小游戏&#xff0c;下…

Power BI 傻瓜入门 1. 数据分析术语:Power BI风格

本章内容包括&#xff1a; 了解Power BI可以处理的不同类型的数据了解您的商业智能工具选项熟悉Power BI术语 数据无处不在。从你醒来的那一刻到你睡觉的时候&#xff0c;某个系统会代表你收集数据。即使在你睡觉的时候&#xff0c;也会产生与你生活的某些方面相关的数据。如…

react封装一个简单的upload组件(待完善)

目录 react封装一个简单的upload组件component / uploadImg / uploadImg.jsx使用效果 react封装一个简单的upload组件 component / uploadImg / uploadImg.jsx import React, { useState } from react; import { LoadingOutlined, PlusOutlined } from ant-design/icons; imp…

基于SpringBoot的家具商城管理系统

基于SpringBoot的家具商城管理系统的设计与实现【文末源码】 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 家具详情 通知公告 登录界面 管理员界面 摘要 一段关于基于…

Vue3踩坑指南

vue.config.ts不起作用 关于项目 项目使用的还是vue-cli搭建的&#xff0c;底层还是webpack&#xff0c;没有使用新的vite搭建。 踩坑1&#xff1a;vue.config.ts不起作用 我本着既然是vue3 ts的项目&#xff0c;那么为了规范&#xff0c;项目中所有的js文件都得替换成ts文…

浅析人脸活体检测技术的功能及几种分类

在日常生活工作中&#xff0c;出现了人脸验证、人脸支付、人脸乘梯、人脸门禁等等常见的应用场景。这说明人脸识别技术已经在门禁安防、金融行业、教育医疗等领域被广泛地应用&#xff0c;人脸识别技术的高速发展与应用同时也出现不少质疑。其中之一就是人脸识别很容易被照片、…

03、Python 字符串高级用法

目录 Python 字符串高级用法转义字符字符串格式化序列相关的方法大小写相关的方法dir 可以查看某个类的所有方法删除空白查找、替换相关方法 Python 字符串高级用法 转义字符 字符串格式化 序列相关的方法 字符串本质就是由多个字符组成&#xff0c;字符串的本质就是不可变序…

为什么spring默认采用单例bean

概 述 熟悉 Spring开发的朋友都知道 Spring 提供了 5种 scope&#xff0c;分别是&#xff1a; singleton: 单例模式&#xff0c;当spring创建applicationContext容器的时候&#xff0c;spring会欲初始化所有的该作用域实例&#xff0c;加上lazy-init就可以避免预处理&#xf…

antd组件onChange回调,需要立即执行改变value与防抖节省接口开销。

文章目录 普通使用使用防抖节省开销页面功能复杂需要value受控回调需要部分代码立即执行&#xff0c;部分代码防抖延时执行useRefuseCallback 小结 普通使用 当我们使用Antd的input或者select进行搜索时&#xff0c;onChange回调会即时执行。 import { Input } from "an…

C语言之通讯录的实现篇优化版

目录 动态内存管理 通讯录声明 静态版本 动态版本 ​初始化通讯录 静态版本 动态版本 Add增加通讯录 静态版本 动态版本 Checkcapacity增容 DestroyContact释放动态空间 文件操作 SaveContact保存信息到文件中 初始化通讯录 旧版本 文件版本 LoadContact加载…

Openssl数据安全传输平台008:业务数据分析+工厂方法

文章目录 UML图1.1 客户端1.2 服务器端 UML图 1.1 客户端 // 准备要发送的数据 struct RequestMsg {//1 密钥协商 //2 密钥校验; // 3 密钥注销int cmdType; // 报文类型string clientId; // 客户端编号string serverId; // 服务器端编号string sign;string data; };1.2 服务器…

Unity之ShaderGraph如何实现UV抖动

前言 今天我们通过噪波图来实现一个UV抖动的效果。 如下图所示&#xff1a; 关键节点 Time&#xff1a;提供对着色器中各种时间参数的访问 UV&#xff1a;提供对网格顶点或片段的UV坐标的访问。可以使用通道下拉参数选择输出值的坐标通道。 SimpleNoise&#xff1a;根据…

Windows Server 2019 搭建FTP站点

目录 1.添加IIS及FTP服务角色 2.创建FTP账户&#xff08;用户名和密码&#xff09;和组 3.设置共享文件夹的权限 4.添加及设置FTP站点 5.配置FTP防火墙支持 6.配置安全组策略 7.客户端测试 踩过的坑说明&#xff1a; 1.添加IIS及FTP服务角色 a.选择【开始】→【服务器…

电流监测芯片SGM8199A2应用电路设计

SGM8199是一系列具有电压输出功能的双向电流监测芯片&#xff0c;用于监测共模电压范围内分流电阻上的压降&#xff0c;而不受电源电压的影响。该器件具有-0.1V至26V的宽共模电压范围输入。低偏移使得在监测电流时允许分流器上的满量程最大压降为10mV。SGM8199系列提供三种固定…

关于vant 的tabbar功能

1、想要实现tabbar页面A&#xff0c;其他的页面B&#xff08;非tabbar页面&#xff09;。 从A页面进入B页面&#xff0c;底部的active选中效果应该被取消掉&#xff0c;但是还是选中A。 按照官网的说法有两个方法 一、根据path路径 二、自定义的model 但是&#xff01;但是…