Python实现简单的Web服务器

Python实现简单的Web服务器

一、课程介绍

2. 内容简介

互联网在过去20年里已经大大地改变了我们的生活方式,影响着社会。但是反观互联网,它的基础-web的核心原理并没有改变多少。大部分web系统仍旧遵守 Tim Berners-Lee 20 多年前提出的 W3C 标准,大部分web服务器接收的信息格式与接收的方式与过去并无二致。

本课程将通过使用 Python 语言实现一个 Web 服务器,探索 HTTP 协议和 Web 服务的基本原理,同时学习 Python 如何实现 Web 服务请求、响应、错误处理及CGI协议,最后会根据项目需求使用 Python 面向对象思路对代码进行重构。

3. 课程知识点

本课程项目完成过程中,我们将学习:

  1. HTTP 协议基本原理
  2. 简单的 Web 服务器框架
  3. Python 语言的网络开发
  4. Web 服务请求,响应及错误处理的实现
  5. CGI 协议的 Python 实现
  6. 使用 Python 面向对象思想重构代码

##二、实验环境

打开终端,进入Code目录,创建 web-server 文件夹, 并将其作为我们的工作目录。

$ cd Code
$ mkdir web-server && cd web-server

本实验使用httpie代替浏览器发送请求并在终端打印响应信息。

$ sudo apt-get install httpie

##三、实验原理

想要了解http原理, Introduction to HTTP 是一个不错的选择,也可以参考它的中文翻译版: HTTP 下午茶

这里我们简单过一遍我们需要了解的部分。

一般我们的web程序都运行在 TCP/IP 协议上,程序之间使用 socket(套接字) 进行通信,它能够让计算机之间的通信就像写文件和读文件一样简单。
一个 tcp socket 由一个IP地址和端口号组成。

  • IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”,写成10进制的形式就是我们常见的 174.136.14.108。我们通过IP地址来标识所连接的主机。
  • 端口号是一个范围在0-65535之间的数字,一台主机上可能同时有多个sockets,因此需要端口号进行标识。端口号0-1023 是保留给操作系统使用的,我们可以使用剩下的端口号。

超文本传输协议(HTTP)描述了一种程序之间交换数据的方法,它非常简单易用,在一个socket连接上,客户端首先发送请求说明它需要什么,然后服务器发送响应,并在响应中包含客户端的数据。响应数据也许是从本地磁盘上复制来的,也许是程序动态生成的。传输过程如图:

HTTP请求就是一段文本,任何程序都能生成一个http请求,就像生成文本一样简单。这段文本需要包含以下这些部分:

  • HTTP method:HTTP请求方法。最常用的就是 GET(抓取数据)与POST(更新数据或者上传文件)

  • URL:通常是客户端请求的文件的路径,比如 /research/experiments.html, 但是是否响应文件都是由服务器决定的。

  • HTTP version:HTTP版本。通常是 HTTP/1.0 或 HTTP/1.1

  • header field:HTTP头内的键值对,做一些基本设置,就像下面这样。

    #客户端接受的数据类型
    Accept: text/html
    #客户端接受的语言
    Accept-Language: en, fr
    If-Modified-Since: 16-May-2005

  • body: 一些与请求有关的负载数据了。比如在一个网站登陆的时候提交登陆表单,那负载数据就是你的账号与密码信息了。

HTTP响应的结构类似于请求:

  • status code:状态码。请求成功响应200,请求的文件找不到则响应404。
  • status phrase:对状态码的描述。

##四、实验步骤

###1.你好, web

现在就来写我们第一个web服务器吧, 基本概念非常简单:

  1. 等待某个人连接我们的服务器并向我们发送一个HTTP请求
  2. 解析该请求
  3. 了解该请求希望请求的内容
  4. 服务器根据请求抓取需要的数据(从服务器本地文件中读或者程序动态生成)
  5. 将数据格式化为请求需要的格式
  6. 送回HTTP响应

步骤1,2,6的操作对所有web应用都是一样的,这部分内容Python标准库中的 BaseHTTPServer 模块可以帮助我们处理。我们只需要关注步骤3~5。

首先在工作目录下创建 server.py 文件

#-*- coding:utf-8 -*-
import BaseHTTPServerclass RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):'''处理请求并返回页面'''# 页面模板Page = '''\<html><body><p>Hello, web!</p></body></html>'''# 处理一个GET请求def do_GET(self):self.send_response(200)self.send_header("Content-Type", "text/html")self.send_header("Content-Length", str(len(self.Page)))self.end_headers()self.wfile.write(self.Page)#----------------------------------------------------------------------if __name__ == '__main__':serverAddress = ('', 8080)server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)server.serve_forever()

模块的 BaseHTTPRequestHandler 类会帮我们处理对请求的解析,并通过确定请求的方法来调用其对应的函数,比如方法是 GET ,该类就会调用名为 do_GET 的方法。RequestHandler 继承了 BaseHTTPRequestHandler 并重写了 do_GET 方法,其效果如代码所示是返回 Page 的内容。 Content-Type 告诉了客户端要以处理html文件的方式处理返回的内容。end_headers 方法会插入一个空白行,如之前的request结构图所示。

运行我们的第一个 web服务器

$ python server.py

可以在浏览器地址输入 127.0.0.1:8080 进行查看

方便起见,还是让我们新开一个终端窗口,使用httpie来查看输出(之后都使用httpie来查看输出)

$ http 127.0.0.1:8080

httpie很贴心地显示了响应报文的全部内容。

###2.显示请求的信息

修改之前的代码来显示请求的信息,同时重新整理一下代码:

class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):# ...页面模板...Page="..待设计.."def do_GET(self):page = self.create_page()self.send_content(page)def create_page(self):# ...待实现...def send_content(self, page):# ...待实现...

send_content 与之前 do_GET 内的代码一样:

def send_content(self, page):self.send_response(200)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(page)))self.end_headers()self.wfile.write(page)

设计页面模版

Page = '''\
<html>
<body>
<table>
<tr>  <td>Header</td>         <td>Value</td>          </tr>
<tr>  <td>Date and time</td>  <td>{date_time}</td>    </tr>
<tr>  <td>Client host</td>    <td>{client_host}</td>  </tr>
<tr>  <td>Client port</td>    <td>{client_port}</td> </tr>
<tr>  <td>Command</td>        <td>{command}</td>      </tr>
<tr>  <td>Path</td>           <td>{path}</td>         </tr>
</table>
</body>
</html>
'''

实现create_page

def create_page(self):values = {'date_time'   : self.date_time_string(),'client_host' : self.client_address[0],'client_port' : self.client_address[1],'command'     : self.command,'path'        : self.path}page = self.Page.format(**values)return page

main中的内容不用去修改它。

运行看看

$ http 127.0.0.1:8080/something.html

效果图:

注意到它仍旧返回了200 OK而不是404 Not Found,即使 something.html 文件并不存在。那是因为我们现在的web服务器还没有实现找不到文件就返回404错误的功能。反过来说,只要我们想,可以通过编程实现任何我们想要的效果,像是随机返回一个维基百科的页面或是帮老王家订一个披萨(并不会)。

怎么解决返回404的问题呢,首先得有返回文件的功能吧。

3.响应静态页面

所以这一步就该处理静态页面了,处理静态页面就是根据请求的页面名得到磁盘上的页面文件并返回。

在当前目录下创建新文件 plain.html,这是我们测试用的静态页面

<html>
<head>
<title>Plain Page</title>
</head>
<body>
<h1>Plain Page</h1>
<p>Nothin' but HTML.</p>
</body>
</html>

server.py 中导入需要的库

import sys, os, BaseHTTPServer

为我们的服务器程序写一个异常类

class ServerException(Exception):'''服务器内部错误'''pass

重写 do_GET 函数:

def do_GET(self):try:# 文件完整路径full_path = os.getcwd() + self.path# 如果该路径不存在...if not os.path.exists(full_path):#抛出异常:文件未找到raise ServerException("'{0}' not found".format(self.path))# 如果该路径是一个文件elif os.path.isfile(full_path):#调用 handle_file 处理该文件self.handle_file(full_path)# 如果该路径不是一个文件else:#抛出异常:该路径为不知名对象raise ServerException("Unknown object '{0}'".format(self.path))# 处理异常except Exception as msg:self.handle_error(msg)

首先看完整路径的代码,os.getcwd() 是当前的工作目录,self.path 保存了请求的相对路径,不要忘了 RequestHandler 继承自 BaseHTTPRequestHandler,它已经帮我们将请求的相对路径保存在self.path中了。

编写文件处理函数:

def handle_file(self, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()self.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(self.path, msg)self.handle_error(msg)

注意到我们是以二进制模式打开文件的,这样读文件的时候就不会对读取的内容做多余的处理。

接着,实现我们的错误处理函数并设计错误页面模板

Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content)

运行看看

$ http 127.0.0.1:8080/plain.html

再测试一下错误的路径

$ http 127.0.0.1:8080/something.html

确实返回了错误页面但同时注意到返回的是200状态码,我们希望它能够返回404,所以还需要修改一下 handle_errorsend_content 函数

def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)

测试看看

$ http 127.0.0.1:8080/something.html

这回就对了。

至今为止的代码:

#-*- coding:utf-8 -*-
import sys, os, BaseHTTPServerclass ServerException(Exception):'''服务器内部错误'''passclass RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""def do_GET(self):try:# 文件完整路径full_path = os.getcwd() + self.path# 如果该路径不存在...if not os.path.exists(full_path):#抛出异常:文件未找到raise ServerException("'{0}' not found".format(self.path))# 如果该路径是一个文件elif os.path.isfile(full_path):#调用 handle_file 处理该文件self.handle_file(full_path)# 如果该路径不是一个文件else:#抛出异常:该路径为不知名对象raise ServerException("Unknown object '{0}'".format(self.path))# 处理异常except Exception as msg:self.handle_error(msg)def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)def handle_file(self, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()self.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(self.path, msg)self.handle_error(msg)if __name__ == '__main__':serverAddress = ('', 8080)server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)server.serve_forever()

4. 在根url显示首页内容

大部分时候我们都希望能够直接在http://127.0.0.1:8080/ 显示主页内容。要怎么做呢,也许我们可以在do_GET那冗长的if-elif-else判断里再加一个判断请求地址是不是根地址的分支,也许我们可以找到一个更加聪明的方法。

比如说把每一种情况都单独写成一个条件类

class case_no_file(object):'''该路径不存在'''def test(self, handler):return not os.path.exists(handler.full_path)def act(self, handler):raise ServerException("'{0}' not found".format(handler.path))class case_existing_file(object):'''该路径是文件'''def test(self, handler):return os.path.isfile(handler.full_path)def act(self, handler):handler.handle_file(handler.full_path)class case_always_fail(object):'''所有情况都不符合时的默认处理类'''def test(self, handler):return Truedef act(self, handler):raise ServerException("Unknown object '{0}'".format(handler.path))

test方法用来判断是否符合该类指定的条件,act则是符合条件时的处理函数。其中的handler是对RequestHandler实例的引用,通过它,我们就能调用handle_file进行响应。

将原先的if-elif-else分支替换成遍历所有的条件类来看一下区别。

替换前:

def do_GET(self):try:# 文件完整路径full_path = os.getcwd() + self.path# 如果该路径不存在...if not os.path.exists(full_path):#抛出异常:文件未找到raise ServerException("'{0}' not found".format(self.path))# 如果该路径是一个文件elif os.path.isfile(full_path):#调用 handle_file 处理该文件self.handle_file(full_path)# 如果该路径不是一个文件else:#抛出异常:该路径为不知名对象raise ServerException("Unknown object '{0}'".format(self.path))# 处理异常except Exception as msg:self.handle_error(msg)

替换后:

# 所有可能的情况
Cases = [case_no_file(),case_existing_file(),case_always_fail()]def do_GET(self):try:# 文件完整路径full_path = os.getcwd() + self.path#遍历所有可能的情况for case in self.Cases:handler = case()#如果满足该类情况if handler.test(self):#调用相应的act函数handler.act(self)break# 处理异常except Exception as msg:self.handle_error(msg)

这样每当我们需要考虑一个新的情况时,只要新写一个条件处理类然后加到 Cases 中去就行了,是不是比原先在if-elif-else中添加条件的做法看起来更加干净更加清楚呢,毕竟修改原有的代码是一件很有风险的事情,调试起来也非常麻烦。在做功能扩展的同时尽量不要修改原代码是软件开发过程中需要牢记的一点。

回到正题,我们希望浏览器访问根url的时候能返回工作目录下index.html的内容,那就需要再多加一个条件判断啦。

写一个新的条件处理类

class case_directory_index_file(object):def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')#判断目标路径是否是目录&&目录下是否有index.htmldef test(self, handler):return os.path.isdir(handler.full_path) and \os.path.isfile(self.index_path(handler))#响应index.html的内容def act(self, handler):handler.handle_file(self.index_path(handler))

加到Cases

Cases = [case_no_file(),case_existing_file(),case_directory_index_file(),case_always_fail()]

在工作目录下添加index.html文件

<html>
<head>
<title>Index Page</title>
</head>
<body>
<h1>Index Page</h1>
<p>Welcome to my home.</p>
</body>
</html>

测试一下

$ http 127.0.0.1:8080

效果图

5.CGI协议

当然,大部分人都不希望每次给服务器加新功能都要到服务器的源代码里进行修改。如果程序能独立在另一个脚本文件里运行那就再好不过了。什么是CGI? 本小节会实现CGI的效果。

接下来的例子中,我们会在html页面上显示当地时间。

创建新文件 time.py

from datetime import datetime
print '''\
<html>
<body>
<p>Generated {0}</p>
</body>
</html>'''.format(datetime.now())

新建一个处理脚本文件的条件类:

class case_cgi_file(object):'''脚本文件处理'''def test(self, handler):return os.path.isfile(handler.full_path) and \handler.full_path.endswith('.py')def act(self, handler):##运行脚本文件handler.run_cgi(handler.full_path)

实现运行脚本文件的函数

import subprocessdef run_cgi(self, full_path):data = subprocess.check_output(["python", handler.full_path])self.send_content(data)

不要忘了加到Cases中去

Cases = [case_no_file(),case_cgi_file(),case_existing_file(),case_directory_index_file(),case_always_fail()]

查看效果

$ http 127.0.0.1:8080/time.py

6.代码重构

回头看看我们的代码,注意到一个新的问题了吗?虽然条件判断已经被我们整理到几个类中去了,但是像run_cgi只有在路径为py文件的条件下才使用的函数是放在 RequestHandler下的,那以后再加几个新功能,但是这类函数都放到 RequestHandler下的话可想而知RequestHandler 会变的臃肿不堪。当然你会想这算什么问题嘛,把它放到各自的条件类下不就好了噢。

各自的代码归各自是个好办法,但有时候不同的条件类内可能会有功能相同的函数,这时候我们都知道重复相同的代码是软件开发里很忌讳的一件事情,那么怎么处理重复的代码呢?

可以抽象出一个基类嘛,遇到重复的内容就放在基类的下面,所有的条件类都继承这个基类。

class base_case(object):'''条件处理基类'''def handle_file(self, handler, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()handler.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(full_path, msg)handler.handle_error(msg)def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')#要求子类必须实现该接口def test(self, handler):assert False, 'Not implemented.'def act(self, handler):assert False, 'Not implemented.'

子类继承基类,依此类推进行修改

class case_directory_index_file(base_case):def test(self, handler):return os.path.isdir(handler.full_path) and \os.path.isfile(self.index_path(handler))def act(self, handler):self.handle_file(handler, self.index_path(handler))

重构后的全部代码

#-*- coding:utf-8 -*-
import sys, os, BaseHTTPServer, subprocess#-------------------------------------------------------------------------------class ServerException(Exception):'''服务器内部错误'''pass#-------------------------------------------------------------------------------class base_case(object):'''条件处理基类'''def handle_file(self, handler, full_path):try:with open(full_path, 'rb') as reader:content = reader.read()handler.send_content(content)except IOError as msg:msg = "'{0}' cannot be read: {1}".format(full_path, msg)handler.handle_error(msg)def index_path(self, handler):return os.path.join(handler.full_path, 'index.html')def test(self, handler):assert False, 'Not implemented.'def act(self, handler):assert False, 'Not implemented.'#-------------------------------------------------------------------------------class case_no_file(base_case):'''文件或目录不存在'''def test(self, handler):return not os.path.exists(handler.full_path)def act(self, handler):raise ServerException("'{0}' not found".format(handler.path))#-------------------------------------------------------------------------------class case_cgi_file(base_case):'''可执行脚本'''def run_cgi(self, handler):data = subprocess.check_output(["python", handler.full_path])handler.send_content(data)def test(self, handler):return os.path.isfile(handler.full_path) and \handler.full_path.endswith('.py')def act(self, handler):self.run_cgi(handler)#-------------------------------------------------------------------------------class case_existing_file(base_case):'''文件存在的情况'''def test(self, handler):return os.path.isfile(handler.full_path)def act(self, handler):self.handle_file(handler, handler.full_path)#-------------------------------------------------------------------------------class case_directory_index_file(base_case):'''在根路径下返回主页文件'''def test(self, handler):return os.path.isdir(handler.full_path) and \os.path.isfile(self.index_path(handler))def act(self, handler):self.handle_file(handler, self.index_path(handler))#-------------------------------------------------------------------------------class case_always_fail(base_case):'''默认处理'''def test(self, handler):return Truedef act(self, handler):raise ServerException("Unknown object '{0}'".format(handler.path))#-------------------------------------------------------------------------------class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):'''请求路径合法则返回相应处理否则返回错误页面'''Cases = [case_no_file(),case_cgi_file(),case_existing_file(),case_directory_index_file(),case_always_fail()]# 错误页面模板Error_Page = """\<html><body><h1>Error accessing {path}</h1><p>{msg}</p></body></html>"""def do_GET(self):try:# 得到完整的请求路径self.full_path = os.getcwd() + self.path# 遍历所有的情况并处理for case in self.Cases:if case.test(self):case.act(self)break# 处理异常except Exception as msg:self.handle_error(msg)def handle_error(self, msg):content = self.Error_Page.format(path=self.path, msg=msg)self.send_content(content, 404)# 发送数据到客户端def send_content(self, content, status=200):self.send_response(status)self.send_header("Content-type", "text/html")self.send_header("Content-Length", str(len(content)))self.end_headers()self.wfile.write(content)#-------------------------------------------------------------------------------if __name__ == '__main__':serverAddress = ('', 8080)server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)server.serve_forever()

五、讨论

通过重构我们发现,真正实施行为(Action)的代码逻辑可以抽出来进行封装(封装成各种条件处理类),而 RequestHandler类 或是 basecase类 提供了供条件处理类使用的接口,它们可以看作是一系列服务(Service),在软件设计中我们常常会把业务代码进行分层,将行为与服务分开,降低耦合,更有利于我们开发维护代码。

通过统一接口,以及cgi程序,我们的代码功能扩展变的更加容易,可以专心于编写功能代码,而不用去关心其他部分。case 的添加虽然仍在server代码中,但我们也可以把它放到配置文件中,由server读取配置文件。

我们的 server 现在还是一个在新手村里打史莱姆的小菜鸡,你会给它添加什么功能让它成长成什么样子呢?

六、参考资料

  • A Simple Web Server
  • 500 Lines or Less
  • Introduction to HTTP
  • HTTP 下午茶

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

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

相关文章

python基础--修饰器

修饰器(语法糖) 在python中函数实际上就是一个对象 def outer(x):def inner(y):return x yreturn innerprint(outer(6)(5))def double(x):return x * 2 def triple(x):return x * 3def calc_number(func, x):print(func(x))calc_number(double, 3) calc_number(triple, 3)函…

鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断

关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多&#xff0c;比如中断控制器&#xff0c;中断源&#xff0c;中断向量&#xff0c;中断共享&#xff0c;中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念…

一堆自定义C#代码片段,让你开发效率飞涨

SharpBoxes 是一款用于 Visual Studio 的扩展&#xff0c;作者为本人&#xff1b; 该扩展旨在提高开发效率。它为开发人员提供了一组日常使用频率较高的代码片段&#xff0c;让你在编写代码时能够更快地插入常用的代码段。通过安装这个扩展&#xff0c;你可以使用快捷键轻松插…

Python基础之运算符操作

在Python中&#xff0c;运算符的作用就是用于执行各种的运算操作&#xff0c;常见的运算符有算数运算符、比较运算符、逻辑运算符、赋值运算符、成员运算符、身份运算符等。下面我们就来看看在Python中这些运算的详细操作。 算术运算符 算术运算符是用来执行一些基本的数学运…

Shiro + JWT 进行登录验证

Shiro是一个关于java的安全框架&#xff0c;可以实现用户的认证和授权&#xff0c;简单易用。 首先导入依赖 <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.1</version><…

HarmonyOS开发案例:【电子相册】

介绍 如何实现一个简单的电子相册应用的开发&#xff0c;主要功能包括&#xff1a; 实现首页顶部的轮播效果。 实现页面跳转时共享元素的转场动画效果。 实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。 相关概念 [Swiper]&#xff1a;滑块视图容器&#x…

W801学习笔记二十二:英语背单词学习应用——下

续上篇&#xff1a; W801学习笔记二十一&#xff1a;英语背单词学习应用——上 五、处理用户交互 由于英语也是采用了和唐诗一样的《三分钟限时挑战》《五十题竞速挑战》《零错误闯关挑战》&#xff0c;所以用户交互的逻辑和唐诗是一样的。所以&#xff0c;我们抽一个基类&a…

Leetcode—138. 随机链表的复制【中等】

2024每日刷题&#xff08;129&#xff09; Leetcode—138. 随机链表的复制 实现代码 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;random NULL;} }; */class Solution { public:Node* copyRan…

海洋行业工业气体检测传感器的重要性

海洋行业是一个广阔而复杂的领域&#xff0c;涉及多个分支和应用&#xff0c;包括浮式生产、储存和卸载&#xff08;FPSO&#xff09;装置、渡轮和潜艇等。这些船舶和设施在执行任务时&#xff0c;都可能遇到各种潜在的气体危害。因此&#xff0c;对于海洋行业来说&#xff0c;…

C++语法|可调用对象与function类型

文章目录 引入function的使用function类型的典型应用 引入 还记得C语言中的函数指针数组吗&#xff1f; 我们通过函数指针数组实现一个&#xff0c;图书管理系统的界面&#xff1a; #include <stdio.h> void doShowAllBooks() {printf("查看所有书籍信息\n")…

STM32接入CH340芯片的初始化进入升级模式(死机)问题处理

目录 1. 问题描述2. 问题分析2.1 CH340G/K 的初始化波形2.2 第1种USB升级电路2.3 第2种USB升级电路2.4 第3种USB升级电路2.5 第4种USB升级电路 3. 总结 1. 问题描述 我所用的CH340G&#xff08;CH340K也用过&#xff09;接在MCU的电路中&#xff0c;在插入CH340G/K 的接插件&a…

Unity EventSystem入门

概述 相信在学习Unity中&#xff0c;一定有被UI事件困扰的时候把&#xff0c;当添加UICanvas的时候&#xff0c;Unity会为我们自动添加EventSystem&#xff0c;这个是为什么呢&#xff0c;Unity的UI事件是如何处理的呢&#xff0c;在使用各个UI组件的时候&#xff0c;一定有不…

Java面试题:如何使用原子类(如AtomicInteger)来实现线程安全的计数

在 Java 中&#xff0c;java.util.concurrent.atomic 包提供了一组原子类&#xff0c;它们用于在多线程环境中执行原子操作。AtomicInteger 是这个包中最基本的原子类之一&#xff0c;它提供了一种线程安全的方式来进行整数的原子操作。以下是如何使用 AtomicInteger 来实现线程…

2269. 找到一个数字的 K 美丽值C++

一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目&#xff1a; 子字符串长度为 k 。子字符串能整除 num 。 给你整数 num 和 k &#xff0c;请你返回 num 的 k 美丽值。 注意&#xff1a; 允许有 前缀 0 。0 不能整除任何值。 一个 子字符串 是一个字符…

Redis(Redis配置和订阅发布)

文章目录 1.Redis配置1.网络配置1.配置文件位置 /etc/redis.conf2.bind&#xff08;注销支持远程访问&#xff09;1.默认情况bind 127.0.0.1 只能接受本机的访问2.首先编辑配置文件3.进入命令模式输入/bind定位&#xff0c;输入n查找下一个&#xff0c;shift n查找上一个&…

嵌入式实时操作系统在工业领域的应用

嵌入式实时操作系统是面向嵌入式系统的、保证在一定时间限制内完成各种程序的执行并提供资源分配、调度、输入输出控制以及数据管理等服务的软件。嵌入式实时操作系统一般包含核心功能和扩展功能&#xff0c;核心功能主要包含任务管理、任务同步与通信、时钟/定时器管理和中断异…

OpenHarmony 实战开发—— refreshlayout 组件开发学习指南~

1. RefreshLayout_harmonyos 功能介绍 1.1. 组件介绍&#xff1a; RefreshLayout_harmonyos 是一款下拉刷新组件 1.2. 手机模拟器上运行效果&#xff1a; 2. RefreshLayout_harmonyos 使用方法 2.1 在目录 build.gradle 下 implementation project(":refreshlayout_ha…

新能源汽车动力电池热管理-液冷方案应用原理与应用前景简介

前言 动力电池是新能源汽车的核心部件之一&#xff0c;其性能和寿命直接影响着车辆的续航里程和使用成本。液冷方案作为一种常见的动力电池温控解决方案&#xff0c;被广泛应用于新能源汽车领域。本文将详细介绍液冷方案的原理、发展方向以及市场前景。 一、液冷方案的原理 …

Jmeter 中 CSV 如何参数化测试数据并实现自动断言

当我们使用Jmeter工具进行接口测试&#xff0c;可利用CSV Data Set Config配置元件&#xff0c;对测试数据进行参数化&#xff0c;循环读取csv文档中每一行测试用例数据&#xff0c;来实现接口自动化。此种情况下&#xff0c;很多测试工程师只会人工地查看响应结果来判断用例是…

CentOS7编译安装freeswitch1.10.11

由于 FreeSWITCH 更新非常快&#xff0c;请自己查找最新的版本&#xff0c;如&#xff0c;截止 2022年6月4日&#xff0c;最稳定的发行版是&#xff1a;1.10.11 下载源代码&#xff1a; wget https://files.freeswitch.org/freeswitch-releases/freeswitch-1.10.11.-release.…