tomcat(3)连接器

【0】README
0.1)本文部分内容转自“深入剖析tomcat”,旨在学习 tomcat(3)连接器 的基础知识;
0.2)Catalina 中有两个主要的模块:连接器(ServerSocket) 和 容器(Servlet容器);(干货——Catalina 中有两个主要的模块:连接器(ServerSocket) 和 容器(Servlet容器)
0.3)for complete source code, please visit https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter3;
0.4)温馨建议:建议阅读本文之前,已阅读过 tomcat(1~2)的系列文章,因为它们是环环相扣的;

【1】StringManager类
0)intro to StringManager:该类用于处理 properties文件, 这些文件记录其所在包的类的错误信息;
1)problem+solution:
1.1) problem:Tomcat处理错误消息的方法是,将错误消息存储在一个 properties 文件中,便于读取和编辑,如果将所有类使用的错误消息都存储在一个大的properties文件中,那维护这个文件将会很头疼;
1.2)solution:Tomcat 将properties文件划分到不同的包中,每个properties文件都是用 org.apache.catalina.util.StringManager 类的一个实例来处理;当包中的某个类需要在其包内的properties文件中查找错误消息时,它会先获取对应的 StringManager 实例;StringManager可以被包下的所有 类所共享;
2)StringManager是单例类:代码如下
// StringManager的source code.
// 该类用于处理 properties文件, 这些文件记录其所在包的类的错误信息;
public class StringManager {private static Hashtable managers = new Hashtable();private String packageName;private StringManager(String packageName) {this.packageName = packageName;}public synchronized static StringManager getManager(String packageName) {StringManager manager = (StringManager) managers.get(packageName);if(manager == null) {manager = new StringManager(packageName);managers.put(packageName, manager);}return manager;}
}
2.1)StringManager的app 荔枝:从 com.baidu 包下的类中使用 StringManager 的方法
StringManager  manager = StringManger.getManager("com.baidu");
2.2)LocalStrings.properties 文件的第一行非注释内容如下:
httpConnector.alreadyInitialized=HTTP connector has already been initialized
调用 StringManager 的 getString() 方法,传入httpConnector.alreadyInitialized 就可以返回 value=HTTP connector has already been initialized

【2】应用程序
1)本应用程序包含3个模块: 连接器模块,启动模块 和 核心模块;
1.1)连接器模块有以下5个类型(types):
t1)连接器及其支持类(HttpConnector and HttpProcessor);
t2)表示HTTP 请求的类(HttpRequest)及其支持类;
t3)表示HTTP响应的类(HttpResponse)及其支持类;
t4)外观类(HttpRequestFacade and HttpResponseFacade);
t5)常量类;
1.2)启动模块:只有一个类(Bootstrap),负责启动应用程序;
1.3)核心模块包括两个类:servletProcessor and StaticResourceProcessor;
2)应用程序的细节(details)
d1)启动应用程序;
d2)连接器;
d3)创建 HttpRequest 对象;
d4)创建 HttpResponse 对象;
d5)静态资源处理器和 servlet处理器;
d6)运行应用程序;
【2.1】启动应用程序 Bootstrap类
// 启动应用程序
public final class Bootstrap {public static void main(String[] args) {HttpConnector connector = new HttpConnector(); // 连接器connector.start(); //启动一个线程}
}
【2.2】HttpConnector类
1)intro to HttpConnector类:该类负责创建一个服务器套接字,该套接字会等待传入的HTTP请求;
2)HttpConnector类: 实现了 Runnable接口,这样可以专用于自己的线程;
3)run方法包含了一个while循环,该循环中执行如下3个操作(operations):
(干货——HttpConnector需要完成的操作——
创建服务器套接字并接受 client发出的HTTP请求,之后调用连接器的支持类 HttpProcessor(HTTP处理器)的process方法.
o1)等待HTTP请求;
o2)为每个请求创建一个 HttpProcessor实例;
o3)调用 HttpProcessor 对象的 process() 方法;(干货——我们赶紧转向 HttpProcessor):
// 等待HTTP 请求 的工作(连接器)
public class HttpConnector implements Runnable { // 创建服务器套接字并接受 client发出的HTTP请求,之后调用连接器的支持类 HttpProcessor(HTTP处理器)的process方法.boolean stopped;private String scheme = "http";public String getScheme() {return scheme;}public void run() {ServerSocket serverSocket = null;int port = 8080;try {serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));}catch (IOException e) {e.printStackTrace();System.exit(1);}while (!stopped) {// Accept the next incoming connection from the server socketSocket socket = null;// 负责创建一个服务器套接字。try {socket = serverSocket.accept(); // 等待HTTP请求}catch (Exception e) {continue;}// Hand this socket off to an HttpProcessorHttpProcessor processor = new HttpProcessor(this);processor.process(socket);}}public void start() {Thread thread = new Thread(this);thread.start();}
}
【2.2.1】HttpProcessor类
1)HttpProcessor类的process()方法接收来自传入的HTTP请求的套接字。对每个传入的HTTP请求,它要完成4个操作(operations):(干货——HttpProcessor类需要完成4个操作)
HttpProcessor
o1)创建一个 HttpRequest对象;
o2)创建一个 HttpResponse对象;
o3)解析HTTP 请求的第一行内容和请求头信息,填充 HttpRequest对象;
o4)将 HttpRequest 对象和 HttpResponse对象传递给 servletProcessor 或 StaticResourceProcessor 的process() 方法;
public void process(Socket socket) {@SuppressWarnings("deprecation")SocketInputStream input = null;OutputStream output = null;try {input = new SocketInputStream(socket.getInputStream(), 2048);output = socket.getOutputStream();// create HttpRequest object and parserequest = new HttpRequest(input);// create HttpResponse objectresponse = new HttpResponse(output);response.setRequest(request);response.setHeader("Server", "Pyrmont Servlet Container"); // 向客户发送响应头信息parseRequest(input, output); // 解析请求,填充HttpReqeust对象(请求的是静态资源还是servlet,具体是什么静态资源或servlet)parseHeaders(input); // 解析请求头,//check if this is a request for a servlet or a static resource//a request for a servlet begins with "/servlet/"if (request.getRequestURI().startsWith("/servlet/")) {ServletProcessor processor = new ServletProcessor();processor.process(request, response);}else {StaticResourceProcessor processor = new StaticResourceProcessor();processor.process(request, response);}// Close the socketsocket.close();// no shutdown for this application}catch (Exception e) {e.printStackTrace();}}
对上述代码的分析(Analysis):
A1)对于HTTP请求的review:(第一行是请求方法——URI——协议/版本;之后的文本行直到空行,空行前的文本是请求头信息,空行后的文本是请求实体正文)
  1. Post /examples/default.jsp HTTP/1.1  // the first line
  2. Accept: text/plain; text/html  // request header begins
  3. Accept-Language: en-gb   
  4. Connection: Keep-Alive  
  5. Host: localhost  
  6. User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)  
  7. Content-Length: 33  
  8. Content-Type: application/x-www-form-urlencoded  
  9. Accept-Encoding: gzip, deflate   // request header ends
  10. // 这里是空行(CRLF)   // empty line
  11. lastName=Yun&firstName=Lin  // request entiry body
A2)parseRequest方法的解析对象:是 http请求的 第一行(请求方法——URI——协议/版本)
A3)parseHeader方法的解析对象:是http请求的请求头信息;

【2.3】创建 HttpRequest对象
0)HttpProcessor 中的process()方法:该方法负责填充该对象中的属性,一句话说完,该类就是封装了一些Http请求信息;(servlet程序员可以通过调用 HttpserveltRequest类的一些方法获取 HTTP请求信息)
1)HttpRequest实现了 HttpservletRequest接口:其外观类是 HttpRequestFacade类:
2)HttpProcessor的process方法调用 parseRequest 和 parseHeader 方法,而parseHeader() 方法调用 addHeader()方法和addCookie() 填充HttpReqeust类的相关信息;
3)解析HTTP请求,process方法的执行流程,非常复杂,分为5个steps:
step1)读取套接字的输入流;
step2)解析请求行;
step3)解析请求头;
step4)解析Cookie;
step5)获取参数;
3.1)读取套接字的输入流
3.2)解析请求行:HttpProcessor 类的 process() 方法会调用私有方法parseRequest() 来解析请求行,即HTTP请求的第1行内容;
3.2.1)parseRequest方法首先会调用 SocketInputStream类的readRequestLine方法;
3.2.2)parseRequest方法从请求行中获取请求方法,URI 和 请求协议的版本信息:(对parseRequest()方法进行分解阐述)
  private void parseRequest(@SuppressWarnings("deprecation") SocketInputStream input, OutputStream output)throws IOException, ServletException {input.readRequestLine(requestLine); <span style="font-family: 宋体;">// Parse the incoming request line</span>String method =new String(requestLine.method, 0, requestLine.methodEnd);String uri = null;String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
3.2.3)在URI后面可能还有查询字符串,调用setQueryString方法来解决;(干货——查询字符串如name=tang&pwd=123456)
 // Parse any query parameters out of the request URIint question = requestLine.indexOf("?"); // 询问是否有查询字符串if (question >= 0) {request.setQueryString(new String(requestLine.uri, question + 1,requestLine.uriEnd - question - 1));uri = new String(requestLine.uri, 0, question);}else {request.setQueryString(null);uri = new String(requestLine.uri, 0, requestLine.uriEnd);}
3.2.4)parseRequest会进行相对路径和绝对路径的检查;
 // Checking for an absolute URI (with the HTTP protocol)if (!uri.startsWith("/")) {int pos = uri.indexOf("://");// Parsing out protocol and host nameif (pos != -1) {pos = uri.indexOf('/', pos + 3);if (pos == -1) {uri = "";}else {uri = uri.substring(pos);}}}
3.2.5)检查查询字符串中是否包含会话标识符(jsessionid),解析后并将其填充到 HttpRequest;(干货——会话标识符:浏览器的会话使用存储在 SessionID 属性中的唯一标识符进行标识。
 // 检查是否有会话标识符,如果有的话,做进一步解析// Parse any requested session ID out of the request URIString match = ";jsessionid=";int semicolon = uri.indexOf(match);if (semicolon >= 0) {String rest = uri.substring(semicolon + match.length());int semicolon2 = rest.indexOf(';');if (semicolon2 >= 0) {request.setRequestedSessionId(rest.substring(0, semicolon2));rest = rest.substring(semicolon2);}else {request.setRequestedSessionId(rest);rest = "";}request.setRequestedSessionURL(true);uri = uri.substring(0, semicolon) + rest;}else {request.setRequestedSessionId(null);request.setRequestedSessionURL(false);}// Normalize URI (using String operations at the moment)String normalizedUri = normalize(uri);    
3.2.6)parseRequest方法将URI 传入到 normaliza方法,对非正常的URL 进行修正;(如,出现'\'将其修正为 '/')
3.2.7)最后,parseRequest方法会设置HttpRequest对象的一些属性
// Set the corresponding request properties((HttpRequest) request).setMethod(method);request.setProtocol(protocol);if (normalizedUri != null) {((HttpRequest) request).setRequestURI(normalizedUri);}else {((HttpRequest) request).setRequestURI(uri);}if (normalizedUri == null) {throw new ServletException("Invalid URI: " + uri + "'");}}
3.3)解析请求头(HttpHeader):有5件事情需要了解(things)
t1)可以通过无参构造器创建HttpHeader 实例;
t2)可以将其实例传给 SocketInputStream类的readHeader() 方法;
t3)获取请求头的名字和值,使用如下方法:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
t4)parseHeaders() 方法有一个while循环,后者不断从SocketInputStream 中读取请求头信息,直到全部读完;
t5)然后,可以通过检查HttpHeader实例的nameEnd 和valueEnd 字段来判断是否已经从输入流中读取了所有的请求头信息;
3.3.1)当读取完请求头的名称和值之后,调用 HttpReqeust 的 addHeader() 方法,将其添加到HttpRequest 对象的HashMap请求头中:request.addHeader(name, value);
 // protected HashMap headers = new HashMap(); headers 就是一个hashmap(键值对)public void addHeader(String name, String value) {name = name.toLowerCase();synchronized (headers) {ArrayList values = (ArrayList) headers.get(name);if (values == null) {values = new ArrayList();headers.put(name, values);}values.add(value);}}
3.3.2)某些请求头包含一些属性设置信息
 private void parseHeaders(@SuppressWarnings("deprecation") SocketInputStream input)throws IOException, ServletException {while (true) {@SuppressWarnings("deprecation")HttpHeader header = new HttpHeader();;// Read the next headerinput.readHeader(header);if (header.nameEnd == 0) {if (header.valueEnd == 0) {return;}else {throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));}}String name = new String(header.name, 0, header.nameEnd);String value = new String(header.value, 0, header.valueEnd);request.addHeader(name, value); // this line.// do something for some headers, ignore others.if (name.equals("cookie")) {Cookie cookies[] = RequestUtil.parseCookieHeader(value); // this line , parse Cookie infofor (int i = 0; i < cookies.length; i++) {if (cookies[i].getName().equals("jsessionid")) {// Override anything requested in the URLif (!request.isRequestedSessionIdFromCookie()) {// Accept only the first session id cookierequest.setRequestedSessionId(cookies[i].getValue());request.setRequestedSessionCookie(true);request.setRequestedSessionURL(false);}}request.addCookie(cookies[i]);}}else if (name.equals("content-length")) {int n = -1;try {n = Integer.parseInt(value);}catch (Exception e) {throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));}request.setContentLength(n);}else if (name.equals("content-type")) {request.setContentType(value);}} //end while}
3.4)解析Cookie
3.4.1)intro to Cookie:Cookie是由浏览器作为HTTP请求头的一部分发送的。这样的请求头名称是 cookie,其对应值是一些名值对。(干货——intro to Cookie)
Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,对于JSP而言也可以直接写入jsessionid,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等,服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。(干货——Cookie的作用)

3.4.2)看个荔枝:下面是一个Cookie请求头的荔枝,包含两个Cookie:userName 和 password;
Cookie:userName=tang; password=xiao;
3.4.3)对Cookie的解析 通过 RequestUtil.parseCookieHeader() 方法来完成:解析完Cookie后,交由 HttpProcessor 类 的 parseHeader() 方法处理;
 // do something for some headers, ignore others.if (name.equals("cookie")) {Cookie cookies[] = RequestUtil.parseCookieHeader(value); // this line , parse Cookie infofor (int i = 0; i < cookies.length; i++) {if (cookies[i].getName().equals("jsessionid")) {// Override anything requested in the URLif (!request.isRequestedSessionIdFromCookie()) {// Accept only the first session id cookierequest.setRequestedSessionId(cookies[i].getValue());request.setRequestedSessionCookie(true);request.setRequestedSessionURL(false);}}request.addCookie(cookies[i]);}}else if (name.equals("content-length")) {int n = -1;try {n = Integer.parseInt(value);}catch (Exception e) {throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));}request.setContentLength(n);}else if (name.equals("content-type")) {request.setContentType(value);}} //end while
 public static Cookie[] parseCookieHeader(String header) {if ((header == null) || (header.length() < 1))return (new Cookie[0]);ArrayList cookies = new ArrayList();while (header.length() > 0) {int semicolon = header.indexOf(';');if (semicolon < 0)semicolon = header.length();if (semicolon == 0)break;String token = header.substring(0, semicolon);if (semicolon < header.length())header = header.substring(semicolon + 1);elseheader = "";try {int equals = token.indexOf('=');if (equals > 0) {String name = token.substring(0, equals).trim();String value = token.substring(equals+1).trim();cookies.add(new Cookie(name, value));}} catch (Throwable e) {;}}return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));}

3.5)获取参数
0)intro: 参数由 ParameterMap进行封装;参数可以出现在查询字符串或请求体中。若用户使用GET 方法请求servlet,则所有的参数都会在查询字符串中;若用户使用 POST 方法请求servlet,则请求体中也可能会有参数;所有的键值对都会存储在一个 HashMap对象中;
1)parseParameters() 方法如何工作: 由于参数可以存在于查询字符串或 HTTP请求体中,故parseParameter()方法 必须对这两者进行检查
1.1)首先检查parse是否为true,是否被解析过(参数只需要解析一次即可)
 if (parsed)return;
1.2)然后该方法会创建一个ParameterMap对象以存储参数信息;
ParameterMap results = parameters;
if (results == null)results = new ParameterMap();
1.3)打开ParameterMap的锁,使其可写:
results.setLocked(false);
1.4)检查字符串encoding,若encoding为null,使用默认编码;
String encoding = getCharacterEncoding();if (encoding == null)encoding = "ISO-8859-1";
1.5)对参数进行解析,调用RequestUtil.parseParameters() 方法完成;
// Parse any parameters specified in the query stringString queryString = getQueryString();try {RequestUtil.parseParameters(results, queryString, encoding);}catch (UnsupportedEncodingException e) {;}
1.6)检查HTTP 请求体是否包含请求参数。若用户使用POST提交请求时,请求体会包含参数;下面的代码用于解析请求体:
protected void parseParameters() {if (parsed)return;ParameterMap results = parameters;if (results == null)results = new ParameterMap();results.setLocked(false);String encoding = getCharacterEncoding();if (encoding == null)encoding = "ISO-8859-1";// Parse any parameters specified in the query stringString queryString = getQueryString();try {RequestUtil.parseParameters(results, queryString, encoding);}catch (UnsupportedEncodingException e) {;}// Parse any parameters specified in the input streamString contentType = getContentType();if (contentType == null)contentType = "";int semicolon = contentType.indexOf(';');if (semicolon >= 0) {contentType = contentType.substring(0, semicolon).trim();}else {contentType = contentType.trim();}if ("POST".equals(getMethod()) && (getContentLength() > 0)&& "application/x-www-form-urlencoded".equals(contentType)) {try {int max = getContentLength();int len = 0;byte buf[] = new byte[getContentLength()];ServletInputStream is = getInputStream();while (len < max) {int next = is.read(buf, len, max - len);if (next < 0 ) {break;}len += next;}is.close();if (len < max) {throw new RuntimeException("Content length mismatch");}RequestUtil.parseParameters(results, buf, encoding);}catch (UnsupportedEncodingException ue) {;}catch (IOException e) {throw new RuntimeException("Content read fail");}}// Store the final resultsresults.setLocked(true);parsed = true;parameters = results;}
1.7)最后,parseParameters() 方法会锁定ParameterMap对象,将parsed设置为true,将result设置为 parameters;
 // Store the final resultsresults.setLocked(true);parsed = true;parameters = results;


【2.4】创建 HttpResponse对象
1)intro to HttpResponse对象:该类实现 HttpservletResponse接口,与其对应的外观类是 HttpResponseFacade;
2)什么是  Writer? 在servlet中,可以使用PrintWriter 对象向输出流中写字符。可以使用任意 编码格式,但在向浏览器发送字符的时候,实际上都是字节流。
3)如何创建PringWriter对象呢?可以通过传入一个java.io.OutputStream 实例来创建 PrintWriter对象。所传给PrintWriter类的print方法或println方法的任何字符串都会被转换为字节流,使用基本的输出流发送到客户端;(使用一个 ResponseWriter实例 和 一个基本的ResponseStream对象)
4)OutputStreamWriter类:传入的字符会被转换为使用指定字符集的字节数组。其中所使用的字符集可以通过名称显式指定,也可以使用平台的默认字符集。每次调用写方法时,都会先使用编码转换器对给定字符进行编码转换。在被写入基本输出流之前,返回的字节数组会先存储在缓冲区中。缓冲区的大小是固定的,其默认值足够大。注意,传递给写方法的字符是没有缓冲的;
5)getWriter方法实现如下:
public PrintWriter getWriter() throws IOException {ResponseStream newStream = new ResponseStream(this);newStream.setCommit(false);OutputStreamWriter osr =new OutputStreamWriter(newStream, getCharacterEncoding());writer = new ResponseWriter(osr);return writer;}

【2.5】静态资源处理器和servlet处理器
1)本文的ServletProcessor 类中的process 方法:都会接受一个 HttpRequest对象和一个 HttpResponse对象,而不是Request 和 Response的实例,下面是process方法签名:
public void process(HttpRequest request, HttpResponse response) {
2)process() 方法使用 HttpRequestFacade类 和 HttpResponseFacade 类作为 request 和response 对象的外观类。当调用完 servlet 的 service() 方法后,它还会调用一次 HttpRespose类的 finishResponse()方法;
((HttpResponse) response).finishResponse();
public class ServletProcessor {public void process(HttpRequest request, HttpResponse response) {String uri = request.getRequestURI();String servletName = uri.substring(uri.lastIndexOf("/") + 1);URLClassLoader loader = null;try {// create a URLClassLoaderURL[] urls = new URL[1];URLStreamHandler streamHandler = null;File classPath = new File(Constants.WEB_ROOT);String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;urls[0] = new URL(null, repository, streamHandler);loader = new URLClassLoader(urls);}catch (IOException e) {System.out.println(e.toString() );}Class myClass = null;try {myClass = loader.loadClass("servlet." + servletName);}catch (ClassNotFoundException e) {System.out.println(e.toString());}Servlet servlet = null;try {servlet = (Servlet) myClass.newInstance();HttpRequestFacade requestFacade = new HttpRequestFacade(request);HttpResponseFacade responseFacade = new HttpResponseFacade(response);servlet.service(requestFacade, responseFacade);((HttpResponse) response).finishResponse();}...}
}
补充)本文总结了上述应用程序的调用流程图

对上图的分析(Analysis):
A1)HttpConnector(http连接器).process()方法:创建服务器套接字,接收 client发出的http请求,并调用HttpProcessor(http处理器)进行处理;
A2)HttpProcessor(http处理器).process()方法:
step1)通过套接字创建输入输出流;
step2)传入输入流创建Reqeust对象,传入输出流创建Response对象,且设置Response 的某变量引用Request实例对象;
step3)parseRequest方法:解析请求体的第一行(请求方法——URI——协议/版本),并附加解析出查询字符串(?name=tang&pwd=123456),请求路径是否为绝对路径,会话标识符等
step4)parseHeader方法:解析请求体的请求头信息;(附加会解析cookie)
step5)通过parseRequest方法解析出的URI 判断client请求的是资源是servlet 还是 静态资源,并做相应处理;
step5.1)若是静态资源:直接调用response.sendStaticResource() 方法 读取静态文件数据并发送给client;
step5.2)若是servlet:调用ServletProcessor.process() 方法进行如下处理,首先构建类加载器加载servlet,然后创建该servlet实例,并调用其service方法,service方法发送响应信息到client;

【2.6】运行应用程序
Attention)运行参数(设置classpath)为:
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomcat.chapter3.startup.Bootstrap




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

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

相关文章

java正则表达式验证_如何在Java中验证电话号码(正则表达式+ Google libphonenumber)...

java正则表达式验证关于如何在不同国家&#xff08;例如美国&#xff0c;美国&#xff09;使用Java验证电话号码的快速指南。 带有正则表达式和Google libphonenumber API的示例程序。 1.简介 在本教程中&#xff0c;我们将学习如何在java中验证电话号码 。 这主要是为了验证美…

漫画:什么是B-树

转载自 玻璃猫 程序员小灰 本文提到的「B-树」&#xff0c;就是「B树」&#xff0c;都是 B-tree 的翻译&#xff0c;里面不是减号-&#xff0c;是连接符-。因为有人把 B-tree 翻成 「B-树」&#xff0c;让人以为「B树」和「B-树」是两种树&#xff0c;实际上两者就是同一种树。…

tomcat(4)Tomcat的默认连接器

【0】README0.0&#xff09;本文部分文字描述转自&#xff1a;“深入剖析tomcat”&#xff0c;旨在学习 tomat(4)Tomat的默认连接器 的基础知识&#xff1b;0.1&#xff09;Tomcat中的连接器是一个独立的模块&#xff0c;可以插入到servlet容器中&#xff0c;而且还有很多连接器…

kafka mirror_SSL Kafka经纪人从Kafka Mirror Maker迁移到Brooklin的挑战

kafka mirror问题 从卡夫卡镜子制造商转移到布鲁克林有我在这里所写的优势。 但是&#xff0c;进行这种迁移并不容易&#xff0c;因为它本来应该如此。 我面临的主要挑战是&#xff1a;在消费者Kafka经纪人和Brooklin之间建立SSL连接 解 SSL问题 事实证明&#xff0c;这个问题…

漫画:什么是B+树

转载自 玻璃猫 程序员小灰这一次我们来介绍 B 树。一个m阶的B树具有如下几个特征&#xff1a; 1.根结点至少有两个子女。 2.每个中间节点都包含k-1个元素和k个孩子&#xff0c;其中 m/2 < k < m 3.每一个叶子节点都包含k-1个元素&#xff0c;其中 m/2 < k < m 4.所…

drools 规则流_约束流–没有Drools规则语言的现代Java约束

drools 规则流传统上&#xff0c;要使用OptaPlanner进行扩展&#xff0c;您必须学习DRL。 不再。 借助受Java 8 Streams和SQL启发的新Constraints Streams API&#xff0c;您现在可以用Java &#xff08;或Kotlin或Scala&#xff09; 编写约束&#xff0c;并且仍然可以从增量计…

char类型和Unicode编码

【0】README0.1&#xff09;本文对 char类型和Unicode编码 的总结并不完整&#xff0c;仅供参考&#xff1b;0.2&#xff09;本文获取Unicode辅助字符的代码点的idea转自&#xff1a; http://blog.csdn.net/xujinsmile/article/details/8526387 &#xff0c; http://bbs.csdn.n…

漫画:什么是一致性哈希

转载自 玻璃猫 程序员小灰一年之前——未来两年内&#xff0c;系统预估的总订单数量可达一亿条左右。 按Mysql单表存储500万条记录来算&#xff0c;暂时不必分库&#xff0c;单库30个分表是比较合适的水平分表方案。 于是小灰设计了这样的分表逻辑&#xff1a; 订单表创建单库3…

小程序真机测试错误代码_测试不充分:知道您的代码是否真的可以投入生产的5种方法...

小程序真机测试错误代码当今的企业都在提高软件交付速度。 但是&#xff0c;发布周期较短时&#xff0c;通常会牺牲代码质量。 当今的DevOps和QA团队承受着防止代码缺陷进入生产的压力&#xff0c;但他们还需要管理空前的工作负载&#xff0c;并且需要比以往更少的时间和资源来…

tomcat(5)servlet容器

【0】README0.0&#xff09;本文部分文字描述转自&#xff1a;“深入剖析tomcat”&#xff0c;旨在学习 tomcat(5)servlet容器 的基础知识&#xff1b;0.1&#xff09;intro to servlet容器&#xff1a;servlet容器是用来处理请求servlet资源&#xff0c;并为web客户端填充resp…

漫画算法:如何判断链表有环

转载自 玻璃猫 程序员小灰 大四毕业前夕&#xff0c;计算机学院&#xff0c; 正在四处求职的小灰碰到了同系的学霸大黄…… 小灰边说边回忆着上周去面试的情形…… 有一个单向链表&#xff0c;链表当中有可能出现“环”&#xff0c;就像下图这样。如何用程序判断出这个链表是…

jdk 加密_使用JDK的密码流的加密怪癖(以及该怎么做)

jdk 加密在我们的日常工作中&#xff0c;我们经常遇到经常性的主题&#xff0c;即将数据&#xff08;例如文件&#xff09;从一个位置传输到另一个位置。 这听起来像是一个非常简单的任务&#xff0c;但让我们通过声明这些文件可能包含机密信息并可以通过非安全的通信渠道进行传…

tomcat(5)servlet容器(lastest version)

【0】README0.0&#xff09;本文部分文字描述转自&#xff1a;“深入剖析tomcat”&#xff0c;旨在学习 tomcat(5)servlet容器 的基础知识&#xff1b;0.1&#xff09;intro to servlet容器&#xff1a;servlet容器是用来处理请求servlet资源&#xff0c;并为web客户端填充resp…

漫画:什么是跳跃表

转载自 玻璃猫 程序员小灰这是发生在很多年以前的故事......几天以前......几天之后......拍卖行的商品总数量有几十万件&#xff0c;对应数据库商品表的几十万条记录。如果是按照商品名称精确查询还好办&#xff0c;可以直接从数据库查出来&#xff0c;最多也就上百条记录。如…

带有Prometheus的弹簧靴和Micrometer第5部分:旋转Prometheus

以前&#xff0c;我们获得了Spring Boot Application适配器&#xff0c;以便为Prometheus公开端点。 该博客将重点介绍如何设置和配置Prometheus&#xff0c;以便为Spring Boot端点提供服务器。 因此&#xff0c;让我们开始使用docker来启动Prometheus服务器。 在继续进行Pr…

漫画:什么是动态规划?(整合版)

转载自 玻璃猫 程序员小灰 ———————————— 题目&#xff1a; 有一座高度是10级台阶的楼梯&#xff0c;从下往上走&#xff0c;每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。 比如&#xff0c;每次走1级台阶&#xff0c;一共走10步&#xff…

tomcat(6)生命周期

【0】README0.1&#xff09;本文部分文字描述转自“深入剖析tomcat”&#xff0c;旨在学习 “tomcat生命周期” 的基础知识&#xff1b;0.2&#xff09;for source code, please visit https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter6 0.3&#xff09;温…

移位操作提高代码的可读性_本地记录或类,以提高流操作的可读性

移位操作提高代码的可读性Java 14带有Records的预览语言功能—一种特殊的轻量级类&#xff0c;可以与其他语言中的类似构造进行比较&#xff0c;例如C&#xff03;中的record类&#xff0c;Kotlin中的data类和Scala中的case类。 A&#xff09;已经有许多博客文章解释Java 14记…

漫画算法:无序数组排序后的最大相邻差值

转载自 玻璃猫 程序员小灰 小灰一边回忆一边讲述起当时面试的情景...... 题目&#xff1a;有一个无序整型数组&#xff0c;如何求出这个数组排序后的任意两个相邻元素的最大差值&#xff1f;要求时间和空间复杂度尽可能低。&#xff08;例如&#xff1a;无序数组 2,3,1,4,6&…

serv-u 数据备份_如何使用用户数据脚本在EC2实例上安装Apache Web Server

serv-u 数据备份你好朋友&#xff0c; 在本教程中&#xff0c;我们将看到如何使用用户数据脚本在EC2实例上安装Apache Web Server。 在我以前的教程之一中&#xff0c;我已经解释了如何使用AWS控制台启动EC2实例。如果您还没有完成该操作&#xff0c;我建议您先进行一下操作。…