(四)——实践.快速迭代.并行开发

最后更新于:2022-04-01 20:59:38

> 本文链接:[Web开发新人培训系列(四)——实践.快速迭代.并行开发](http://rapheal.sinaapp.com/2015/04/23/webdev-iterate/ "本文固定链接 http://rapheal.sinaapp.com/2015/04/23/webdev-iterate/") > 来源:[拉风的博客](http://rapheal.sinaapp.com/) ## 背景 我们先看看我们之前的开发场景: [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1dba557.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2015/04/SVN%E5%BC%80%E5%8F%91.png) 程序员a开发功能A,程序员b开发功能B,当开发完毕后提测上线。 但是公众平台的功能越来越多,涉及的开发人员也越来越多。 于是乎就出现了程序员c、d、e、f分别开发功能C、D、E、F。 大家一起开发完功能提测上线。貌似看起来没任何问题。 突然有一天,功能D和F因为种种原因临时决定不上线,这下程序员d、f就急急忙忙把对应的代码给注释去掉。 这种情况越来越多,我们依靠人工的注释代码/回退代码带来了许许多多的问题。我们必须要找一个合理的办法来解决这个混乱的现象! 我梳理一下上边几个关键点: 1. 开发人员多、上线需求多 2. 迭代速度非常快 3. 临上线前,部分功能需要变更重新修改或者不上线 4. 现网容易出故障/投诉 ## 目标 梳理完刚刚几个关键点,我们发现1-3是我们很难没法掌控的流程。 也就是现状是1-3是我们的输入,而4是我们的输出。我们需要有个合理的方案,让1-3即便发生的情况下,4能够尽量少发生,这就是我们的目标。 ## 方案 问题就出现在大家混在一起快速开发导致。一个很自然的做法,我们拆开多个分支并行开发。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1ddc9c2.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2015/04/%E5%A4%9A%E5%88%86%E6%94%AF%E5%BC%80%E5%8F%91.png) 当功能上完线后,我们需要把上线后的代码合并到主干去。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1e1d0bb.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2015/04/%E5%90%88%E5%9B%9E%E4%B8%BB%E5%B9%B2.png) 还有一种情况就是,功能A、B、C、D同时拉分支出来进行开发,但是由于功能C的复杂性,导致功能C拖延了一周才上线,但是功能C在上线前,功能A、B、D已经上线,我们需要把这些功能的代码也要合进当前的功能C分支。于是乎我们把主干相关的新改动合并到分支C去。 于是乎出现了我之前在组内分享的一张图: [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1e40554.jpg)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2015/04/%E5%A4%8D%E6%9D%82%E7%9A%84%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86.jpg) 再看一下整个方案的流程: 1. 按照功能拆分对应的分支,并行开发,互不影响。 2. 分支上线前,代码对齐主干 3. 分支上线后,代码合回主干 我们看到,SVN主干一直跟现网某一时间点的版本一致,一旦发生问题,我们可以很快立马回退到任何时间点的状态。 ## 高效准确 有合理的执行方案,我们还要求要更高效准确地执行。 我们梳理一下从开发 —— 提测 —— 上线整个流程,当中我们项目提测流程可以参考我上一篇文章[《Web开发新人培训系列(三)——上线》](http://rapheal.sinaapp.com/2015/01/22/webdev-release/)。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1e65598.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2015/04/%E4%BB%A5%E5%89%8D%E6%B5%81%E7%A8%8B.png) 为了达到高效而准确的目的,我们做了辅助工具用于在整个提测上线流程自动化(上图中红色部分是我们主要优化的流程节点)。 我们在两方面做了一些贡献。 1. 效率 * 提测时仅需要填写分支名字,再也不需要一个文件一个文件输入到变更系统,省去不少体力活(从3-4分钟的提单时间降低到10秒完成提单)。 * merge分支主干时,不再需要自己手工本地co代码,选择revision,工具智能选择需要合并的代码,生成备注日志(10秒完成全部操作)。 2. 准确 * 智能提测,不遗漏文件。 * 提测时根据变更的文件,智能提示相关提测功能点,测试人员很容易知道当前变更会影响多少页面/功能点。 * 上线前检测是否已经对齐主干,避免覆盖现网新改动的代码。 * 上线后检测当前分支是否已经合并回分支,避免没有把代码更新回主干。 ## 后话 分析问题 —— 得出方案 —— 高效准确执行方案 —— 继续高效准确执行 很自然的一个流程,但是要克服惰性去执行它不容易。 最后感谢rexschuang、zhikaimai陪我一起讨论了好多次,并把整个工具完善落地。 我们还在探讨整个web开发生产活动的每个环节(提需求 -> 交互 -> 视觉 -> 开发 -> 连调 -> 提测 ->测试 ->上线)是否能够再效率准确的点,也欢迎大家一起探讨。
';

(三)——上线

最后更新于:2022-04-01 20:59:36

> 本文链接:[Web开发新人培训系列(三)——上线](http://rapheal.sinaapp.com/2015/01/22/webdev-release/ "本文固定链接 http://rapheal.sinaapp.com/2015/01/22/webdev-release/") > 来源:[拉风的博客](http://rapheal.sinaapp.com/) ## 前言 上一篇我们介绍清楚了一个普通Web应用的网络结构,浏览器发起一个请求的目的无非就是想要Web服务器给他一个资源。资源对于这一篇的主题又有什么联系?看来没有解释清楚我这里想说的“资源”,上线这个话题没法继续了。 发个招聘广告:微信开放平台基础部招后台开发,有意者把简历发到我邮箱:raphealguo@qq.com ## 资源 我们看看浏览器其实最后收到服务器返回的都是什么“资源”:描述页面结构的HTML文件;网页的样式CSS文件;网页的脚本JS文件;网页的音频/视频/Flash…… 因此我们得到一个简单的结论: > 资源 == 文件 对于那些动态数据(通过PHP/Java等执行后得到的结果),例如:HTML内容;JSON数据…… 其实我们也可以把这个请求的资源等同于对应的CGI执行文件(PHP脚本文件/CGI二进制等程序) 浏览器需要知道服务器回的是什么内容,所以每次的响应包还有一个类型说明这个资源是什么东西,也就是我们经常说的MIME type`(Multipurpose Internet Mail Extensions)代表互联网媒体类型(Internet media type)` > MIME type == 文件类型 ## 上线? 好,既然浏览器每次需要服务器返回一个资源,那我们的Web服务器其实就是放着对应服务所需要的资源。所以我们把这个操作定义为上线: > 上线 == 把资源放上Web服务器 而资源等同于文件,因此: > 上线 == 把文件放上Web服务器 像云龙([@前端农民工](http://weibo.com/fouber))说过的,Web架构最重要的能力就是:如何管理资源跟如何部署资源,这里的部署资源我们可以简单认为就是上线。 留意一下,下文为了通顺,有些地方用了`发布` 跟 `部署`这个字眼来描述*上线*的一个动作。 ## 静态资源 & 动态资源 其实我们可以对Web服务器上的资源做个简单的分类,像JS文件、CSS文件这些资源对于所有用户来说都是一样的,我们把它们归类到静态资源。对于不同用户,它们看到的网页其实是不一致的(例如页面里边有他们各自的昵称,id等信息),这是通过CGI程序实时计算,为不同用户生成不同的HTML产生,我们把这样的HTML资源归类为动态资源。 由于动态资源是需要实时获取的,因此请求最后都需要落到我们的CGI程序上。而对于静态资源来说,所有请求都是一样的,不需要通过CGI去实时获取文件内容做响应。 ## 1台Web服务器 我们假设世界很美好,我们仅需要一台服务器就能抗住所有的访问。OK,现在我们在自己机器(下文把开发阶段使用的机器称为`开发机`)上开发完毕的代码通过文件传输到Web服务器。 传输完毕的那一时刻,我们就完成了上线!! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d0e875.png) 对于像二进制等程序,在传输完成后,我们还需要重启该程序,让新的程序生效。 ## 多台Web服务器 但是现实往往不是这样的,在前一篇文章也介绍过,一般我们需要多台Web服务器,在它们上层的proxy机器把用户的请求平摊在这些机器上。我们现实的网络结构大多数是这样的: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d25671.png) 看到这里,你有没有意识到其实上线的过程隐含一个什么样的问题!? 刚刚我们在只有一台服务器的情况下,我们只需要把文件传输到这台服务器就算此次上线完毕。同样道理在多台服务器的网络结构下,我们把文件依次上传到这些服务器上,对于所有服务器都传输完毕后才算此次上线完毕。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d3ebab.png) 你可以注意到,在上线的过程中,有一些服务器是新资源,有一些服务器是旧资源。问题就来了: *服务器1已经部署上新的资源,而服务器2上还是旧资源。假设这样一个场景,用户第一个HTML请求到了服务器1,然后返回一个新的HTML,接着用户第二个JS请求又跑到服务器2。* 在用户侧的表现就是一个旧的JS文件作用在一个新的HTML上,这样可能就引发了异常。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d5ce15.png) 存在问题我们就要解决它!分析一下:既然问题是出在服务器资源不一致,那有没有办法使得服务器资源一致呢?仔细想想没法做到资源的实时一致,多台服务器的网络结构下必然会存在某小段时间间隙存在资源不一致的现象。既然我们没法解决这个问题的根本,那就绕开,我们让同一个用户的多次请求永远都在同一台机器上,如何? *用户第一个HTML请求到了服务器1,用户第二个JS请求还是到服务器1,其他CSS请求、Flash请求……所有资源都落在服务器1* 这样在没完成上线的这个间隙时间,用户要么访问到的全部都是旧资源,要么全部都是新资源。这样就没有新旧资源依赖关系混乱造成的异常了。非常完美! 那剩下的问题就是我们怎么让同一个用户的请求一直都落在同一台机器上了!我们按照下边三步走,通过proxy把同一个用户的请求转发到同一个Web服务器: 1. proxy反向代理机器收到用户请求 2. proxy通过这个请求某些flagid标记得知当前这个请求是A用户的 3. 接着proxy定义一个hash算法getServer,通过计算`svr = getServer(flag)`得到最后这个请求要去到的Web服务器svr 所以我们只要保证getServer这个hash算法每次输出是唯一即可,这个很容易做到。嗯,最后遗留了一个小问题,用户的请求带上的flagid是怎么带的?我们先不纠结这个怎么做,我们只要在用户登陆我们系统后,在cookies种上一个能标记出这个用户的id即可。 ## 灰度 我们回顾一下刚刚讲到了那个让我们头疼的问题在经过我们解决之后得到一个什么样的现象: * 用户a所有的资源请求都落在服务器1 * 用户b所有的资源请求都落在服务器2 到这里,我们看到如果我们上线一个新的功能,在上线的过程中,有一些用户可以体验到新功能,一部分用户则体验不到。这是个不错的AB test方法!倘若我们有一个非常重要的更新,或者做了一个可能对后台服务器有压力的功能,我们可以通过控制这个上传的过程先让小部分用户体验,确认新功能在各方面都正常(包括:用户体验、服务器压力等等)之后,我们才把这次更新慢慢部署到所有服务器上。 我们竟然在解决一个问题的情况下还得到了一个非常棒的灰度发布方案! 当然我们也可以通过代码来控制新功能的灰度,有时候我们也可以两种灰度方案相互结合,有时候可以避免更大的灾难。 最后我们简单总结一下: > 在灰度上线过程中,新/旧两套资源是同时存在现网服务的。 ## 测试环境 我们现在整个过程是这样: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d7dd7a.png) 按照这个流程,测试人员在开发机测试完毕后,把开发机的资源发布到现网服务器。这里存在一些非常容易出错的环节: 1. 测试过程中,开发人员在开发机修改代码,可能会打断测试流程 2. 在发布的过程中,如果开发机器的代码不小心被某个开发删掉或者修改,就会出现现网bug 3. …… 归根到底,出现这些问题的原因就是我们没有把上线和不稳定的开发环境相互隔离! 既然这样,我们在开发机跟现网服务器中间在加入一个中间层:测试机。为了保证资源发布到测试机的过程不会发生以上问题,所以我们再加入一层:编译机。我们来看现在上线的流程: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d94f5a.png) 开发人员在不稳定的环境开发,通过辅助系统(我们这边称为变更系统)提个变更单记录需要上线的资源。开发每次在修改完确定代码稳定后,去编译机上把需要变更的资源更新。接着测试人员通过变更系统可以把编译机上的文件部署到测试机,测试就可以专心在一个稳定的测试环境上开始工作了。直到整个发布特性都测试完毕,再通过变更系统把测试机变更的资源发布到现网Web服务器。 到这里好像已经把上线涉及的所有问题都说清楚了。但是还没考虑全!再回顾一下我上一篇文章[《Web开发新人培训系列(二)——经典的Web应用网络模型》](http://rapheal.sinaapp.com/2014/10/24/webdev-network-model/ "《Web开发新人培训系列(二)——经典的Web应用网络模型》")。到现在为止我们一直在讲述如何把资源上线到我们自己的Web服务器,而忽略了Web网络结构重要的角色CDN。 ## CDN 上次说到我们把资源部署到CDN有两种方式: 1. 主动推送资源到CDN 2. CDN被动回源到Web服务器请求资源 我们设定一个场景:要上线的资源index.php和index.js,由于index.js是静态资源,所以我们把它部署到CDN上。 我们留意到一个正常的访问是html页面请求之后才发起对静态资源请求,也就是有新的js引用(index.php),才有新的js请求。所以我们在index.php部署前,先把静态资源部署好,这样就不会有混乱的现象出现了。 接下来下边简单叙述一下两种方式的上线时序。 ### 主动推送资源到CDN 在我们把index.php部署到我们的Web服务器上之前,我们需要先把index.js的内容推送到CDN的全部节点上,等待推送完毕后,我们才能上线动态资源index.php。 ### CDN被动回源 对于回源,有个非常重要的节点,就是回源机器!也就是CDN收到JS文件请求index.js,然后CDN节点上没有这个文件,它需要向回源机器要这个资源。我们上线第一步是把静态资源index.js先部署到回源机器。 ### 遗留问题 也许你会注意到,如果我把新的index.js部署到CDN后,旧的index.js就没了,这样就违背了我们刚刚前边说的结论:灰度上线过程,新旧资源在现网服务共存。 当然这个问题是可以解决的,为了不把问题再扩散,这篇文章就到此结束吧,下次再整理文章详细说一下我们在实际项目中整套资源的部署图。 ## 后话 我计划下一篇文章大概写一下SVN的相关概念,紧接着再有一篇专门写我们如何把SVN代码管理跟上线流程更规范化,提高在上线/测试环节的效率以及减少我们人工操作带来的失误。
';

(二)——经典的Web应用网络模型

最后更新于:2022-04-01 20:59:33

> 本文链接:[Web开发新人培训系列(二)——经典的Web应用网络模型](http://rapheal.sinaapp.com/2014/10/24/webdev-network-model/ "本文固定链接 http://rapheal.sinaapp.com/2014/10/24/webdev-network-model/") > 来源:[拉风的博客](http://rapheal.sinaapp.com/) ## 前言 这篇文章要介绍的是一个常见Web应用基本的过程跟网络模型,当然,对于多数的Client/Server应用也是适用的。延续这个系列文章的风格,只管通俗不管严谨。 ## 概览 总体模型概览图: [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c098aa.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/%E6%A6%82%E8%A7%88.png) ## DNS 用户点开/输入一个链接http://www.qq.com/index.html之后,浏览器需要先找到www.qq.com这个域名对应的IP地址,因为计算机是通过IP作为门牌号的,而域名你可以认为是这个IP的别名,方便人类记忆使用。 一般来说,浏览器会先询问本地DNS缓存,如果没有记录过这个域名映射的IP,那就向本地的DNS网关询问,如果网关也不知道,就继续往上一层的DNS服务器询问,直到拿到这个IP地址。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c26f1e.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/DNS.png) 一般来说,一台服务器处理的请求是有限的,因此大型的应用都会有多台proxy机器,我们可以让DNS服务器在第一个请求返回IP1,第二个请求返回IP2,……这样用户的请求就会均匀的落在这些机器上,这个就是DNS负载均衡。CDN就是通过智能DNS算出离用户最近的CDN节点的IP地址,这样用户可以访问一台离他最近的机器,大大节约连接时间。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c3e86f.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/DNS%E8%B4%9F%E8%BD%BD.png) ## 代理与反向代理 一般来说,浏览器跟真正提供Web服务的机器是没有直接连接的,他们中间都会有代理跟反向代理。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c566e1.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/%E4%BB%A3%E7%90%86%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86.png) 大部分的公司都会内部的计算机都配置了代理服务器,其作用是所有内部的网络请求都是通过代理去连接对方服务器,可以在代理服务器这里做恶意请求/响应的拦截,还可以缓存内部网络所需的公共资源。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c6ea9d.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86.png) 反向代理就是以代理服务器来接收网络连接请求,我们上下文称Proxy机器指的就是反向代理机器,Proxy机器收到请求后会经过一定的分析最后把请求内容转发给内网对应的Web服务器,Web服务器的HTTP响应包会先到Proxy机器,然后再到用户机器。 反向代理的好处是可以负载均衡,在它后边可以有多台工作的Web服务器,这样分层次之后,很多职责就明确很多了:Proxy机器负责负载均衡、拦截恶意请求、维持长连接,还可以屏蔽不工作的Web服务器;而Web服务器就只要关心自己处理的Web业务逻辑即可。 往往Proxy服务器跟用户机器保持长连接,这样可以节省用户每次跟服务器建立连接的消耗,而Proxy服务器跟Web服务器采用短连接的方式,这样可以有效节约Web服务器的资源。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1c8b0bb.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86.png) ## Web server Web server的职责就是根据用户的请求,返回其所需要的响应内容。往往Web server只涉及业务测逻辑的判断以及数据的组装,而真正的数据位于后端的存储Server(本文不涉及)。 对于一般应用来说,Web server返回的是动态产生的内容(每个用户都不一致的动态内容或者经常编辑变动的内容),如页面的HTML内容、JSON数据、XML数据等。而Javascript文件、CSS文件、图片这些静态资源(不根据用户而变动的资源)往往存放在CDN中。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1ca72bd.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/Web-server.png) ## 浏览器 从浏览器发起请求,经历以上讲述的步骤处理后,浏览器发起到从Web sever返回的HTTP包。一般来说这个响应是返回网页的HTML。 接着浏览器开始解析收到的HTML包,HTML里边一般会把样式CSS跟脚本Javascript作为外链请求。本文不涉及页面渲染内容,主要为了讨论整体应用的模型,因此这块留以后探讨写文章。 ## CDN 从上边讨论知道,对于动态的内容,请求总是到Web server去动态计算获取内容,但是对于不随用户状态变化的内容我们把内容推送到CDN节点上。 静态资源的域名跟页面HTML的域名一般来说是不一样的,因为静态资源的请求需要解析到CDN节点去。我们假设主请求是:www.qq.com/index.html;CDN请求是cdn.qq.com/index.css。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1cc071d.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/CDN.png) 一般Web应用把静态内容推到CDN有两种模式,一种是在上线前主动将内容推送到CDN节点,一种是CDN发现本地没有该文件时,回源到Web server机器取内容,然后缓存在他本地。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1ce2993.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/10/CDN%E6%8E%A8%E9%80%81%E5%86%85%E5%AE%B9.png)
';

(一)——协议

最后更新于:2022-04-01 20:59:31

> 本文链接:[Web开发新人培训系列(一)——协议](http://rapheal.sinaapp.com/2014/07/11/webdev-protocol/ "本文固定链接 http://rapheal.sinaapp.com/2014/07/11/webdev-protocol/") > 来源:[拉风的博客](http://rapheal.sinaapp.com/) ## 前言 近期给实习生介绍一些基本概念,顺便梳理一下文章,方便之后可重复利用。 从事Web开发必须要了解从浏览器输入URL后到页面加载完之后的整个大概的流程,当然可以就某些点细化的研究下去,这一过程涉及到:HTTP/HTTPS协议、代理/反向代理、Web server、CGI、浏览器端的知识(HTML+Javascript+CSS) 只有把整个流程都梳理清楚,才有能力去讨论其他问题,例如如何去优化Web性能、如何利用工程化提高效率、在某些业务场景的技术方案取舍等。 本篇文章会以一个例子介绍计算机网络基本工作的过程,最终再详细介绍HTTPS是如何解决网络包加密问题。绘说明图找不到相关好的工具,所以文章引用了几张来自[《图解TCP/IP》](http://book.douban.com/subject/24737674/)的图 ## 快递 我们来看一个常常出现的场景: Alice在淘宝买了一件衣服,我们输入收货地址收货人付款后,卖家Bob帮我们把衣服打包放进纸箱里,贴上一个快递单: > *寄件人:Bob > 地址:北京市朝阳区朝阳路1号 > 收件人:Alice > 地址:广东省广州市新港中路197号C公司* 快递公司D派人去Bob家收到包裹,接着整理了公司的包裹,分批往个个城市送货。由于D公司没有直达广州的货运车,因此这些包裹先随着去上海的车到达上海的中转站之后,再把要发往广州的包裹搬到另一辆车发往广州中转站。 快递员Rapheal把快递送到C公司,快递中心的前台mm帮Alice签收后转交给了Alice。 ## 计算机网络 以上的场景几乎就是计算机网络的场景了,我们做个类比(类比只到网络层): > 朝阳路1号 == 北京某台计算机ip地址 > 新港中路197号 == 广州某台计算机的ip地址 > 快递单 == 包头 > 衣服 == 包体 > 快递公司的中转站 == 交换机/路由器 > Bob == 北京某台计算机的进程B(端口) > Alice == 广州C公司局域网里边的某台计算机的进程A(端口) > C公司的快递中心 == C公司局域网的网关 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfdbe71ee11.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/07/1.png) 上述类比并不严谨,这里仅仅简单解释一下网络里边发送/接收包的大概过程跟概念: 1. 首先接入网络的计算机/路由器都要有一个ip地址,例如Alice的ip地址是120.0.0.2,Bob的ip地址是150.0.0.2。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfdbe778ce8.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/07/2.png)图片来自[《图解TCP/IP》](http://book.douban.com/subject/24737674/) 2. Alice发送HTTP数据给Bob,实际上是Alice机器上的的进程a发送数据,Bob机器上的的进程b在接收数据,因为真正想要数据的进程才是最终应用层。 3. 进程a发送数据是通过socket发出去,socket需要绑定一个端口,用于区分计算机内的不同进程。进程a使用的socket绑定了2048端口,进程b使用的socket绑定了4096端口。 4. 所以Alice跟Bob通信实际上等价于:120.0.0.2的2048端口发送数据到150.0.0.2的4096端口 Alice的计算机发送数据: 1. 进程a先把数据打包好,贴上单(HTTP协议头部):我来自Chrome浏览器; 我要Bob的index.html 2. 再打包贴上单(TCP协议头部):发送端口2048,接收端口4096 3. 继续包装贴上(IP协议头部):发送地址:120.0.0.2,接收地址:150.0.0.2。 4. 这个包裹丢给快递公司(网络),接着就是快递公司负责送往目的地Bob的机器了。 路由器转发数据包: 1. 快递公司中转站负责把当前的包裹传递到下一个快递中心,这也是路由器转发的原理 2. 在北京的路由器先把包转到了离他近一点的天津,天津再转发到上海,上海再转发到广州,广州在转给海珠区的邮件中心,最后再到达目的地。 以下图片来自[《图解TCP/IP》](http://book.douban.com/subject/24737674/): [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfdbe7a5cf6.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/07/3.png) Bob的计算机接收数据包: 1. 计算机收到来自150.0.0.2的包,解开后发现原来是送到端口4096去的,于是乎把包裹才开丢给了进程b(进程b使用的socket绑定了4096端口) 2. 进程b拿到数据之后就可以做自己的操作了,如果他要回包给Alice,那就按照Alice打包的过程,生成一个包发送出去。 我们经常会听到计算机网络分成7层,可以这么理解:Bob把衣服装个箱子,箱子上贴个单说Alice收,接着再拿一个箱子装着,上边又贴了一个单写着腾讯公司收,最后再装一个箱子贴着广州新港中路197号收。这里的例子分成了三层,网络中运输的数据包也是这样的结构,IP包里边装着TCP包,TCP包里边装着HTTP包,每个协议层都有自己的包头(单)跟包体(箱子)。 以下图片来自[《图解TCP/IP》](http://book.douban.com/subject/24737674/): [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfdbe7c99bf.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/07/4.png) ## 协议 协议的目的就是让通信的双方知道当前这个数据包是怎么样一个组成格式,一般:包头就是双方约定好的一些信息,包体就是这次通信传输的数据。 Alice拿到快递之后从快递单(包头)就可以知道是Bob发给他的衣服,然后拆开之后就拿到衣服(包体),从而完成了此次通信。所以协议本身并没有那么复杂难解:协议 == 包头 + 包体。 协议繁琐的地方在于,计算机怎么识别每一层的包头,识别后做什么操作。例如路由器要识别出IP,然后决定当前数据包往那个地方转发;浏览器要识别出包头的Content-type,来决定当前这个包体是图片还是HTML。 刚刚说了包体其实就是数据本身,数据本身就没什么好理解的了,数据是什么,它就是什么,所以理解每一层的协议就在于理解其包头的含义。所以接下来我以HTTP头来介绍HTTP协议。 ## HTTP协议 刚刚说到其实协议就是让浏览器跟服务器互相知道他们要什么,因此浏览器发给服务器的包就要带着一些用户操作信息,服务器发给浏览器的包就要带着用户想要的数据信息。 当浏览器输入一个URL,相当于浏览器发起一个HTTP请求包出去,浏览器会把一些自身信息以及用户操作的信息写在包头上带到服务器,这样服务器才知道你在用什么浏览器想请求它的什么资源。 HTTP请求包的常见包头如下(紫色为说明文字): * GET / HTTP/1.1 *获取的路径以及HTTP版本* * Host: www.qq.com:8080 *服务器主机名字&端口号* * Connection: keep-alive *用于长连* * User-Agent: Chrome/35.0.1916.153 *浏览器基本信息* * Accept-Encoding: gzip,deflate,sdch *告诉服务器支持的编码* * Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,nl;q=0.4,zh-TW;q=0.2 *告诉服务器优先支持的语言* * Cookie: id=1;username=raphealguo; *解决HTTP无状态重要的Cookie,里边是key=value对* 紧接着服务器收到HTTP请求,经过CGI处理之后回复一个HTTP响应包,服务器需要告诉你包里边是什么(Content-Type),包里边有多少东西(Content-Length),服务器版本是什么等等。 HTTP响应包常见的包头如下: * HTTP/1.1 200 OK *HTTP版本以及状态码* * Server: nginx/1.4.1 *服务器版本* * Date: Mon, 30 Jun 2014 09:44:10 GMT *回包时间* * Content-Type: text/html; charset=UTF-8 *包里边的类型* * Content-Length: 14534 *包体的长度* * Connection: keep-alive *用于长连* * Cache-Control: no-cache, must-revalidate *用于缓存控制* 具体的HTTP协议的细节推荐阅读《HTTP权威指南》。 ## HTTPS协议 北京的Bob发了一个快递到广州的Alice,途中经过了上海,上海快递中心出现了一个黑客H,他偷偷打开了Bob给Alice的快递,然后偷偷把里边的衣服剪烂,再按照原样包装好发往广州,可以看到对于这样简单包装的传输在中途是可以偷偷修改里边的东西。 HTTP的数据包是明文传输,也即是如果中途某个黑客嗅探到这个HTTP包,他可以偷偷修改里边包的内容,至于Bob跟Alice是互相不知道这个动作的,因此我们必须要有一个方案来防止这种不安全的篡改行为,有个方法就是加密! ### 非对称加密 Bob将衣服放到一个保险箱里边锁起来,他打了个电话告诉Alice保险箱开柜密码是1234,而黑客H不知道密码,所以他看不到保险箱里边的东西,Alice收到快递后用预先沟通好的密码就可以打开保险箱了。 这里保护的手段就是Bob对物品进行加密,同时给了告诉Alice解密的方法! 那如果现在要求Bob的密码只能通过快递传给Alice呢?如果Bob直接传密码给Alice,H如果嗅探到这个快递,那H也知道密码了,这就无法保护快递的安全性了。因此还需要有个方案,让Bob能够告诉Alice密码的同时,H又无法查看到Bob跟Alice通信的数据。 非对称加密在这个时候就发挥作用了,来看看怎么回事:Bob拥有两把钥匙,一把叫做公钥,一把叫做私钥。公钥是公开让全社会都知道,没关系,Bob告诉所有人,你们要传递数据给我的时候请先用这个密钥去加密一下你们的数据,加密后的数据只能通过Bob私自藏着的私钥才能解密。 回到刚刚例子,Bob先发给保险柜(Bob公钥)给Alice,接着Alice把自己的保险柜(Alice公钥)放到Bob的保险柜里边发还给Bob,接着Bob拿到Alice的回包后,用自己的私钥解开了外层保险柜,拿到了里边Alice保险柜。此时Alice跟Bob都有了各自的公钥,接着只要保证每次互相传递数据的时候,把数据放在对方的保险柜里边即可,这样无论如何,H都无法解开保险柜(因为只有各自的私钥才能解开各自的保险柜)。 ### HTTPS隧道 为了使得HTTP传输的安全性,HTTPS就诞生了,同刚刚Bob跟Alice通信一样,HTTPS会有如下的过程: 1. 客户端先跟服务器做一次SSL握手,也就是刚刚Bob跟Alice交换公钥的过程。 2. 此时客户端跟服务器都有了各自的公钥,这时他们中间相当于有了一条安全的HTTPS隧道。 3. 客户端要发送请求时,采用服务器给的公钥对请求包进行加密,然后发出去。 4. 服务器收到请求后,使用自己的私钥解开了这个请求包得到其内容。 5. 服务器响应的时候,采用客户端给的公钥进行加密,然后发还给客户端。 6. 客户端收到响应后,使用自己的私钥解开响应包得到其内容。 7. 结束的时候,双方关闭SSL隧道,丢掉上次交换的公钥。 以下图片来自[《图解TCP/IP》](http://book.douban.com/subject/24737674/): [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfdbe80f8da.png)](http://rapheal-wordpress.stor.sinaapp.com/uploads/2014/07/5.png) ### HTTPS安全? 那HTTPS是不是任何情况都安全的呢?从刚刚的例子中我们可以看到HTTPS其实是一个端到端的安全保障,HTTPS保障的是你这个数据包在传输的过程中无法被查看,但是这个数据包也是可以被黑客偷偷丢掉或者毁坏。来看看HTTPS在什么情况下是不安全的: 1. 私钥被窃取,如果这个被爆了,那HTTPS肯定就不安全了 2. HTTPS是无法保证端内安全,也即是如果你的电脑已经被病毒侵入,病毒可以监听到你开锁的时候,也可以知道你的私钥,那这份数据怎么样都保证不了安全性了。 3. HTTPS下的页面引用了HTTP下的资源,例如HTTPS下的页面引用了HTTP下的Javascript文件,此时如果这个Javascript文件被篡改了,那通过这个JS文件就可以在执行的时候获取到整个网页内容,在这种情况下,某些浏览器会阻止你去加载这些非HTTP资源,有些则会提示错误或者给出警告信息。
';