第3章 WebServer重构

3.1 重构原生Web服务框架

3.1.1 分析原生Web服务框架

        在服务端代码的 ClientHandler 中,请求解析、处理请求、返回响应的代码混杂在一起,这样的设计会导致代码难以维护和理解。为了提高代码的可读性、可维护性和可扩展性,我们需要对这些代码进行重构,并按照功能抽取对应的类,从而使后续的开发和维护更加方便。

        分析目前的代码:

        重构后软件的整体结构如下图:

        重构后的结构主要包含以下几个主要组件:

        1. ClientHandler(Web处理线程):该组件是处理客户端请求的主要线程。它接收客户端发送的HTTP请求,并将请求交给HttpServletRequest进行解析,然后将解析得到的请求信息传递给DispatcherServlet进行核心请求处理。最后,将处理得到的响应信息传递给HttpServletResponse进行缓存和发送给客户端。

        2. HttpServletRequest(请求解析和封装):负责解析和封装客户端发送的HTTP请求的信息,包括请求方法、URL、请求头和请求体等。它将原始的HTTP请求转换为一个请求对象,以方便后续处理逻辑使用。

        3. HttpServletResponse(响应缓存和处理):负责缓存和处理向客户端发送的响应信息。它将处理得到的响应内容暂存起来,并在合适的时机发送给客户端。

        4. DispatcherServlet(请求分发器):封装了核心请求处理逻辑。当ClientHandler接收到客户端请求后,将请求信息传递给DispatcherServlet。它根据请求的URL和方法,决定调用哪个业务处理模块来处理请求,最终得到处理结果。

        重构后的结构将不同功能的代码分离到独立的组件中,增强了代码的可读性和可维护性。HttpServletRequest负责解析和封装请求信息,HttpServletResponse负责缓存和处理响应信息,而DispatcherServlet作为请求分发器,负责将请求分发给相应的业务处理模块进行处理。这样的设计使得代码逻辑更加清晰,方便后续的开发和维护。

3.1.2 重构请求

        为了提高代码的模块化和清晰性,我们将请求部分的代码抽取到一个新的类HttpServletRequest中,该类封装了HTTP请求的解析逻辑,并提供了访问解析结果的方法。

        1. 定义类HttpServletRequest,封装HTTP请求的逻辑。该类包括了以下成员变量:

  • Socket socket: 保存客户端和服务器之间的网络连接
  • String method: 保存HTTP请求的方法,如GET、POST等
  • String uri: 保存HTTP请求的URI,即请求的资源路径
  • String protocol: 保存HTTP请求使用的协议,如HTTP/1.1、HTTP/2.0等
  • HashMap<String, String> headers: 保存HTTP请求头的所有内容,以键值对的形式存储

        2. 定义属性访问方法

  • public String getMethod():返回HTTP请求的方法
  • public String getUri():返回HTTP请求的URI
  • public String getProtocol():返回HTTP请求使用的协议

        3. 定义方法private void parseRequestLine(),用于解析HTTP请求的请求行,包括请求方法、URI和协议版本号,将解析结果输出到控制台,便于后续调试。

        4. 定义方法解析请求头以及获取请求头:

  • private void parseHeaders():解析HTTP请求头部的所有内容,以键值对的形式存储。
  • public String getHeader(String name):根据请求头的名称返回请求头的值

        5. 定义构造函数public HttpServletRequest(Socket socket):接受一个Socket对象作为参数,通过解析Socket中的输入流,初始化该类的成员变量。并定义方法public String readLine(),用于从Socket的输入流中读取一行数据并返回。

        HttpServletRequest类的完整代码示意如下:

/*封装HTTP请求逻辑 */
public class HttpServletRequest {private Socket socket;private String method;private String uri;private String protocol;private HashMap<String, String> headers = new HashMap<>();public HttpServletRequest(Socket socket) throws IOException {this.socket = socket;//解析请求行parseRequestLine();//解析请求头parseHeaders();}/*** 解析请求行方法* @throws IOException 网络出现异常*/private void parseRequestLine() throws IOException{String requestLine = readLine();String[] parts = requestLine.split("\\s");method = parts[0];uri = parts[1];protocol = parts[2];System.out.println("解析请求行:"+requestLine);System.out.println("method:"+method);System.out.println("uri:"+uri);System.out.println("protocol:"+protocol);}/*** 解析请求头方法,将解析结构缓存到一个HashMap中* @throws IOException 网络出现错误*/private void parseHeaders() throws IOException {while (true) {String line = readLine();//解析到空行结束if (line.isEmpty()) {break;}System.out.println("解析请求头:" + line);String[] parts = line.split(":\\s");headers.put(parts[0], parts[1]);}System.out.println("所有请求头:" + headers);}/*** 这段代码的作用是从Socket的输入流中读取一行数据并返回。它通过InputStream获取Socket的输入流,* 然后使用一个StringBuilder对象来存储读取的数据,最终返回读取的数据。* 具体实现逻辑如下:* 1. 创建一个InputStream对象in,并将其设置为socket的输入流。* 2. 创建一个StringBuilder对象builder,用于存储读取的数据。* 3. 定义两个字符变量previous和current,用于记录前一个字符和当前字符。* 4. 定义一个int类型变量b,用于记录从输入流中读取的字节。* 5. 使用while循环从输入流中读取字节,直到读取完一行数据。* 6. 将读取到的字节转换成字符类型,并赋值给变量current。* 7. 判断当前字符是否为行结束符("\r\n"),如果是则退出循环,否则将当前字符添加到builder中。* 8. 将当前字符赋值给previous,以备下次循环使用。* 9. 循环结束后,将builder转换成字符串并返回。* @return 从Socket的输入流中读取一行数据并返回* @throws IOException 出现网络IO错误*/public String readLine() throws IOException{InputStream in = socket.getInputStream();StringBuilder builder= new StringBuilder();//   前一个字符  当前字符char previous = 0, current = 0;int b;//解析请求行while ((b=in.read())!=-1){current = (char) b;if (previous == '\r' && current == '\n'){//遇到行结束就结束读取break;}else if (current != '\r' && current != '\n'){builder.append(current);}previous = current;}return builder.toString();}/*** 获取当前的请求方式* @return 请求方式*/public String getMethod() {return method;}/*** 获取当前请求的 uri* @return 请求资源路径*/public String getUri() {return uri;}/*** 返回当前请求的 协议* @return 返回请求协议*/public String getProtocol() {return protocol;}/*** 查询一个请求头* @param name 请求头名字* @return 请求头的值*/public String getHeader(String name) {return headers.get(name);}
}

        6. 重构ClientHandler类,将解析请求部分替换为HttpServletRequest:

//1. 解析请求
HttpServletRequest request = new HttpServletRequest(socket);
String uri = request.getUri();

        通过重构后,现在ClientHandler类中的请求部分代码得到了简化,提高了代码的可读性和可维护性。同时,HttpServletRequest类封装了HTTP请求解析的逻辑,使得ClientHandler更专注于业务处理部分,使整体结构更清晰。这样的重构有助于提高代码的模块化和可维护性,方便后续的开发和维护。

3.1.3 重构响应

        在进行请求部分的重构后,现在继续对响应逻辑进行重构,将响应代码抽取到HttpServletResponse类中,以优化ClientHandler。

        1. 定义了一个名为HttpServletResponse的类,封装HTTP响应的逻辑。包含:

  • socket:一个socket实例变量,用于表示客户端连接的套接字
  • statusCode:表示HTTP状态码,默认值为200
  • statusReason:表示HTTP状态描述,默认值为"OK"
  • contentFile:表示响应正文对应的实体文件

        在构造函数中,将客户端的套接字作为参数,将其赋给socket实例变量。

        2. 添加方法setContentFile、setStatusCode和setStatusReason用于设置响应正文文件、状态码和状态描述,分别将它们赋给成员变量。

        3. 抽取println方法用于将一行数据发送到网络流中,首先通过socket的getOutputStream方法获取输出流,然后将数据转换为ISO_8859_1编码的字节数组,并发送回车符和换行符。

        4. 抽取send方法用于将HTTP响应发送给客户端:

  • 它首先根据状态码和状态描述拼接一个状态行,并发送给客户端
  • 然后发送响应头,包括Content-Type和Content-Length
  • 最后发送一个空行表示响应头已经发送完成
  • 通过FileInputStream读取contentFile中的数据,并通过OutputStream发送给客户端

        HttpServletResponse类的完整代码示意如下:

/*封装HTTP响应逻辑 */
public class HttpServletResponse {private Socket socket;//状态行相关信息private int statusCode = 200;                   //状态代码private String statusReason = "OK";             //状态描述//响应头相关信息//响应正文相关信息
private File contentFile;                       //响应正文对应的实体文件public HttpServletResponse(Socket socket){this.socket = socket;}public void send() throws IOException {String statusLine = "HTTP/1.1 " + statusCode + " " + statusReason;//发送状态行println(statusLine);System.out.println("发送状态行: "+statusLine);//发送响应头println("Content-Type: text/html; charset=utf-8");println("Content-Length: " + contentFile.length());System.out.println("发送响应头: " + "Content-Length: " + contentFile.length());//发送空行println("");//将文件内容发送到浏览器FileInputStream in = new FileInputStream(contentFile);OutputStream out = socket.getOutputStream();byte[] buf = new byte[8*1024];int n;while ((n=in.read(buf))!=-1){out.write(buf, 0, n);}}public void setContentFile(File contentFile){this.contentFile = contentFile;}public void setStatusCode(int statusCode) {this.statusCode = statusCode;}public void setStatusReason(String statusReason){this.statusReason = statusReason;}/*** 发送一行到网络流* @param line 一行* @throws IOException 网络故障*/private void println(String line) throws IOException {OutputStream out = socket.getOutputStream();byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);out.write(data);out.write('\r');//发送回车符out.write('\n');//发送换行符}
}

        5.重构ClientHandler,使用HttpServletResponse类替换响应过程:

//1. 解析请求
HttpServletRequest request = new HttpServletRequest(socket);
HttpServletResponse response = new HttpServletResponse(socket);
String uri = request.getUri();//2. 发送响应
//根据找到静态资源
//类加载路径:target/classes
File root = new File(ClientHandler.class.getClassLoader().getResource(".").toURI()
);
//定位target/classes/static目录(SpringBoot中存放所有静态资源的目录)
File staticDir = new File(root,"static");
//定位target/classes/static目录中的文件
File file = new File(staticDir,uri);response.setContentFile(file);
response.send();

        6. 重构后ClientHandler的代码变得非常简洁,但是测试时候控制台出现了异常信息:

        这个显然是浏览器在请求favicon.ico文件,然而我们的服务器端没有对应的资源造成的问题。解决方案就是按照通行的惯例,在没有找到相应资源时候,给浏览器响应一个错误码404,错误原因是“Not Found”。

        通过重构,现在ClientHandler类中的响应部分代码也得到了简化,提高了代码的可读性和可维护性。HttpServletResponse类封装了HTTP响应的逻辑,使得ClientHandler更专注于业务处理部分。同时,为了更好地处理未找到资源的情况,我们返回了404错误页面,提高了用户体验。

        这样的重构有助于进一步优化代码结构,提高代码的模块化和可维护性,使整体逻辑更加清晰。

3.1.4 HTTP响应状态码

        RFC2616是HTTP/1.1协议的规范,其中定义了HTTP协议中的状态码。以下是RFC2616中定义的HTTP状态码及其含义。

        1xx(信息性状态码):表示接收的请求正在处理。

  • 100 Continue:服务器已接收请求头部,并且客户端应继续发送请求的主体部分
  • 101 Switching Protocols:服务器已经理解了客户端的请求,并将通过升级协议来完成这个请求

        2xx(成功状态码):表示请求已成功被服务器接收、理解、并接受。

  • 200 OK:请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立
  • 202 Accepted:服务器已接受请求,但尚未处理
  • 204 No Content:服务器成功处理了请求,但没有返回任何内容

        3xx(重定向状态码):表示需要客户端执行进一步的操作才能完成请求。

  • 301 Moved Permanently:请求的资源已被永久移动到新URI,将来的引用应使用新URI
  • 302 Found:请求的资源临时从不同的URI响应请求,将来的引用仍然应该使用原来的URI
  • 303 See Other:响应可以被找到在另一个URI,应使用GET方法来检索此资源
  • 304 Not Modified:请求的资源未被修改,客户端可以使用缓存的版本

        4xx(客户端错误状态码):表示客户端在请求的过程中出错。

  • 400 Bad Request:服务器无法理解请求的格式,客户端不应该重复发送这个请求
  • 401 Unauthorized:请求需要用户验证,无法通过验证
  • 403 Forbidden:服务器已经理解请求,但是拒绝执行它
  • 404 Not Found:服务器无法找到请求的资源

        5xx(服务器错误状态码):表示服务器在处理请求的过程中出错。

  • 500 Internal Server Error:服务器遇到了一个意外的情况,无法完成请求
  • 501 Not Implemented:服务器不支持客户端请求的功能
  • 502 Bad Gateway:服务器作为网关或代理,从上游服务器收到了无效的响应
  • 503 Service Unavailable:服务器当前无法处理请求,可能是因为维护或过载

        以上是RFC2616中定义的HTTP状态码及其含义,可以帮助开发者更好地理解HTTP协议中的错误码信息。

3.1.5 处理404错误

        首先在 resources/static 文件夹中创建一个 404 错误的html文件 “404.html”,该文件的HTML代码如下所示:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>404</title>
</head>
<body><p>404 文件没有找到!</p>
</body>
</html>

        然后重构ClientHandler,处理404错误:先检查文件是否存在,如果文件存在,就发送文件;否则设置状态码“404”,状态原因为“Not Found”,并且设置发送404.html文件。代码如下所示:

//1. 解析请求
HttpServletRequest request = new HttpServletRequest(socket);
HttpServletResponse response = new HttpServletResponse(socket);
String uri = request.getUri();//2. 发送响应
//根据找到静态资源
//类加载路径:target/classes
File root = new File(ClientHandler.class.getClassLoader().getResource(".").toURI()
);
//定位target/classes/static目录(SpringBoot中存放所有静态资源的目录)
File staticDir = new File(root,"static");
//定位target/classes/static目录中的文件
File file = new File(staticDir,uri);
//检查文件是否存在
if (file.isFile()){//正常发送资源response.setContentFile(file);
}else {//处理404错误response.setStatusCode(404);response.setStatusReason("Not Found");File file404 = new File(staticDir, "404.html");response.setContentFile(file404);;
}
//3. 发送响应
response.send();

        重构后进行测试:请求一个不存在的资源,比如:http://localhost:8088/hi.html 得到如下结果:

3.1.6 重构处理请求过程

        在对ClientHandler的请求和响应逻辑进行重构后,现在可以进一步重构ClientHandler的请求处理过程。将请求处理逻辑抽取到一个新的类DispatcherServlet中,该类作为请求处理器,包含了处理HTTP请求的逻辑。

        1. 抽取请求处理逻辑到一个新的类DispatcherServlet:一个请求处理器,包含了处理HTTP请求的逻辑。具体功能如下:

  • 根据请求中的URI定位到对应的静态资源文件,如果该文件存在则将其发送给浏览器
  • 如果请求的资源不存在,则设置HTTP响应的状态码为404,状态描述为"Not Found",并将静态资源文件404.html发送给浏览器

        该类的静态初始化块中,通过类加载器获取到当前类所在的classpath目录,然后找到其中的static目录作为静态资源文件的根目录。包含属性:

  • root :代表当前classpath的根目录,是资源查找起始位置
  • staticDir :静态资源的位置,静态网页和图片都存储在这个位置

        2. 在service方法中,通过HttpServletRequest的getUri方法获取到请求的URI,然后在静态资源文件根目录下查找相应的文件,如果存在则将其发送给浏览器,如果不存在则发送静态资源文件404.html。

        DispatcherServlet类的完整代码示意如下:

/*封装请求处理逻辑 */
public class DispatcherServlet {private static File root;private static File staticDir;static {try {//根据找到静态资源//类加载路径:target/classesroot = new File(ClientHandler.class.getClassLoader().getResource(".").toURI());//定位target/classes/static目录(SpringBoot中存放所有静态资源的目录)staticDir = new File(root,"static");} catch (URISyntaxException e) {e.printStackTrace();}}public void service(HttpServletRequest request, HttpServletResponse response){String uri = request.getUri();//定位target/classes/static目录中的文件File file = new File(staticDir,uri);//检查文件是否存在if (file.isFile()){//正常发送资源response.setContentFile(file);}else {//处理404错误response.setStatusCode(404);response.setStatusReason("Not Found");File file404 = new File(staticDir, "404.html");response.setContentFile(file404);;}}
}

        3. 重构ClientHandler

        重构后的请求处理线程ClientHandler就非常清爽:

public class ClientHandler implements Runnable {private Socket socket;public ClientHandler(Socket clientSocket){socket = clientSocket;}@Overridepublic void run() {try {//1. 解析请求HttpServletRequest request = new HttpServletRequest(socket);HttpServletResponse response = new HttpServletResponse(socket);String uri = request.getUri();//2. 处理请求DispatcherServlet servlet = new DispatcherServlet();servlet.service(request, response);//3. 发送响应response.send();}catch (IOException e){e.printStackTrace();}finally {//断开连接try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

3.1.7 请求前的空行问题

        在进行大量的测试时候,有可能出现解析请求时候出现了空行情况,收到空请求行后进行请求行解析就会出现异常:

        其原因是HTTP协议中,允许客户端浏览器在HTTP请求前发送空行,也就是一个空行符(CRLF)的作用是分隔请求头和请求体,它表示请求头的结束。在请求头结束之后,如果请求中包含请求体,请求体将会跟在空行之后。由于存在空请求体的请求,所以存在请求行之间有空行的意外。

        在 HTTP/1.1 规范中,如果服务器在开始读取一个消息时收到一个 CRLF,则应该忽略它,以确保服务器在遇到任何异常情况时都能正常工作。(可以参考:RFC2616 4.1 Message Types)

3.1.8 检查请求行

        为解决请求前的空行问题,需要在解析请求行的时候,忽略空行。然后再利用正则表达式检查请求行是否是合乎HTTP协议的标准,进一步增强程序的可靠性。

        可以使用AI工具帮助生成正则表达式。

        一个检查请求行正确的正则表达式如下:

^(GET|POST|PUT|DELETE|HEAD|OPTIONS) ([^?#\s]+)(\?[^#\s]*)? (HTTP\/1\.0|HTTP\/1\.1)$

        这个正则表达式匹配了HTTP请求行的四个部分:请求方法、请求URL、请求参数、HTTP协议版本。

  • ^:表示字符串的开始
  • (GET|POST|PUT|DELETE|HEAD|OPTIONS):匹配HTTP请求的方法,这里使用了分组和|操作符表示多个可能的方法
  • ([^?#\s]+):匹配请求URI,使用了非贪婪的正则表达式表示法,不包含URI中可能存在的参数和锚点
  • (\?[^#\s]*)?:匹配请求URI中的参数,使用了可选分组,匹配以?开头的参数部分,可以不出现
  • (HTTP\/1\.0|HTTP\/1\.1):匹配HTTP协议的版本号,同样使用了分组和|操作符

        其中,\是转义字符,用于匹配特殊字符。正则表达式中的 . 和 | 都是特殊字符,需要用\进行转义。

        先添加错误请求的自定义异常 BadRequestException:

/* 错误请求格式异常 */
public class BadRequestException extends Exception{public BadRequestException() {}public BadRequestException(String message) {super(message);}public BadRequestException(String message, Throwable cause) {super(message, cause);}public BadRequestException(Throwable cause) {super(cause);}public BadRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}

        然后重构请求行解析方法:

/*** 解析请求行方法* @throws IOException 网络出现异常* @throws BadRequestException 请求行格式错误*/
private void parseRequestLine() throws IOException, BadRequestException {String requestLine = readLine();//根据HTTP协议描述,requestLine 有可能是空行!int n = 0;while (requestLine.isEmpty()){//跳过requestLine = readLine();if (n++ == 5){throw new BadRequestException("过多的空请求行!");}}String regex = "^(GET|POST|PUT|DELETE|HEAD|OPTIONS) ([^?#\\s]+)(\\?[^#\\s]*)? (HTTP\\/1\\.0|HTTP\\/1\\.1)$";if (! requestLine.matches(regex)){throw new BadRequestException("错误的请求行格式");}String[] parts = requestLine.split("\\s");method = parts[0];uri = parts[1];protocol = parts[2];System.out.println("解析请求行:"+requestLine);System.out.println("method:"+method);System.out.println("uri:"+uri);System.out.println("protocol:"+protocol);
}

        通过重构,现在ClientHandler类中的请求处理过程变得非常清晰简洁。我们将请求处理逻辑抽取到了DispatcherServlet类中,使得ClientHandler更专注于处理连接和调用请求处理器的功能。这样的设计提高了代码的模块化和可维护性,使整体结构更清晰,更易于后续的开发和维护。

3.2 单例模式

3.2.1 设计模式与单例模式

        设计模式是针对面向对象编程中常见的问题和场景,提出的一套经过反复实践验证的解决方案的方法论,它描述了一组经过测试和证明的解决方案,可以用来解决面向对象编程中的各种问题。

        单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问这个唯一的实例。在单例模式中,通常将该类的构造函数私有化,防止外部直接创建实例,而通过一个静态方法或者变量来获取唯一的实例。

        单例模式可以避免在系统中出现多个相同的对象,减小系统开销,并且方便对这个唯一实例进行统一的管理和控制。在需要频繁创建和销毁对象的场景下,采用单例模式可以提高系统的性能和可维护性。

        在实际开发中,单例模式的应用非常广泛,例如,线程池、数据库连接池、日志系统等等都可以采用单例模式来保证全局唯一性和统一管理。但是,在使用单例模式时也需要注意一些问题,例如线程安全性、延迟加载等等。

3.2.2 使用单例模式重构请求处理DispatcherServlet

        使用单例模式重构请求处理DispatcherServlet可以优化资源的创建和提高软件效能。在Java中,创建对象的过程涉及一定的内存和时间开销,如果可以减少对象的创建次数,可以提升程序性能。在这里,我们可以使用饿汉单例模式来确保DispatcherServlet在整个应用程序中只有一个实例。

        首先,我们需要将DispatcherServlet类设计为单例模式。饿汉单例模式的实现比较简单,可以在类加载时就创建唯一的实例对象,保证了线程安全性。

        接下来,我们需要在ClientHandler类中使用DispatcherServlet的单例实例。

        通过以上重构,我们将DispatcherServlet类设计为了饿汉单例模式,确保整个应用程序中只有一个DispatcherServlet实例。同时,在ClientHandler类中使用DispatcherServlet.getInstance()来获取该单例实例。

        以下是将DispatcherServlet重构为饿汉单例模式的代码:

public class DispatcherServlet {// 1. 将构造方法私有化,防止外部通过new创建实例private DispatcherServlet(){}//2. 定义一个静态变量来保存实例,并进行初始化private static DispatcherServlet instance = new DispatcherServlet();// 3. 提供一个公有的静态方法来获取实例public static DispatcherServlet getInstance() {return instance;}// 略去 请求处理代码 ...
}

        在上面的代码中,我们将DispatcherServlet的构造函数设置为私有,这样外部就无法通过new DispatcherServlet()来实例化对象。同时,我们在类加载时就创建了一个唯一的DispatcherServlet实例,并通过静态方法getInstance()来获取该实例。

        接下来,我们需要在ClientHandler类中使用DispatcherServlet的单例实例:

DispatcherServlet servlet = DispatcherServlet.getInstance();

        通过以上重构,我们将DispatcherServlet类设计为了饿汉单例模式,确保整个应用程序中只有一个DispatcherServlet实例。

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

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

相关文章

ComfyUI搭建和注意事项for WIN[笔记]

下载ComfyUI(GitHub - comfyanonymous/ComfyUI: The most powerful and modular stable diffusion GUI, api and backend with a graph/nodes interface.) 从源码上搭建比较麻烦&#xff0c;一般不推荐&#xff0c;所以跑到release里面找一个下载。我的显卡是GeFore GTX 1050 …

飞腾E2000运行Zephyr操作系统

Phytium-Zephyr-SDK 1. 仓库介绍 1.1 本仓库特色 此项目是一个开源软件&#xff0c;专为物联网领域设计&#xff0c;基于Zephyr实时操作系统&#xff0c;针对Phytium系列CPU进行了专门的适配和应用开发。我们的目标是降低开发者的使用门槛&#xff0c;提供了部署文档和使用指…

同向双指针(滑动窗口)算法

209. 长度最小的子数组 这里的更新结果就题来定 class Solution {public int minSubArrayLen(int target, int[] nums) {int sum 0;int len 0;int f 0;for(int left 0, right 0; right < nums.length;){//求和sum nums[right];while(sum > target){//lenint t ri…

MLP实现fashion_mnist数据集分类(1)-模型构建、训练、保存与加载(tensorflow)

1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、fashion_mnist数据集下载与展示 (train_image,train_label),(test_image,test_label) tf.keras.datasets.fashion_mnist.l…

张大哥笔记:卖盗版网课,获利 100 万被抓

这几天刷视频&#xff0c;看到一个新闻&#xff0c;某大学生卖盗版网课&#xff0c;把别人2000多正版网课&#xff0c;以做活动名义售卖20元&#xff0c;获利100多万被抓。 下方图片来自&#xff1a;极目新闻 卖这种盗版网课&#xff0c;门槛低&#xff0c;成本低&#xff0c;…

解决3D模型只显示线框材质的方法---模大狮模型网

在3D建模和渲染过程中&#xff0c;正确的材质和纹理是呈现逼真效果的关键。然而&#xff0c;有时候用户可能会遇到一个常见问题&#xff0c;即3D模型在渲染或查看时只显示线框材质&#xff0c;而没有正确的表面纹理和颜色。本文将介绍解决这一问题的几种方法&#xff0c;帮助用…

7 人赚 960 亿美元,数字天才的首次独舞

巴菲特股东大会 一年一度的巴菲特股东大会如常召开&#xff0c;只不过这次坐在老爷子左手边的不再是老搭档查理芒格&#xff0c;而是钦点的未来继任者&#xff0c;格雷格阿贝尔。 随着芒格&#xff08;99岁&#xff09;的离开&#xff0c;巴菲特&#xff08;93岁&#xff09;也…

【.sh】Shell 脚本文件的基础内容

目录 &#x1f30a;1. 基础知识 &#x1f30a;2. 简单示例 &#x1f30d;2.1 示例一 输出"Hello, World!" &#x1f30d;2.2 示例二 输入输出创建 &#x1f30a;1. 基础知识 .sh文件是什么&#xff1f; .sh文件即 Shell 脚本文件&#xff0c;通常用来执行一系列…

Ranni: Taming Text-to-Image Diffusion for Accurate Instruction Following

Ranni: Taming Text-to-Image Diffusion for Accurate Instruction Following abstract 我们引入了一个语义面板作为解码文本到图像的中间件&#xff0c;支持生成器更好地遵循指令 Related work 最近的工作还通过包含额外的条件&#xff08;如补全掩码[15&#xff0c;45]、…

10 华三vlan技术介绍

AI 解析 -Kimi-ai Kimi.ai - 帮你看更大的世界 (moonshot.cn) 虚拟局域网&#xff08;VLAN&#xff09;技术是一种在物理网络基础上创建多个逻辑网络的技术。它允许网络管理员将一个物理网络分割成多个虚拟的局域网&#xff0c;这些局域网在逻辑上是隔离的&#xff0c;但实际…

【LeetCode刷题记录】105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树

105 从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,1…

Pandas入门篇(三)-------数据可视化篇2(pandas-plot篇)

目录 概述一、格式1. 生成pandas.plotting对象来生成图表2. 调用plot()函数来生成图表3.支持的图表类型 二、单变量绘图常用图表1. 柱状图&#xff08;bar&#xff09;使用场景代码实现 2. 折线图&#xff08;line&#xff09;&#xff08;默认即为折线图&#xff09;适用场景代…

计算机毕业设计 | vue+springboot 在线花店后台管理系统(附源码)

1&#xff0c;绪论 1.1 项目背景 随着社会发展&#xff0c;网上购物已经成为我们日常生活的一部分。但是&#xff0c;至今为止大部分电商平台都是从人们日常生活出发&#xff0c;出售都是一些日常用品比如&#xff1a;食物、服装等等&#xff0c;并未发现一个专注于鲜花的电商…

从一到无穷大 #25 DataFusion:可嵌入,可扩展的模块化工业级计算引擎实现

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言架构总览与可扩展性Catalog and Data SourcesFront End逻辑计划与逻辑计划优化器…

使用台式机打开腾讯会议后打开麦克风提示:未检测到可用的麦克风,请插入设备后重试

解决方法如下&#xff1a; 台式机是不带输入设备的&#xff0c;也就是不自带麦克风的&#xff0c;而笔记本电脑是带的。这时候在台式机上可以通过插入有线耳机充当输入设备&#xff0c;就可以正常使用麦克风功能了。 插入之后&#xff0c;可以看到设置里面声音界面包含了一个…

new mars3d.control.MapSplit({实现点击卷帘两侧添加不同图层弹出不同的popup

new mars3d.control.MapSplit({实现点击卷帘两侧添加不同图层弹出不同的popup效果&#xff1a; 左侧&#xff1a; 右侧&#xff1a; 说明&#xff1a;mars3d的3.7.12以上版本才支持该效果。 示例链接&#xff1a; 功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 相关代…

关于视频号小店,常见问题解答,开店做店各方面详解

大家好&#xff0c;我是电商笨笨熊 视频号小店作为今年风口&#xff0c;一个新推出的项目&#xff0c;凭借着自身流量加用户群体的优势吸引了不少的电商玩家。 但对于很多玩家来说&#xff0c;视频号小店完全是一个新的项目、新的领域&#xff0c;因此也会存在很多的疑问&…

数据分析从入门到精通 2.pandas修真之前戏基础

从爱上自己那天起&#xff0c;人生才真正开始 —— 24.5.6 为什么学习pandas numpy已经可以帮助我们进行数据的处理了&#xff0c;那么学习pandas的目的是什么呢? numpy能够帮助我们处理的是数值型的数据&#xff0c;当然在数据分析中除了数值型的数据还有好多其他类型…

接口自动化测试之-requests模块详解

一、requests背景 Requests 继承了urllib2的所有特性。Requests支持HTTP连接保持和连接池&#xff0c;支持使用cookie保持会话&#xff0c;支持文件上传&#xff0c;支持自动确定响应内容的编码&#xff0c;支持国际化的 URL 和 POST 数据自动编码。 二、requests安装 利用p…

CI/CD笔记.Gitlab系列.新用户管理

CI/CD笔记.Gitlab系列 新用户管理 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_285502…