从url到页面展示的经过

1.DNS解析

DNS解析过程充当了一个翻译的角色,实现了网址到IP地址的转换。 是一个递归查询的过程。
所有网址真正的解析过程为: . => .com => google.com. => www.google.com.。

Image.png

  • DNS缓存
    DNS解析过程通常有很多请求,为了实现优化,使用了DNS缓存。
    DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
    • 在你的chrome浏览器中输入:chrome://net-internals/#dns,你可以看到chrome浏览器的DNS缓存。
    • window命令行输入ipconfig /displaydns 可以查询缓存
      系统缓存主要存在/etc/hosts(Linux系统)中。

DNS负载均衡,又叫做DNS重定向。CDN利用DNS的重定向技术,DNS服务器返回一个和用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需内容。

2. TCP连接

Linux TCP/IP四层概念模型 对应网络协议
应用层 HTTP、DNS、FTP、SMTP等
运输层 TCP、UDP
网络层 IP协议(网际协议)、ICMP(Internet控制消息协议)、
ARP(地址解析协议)、 RARP、AKP、UUCP
数据链路层 Ethernet、FDDI和能传输IP数据包的任何协议。

TCP和UDP是两种运输层的协议

  • TCP是一种面向连接的协议,提供可靠的数据传输,一般服务质量要求比较高的情况,使用这个协议。在正式收发数据前,必须和对方建立可靠的连接。
    一个TCP连接必须要经过三次“对话”才能建立起来。

三次对话的简单过程:
1.主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次>对话;
2.主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;
3.主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。

三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

  • UDP是一种用户数据报协议,是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。它不与对方建立连接,而是直接就把数据包发送过去!
    UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。

比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。
例如,在默认状态下,一次“ping”操作发送4个数据包(如图2所示)。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。
正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

  • TCP与UDP的区别:

1.实现信息的可靠传递方面不同。

TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。 与TCP不同,UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。相对于TCP协议,UDP协议的另外一个不同之处在于如何接收突发性的多个数据报。不同于TCP,UDP并不能确保数据的发送和接收顺序。事实上,UDP协议的这种乱序性基本上很少出现,通常只会在网络非常拥挤的情况下才有可能发生。

2.速度:

UDP具有TCP所望尘莫及的速度优势。虽然TCP协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重的影响。反观UDP由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度得到了保证。

3.长度:

UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。

TCP支持的应用协议主要有:Telnet、FTP、SMTP等;
UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。

* 重点: HTTP是用TCP协议传输的。

  • DNS解析会同时占用UDP和TCP端口

    如果用wireshark、sniffer或古老些的tcpdump抓包分析,会发现几乎所有的情况都是在使用UDP,使用TCP的情况非常罕见,神秘兮兮。其实当解析器发出一个request后,返回的response中的tc删节标志比特位被置1时,说明反馈报文因为超长而有删节。这是因为UDP的报文最大长度为512字节。解析器发现后,将使用TCP重发request,TCP允许报文长度超过512字节。既然TCP能将data stream分成多个segment,它就能用更多的segment来传送任意长度的数据。

    另外一种情况是,当一个域的辅助域名服务器启动时,将从该域的主域名服务器primary DNS server执行区域传送。除此之外,辅域名服务器也会定时(一般时3小时)向PDS进行查询以便了解SOA的数据是否有变动。如有变动,也会执行一次区域传送。区域传送将使用TCP而不是UDP,因为传送的数据量比一个request或response多得多。

    DNS主要还是使用UDP,解析器还是服务端都必须自己处理重传和超时。DNS往往需要跨越广域网或互联网,分组丢失率和往返时间的不确定性要更大些,这对于DNS客户端来说是个考验,好的重传和超时检测就显得更重要了。

    域名解析时使用UDP协议:
    客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

  • 选择:如果比较UDP包和TCP包的结构,很明显UDP包不具备TCP包复杂的可靠性与控制机制。与TCP协议相同,UDP的源端口数和目的端口数也都支持一台主机上的多个应用。一个16位的UDP包包含了一个字节长的头部和数据的长度,校验码域使其可以进行整体校验。(许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。)
    很明显,当数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,TCP协议是当然的选择。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择,如:DNS交换。把SNMP建立在UDP上的部分原因是设计者认为当发生网络阻塞时,UDP较低的开销使其有更好的机会去传送管理数据。TCP丰富的功能有时会导致不可预料的性能低下,但是我们相信在不远的将来,TCP可靠的点对点连接将会用于绝大多数的网络应用。

3. 发送HTTP请求

在这之前先了解客户端和服务器如何利用HTTPS构建联系

HTTP协议是使用TCP作为其传输层协议的。
HTTPS协议: HTTPS协议的本质就是HTTP + SSL(or TLS)。在HTTP报文进入TCP报文之前,先使用SSL对HTTP报文进行加密。从网络的层级结构看它位于HTTP协议与TCP协议之间。
HTTPS过程: 客户端与服务器进行一个握手(TLS/SSL握手)

爱丽丝:客户端,鲍勃:服务器。
第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给鲍勃。
第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。
第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。

注意:
(1)生成对话密钥一共需要三个随机数。
(2)握手之后的对话使用"对话密钥"加密(对称加密),服务器的公钥和私钥只用于加密和解密"对话密钥"(非对称加密),无其他作用。
(3)服务器公钥放在服务器的数字证书之中。
(4)采用DH算法后,Premaster secret不需要传递,双方只要交换各自的参数,就可以算出这 个随机数。
(5) session ID的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的"对话密钥",而不必重新生成一把。但是只能是保留在一台服务器上。
还有另一种方法是发送一个服务器在上一次对话中发送过来的session ticket。这个session ticket是加密的,只有服务器才能解密,其中包括本次对话的主要信息,比如对话密钥和加密方法。当服务器收到session ticket以后,解密后就不必重新生成对话密钥了。

发送HTTP请求:

前端工程师眼中的HTTP,它主要发生在客户端。发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。

3.1. 请求行

格式:Method Request-URL HTTP-Version CRLF
eg: GET index.html HTTP/1.1

常用的方法(method)有: GET, POST, PUT, DELETE, OPTIONS, HEAD。

GET - 从指定的资源请求数据。
POST - 向指定的资源提交要被处理的数据

GET的语义是请求获取指定的资源。GET方法是安全(指可见的意思)、幂等、可缓存的(除非有 Cache-Control Header的约束),GET方法的报文主体没有任何语义。

POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。为了针对其不可缓存性,有一系列的方法来进行优化。


_3WME49YY$P@9A(}6%~YC8D.png
改变服务器状态 不改变服务器状态
幂等 put get
不幂等 post

get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
在做数据查询时,建议用Get方式;
而在做数据添加、修改或删除时,建议用Post方式。

3.2. 请求报头

请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。
常见的请求报头有:
Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
Accept用于指定客户端用于接受哪些类型的信息,
Accept-Encoding与Accept类似,它用于指定接受的编码方式。Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。

3.3. 请求正文

当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。

4. 服务器处理请求并返回HTTP报文

对应的就是后端工程师眼中的HTTP。后端从在固定的端口接收到TCP报文开始,这一部分对应于编程语言中的socket。它会对TCP连接进行处理,对HTTP协议进行解析,并按照报文格式进一步封装成HTTP Request对象,供上层使用。这一部分工作一般是由Web服务器去进行。

HTTP响应报文也是由三部分组成: 状态码, 响应报头和响应报文。

4.1. 状态码

状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息–表示请求已接收,继续处理。
  • 2xx:成功–表示请求已被成功接收、理解、接受。
  • 3xx:重定向–要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误–请求有语法错误或请求无法实现。
  • 5xx:服务器端错误–服务器未能实现合法的请求。200 OK 服务器成功处理了请求(这个是我们见到最多的)

200 ok

206 客户发送了一个带有range头的请求,服务器完成了它。

301/302 Moved Permanently(重定向)请求的URL已移走。Response中应该包含一个Location URL, 说明资源现在所处的位置,301和302 非常相似, 一个是永久转移,一个是临时转移。

304 Not Modified(未修改)客户的缓存资源是最新的, 要客户端使用缓存

403 资源不可用,无权限。

404 Not Found 未找到资源

500 后台服务器遇到错误

501 Internal Server Error服务器遇到一个错误,使其无法对请求提供服务

502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。

504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)

4.2. 响应报头(Response Headers)

常见的响应报头字段有:
Server 服务器应用软件名称和版本
Location 告诉客户端实在在哪里,用于定向
http://yinyue.baidu.com/的location是http://music.baidu.com/
Content-Encoding 主体编码格式
Cache-Control:private指示响应是针对单个用户的,不能由共享缓存存储。
Cache-Control:public指示响应是针对单个用户的,不能由共享缓存存储。私有缓存可以存储响应。
Connection:Keep-Alive 不访问服务器但保持连线
Content-Type:text/html;charset=utf-8 主体的MIME
Content-Location 资源实际位置(一般不公布)
ETag 主体的实体标记
Expires 过期时间
If-None-Match 如果ETag和当前文档ETag不符合,获取资源
If-Modified-Since 除非在某个指定日期之后修改过,否则限制这个请求
If-Unmodified-Since 在某个指定日期之后没有修改过,否则现在请求

4.3. 响应报文

服务器返回给浏览器的文本信息,通常HTML, CSS, JS, 图片等文件就放在这一部分。

4.4. http缓存

浏览器缓存,以及缓存代理服务器具有缓存功能。

好处:减少了冗余的数据传输,节省了网费。减少了服务器的负担, 大大提高了网站的性能。加快了客户端加载网页的速度

Web服务器通过2种方式来判断浏览器缓存是否是最新的。

第一种, 浏览器把缓存文件的最后修改时间通过 header ”If-Modified-Since“来告诉Web服务器。

浏览器客户端想请求一个文档, 首先检查本地缓存,发现存在这个文档的缓存, 获取缓存中文档的最后修改时间,通过: If-Modified-Since, 发送Request给Web服务器。Web服务器收到Request,将服务器的文档修改时间(Last-Modified): 跟request header 中的,If-Modified-Since相比较, 如果时间是一样的, 说明缓存还是最新的, Web服务器将发送304 Not Modified给浏览器客户端, 告诉客户端直接使用缓存里的版本。

第二种, 浏览器把缓存文件的ETag, 通过header "If-None-Match", 来告诉Web服务器。

ETag是Web服务端产生的,然后发给浏览器客户端。浏览器客户端是不用关心Etag是如何产生的。

为什么使用ETag呢? 主要是为了解决Last-Modified 无法解决的一些问题。

(1). 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。

(2). 某些文件的修改非常频繁,在秒以下的时间内进行修改. Last-Modified只能精确到秒。

(3). 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了。

缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存)。 普遍的缓存案例:

  • 一个检索请求的成功响应: 对于 GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应.
  • 不变的重定向: 响应状态码:301.
  • 错误响应: 响应状态码:404 的一个页面.
  • 不完全的响应: 响应状态码 206,只返回局部的信息.
  • 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应.

HTTP/1.1定义的 [Cache-Control]用来区分对缓存机制的支持情况, 请求报头和响应报头都支持这个属性。
强制确认缓存:no-cache
禁止缓存:no-store
公用:public
私用:private
max-age=<seconds> :缓存过期时间.例如图片、css、js等静态资源。
must-revalidate:缓存校用
Vary: User-Agent头,缓存服务器需要通过UA判断是否使用缓存的页面。

利用fiddler查看网页的缓存

Request
Cache-Control: max-age=0 : 以秒为单位
If-Modified-Since: Mon, 19 Nov 2012 08:38:01 GMT : 缓存文件的最后修改时间。
If-None-Match: "0693f67a67cc1:0" : 缓存文件的Etag值
Cache-Control: no-cache : 不使用缓存
Pragma: no-cache : 不使用缓存

Response
Cache-Control: public : 响应被缓存,并且在多用户间共享,
Cache-Control: private : 响应只能作为私有缓存,不能在用户之间共享
Cache-Control:no-cache : 提醒浏览器要从服务器提取文档进行验证
Cache-Control:no-store : 绝对禁止缓存(用于机密,敏感文件)
Cache-Control: max-age=60 : 60秒之后缓存过期(相对时间)
Date: Mon, 19 Nov 2012 08:39:00 GMT : 当前response发送的时间
Expires: Mon, 19 Nov 2012 08:40:01 GMT : 缓存过期的时间(绝对时间)
Last-Modified: Mon, 19 Nov 2012 08:38:01 GMT : 服务器端文件的最后修改时间
ETag: "20b1add7ec1cd1:0" : 服务器端文件的Etag值

如果同时存在cache-control和Expires怎么办呢?
浏览器总是优先使用cache-control,如果没有cache-control才考虑Expires
是否选用缓存
按F5刷新浏览器, 浏览器会去Web服务器验证缓存。
如果是在地址栏输入网址然后回车,浏览器会"直接使用有效的缓存", 而不会发http request 去服务器验证缓存.

5. 浏览器解析渲染页面

浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以应该尽可能少的减少reflow和repain。


775625205-57d4063b7b60d_articlex.png

JS的解析是由浏览器中的JS解析引擎完成的。

JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。

JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。

浏览器在解析过程中,如果遇到请求外部资源时,如图像,iconfont,JS等。浏览器将重复1-6过程下载该资源。请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,不仅要等到文档中JS文件加载完毕还要等待解析执行完毕,才会继续HTML的渲染过程。

原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。

CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。

6. 优化

如何尽快的加载资源?

答案就是能不从网络中加载的资源就不从网络中加载,当我们合理使用缓存,将资源放在浏览器端,这是最快的方式。如果资源必须从网络中加载,则要考虑缩短连接时间,即DNS优化部分;减少响应内容大小,即对内容进行压缩。另一方面,如果加载的资源数比较少的话,也可以快速的响应用户。当资源到达浏览器之后,浏览器开始进行解析渲染,浏览器中最耗时的部分就是reflow,所以围绕这一部分就是考虑如何减少reflow的次数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容