范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

惊呆了!手写4个mini版的tomcat

  写在前面
  Apache Tomcat 是Java Servlet, JavaServer Pages (JSP),Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。
  今天,我们就来手写tomcat,但是说明一下:咱们不是为了装逼才来写tomcat,而是希望大家能更多的理解和掌握tomcat。
  废话不多说了,直接开干。  基本结构tomcat架构图
  我们可以把上面这张架构图做简化,简化后为:
  什么是http协议
  Http是一种网络应用层协议,规定了浏览器与web服务器之间如何通信以及数据包的结构。
  通信大致可以分为四步:  先建立连接。  发送请求数据包。  发送响应数据包。  关闭连接。
  优点  web服务器可以利用有限的连接为尽可能多的客户请求服务。 tomcat中Servlet的运作方式在浏览器地址栏输入http://ip:port/servlet-day01/hello  浏览器依据IP、port建立连接(即与web服务器之间建立网络连接)。  浏览器需要将相关数据打包(即按照http协议要求,制作一个 请求数据包,包含了一些数据,比如请求资源路径),并且将请求 数据包发送出去。  web服务器会将请求数据包中数据解析出来,并且将这些数据添加 到request对象,同时,还会创建一个response对象。  web服务器创建Servlet对象,然后调用该对象的service方法(会将request和response作为参数)。注:在service方法里面,通过使用request获得请求相关的数据, 比如请求参数值,然后将处理结果写到response。  web服务器将response中的数据取出来,制作响应数据包,然后发送给浏览器。  浏览器解析响应数据包,然后展现。
  可以总结唯一张图:
  什么是Servlet呢?
  Servlet是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能,统一接口。由其他内部厂商如tomcat,jetty内部实现web的功能。如一个http请求到来:容器将请求封装为servlet中的HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse返回给客户端的过程。
  什么是Servlet规范?从 Jar 包上来说,Servlet 规范就是两个 Jar 文件。servlet-api.jar 和 jsp-api.jar,Jsp 也是一种 Servlet。  从package上来说,就是 javax.servlet 和 javax.servlet.http 两个包。  从接口来说,就是规范了 Servlet 接口、Filter 接口、Listener 接口、ServletRequest 接口、ServletResponse 接口等。类图如下:
  第一版:Socket版
  使用Socket编程,实现简单的客户端和服务端的聊天。
  服务端代码如下:  package com.tian.v1;  import java.io.*; import java.net.*;   public class Server {      public static String readline = null;     public static String inTemp = null;     public static String turnLine = " ";     public static final String client = "客户端:";     public static final String server = "服务端:";     public static final int PORT = 8090;      public static void main(String[] args) throws Exception {         ServerSocket serverSocket = new ServerSocket(PORT);         System.out.println("服务端已经准备好了");         Socket socket = serverSocket.accept();          BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));         BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));         PrintWriter socketOut = new PrintWriter(socket.getOutputStream());         while (true) {             inTemp = socketIn.readLine();             if (inTemp != null &&inTemp.contains("over")) {                 systemIn.close();                 socketIn.close();                 socketOut.close();                 socket.close();                 serverSocket.close();             }             System.out.println(client + inTemp);             System.out.print(server);              readline = systemIn.readLine();              socketOut.println(readline);             socketOut.flush();         }     } }
  客户端代码如下:  package com.tian.v1;  import java.io.*; import java.net.*;  public class Client {      public static void main(String[] args) throws Exception {         String readline;         String inTemp;         final String client = "客户端说:";         final String server = "服务端回复:";          int port = 8090;         byte[] ipAddressTemp = {127, 0, 0, 1};         InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp);          //首先直接创建socket,端口号1~1023为系统保存,一般设在1023之外         Socket socket = new Socket(ipAddress, port);          BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));         BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));         PrintWriter socketOut = new PrintWriter(socket.getOutputStream());         while (true) {             System.out.print(client);             readline = systemIn.readLine();              socketOut.println(readline);             socketOut.flush();             //处理             inTemp = socketIn.readLine();             if (inTemp != null && inTemp.contains("over")) {                 systemIn.close();                 socketIn.close();                 socketOut.close();                 socket.close();             }             System.out.println(server + inTemp);         }     } }
  过程如下:
  ,时长00:44  第二版:我们直接请求http://localhost:8090
  实现代码如下:  package com.tian.v2;  import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket;  public class MyTomcat {     /**      * 设定启动和监听端口      */     private int port = 8090;      /**      * 启动函数      *      * @throws IOException      */     public void start() throws IOException {         System.out.println("my tomcat starting...");         String responseData = "6666666";         ServerSocket socket = new ServerSocket(port);         while (true) {             Socket accept = socket.accept();             OutputStream outputStream = accept.getOutputStream();             String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData;             outputStream.write(responseText.getBytes());             accept.close();         }     }      /**      * 启动入口      */     public static void main(String[] args) throws IOException {         MyTomcat tomcat = new MyTomcat();         tomcat.start();     } }
  再写一个工具类,内容如下;  ackage com.tian.v2;  public class HttpProtocolUtil {      /**      * 200 状态码,头信息      *      * @param contentLength 响应信息长度      * @return 200 header info      */     public static String getHttpHeader200(long contentLength) {         return "HTTP/1.1 200 OK  " + "Content-Type: text/html  "                 + "Content-Length: " + contentLength + "  " + "r ";     }      /**      * 为响应码 404 提供请求头信息(此处也包含了数据内容)      *      * @return 404 header info      */     public static String getHttpHeader404() {         String str404 = "

404 not found

"; return "HTTP/1.1 404 NOT Found " + "Content-Type: text/html " + "Content-Length: " + str404.getBytes().length + " " + "r " + str404; } }   启动main方法:   使用IDEA访问:   在浏览器访问:   自此,我们的第二版本搞定。下面继续第三个版本; 第三版:封装请求信息和响应信息   一个http协议的请求包含三部分: 方法 URI 协议/版本 请求的头部 主体内容   比如 POST /index.html HTTP/1.1 Accept: text/plain; text/html Accept-Language: en-gb Connection: Keep-Alive Host: localhost User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Content-Length: 33 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate lastName=tian&firstName=JohnTian   简单的解释 数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/index.html,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。 请求头部从第二行开始,使用英文冒号(:)来分离键和值。 请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。   类似于http协议的请求,响应也包含三个部分。 协议 状态 状态描述 响应的头部 主体内容   比如: HTTP/1.1 200 OK Server: Microsoft-IIS/4.0 Date: Mon, 5 Jan 2004 13:13:33 GMT Content-Type: text/html Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT Content-Length: 112 HTTP Response Example Welcome to Brainy Software   简单解释 第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。 之后表示响应头部。 响应头部和主体内容之间使用空行来分离。   代码实现   创建一个工具类,用来获取静态资源信息。 package com.tian.v3; import com.tian.v2.HttpProtocolUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * 提取了一些共用类和函数 */ public class ResourceUtil { /** * 根据请求 url 获取完整绝对路径 */ public static String getPath(String url) { String path = ResourceUtil.class.getResource("/").getPath(); return path.replaceAll("", "/") + url; } /** * 输出静态资源信息 */ public static void outputResource(InputStream input, OutputStream output) throws IOException { int count = 0; while (count == 0) { count = input.available(); } int resourceSize = count; output.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); long written = 0; int byteSize = 1024; byte[] bytes = new byte[byteSize]; while (written < resourceSize) { if (written + byteSize > resourceSize) { byteSize = (int) (resourceSize - written); bytes = new byte[byteSize]; } input.read(bytes); output.write(bytes); output.flush(); written += byteSize; } } }   另外HttpProtocolUtil照样用第二版本中。   再创建Request类,用来解析并存放请求相关参数。 package com.tian.v3; import java.io.IOException; import java.io.InputStream; public class Request { /** * 请求方式, eg: GET、POST */ private String method; /** * 请求路径,eg: /index.html */ private String url; /** * 请求信息输入流   * 示例 *
      *  GET / HTTP/1.1      *  Host: localhost      *  Connection: keep-alive      *  Pragma: no-cache      *  Cache-Control: no-cache      *  Upgrade-Insecure-Requests: 1      *  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36      * 
*/ private InputStream inputStream; public Request() { } public Request(InputStream inputStream) throws IOException { this.inputStream = inputStream; int count = 0; while (count == 0) { count = inputStream.available(); } byte[] bytes = new byte[count]; inputStream.read(bytes); // requestString 参考:this.inputStream 示例 String requestString = new String(bytes); // 按换行分隔 String[] requestStringArray = requestString.split("n"); // 读取第一行数据,即:GET / HTTP/1.1 String firstLine = requestStringArray[0]; // 遍历第一行数据按空格分隔 String[] firstLineArray = firstLine.split(" "); this.method = firstLineArray[0]; this.url = firstLineArray[1]; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public InputStream getInputStream() { return inputStream; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } }   把第二版的MyTomcat进行小小调整: package com.tian.v3; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class MyTomcat { private static final int PORT = 8090; public void start() throws IOException { System.out.println("my tomcat starting..."); ServerSocket socket = new ServerSocket(PORT); while (true) { Socket accept = socket.accept(); OutputStream outputStream = accept.getOutputStream(); // 分别封装 Request 和 Response Request request = new Request(accept.getInputStream()); Response response = new Response(outputStream); // 根据 request 中的 url,输出 response.outputHtml(request.getUrl()); accept.close(); } } public static void main(String[] args) throws IOException { MyTomcat tomcat = new MyTomcat(); tomcat.start(); } }   然后再创建一个index.html,内容很简单: hello world

you already succeed!

  这一需要注意,index.html文件的存放路径不放错了,视本地路径来定哈,放在classes文件夹下的。你可以debug试试,看看你应该放在那个目录下。   启动MyTomcat。   访问 http://localhost:8090/index.html   自此,我们针对于Http请求参数和相应参数做了一个简单的解析以及封装。   尽管其中还有很多问题,但是字少看起来有那点像样了。我们继续第四版, 第四版:实现动态请求资源   用过servlet的同学都知道,Servlet中有三个很重要的方法init、destroy 、service 。其中还记得我们自己写LoginServlet的时候,还会重写HttpServlet中的doGet()和doPost()方法。下面们就自己来搞一个:   Servlet类代码如下: public interface Servlet { void init() throws Exception; void destroy() throws Exception; void service(Request request, Response response) throws Exception; }   然后再写一个HttpServlet来实现Servlet。   代码实现如下: package com.tian.v4; public abstract class HttpServlet implements Servlet { @Override public void init() throws Exception { } @Override public void destroy() throws Exception { } @Override public void service(Request request, Response response) throws Exception { String method = request.getMethod(); if ("GET".equalsIgnoreCase(method)) { doGet(request, response); } else { doPost(request, response); } } public abstract void doGet(Request request, Response response) throws Exception; public abstract void doPost(Request request, Response response) throws Exception; }   下面我们就来写一个自己的Servlet,比如LoginServlet。 package com.tian.v4; public class LoginServlet extends HttpServlet { @Override public void doGet(Request request, Response response) throws Exception { String repText = "

LoginServlet by GET method

"; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void doPost(Request request, Response response) throws Exception { String repText = "

LoginServlet by POST method

"; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void init() throws Exception { } @Override public void destroy() throws Exception { } }   大家是否还记得,我们在学习Servlet的时候,在resources目录下面有个web.xml。我们这个版本也把这个xml文件给引入。 <?xml version="1.0" encoding="utf-8"?> login com.tian.v4.LoginServlet login /login   既然引入了xml文件,那我们就需要去读取这个xml文件,并解析器内容。所以这里我们需要引入两个jar包。 dom4j dom4j 1.6.1 jaxen jaxen 1.1.6   万事俱备,只欠东风了。这时候我们来吧MyTomcat这个类做一些调整即可。   下面有个很重要的initServlet()方法,刚刚是对应下面这张图中的List servlets,但是我们代码里使用的是Map来存储Servlet的,意思就那么个意思,把Servlet放在集合里。   这也就是为什么大家都把Tomcat叫做Servlet容器的原因,其实真正的容器还是java集合。 package com.tian.v4; import com.tian.v3.RequestV3; import com.tian.v3.ResponseV3; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.List; import java.util.Map; public class MyTomcat { /** * 设定启动和监听端口 */ private static final int PORT = 8090; /** * 存放 Servlet信息,url: Servlet 实例 */ private Map servletMap = new HashMap<>(); public void start() throws Exception { System.out.println("my tomcat starting..."); initServlet(); ServerSocket socket = new ServerSocket(PORT); while (true) { Socket accept = socket.accept(); OutputStream outputStream = accept.getOutputStream(); // 分别封装 RequestV3 和 ResponseV3 RequestV4 requestV3 = new RequestV4(accept.getInputStream()); ResponseV4 responseV3 = new ResponseV4(outputStream); // 根据 url 来获取 Servlet HttpServlet httpServlet = servletMap.get(requestV3.getUrl()); // 如果 Servlet 为空,说明是静态资源,不为空即为动态资源,需要执行 Servlet 里的方法 if (httpServlet == null) { responseV3.outputHtml(requestV3.getUrl()); } else { httpServlet.service(requestV3, responseV3); } accept.close(); } } public static void main(String[] args) throws Exception { MyTomcat tomcat = new MyTomcat(); tomcat.start(); } /** * 解析web.xml文件,把url和servlet解析出来, * 并保存到一个java集合里(Map) */ public void initServlet() throws Exception { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml"); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); List list = rootElement.selectNodes("//servlet"); for (Element element : list) { // show Element servletnameElement = (Element) element.selectSingleNode("servlet-name"); String servletName = servletnameElement.getStringValue(); // server.ShowServlet Element servletclassElement = (Element) element.selectSingleNode("servlet-class"); String servletClass = servletclassElement.getStringValue(); // 根据 servlet-name 的值找到 url-pattern Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name="" + servletName + ""]"); // /show String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue(); servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance()); } } }   启动,再次访问 http://localhost:8090/index.html   同时,我们可以访问 http://localhost:8090/login   到此,第四个版本也搞定了。   但是前面四个版本都有一个共同的问题,全部使用的是BIO。   BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。   所以,大家在网上看到的手写tomcat的,也有使用线程池来做的,这里希望大家能get到为什么使用线程池来实现。另外,其实在tomcat高版本中已经没有使用BIO了。   而 HTTP/1.1默认使用的就是NIO了。   但这个只是通信方式,重点是我们要理解和掌握tomcat的整体实现。 总结   另外,发现上面都是讲配置文件解析,并将对应数据保存起来。熟悉这个套路后,大家是不是想到,我们很多配置项都是在server.xml中,还记得否?也是可以通过解析某个目录下的server.xml文件,并把内容赋给java中相应的变量罢了。   比如:   1.server.xml中的端口配置,我们是在代码里写死的而已,改成MyTomcat启动的时候去解析并获取不久得了吗?   2.我们通常是将我们项目的打成war,然后解压到某个目录下,最后还不是可以通过读取这个解压后的某个目录中找到web.xml,然后用回到上面的web.xml解析了。   本文主要是分享如何从一个塑料版到黄金版、然后铂金版,最后到砖石版。可以把加入线程池的版本称之为星耀版,最后把相关server.xml解析,以及读取我们放入到tomcat中项目解析可以称之为王者版。

来一手粉色系穿搭,减龄又迷人期待这个冬天想要冬天成为最靓的崽,那就要尝试一些迷人的色彩穿搭,而这中间怎么可能少的了粉色,冬季来一手粉色系穿搭,减龄又迷人。草莓粉羊羔毛连帽外套橘子毛衣刺绣牛仔裤粉白运动鞋粉色连杨颖因生理期被嘲不敬业,宋智孝却圈粉无数,后因造型遭南韩网暴一宋智孝最近是在撕足力健的代言吗?不然为什么打扮成中老年人最爱的贵妇风?这一系列铁T味十足的照片,出自2021年的AAA颁奖礼,当天宋智孝顶着油腻的短发穿着臃肿的黑袍画着脏脏的眼妆曾遥不可及的机械美学,如今被OPPO用7699元拿下了不知道差友们听没听说过这么一个东西,它是一台几百年前的机器,但是却可以通过纯纯的机械结构来进行数学运算虽然这台机器的完全体没有被正式发明出来,它也早就销声匿迹了,但是互联网上仍然流高跟鞋与X型腿及O型腿之间的关系高跟鞋可以将身材拔高,让双腿显得更加修长,常常是众多女士在正式场合中的标配。高跟鞋虽好,但却不够舒适,常常会损害趾关节踝关节以及双腿的健康引发运动功能障碍,甚至可能会付出惨重的永久洛克人游戏即将改编真人电影,来看看哪些演员适合成为洛克人34年后,洛克人将在Netflix以真人版电影出现。以下是6位适合主演洛克人的新星。Netflix正在制作洛克人电影,这里列出了非常适合担任主角的演员名单。流媒体巨头Capcom的中植系掌舵人突然去世,围绕原乐视大厦的罗生门剧情走向何方?12月18日清晨,中植企业集团创始人解直锟因病突然离世。就在不久前,原乐视大厦的拍卖与质疑掀开了这位低调的富豪和背后庞大的中植系商业帝国面纱。公开资料显示,目前中植系总计资产规模超央视名嘴白岩松患病5年,近况惋惜,年仅52岁却活成这样在我们的印象当中,总有几个主持人的面孔经久不衰,我们今天要说的这么人就是白岩松老师,又被网友们尊称为央视名嘴!说起白岩松,大部分年轻人都会和我一样,感觉白岩松是个非常严肃且不苟言笑为什么宇宙速度极限低于光速如果你想在宇宙中以最快的速度旅行,最好的办法就是将尽可能多的能量注入到你能找到的尽可能小的质量中。当你逐渐增加你的粒子的动能和动量时,它在太空中的移动速度会更快,接近宇宙的极限速度詹皇里程碑!詹姆斯达成3万分9千板9千助1千帽无愧联盟牌面代言人北京时间12月20日,詹姆斯在浓眉哥伤停情况下,展现自己在防守端的威力,他本场面对公牛已经有盖帽进账,从而职业生涯迎来1000记盖帽里程碑,詹姆斯成为NBA历史上第一位贡献至少30SpaceX公司发射载有52颗星际连接卫星的火箭据塔斯社12月19日报道,Spacex公司网站上的一则广播显示,该公司周六发射了一枚猎鹰9号火箭,携带了一组星际连接卫星。火箭于当地时间凌晨4点42分(莫斯科时间15点42分)从加英雄联盟手游2。6版本更新,带你优先体验,准备好了吗哈喽,大家好,我是毒蠍,大家都知道,英雄联盟手游新版本将于12月22日更新,但是大家都知道这次更新内容有些啥不?所以这次就给大家聊聊本次的版本更新吧。本次版本更新我们将从2。5版本
盘点引起失眠的坏习惯如何提上睡眠质量?睡眠质量对人体非常重要。在睡眠过程中,身体会不断修复和休息,分泌大量激素,保证人体各器官的正常运转。然而,生活中的许多坏习惯会影响睡眠质量,导致睡眠质量的下降,这对人体非常危险。如浅谈王者匹配机制我在星耀一上王者晋级赛打了差不多5到6次吧,但每次晋级赛总会遇到各种问题,我也怀疑匹配机制是不是故意的。有一局快赢的时候,输出位的队友挂机了,给对面直接翻盘也有队友抢了不会玩的英雄29号充值返利来袭,典藏皮肤降价,3款限定返场,10点券留给电玩Hello,大家好,这里是头号游戏,每天都会带来最新的游戏资讯!王者荣耀在之前的时候就已经预告了将会在29号迎来一次版本更新,目前这一次的版本更新内容已经发出了公告,将会在29号的LOL从代码上来说最难的是哪个英雄?这个问题有意思,虽然没接触过做游戏,但我好歹是个敲代码的,也玩过好几年时间的LOL,应该有资格来说一说。在我看来,一个英雄的代码复杂度是和玩家交互的复杂度成正比,可以理解为操作越花90后券商交易员被老婆晒月入8万上热搜!中金回应停职调查中90后券商交易员月入超8万上热搜,不过这次晒薪酬的并非员工本人,而是其妻子所为。7月28日晚间,一则90后券商交易员月入超8万相关截图在社交平台疯传。相关截图显示,有博主在小红书上佩洛西,会不会铤而走险?7月29日,在市场氛围相对造好的情况下,A股却出现了下跌,不用说,定然有令市场担忧的因素发生。这当然会让我们将目光聚焦在29日,美国众议院议长佩洛西的亚洲之行上。倘若佩洛西窜台,保女主角首次登场,至少等到2024年才推出,R星六代的爆料又来了七月上旬的时候,博士和大家分享了文章全力开发六代新作,R星宣布将减少荒野大镖客2线上模式的投入,介绍了RockstarGames的一些举措,他们把相关资源都投入到R星六代的开发中,原神2。8版本残像暗战前2关攻略,用对buff轻松取胜我已经有过一次旅行。所以,你也要像我一样抵达终点,才能在自己的眼中,留下这个世界的沉淀。大家好,我是boyue,感谢打开这篇原神游戏文。boyue会经常分享一些原神资讯与攻略给大家WE与LGD巅峰对决,WE巨大优势被翻11连败,水友估计是买了看过昨天比赛的兄弟们都知道了,昨天WE与LGD的巅峰对决终究还是老干爹更胜一筹,WE则继续延续自己的不胜传说。关注这场比赛的水友非常多,毕竟在赛前LPL官方还专门找来了WE的各队员坚持读书运动早睡,收获更好的自己共产党员202207271807发表于北京好的人生,离不开好的习惯。多读书勤运动早睡觉,坚持做好这三件小事,你会收获更好的自己。读书,决定人生的高度读书,是拓宽视野的捷径,也是成本永远做自己,不给别人添麻烦在被一遍遍雷声给惊醒,雨滴声从窗外传入,终于还是下雨了,在炙热难耐的高温下,终究还是没抵挡住阴晴不定的天气,给拿下了,迎来了清脆淅沥的小中雨。气温,骤然间被降了下来,终于感受到了一