[HTTP] HTTP协议之MIME类型(多媒体资源类型)

news/2024/9/22 7:20:29/文章来源:https://www.cnblogs.com/johnnyzen/p/18333674

1 概述

MIME 的定义、由来

  • MIME(Multipurpose Internet Mail Extensions) 多用途互联网邮件扩展类型
  • MIME 是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开

多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

  • 诞生的原因:最初是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。
  • HTTP协议借用:正因为MIME在电子邮件系统中工作得非常出色,所以 HTTP 协议也采用了它,用它来描述、并标记万维网的多媒体内容

即:在万维网( World Wide Web)中使用的HTTP协议中也使用了MIME的框架,标准被扩展为互联网多媒体资源类型

MIME 类型的描述格式

  • MIME type := 一种主要的对象类型 / 一种特定的子类型

例如:text/plaintext/htmlimage/jpegapplication/jsonapplication/x-www-form-urlencodedmultipart/form-data

MIME 类型

  • 总的可以划分为:
  • application/*
  • audio/*
  • chemical/*
  • image/*
  • message/*
  • model/*
  • multipart/*
  • text/*
  • video/*
  • ...

2 Web开发实践篇

application/x-www-form-urlencoded (浏览器的默认类型)

前端普通表单场景

  • HTML 表单代码
<form action="http://localhost:8888/task/" method="POST">First name: <input type="text" name="firstName" value="Mickey&"><br>Last name: <input type="text" name="lastName" value="Mouse "><br><input type="submit" value="提交">
</form>
  • 通过测试发现可以正常访问接口,在Chrome开发者工具中可见,表单上传MIME格式为application/x-www-form-urlencoded(Request Headers中),参数的格式为key=value&key=value

  • 我们可以看出,服务器知道参数用符号&间隔,如果参数值中需要&,则必须对其进行编码。编码格式就是application/x-www-form-urlencoded(将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,将特殊符号转义为ASCII HEX值)。
  • application/x-www-form-urlencoded是浏览器默认的MIME类型

ps:可以在这个网址测试表单:http://www.runoob.com/try/try.php?filename=tryhtml_form_submit

后端接口调用场景

  • 在代码中使用application/x-www-form-urlencoded MIME 类型设置Request属性调用接口。
private static String doPost(String strUrl, String content) {String result = "";try {URL url = new URL(strUrl);//通过调用url.openConnection()来获得一个新的URLConnection对象,并且将其结果强制转换为HttpURLConnection.HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();urlConnection.setRequestMethod("POST");//设置连接的超时值为30000毫秒,超时将抛出SocketTimeoutException异常urlConnection.setConnectTimeout(30000);//设置读取的超时值为30000毫秒,超时将抛出SocketTimeoutException异常urlConnection.setReadTimeout(30000);//将url连接用于输出,这样才能使用getOutputStream()。getOutputStream()返回的输出流用于传输数据urlConnection.setDoOutput(true);//设置通用请求属性为默认浏览器编码类型urlConnection.setRequestProperty("content-type", "application/x-www-form-urlencoded");//getOutputStream()返回的输出流,用于写入参数数据。OutputStream outputStream = urlConnection.getOutputStream();outputStream.write(content.getBytes());outputStream.flush();outputStream.close();//此时将调用接口方法。getInputStream()返回的输入流可以读取返回的数据。InputStream inputStream = urlConnection.getInputStream();byte[] data = new byte[1024];StringBuilder sb = new StringBuilder();//inputStream每次就会将读取1024个byte到data中,当inputSteam中没有数据时,inputStream.read(data)值为-1while (inputStream.read(data) != -1) {String s = new String(data, Charset.forName("utf-8"));sb.append(s);}result = sb.toString();inputStream.close();} catch (IOException e) {e.printStackTrace();}return result;
}public static void main(String[] args) {String str = doPost("http://localhost:8888/task/", "firstName=Mickey%26&lastName=Mouse ");System.out.println(str);
}

multipart/form-data

定义

定义

  • 定义

媒体类型multipart/form-data遵循multipart MIME数据流定义(该定义可以参考Section 5.1 - RFC2046),大概含义就是:媒体类型multipart/form-data数据体由多个部分组成,这些部分由一个固定边界值Boundary分隔

  • 使用方式
  • 媒体类型multipart/form-data常用于POST方法下的HTTP请求,至于作为HTTP响应的场景相对少见。

multipart/form-data请求体布局

# 请求头 - 这个是必须的,需要指定Content-Type为multipart/form-data,指定唯一边界值
Content-Type: multipart/form-data; boundary=${Boundary}# 请求体
--${Boundary}
Content-Disposition: form-data; name="name of file"
Content-Type: application/octet-streambytes of file
--${Boundary}
Content-Disposition: form-data; name="name of pdf"; filename="pdf-file.pdf"
Content-Type: application/octet-streambytes of pdf file
--${Boundary}
Content-Disposition: form-data; name="key"
Content-Type: text/plain;charset=UTF-8text encoded in UTF-8
--${Boundary}--

区别

  • 媒体类型multipart/form-data相对于其他媒体类型(如application/x-www-form-urlencoded)来说,最明显的不同点是:
  • 请求头的Content-Type属性除了指定为multipart/form-data,还需要定义boundary参数
  • 请求体中的请求行数据是由多部分组成,boundary参数的值模式--${Boundary}用于分隔每个独立的分部
  • 每个部分必须存在请求头Content-Disposition: form-data; name="${PART_NAME}"; ,这里的${PART_NAME}需要进行URL编码,另外filename字段可以使用,用于表示文件名称,但是其约束性比name属性低(因为并不确认本地文件是否可用或者是否有异议)
  • 每个部分可以单独定义Content-Type和该部分的数据体
  • 请求体以boundary参数的值模式--${Boundary}--作为结束标志

{% note warning flat %} RFC7578中提到两个multipart/form-data过期的使用方式:
其一是Content/Transfer-Encoding请求头的使用,这里也不展开其使用方式;
其二是请求体中单个表单属性传输多个二进制文件的方式建议换用multipart/mixed(一个"name"对应多个二进制文件的场景)

  • 特殊地:如果某个部分的内容为文本,其Content-Typetext/plain,可指定对应的字符集,如Content-Type: text/plain;charset=UTF-8
    可以通过_charset_属性指定默认的字符集,用法如下:
Content-Disposition: form-data; name="_charset_"UTF-8
--ABCDE--
Content-Disposition: form-data; name="field"...text encoded in UTF-8...
ABCDE--

Boundary 参数取值规约

  • Boundary参数取值规约如下:
  • Boundary的值必须以英文中间双横杠--开头,这个--称为前导连字符
  • Boundary的值除了前导连字符以外的部分不能超过70个字符
  • Boundary的值不能包含HTTP协议或者URL禁用的特殊意义的字符,例如英文冒号:
  • 每个--${Boundary}之前默认强制必须为CRLF,如果某一个部分的文本类型请求体以CRLF结尾,那么在请求体的二级制格式上,必须显式存在2个CRLF,如果某一个部分的请求体不以CRLF结尾,可以只存在一个CRLF,这两种情况分别称为分隔符的显式类型和隐式类型。

说的比较抽象,见下面的例子:

# 请求头
Content-type: multipart/data; boundary="--abcdefg"--abcdefg
Content-Disposition: form-data; name="x"
Content-type: text/plain; charset=asciiIt does NOT end with a linebreak # <=== 这里没有CRLF,隐式类型
--abcdefg
Content-Disposition: form-data; name="y"
Content-type: text/plain; charset=asciiIt DOES end with a linebreak # <=== 这里有CRLF,显式类型--abcdefg## 直观看隐式类型的CRLF
It does NOT end with a linebreak CRLF --abcdefg## 直观看显式类型的CRLF
It DOES end with a linebreak CRLF CRLF --abcdefg

前端普通表单场景

CASE1 spring:MultipartFile

  • 那么当服务器使用multipart/form-data接收POST请求时,服务器怎么知道每个参数的开始位置和结束位置呢?
<form action="http://localhost:8888/task/" method="POST" enctype="multipart/form-data">First name: <input type="text" name="firstName" value="Mickey&"><br>Last name: <input type="text" name="lastName" value="Mouse "><br><input type="submit" value="提交">
</form>
  • 我们在开发者工具中可以看出multipart/form-data不会对参数编码,使用的boundary(分割线),相当于&boundary的值是----Web**AJv3

CASE2 spring:MultipartHttpServletRequest

//org.springframework.boot:spring-boot-starter-web:2.6.0
@RestController
public class TestController {@PostMapping(path = "/test")public ResponseEntity<?> test(MultipartHttpServletRequest request) {return ResponseEntity.ok("ok");}
}
  • Postman的模拟请求如下:

  • 后台控制器得到的请求参数如下:

后面编写的客户端可以直接调用此接口进行调试。

前端文件上传场景

  • 上传文件时,也要指定MIME类型为multipart/form-data
<form method="POST" action="http://localhost:8888/upload" enctype="multipart/form-data"><input type="file" name="file" /><input type="submit" value="Upload" />
</form>
  • 如果是SpringMVC项目,要服务器能接受multipart/form-data类型参数,还要在spring上下文配置以下内容,SpringBoot项目则不需要。
  • 定义解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><property name="defaultEncoding" value="utf-8"></property><!-- 设置最大上传文件大小 --><property name="maxUploadSize" value="100000"/>
</bean>
  • controller层API
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;@Controller
public class YourController {@PostMapping("/upload")public String handleFileUpload(MultipartFile file) {// 处理上传的文件, 例如保存文件// file.transferTo(new File("your-desired-location"));return "success"; // 返回处理结果页面}
}
  • 模拟:发起请求

我们可以通过FormData对象模拟表单提交,用原始的XMLHttpRequest来发送数据,让我们可以在Chrome开发工具中查看到具体格式:

<form id="form">First name: <input type="text" name="firstName" value="Mickey"><br>Last name: <input type="text" name="lastName" value="Mouse"><br><input type="file" name="file"><br>
</form><button onclick="submitForm()">提交</button><script>function submitForm() {var formElement = document.getElementById("form");var xhr = new XMLHttpRequest();xhr.open("POST", "/upload");xhr.send(new FormData(formElement));}
</script>
  • Demo

后端接口调用场景

CASE1 封装请求体转换为字节容器的模块

  • 这里的边界值全用显式实现边界值直接用固定前缀加上UUID生成即可。简单实现过程中做了一些简化:
  • 只考虑提交文本表单数据和二进制(文件)表单数据
  • 基于上一点,每个部分都明确指定Content-Type这个请求头
  • 文本编码固定为UTF-8
MultipartWriter
public class MultipartWriter {private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;private static final byte[] FIELD_SEP = ": ".getBytes(StandardCharsets.ISO_8859_1);private static final byte[] CR_LF = "\r\n".getBytes(StandardCharsets.ISO_8859_1);private static final String TWO_HYPHENS_TEXT = "--";private static final byte[] TWO_HYPHENS = TWO_HYPHENS_TEXT.getBytes(StandardCharsets.ISO_8859_1);private static final String CONTENT_DISPOSITION_KEY = "Content-Disposition";private static final String CONTENT_TYPE_KEY = "Content-Type";private static final String DEFAULT_CONTENT_TYPE = "multipart/form-data; boundary=";private static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream";private static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain;charset=UTF-8";private static final String DEFAULT_CONTENT_DISPOSITION_VALUE = "form-data; name=\"%s\"";private static final String FILE_CONTENT_DISPOSITION_VALUE = "form-data; name=\"%s\"; filename=\"%s\"";private final Map<String, String> headers = new HashMap<>(8);private final List<AbstractMultipartPart> parts = new ArrayList<>();private final String boundary;private MultipartWriter(String boundary) {this.boundary = Objects.isNull(boundary) ? TWO_HYPHENS_TEXT +UUID.randomUUID().toString().replace("-", "") : boundary;this.headers.put(CONTENT_TYPE_KEY, DEFAULT_CONTENT_TYPE + this.boundary);}public static MultipartWriter newMultipartWriter(String boundary) {return new MultipartWriter(boundary);}public static MultipartWriter newMultipartWriter() {return new MultipartWriter(null);}public MultipartWriter addHeader(String key, String value) {if (!CONTENT_TYPE_KEY.equalsIgnoreCase(key)) {headers.put(key, value);}return this;}public MultipartWriter addTextPart(String name, String text) {parts.add(new TextPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE, name), DEFAULT_TEXT_CONTENT_TYPE, this.boundary, text));return this;}public MultipartWriter addBinaryPart(String name, byte[] bytes) {parts.add(new BinaryPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE, name), DEFAULT_BINARY_CONTENT_TYPE, this.boundary, bytes));return this;}public MultipartWriter addFilePart(String name, File file) {parts.add(new FilePart(String.format(FILE_CONTENT_DISPOSITION_VALUE, name, file.getName()), DEFAULT_BINARY_CONTENT_TYPE, this.boundary, file));return this;}private static void writeHeader(String key, String value, OutputStream out) throws IOException {writeBytes(key, out);writeBytes(FIELD_SEP, out);writeBytes(value, out);writeBytes(CR_LF, out);}private static void writeBytes(String text, OutputStream out) throws IOException {out.write(text.getBytes(DEFAULT_CHARSET));}private static void writeBytes(byte[] bytes, OutputStream out) throws IOException {out.write(bytes);}interface MultipartPart {void writeBody(OutputStream os) throws IOException;}@RequiredArgsConstructorpublic static abstract class AbstractMultipartPart implements MultipartPart {protected final String contentDispositionValue;protected final String contentTypeValue;protected final String boundary;protected String getContentDispositionValue() {return contentDispositionValue;}protected String getContentTypeValue() {return contentTypeValue;}protected String getBoundary() {return boundary;}public final void write(OutputStream out) throws IOException {writeBytes(TWO_HYPHENS, out);writeBytes(getBoundary(), out);writeBytes(CR_LF, out);writeHeader(CONTENT_DISPOSITION_KEY, getContentDispositionValue(), out);writeHeader(CONTENT_TYPE_KEY, getContentTypeValue(), out);writeBytes(CR_LF, out);writeBody(out);writeBytes(CR_LF, out);}}public static class TextPart extends AbstractMultipartPart {private final String text;public TextPart(String contentDispositionValue,String contentTypeValue,String boundary,String text) {super(contentDispositionValue, contentTypeValue, boundary);this.text = text;}@Overridepublic void writeBody(OutputStream os) throws IOException {os.write(text.getBytes(DEFAULT_CHARSET));}@Overrideprotected String getContentDispositionValue() {return contentDispositionValue;}@Overrideprotected String getContentTypeValue() {return contentTypeValue;}}public static class BinaryPart extends AbstractMultipartPart {private final byte[] content;public BinaryPart(String contentDispositionValue,String contentTypeValue,String boundary,byte[] content) {super(contentDispositionValue, contentTypeValue, boundary);this.content = content;}@Overridepublic void writeBody(OutputStream out) throws IOException {out.write(content);}}public static class FilePart extends AbstractMultipartPart {private final File file;public FilePart(String contentDispositionValue,String contentTypeValue,String boundary,File file) {super(contentDispositionValue, contentTypeValue, boundary);this.file = file;}@Overridepublic void writeBody(OutputStream out) throws IOException {try (InputStream in = new FileInputStream(file)) {final byte[] buffer = new byte[4096];int l;while ((l = in.read(buffer)) != -1) {out.write(buffer, 0, l);}out.flush();}}}public void forEachHeader(BiConsumer<String, String> consumer) {headers.forEach(consumer);}public void write(OutputStream out) throws IOException {if (!parts.isEmpty()) {for (AbstractMultipartPart part : parts) {part.write(out);}}writeBytes(TWO_HYPHENS, out);writeBytes(this.boundary, out);writeBytes(TWO_HYPHENS, out);writeBytes(CR_LF, out);}
}

这个类已经封装好三种不同类型的部分请求体实现,forEachHeader()方法用于遍历请求头,而最终的write()方法用于把请求体写入到OutputStream中。

HttpURLConnection 实现

实现代码如下(只做最简实现,没有考虑容错和异常处理):

public class HttpURLConnectionTest {private static final String URL = "http://localhost:9099/test";public static void main(String[] args) throws Exception {MultipartWriter writer = MultipartWriter.newMultipartWriter();writer.addTextPart("name", "throwable").addTextPart("domain", "vlts.cn").addFilePart("ico", new File("I:\\doge_favicon.ico"));DataOutputStream requestPrinter = new DataOutputStream(System.out);writer.write(requestPrinter);HttpURLConnection connection = (HttpURLConnection) new java.net.URL(URL).openConnection();connection.setRequestMethod("POST");connection.addRequestProperty("Connection", "Keep-Alive");// 设置请求头writer.forEachHeader(connection::addRequestProperty);connection.setDoInput(true);connection.setDoOutput(true);connection.setConnectTimeout(10000);connection.setReadTimeout(10000);DataOutputStream out = new DataOutputStream(connection.getOutputStream());// 设置请求体writer.write(out);StringBuilder builder = new StringBuilder();BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));String line;while (Objects.nonNull(line = reader.readLine())) {builder.append(line);}int responseCode = connection.getResponseCode();reader.close();out.close();connection.disconnect();System.out.printf("响应码:%d,响应内容:%s\n", responseCode, builder);}
}

执行响应结果:

响应码:200,响应内容:ok

可以尝试加入两行代码打印请求体:

MultipartWriter writer = MultipartWriter.newMultipartWriter();
writer.addTextPart("name", "throwable").addTextPart("domain", "vlts.cn").addFilePart("ico", new File("I:\\doge_favicon.ico"));
DataOutputStream requestPrinter = new DataOutputStream(System.out);
writer.write(requestPrinter);

控制台输出如下;

JDK内置HttpClient实现

JDK11+内置了HTTP客户端实现,具体入口是java.net.http.HttpClient,实现编码如下:

public class HttpClientTest {private static final String URL = "http://localhost:9099/test";public static void main(String[] args) throws Exception {HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.of(10, ChronoUnit.SECONDS)).build();MultipartWriter writer = MultipartWriter.newMultipartWriter();writer.addTextPart("name", "throwable").addTextPart("domain", "test.cn").addFilePart("ico", new File("I:\\doge_favicon.ico"));ByteArrayOutputStream out = new ByteArrayOutputStream();writer.write(out);HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();writer.forEachHeader(requestBuilder::header);HttpRequest request = requestBuilder.uri(URI.create(URL)).method("POST", HttpRequest.BodyPublishers.ofByteArray(out.toByteArray())).build();HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());System.out.printf("响应码:%d,响应内容:%s\n", response.statusCode(), response.body());}
}

内置的HTTP组件几乎都是使用Reactive编程模型,使用的API都是相对底层,灵活性比较高但是易用性不高。

CASE2 HttpURLConnection

  • 在代码中使用multipart/form-data MIME 类型设置Request属性调用接口时,其中boundary的值可以在设置Content-Type时指定,让服务器知道如何拆分它接受的参数。

通过以下代码的调用接口:

private static String doPost(String strUrl, Map<String, String> params, String boundary) {String result = "";try {URL url = new URL(strUrl);HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();urlConnection.setRequestMethod("POST");urlConnection.setConnectTimeout(30000);urlConnection.setReadTimeout(30000);urlConnection.setDoOutput(true);//设置通用请求属性为multipart/form-dataurlConnection.setRequestProperty("content-type", "multipart/form-data;boundary=" + boundary);DataOutputStream dataOutputStream = new DataOutputStream(urlConnection.getOutputStream());for (String key : params.keySet()) {String value = params.get(key);//注意!此处是\r(回车:将当前位置移到本行开头)、\n(换行:将当前位置移到下行开头)要一起使用dataOutputStream.writeBytes("--" + boundary + "\r\n");dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + encode(key) + "\"\r\n");dataOutputStream.writeBytes("\r\n");dataOutputStream.writeBytes(encode(value) + "\r\n");}//最后一个分隔符的结尾后面要跟"--"dataOutputStream.writeBytes("--" + boundary + "--");dataOutputStream.flush();dataOutputStream.close();InputStream inputStream = urlConnection.getInputStream();byte[] data = new byte[1024];StringBuilder sb = new StringBuilder();while (inputStream.read(data) != -1) {String s = new String(data, Charset.forName("utf-8"));sb.append(s);}result = sb.toString();inputStream.close();} catch (Exception e) {e.printStackTrace();}return result;
}private static String encode(String value) throws UnsupportedEncodingException {return URLEncoder.encode(value, "UTF-8");
}public static void main(String[] args) {Map<String, String> params = new HashMap<>();params.put("firstName", "Mickey");params.put("lastName", "Mouse");//自定义boundary,有两个要求:使用不会出现在发送到服务器的HTTP数据中的值;并在请求消息中的分割位置都使用相同的值String boundary = "abcdefg";String str = doPost("http://localhost:8888/testFile", params, boundary);System.out.println(str);
}

通过debug,可以看出dataOutputStream的值如下:

参考与推荐文献

  • RFC-7578
  • RFC-2046
  • 理解HTTP协议中的multipart/form-data - Weixin/Throwable

X 参考文献

  • 《HTTP权威指南 - 人民邮电出版社》
  • Post请求的3种编码格式:application/x-www-form-urlencoded和multipart/form-data和application/json - CSDN
  • [Linux/Bash/Shell] curl & wget - 博客园/千千寰宇

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

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

相关文章

Splunk Enterprise 9.3 发布,新增功能概览

Splunk Enterprise 9.3 发布,新增功能概览Splunk Enterprise 9.3.0 (macOS, Linux, Windows) - 机器数据管理和分析 Splunk Enterprise 9.3 于 2024 年 7 月发布。新增功能新功能、增强或更改 描述对 Ingest Actions file system 目标的官方支持 将数据路由到 NFS 或本地文件系…

使用finallshell连接linux

用户可以去FinalShell的官网上下载,只需点击下载地址,即可轻松下载安装包。 傻瓜式安装点击到底。 使用 双击打开页面,新建连接右击连接,新建》ssh 连接,双击新建的连接,如下界面即连接成功。新建文件夹,右键新建文件夹。新建文件,在文件夹右键新建文件。 命令在命令区…

MQTT原理及案例

MQTT 协议是当今世界上最受欢迎的物联网协议,没有之一。MQTT 协议为设备提供了稳定、可靠、简单易用的通信基础,截至目前通过 MQTT 协议连接的设备已经过亿,广泛应用于 IoT、M2M 等领域。本篇将从最基础的知识开始,向您讲解 MQTT 协议的原理与应用。 目前 MQTT 主流版本有 …

IDEA 字节码工具 jclasslib bytecode viewer

IDEA 字节码工具 jclasslib bytecode viewer1. 下载安装首先我们在 IDEA 的插件市场中搜索 jclasslib bytecode viewer 和进行下载安装,安装过后根据 IDEA 版本的不同,可能需要重启才能使用。2. 使用下载安装过后,在我们的项目中,首先先将整个项目进行编译,编译的作用是将…

题解_P1525 [NOIP2010 提高组] 关押罪犯

目录[NOIP2010 提高组] 关押罪犯题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示题解思路分析程序实现 [NOIP2010 提高组] 关押罪犯 题目背景 NOIP2010 提高组 T3 题目描述 S 城现有两座监狱,一共关押着 \(N\) 名罪犯,编号分别为 \(1\sim N\)。他们之间的…

架构与思维:DNS在架构中的使用

1 介绍 DNS(Domain Name System,域名系统)是一种服务,它是域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP地址数串。 简单来说,DNS就是一个将我们输入的网址(比如www.baidu.com )转换成对应的IP地址(比如19…

python3 unittest+BeautifulReport单个进程输出多个测试报告

最近一个项目中需要由于输出的案例内容非常多(上万条),导致BeautifulReport输出的报告内容非常大(几百兆)。浏览器无法正常处理这么大的测试报告,就算打开了,也不方便阅读和处理,因此需要将报告分成多个输出。 经修改代码,发现单个进程内输出多个测试报告出现问题:第…

windows11解决visual c++6.0 打开提示不兼容弹窗问题

在Windows11系统中,打开Visual C++ 6.0 编辑器,会弹出不兼容弹窗,如图所示下面将给出解决办法,实测有效。 步骤1:重命名MSDEV.EXE文件 步骤2:修改“兼容模式”配置 步骤3:修改“目标”输入框内容 步骤4:重新启动软件人生如逆旅 我亦是行人

零基础快速上手STM32开发(手把手保姆级教程)

1 前言 作为一名嵌入式工程师,STM32 是必须要学习的一款单片机,同时这款单片机资料足够多,而且比较简单,非常适合初学者入门。 STM32 是一款由 STMicroelectronics 公司开发的 32 位微控制器,由于其强大的处理能力和广泛的应用领域,如嵌入式系统、物联网设备、机器人等,…

比网盘、FTP更好用的数据摆渡工具是什么?

企业进行网络隔离后,数据在隔离网间交换时就产生了数据摆渡需求,常见的数据摆渡工具包括移动U盘、网盘、FTP等,企业通常选择网盘、FTP来进行日常的数据摆渡操作。但网盘和FTP在数据摆渡上均存在不同程度的缺陷,具体表现在: 数据量限制:网盘一般面向办公文档型数据,系统往…

全文检索方案

1. 方案概述 本文旨在设计全文搜索功能,包括数据的存储、数据权限、数据接口、数据模型等,从整体设计全文检索方案。 2. 解决方案 全文搜索解决方案主要包含两部分,数据的存储及数据的查询展示,其中数据存储方面,主要分为业务数据存储及附件存储;而数据查询展示主…