浏览器开发-概述

想做个很简单浏览器,这样做可以,更加方便的web交互基础。不过很麻烦。这里留个档案吧。


浏览器内涵

 所谓的“浏览器内核”无非指的是一个浏览器最核心的部分——“Rendering Engine”,直译这个词汇叫做“渲染引擎”,不过我们也常称其为“排版引擎”、“解释引擎”。这个引擎的作用是帮助浏览器来渲染网页的内容,将页面内容和排版代码转换为用户所见的视图。

注:有时候我们所说的“浏览器内核”甚至“渲染引擎”,其实除了渲染引擎,也悄悄包含了javascript引擎,如WebKit,它由渲染引擎WebCore和javascript引擎JSCore组成。

常见的浏览器内核(或者说渲染引擎)有很多个,如Trident、Gecko、WebKit等等,不同的内核对网页编写语法的解释也有不同,进而导致同一个页面在不同内核的浏览器下显示出来的效果也会有所出入,这也是前端工程师需要让作品兼容各种浏览器的原因。

我们常常喜欢把浏览器内核与某浏览器名称直接挂钩起来,如IE内核、Chrome内核,其实是不全面的说法。比如Opera在7.0版本到12.16版本中采用的是独立研发的Presto引擎,但在后续跟随了Chrome的脚步加入了WebKit大本营,放弃了Presto;另外即使名称相同,但版本不同的引擎也可能存在较大差别。比如IE6使用的是Trident早期版本,存在许多bug,性能也较低。而最新的IE11所使用的Trident7.0版本已经可以支持WebGL(3D绘图标准)以及HTML5大部分标准。

下面按照各个主流浏览器,介绍下它们所使用的浏览器内核的历程。

Internet Explorer:

IE开发计划开始于1994年夏天,微软为抵抗当时主流的网景Netscape Navigator,要在Windows中开发适合自己的浏览器,但微软并没有时间从零开始。因此和Spyglass合作,于是IE从早期一款商业性的专利网页浏览器Spyglass Mosaic派生出来,虽然Spyglass Mosaic与NCSA Mosaic(首款应用得最广泛的网页浏览器)甚为相似,但Spyglass Mosaic则相对地较不出名并使用了NCSA Mosaic少量的源代码。

1996年,微软通过给予季度费用和部分收入从Spyglass中取得了Spyglass Mosaic的源代码和授权。从而使IE逐渐成为微软专属软件。它采用的排版引擎(俗称内核)为Trident。每一次新的IE版本发布,也标志着Trident内核版本号的提升。

下面是各Trident版本信息:

冷知识:除Trident之外,微软还有另一个网页浏览器排版引擎,称为Tasman,它是使用在「Internet Explorer for Mac」的排版引擎。相较于Trident,Tasman引擎对网页标准有较佳的支持,但微软自04年开始已经停止了Mac计算机版本的 Internet Explorer的开发。

Safari

        Safari是苹果公司开发的浏览器,使用了KDE(Linux桌面系统)的KHTML作为浏览器的运算核心,Safari所用浏览器内核的名称是大名鼎鼎的WebKit。 Safari在2003年1月7日首度发行测试版,并成为Mac OS X v10.3与之后版本的默认浏览器,也成为苹果其它系列产品的指定浏览器(也已支持Windows平台)。

        如上述可知,WebKit前身是KDE小组的KHTML引擎,可以说WebKit是KHTML的一个开源的分支。当年苹果在比较了Gecko和KHTML后,选择了后者来做引擎开发,是因为KHTML拥有清晰的源码结构和极快的渲染速度。

        需要了解的是,虽然我们称WebKit为浏览器内核(或浏览器引擎),但不太适合直接称之为我们开头提到的Rendering Engine(渲染引擎),因为WebKit本身主要是由两个引擎构成的,一个正是渲染引擎“WebCore”,另一个则是javascript解释引擎“JSCore”,它们均是从KDE的渲染引擎KHTML及javascript解释引擎KJS衍生而来。

        在2010年4月,苹果公司宣布了其浏览器引擎Webkit的最新项目 Webkit2。Webkit2的目标是实现独立进程与非阻断式API。

        WebKit可以说是苹果公司给开源世界的一大贡献,基于此开源引擎,衍生了多个WebKit分支,如下面要介绍的Chrome的浏览器引擎。

Chrome / Chromium

        谷歌Chrome/Chromium浏览器从08年创始至今一直使用苹果公司的WebKit作为浏览器内核原型,是WebKit的一个分支,我们可以称之为Chromium引擎(注意我们这里说的是Chromium引擎,而不是Chromium浏览器)。

        这里顺便介绍下Chrome和Chromium两个浏览器的区别——Chromium浏览器是谷歌为发展自家的浏览器Chrome而开启的计划,所以Chromium相当于Chrome的工程版或称实验版(尽管Chrome自身也有β版阶段),新功能会率先在Chromium上实现,待验证后才会应用在Chrome上。Chromium一天最多可以更新十几二十个版本,实验性的新特性都会现在这里放出,但是Chromium本身其实并不稳定;而Chrome总共有四个更新分支:Canary、Dev、Beta、Stable,稳定性依次增强。

        我们说回引擎。Chromium引擎虽然是属于WebKit的分支,却把WebKit的代码梳理得可读性提高很多,所以以前可能需要一天进行编译的代码,现在只要两个小时就能搞定。因此Chromium引擎和其它基于WebKit的引擎所渲染页面的效果也是有出入的。基于以上原因,有的地方会把Chromium引擎跟WebKit区分开来,有的地方则直接把Chromium引擎归为WebKit(比如维基百科),其实都有其道理。

        然而在13年发布的Chrome 28.0.1469.0版本开始,Chrome放弃Chromium引擎转而使用最新的Blink引擎(基于WebKit2——苹果公司于2010年推出的新的WebKit引擎),Blink对比上一代的引擎精简了代码、改善了DOM框架,也提升了安全性。

Opera

        Opera浏览器,是一款挪威Opera Software ASA公司制作的支持多页面标签式浏览的网络浏览器。是跨平台浏览器可以在Windows、Mac和Linux三个操作系统平台上运行。Opera浏览器创始于1995年4月,到2014年3月4日,官方发布的个人电脑用的最新版本为Opera20。

        Opera的一个里程碑作品是Opera7.0,因为它使用了Opera Software自主开发的Presto渲染引擎,取代了旧版Opera 4至6版本使用的Elektra排版引擎。

        Presto加入了动态功能,例如网页或其部分可随着DOM及Script语法的事件而重新排版。Presto在推出后不断有更新版本推出,使不少错误得以修正,以及阅读Javascript效能得以最佳化,并成为当时速度最快的引擎。

        然而为了减少研发成本,Opera在2013年2月宣布放弃Presto,转而跟随Chrome使用WebKit分支的Chromium引擎作为自家浏览器核心引擎。

        在Chrome与2013年推出Blink引擎(也是基于WebKit的分支)之后,Opera也紧跟其脚步表示将转而使用Blink作为浏览器核心引擎。

Firefox

        Mozilla Firefox是一个开源网页浏览器,原名是Firebird,2004年2月9日,Mozilla Firebird决定改称Mozilla Firefox。Firefox浏览器使用的是Gecko内核,其发展历程如下:

        1997年,网景收购了DigitalStyle。当时,网景浏览器在各方面的表现已经比不上她的主要竞争对手Internet Explorer。网景开始研发下一代的排版引擎,并期望把新的排版引擎应用于下一版本的网景浏览器上。

        1998年初,Mozilla计划开始执行。这个新的排版引擎名为Raptor,以开发源码的方式发放于互联网上。后来,因为商标问题,Raptor改名为NGLayout(即next generation layout之意)。而最后NGLayout就被网景重新命名为Gecko。

        2003年7月15日时代华纳解散了网景公司,大部分开发者被解雇。Mozilla基金会亦在当天成立,继续推动着Gecko的发展。时至今天,Gecko仍继续由Mozilla的雇员和义工所维护和发展。

        最后还是再谈谈javascript引擎(后面统称JS引擎)这东西。我们上述的渲染引擎主要是负责HTML、CSS以及其他一些东西的渲染,而JS引擎则主要负责对javascript的渲染,一个JS引擎的好坏决定了一个浏览器对脚本的加载和执行速度,也影响了其跑分。

        下方列出各种主流浏览器各自的JS引擎,了解下即可:

Firefox:

SpiderMonkey:第一款JavaScript引擎,由Brendan Eich在Netscape Communications时编写,用于Mozilla Firefox 1.0~3.0版本。

Rhino:由Mozilla基金会管理,开放源代码,完全以Java编写。

TraceMonkey:基于实时编译的引擎,其中部份代码取自Tamarin引擎,用于Mozilla Firefox 3.5~3.6版本。

JaegerMonkey:德文Jäger原意为猎人,结合追踪和组合码技术大幅提高性能,部分技术借凿了V8、JavaScriptCore、WebKit:用于Mozilla Firefox 4.0以上版本。

IonMonkey:可以对JavaScript编译后的结果进行优化,用于Mozilla Firefox 18.0以上版本。

OdinMonkey:可以对asm.js进行优化,用于Mozilla Firefox 22.0以上版本。

Chrome:

V8:开源,由Google丹麦开发,是Google Chrome的一部分。

注:我们上面提到Chrome是基于WebKit的分支,而WebKit又由渲染引擎“WebCore”和JS解释引擎“JSCore”组成,可能会让你搞不清V8和JSCore的关系。你可以这样理解——WebKit是一块主板,JSCore是一块可拆卸的内存条,谷歌实际上认为Webkit中的JSCore不够好,才自己搞了一个V8 JS引擎,这就是Chrome比Safari在某些JS测试中效率更高的原因。

IE:

Chakra:中文译名为查克拉,用于Internet Explorer 9的32位版本及IE10+。

Opera:

Linear A:用于Opera 4.0~6.1版本。

Linear B:用于Opera 7.0~9.2版本。

Futhark:用于Opera 9.5~10.2版本。

Carakan:由Opera软件公司编写,自Opera10.50版本开始使用。

其它:

KJS:KDE的ECMAScript/JavaScript引擎,最初由Harri Porten开发,用于KDE项目的Konqueror网页浏览器中。

Narcissus:开放源代码,由Brendan Eich编写(他也参与编写了第一个SpiderMonkey)。

Tamarin:由Adobe Labs编写,Flash Player 9所使用的引擎。

Nitro(原名SquirrelFish):为Safari 4编写。

字符编码-http与web编码总结

大白话之http编码总结:

1、从本质上看url编码过程:【分成path路径部分和querystring查询串部分】

scheme://host[:port#]/path/…/[url-params] (路径) [?query-string][#anchor](查询字符串,#anchor 这个是文章锚点,用于定位文章的小标题)

浏览器的问题主要是路径(pathinfo)查询字符串(querystring)的编码问题。

2、 RFC 3986(2005年出版),建议所有新的URI必须对未保留字符不加以百分号编码;其它字符建议先转换为UTF-8字节序列, 然后对其字节值使用百分号编码。此前的URI不受此标准的影响。【按道理来说,编码问题应该解决了】

但是因为历史原因,部分浏览器的url ,还没有完全采用utf-8编码。说白了大家统一认为path路径是用utf-8编码的没有问题,但是querystring 查询串该怎么编码,意见不统一。

总结来说:Path路径中需要编码的内容都是用utf-8编码的,浏览器编码问题就是querystring查询串的编码问题,因为意见不统一。

3、补充:查询串 querystring 也包括:
表单的enctype属性为application/x-www-form-urlencoded 的post请求,因为请求体中的数据也是经过urlencoded。【虽然这是旧的url编码,需要注意空格会编码成 +】这时候请求体中的数据也相当于querystring ,只是请求体中不包含?符号。

4、将path路径的二进制流    和    querystring请求串的二进制流,拼接在一起就是完整的url二进制请求流。【当然放在请求体中的请求串不算url的一部分】

5、请求头中出现Content-Type字段 ,是为了通知服务器,我有请求体。
比如: Content-Type: application/x-www-form-urlencoded
响应头中出现Content-Type字段 ,是为了通知客户端,我有响应体。
比如: Content-Type: application/json;charset=UTF-8

6、所有http流都是ASCII编码的二进制流,除了表单用post提交且编码用 multipart/form-data 的情况,这时候请求头还是ASCII流,但是请求体中上传文件是二进制原文件流,请求体中的表单元素都是按表单环境中的编码方式   编码成二进制流,至于请求体中的其他的模板内容如——WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition:form-data;name=”name-file”;filename= 等等 还是用ASCII编码的二进制流


一、path路径编码详解

根据rfc3986,无论什么环境,什么浏览器,在path路径编码中,将需要的编码的字符,按utf-8编码。编码后以16进制展示,接着将16进制的展示符号,两两一组(相当于一个字节)为一个单元,在每个单元前添加百分号(%),最后将整个path进行 ASCII编码。

大白话:比如网站路径:www.baidu.com/春节
将春节:utf-8编码,E6 98 A5 E8 8A 82,然后在每个字节前添加百分号(%),即:%E6%98%A5%E8%8A%82【这算进行了一次编码,后面又会对每个字符再进行ASCII编码,变成二进制流】 

第一步网站地址 部分utf-8编码:
www.baidu.com/%E6%98%A5%E8%8A%82

第二步网站地址 全部ASCII编码后 真正的二进制流: 
77 77 77 2E 62 61 69 64 75 2E 63 6F 6D 2F 25 45 
36 25 39 38 25 41 35 25 45 38 25 38 41 25 38 32

二、querystring查询串编码详解

1、在浏览器地址栏输入的查询串

比如输入:  http://www.test.com/春节?name=春节&psw=123
所有浏览器的path路径都是用utf-8编码的,此时查询串的编码根据浏览器不同可以分为两类。

第一类:  将查询串两步编码成二进制流 ,比如 firefox,chrome,Safari,edge。
面对     ?name=春节&psw=123  。
做法是:浏览器根据自身默认的编码方式,对查询串进行编码并添加百分号。然后再继续进行第二步ASCII编码。

##### 都遵循标准url编码规范,都将查询串中需要编码的部分 采用浏览器默认的 utf-8 编码 并添加百分号
##### 编码前  ?name=春节&psw=123 
##### 编码后  ?name=%E6%98%A5%E8%8A%82&psw=123
##### ASCII码继续 编码成二进制流 
3F 6E 61 6D 65 3D 25 45 36 25 39 38 25 41 35 25 
45 38 25 38 41 25 38 32 26 70 73 77 3D 31 32 33

第二类:将查询串一步编码成二进制流,比如IE浏览器。
面对     ?name=春节&psw=123  
做法是:直接把这个  ?name=春节&psw=123  编码成二进制流,一步到位。编码方式与浏览器环境有关。中文IE 默认用 gb2312 编码方式将查询串编码。

### 中文IE 默认用 gb2312 编码查询串
###【主要是用gb2312编码 春节 二字,至于剩余的符号,因为gb2312是ASCII的一个拓展,所有用gb2312编码和ASCII编码都是一样的】
### 查询串 ?name=春节&psw=123 编码后的二进制流
3F 6E 61 6D 65 3D B4 BA BD DA 26 70 73 77 3D 31 32 33

再举一个例子:
chrome:https://wuu.wikipedia.org/wiki/%E6%98%A5%E8%8A%82?%E6%98%A5%E8%8A%82

#Request Headers

:authority: wuu.wikipedia.org
:method: GET
:path: /wiki/%E6%98%A5%E8%8A%82?%E6%98%A5%E8%8A%82
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
cookie: WMF-Last-Access-Global=13-Aug-2018; GeoIP=JP:13:Tokyo:35.69:139.75:v4; WMF-Last-Access=13-Aug-2018
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

我们知道,”春”和”节”的utf-8编码分别是”E6 98 A5″和”E8 8A 82″,因此,”%E6%98%A5%E8%8A%82″就是按照顺序,在每个字节前加上%而得到的。

所以 chrome浏览器,url中的 路径path 和 查询串 query string  都是UTF-8编码。其实Firefox, Safari ,Edge 都是 同样的。

##### utf-8 编码后 %E6%98%A5%E8%8A%82
##### 又经过第二次 ASCII 编码后
25 45 36 25 39 38 25 41 35 25 45 38 25 38 41 25 38 32

至于,IE 浏览器 无论 IE 8 还是 IE 11 ,路径 path是UTF-8 编码,【查询串queryString 则按照 浏览器默认 编码来进行。比如 中文的IE 用的 就是 GB2312编码。 而且是一步到位,直接编码成二进制流,没有百分号编码的过程。】


IE浏览器:www.baidu.com/春节?春节

#Request Headers

GET /%E6%98%A5%E8%8A%82?���� HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/x-ms-xbap, application/x-ms-application, */*
Accept-Language: zh-cn
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: www.baidu.com
Cookie: BAIDUID=DFF7C9EB333036D329E7EB55757711B7:FG=1; BIDUPSID=D05CF78322A0985BC8FD928169BC4BF1; PSTM=1533825584; H_PS_PSSID=; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BD_UPN=1123314151; H_PS_645EC=9c51wK0QsNr9o%2BoxCNqWR7faZJlxa6F7GdNch0OwzQznYCdO%2B5jhAlHofJh5PLalplC3%2BoaOqoNeFg

我们知道,”春”和”节”的GB2312编码(我的操作系统”Windows XP”中文版的默认编码)分别是”B4 BA”和”BD DA”。因此,IE实际上就是将查询字符串,以GB2312编码的格式发送出去。至于,路径pathinfo 还是 用utf-8  的形式,比如春节两字的编码– %E6%98%A5%E8%8A%82【但是IE的查询串没有用%编码,直接GB2312编码成二进制流,与其他浏览器先百分号编码,再编码成二进制流不同】

=======================================================

参考其他文章,说以前Firefox的 pathInfo和queryString都是URL encode按照GB2312编码的。对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。 很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。

按照RFC3986 的规定,需要编码的字符,都应该用utf-8编码成字节流,然后百分号编码。所以这是浏览器的历史遗留问题。

根据现在的浏览器情况:浏览器地址栏直接输入:

Chrome,Safari,Edge,Firefox,pathInfo和query string 都是utf-8编码
IE pathInfo 用utf-8 编码,query string 用 gb2312 编码


2、点击超链接生成的查询串

比如网页中有如下超链接:
<a href="gbk-response/春节?name=春节&psw=123">go</a>

所有浏览器的path路径都是用utf-8编码的,此时查询串的编码根据浏览器不同可以分为两类。

第一类:  将查询串两步编码成二进制流 ,比如 firefox,chrome,Safari,edge。
面对     ?name=春节&psw=123  。
做法是:
1、浏览器首先根据网页中设置的编码方式对查询串进行编码并添加百分号。
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
2、如果网页中找不到指定的编码方法,浏览器就会采用
自身默认的编码方式,对查询串进行编码并添加百分号(中文环境下firefox,chrome,Safari,edge,默认都是utf-8 编码),然后再继续对查询串进行第二步ASCII编码。

第二类:将查询串一步编码成二进制流,比如IE浏览器。
面对     ?name=春节&psw=123  
做法是:
1、浏览器首先根据网页中设置的编码方式对查询串一步到位编码成二进制流。
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
2、如果网页中找不到指定的编码方法,浏览器就会采用
自身默认的编码方式(中文环境下 IE 默认采用gb2312编码)一步到位编码成二进制流。


3、表单提交生成的查询串

如果请求串,由表单提交生成且表单的enctype属性为 application/x-www-form-urlencoded【默认属性】,无论是get还是post方法,都会进行百分号编码【当然这种旧url编码需要注意 空格会编码成 +】

form元素有个enctype属性,可以指定数据编码方式,有如下三种:

1. application/x-www-form-urlencoded: 表单数据编码为键值对,&分隔
2. multipart/form-data: 表单数据编码为一条消息,每个控件对应消息的一部分
3. text/plain: 表单数据以纯文本形式进行编码

详细说明:

form的enctype的编码方式,常用有两种:
application/x-www-form-urlencoded和multipart/form-data
其中 application/x-www-form-urlencoded为默认编码方式。

在form的action为get时,浏览器用x-www-form-urlencoded的编码方式,将表单数据编码为
(name1=value1&name2=value2...),然后把这个字符串append到url后面,用?分隔,跳转
到这个新的url
当form的action为post时,浏览器将form数据封装到http body中,然后发送到server。
在没有type=file时候,用默认的 application/x-www-form-urlencoded 就行。
在有 type=file 时候,要用multipart/form-data编码方式。浏览器会把表单以控件为单位分割,
并且为每个部分加上Content-Dispositon(form-data或file)、Content-Type(默认text/plain)、
name(控件name)等信息,并加上分割符(boundary)。

假设浏览器所在环境,采用UTF-8编码将表单信息编码成querystring【编码选择方式后文会讲到】
1、如果浏览器用get方式请求   http://www.test.com/春节?name=春节&psw=123
则url显示结果为:http://www.test.com/%E6%98%A5%E8%8A%82?name=%B4%BA%BD%DA&pwd=123
2、如果浏览器是post请求方式:则url显示结果为http://www.test.com/%E6%98%A5%E8%8A%82
且请求体中有:name=%B4%BA%BD%DA&pwd=123

不管是放在url中还是请求体中,接下来仍需要将请求串ASCII编码。

### 已经过一次编码的请求串
?name=%E6%98%A5%E8%8A%82&psw=123
### ASCII码继续 编码成二进制流
3F 6E 61 6D 65 3D 25 45 36 25 39 38 25 41 35
25 45 38 25 38 41 25 38 32 26 70 73 77 3D 31
### 注意放在请求体中的请求串(严格来讲已不叫请求串了)
### 因为请求体中的请求串没有问号(?)所以请求体的二进制流要去掉第一个3F

4、表单提交生成的查询串的编码选择

(1)如果网页中 的head标签中 没有指定 网页编码类型

比如缺少类似下面这个的

  <meta http-equiv="content-type" content="text/html;charset=utf-8">

那么浏览器会使用默认的编码方式进行解码,这样会出现乱码的可能。比如mac下的chrome浏览器,我这边测试出来默认编码是用gb2312的。如果网页没直接指明编码方式,那么如果是gb2312编码的网页那会显示正确。如果是utf-8编码的网页,网页就会解析错误,产生乱码。因为浏览器默认是用GB2312解码网页的。

测试了一下Safari,发现浏览器默认编码即不是utf-8,也不是GB2312。
测试了一下Firefox,浏览器默认编码是GB2312。
测试了一下IE,浏览器默认编码是GB2312。

注:浏览器默认编码可能和系统默认编码有关。【如中文系统和英文系统】

(2)如果表单中,指定了accept-charset

网页里的form编码其实不完全取决于网页编码,form标记中有一个accept-charset属性,在非ie浏览器种,如果将其赋值(比如accept-charset=”UTF-8″),则表单会按照这个值表示的编码方式,将需要编码的字符编码成字节流,然后采用  application/x-www-form-urlencoded   非标准URL编码。

<form action="form_action.asp" accept-charset="utf-8">
  <p>First name: <input type="text" name="fname" /></p>
  <p>Last name: <input type="text" name="lname" /></p>
  <input type="submit" value="Submit" />
</form>

定义和用法

accept-charset 属性规定服务器处理表单数据所接受的字符集。此属性的默认值是 “unknown”,表示表单的字符集与包含表单的文档的字符集相同。

提示:请避免使用该属性。应该在服务器端验证文件上传。

浏览器支持

除了 Internet Explorer,accept-charset 属性得到几乎所有浏览器的支持。

注释:accept-charset 属性无法在 Internet Explorer 中正确地工作。如果 accept-charset 属性设置为 “ISO-8859-1″,IE 将发送以 “Windows-1252” 编码的数据。

我在chrome浏览器中测试过,gb2312编码的网页,表单用指定用utf-8编码。

结果我发现,请求头中 Content-Type: application/x-www-form-urlencoded   也不存在 charset=xxx 的字段。

POST http://192.168.2.233/http_encode/gbk-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: 192.168.2.233
Connection: keep-alive
Content-Length: 32
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.2.233
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://192.168.1.133/~cool/http_encode/gbk-post.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

name=%E6%98%A5+%E8%8A%82&pwd=123

 

在ie下,我的兼容解决办法是:

form1.onsubmit=function(){
document.charset=this.getAttribute('accept-charset');
}

三、表单知识补充

1、四种常见的 POST 提交数据方式

(1)application/x-www-form-urlencoded
(2)multipart/form-data
(3)application/json
(4)text/xml(xml-rpc)

2、表单提交方式GET与POST的区别

当用GET提交时,会将表单信息变成query string 查询字符串。根据 网页中 head 标签 里面设定的编码方案  将   query string 中 需要编码的字符  编码成字节流 。然后再用非标准的url编码。【大白话,GET提交表单时,表单信息变成了URL里面的query string,即在http协议的请求行中】

因为 query string  在表单 中 ,采用的 是  application/x-www-form-urlencoded   非标准URL编码。 这和标准的URL编码有区别。比如编码空格时标准url编码成”%20″,非标准url编码成”+”。

========================================================

当用POST提交表单时,会将表单信息变成FORM DATA,存放在http协议中的 请求体中。FORM DATA 和query string 一样,也是将 需要编码的部分,根据网页head中设定的编码方式,编码成字节流。然后采用  application/x-www-form-urlencoded   非标准URL编码。

同时,会在请求头中 加入:

Content-Type: application/x-www-form-urlencoded

注意:无论是GET还是POST方式,将表单信息编码成字节流时,空格是不需要编码的,所以后面采用  application/x-www-form-urlencoded   非标准URL编码时,空格 一定会被编码成 “+”符号。

3、表单中的enctype属性

表单常用属性:

action:url 地址,服务器接收表单数据的地址
method:提交服务器的http方法,一般为post和get
name:参数名属性

######################################
enctype: 表单数据提交时使用的编码类型,默认使用"aplication/x-www-form-urlencoded",
如果是使用POST请求,则请求头中的content-type指定值就是该值。因为包含了请求体。
如果是使用GET请求,通过URL的query string传递参数,没有请求体,所以不需要content-type
######################################

如果表单中有上传文件,编码类型需要使用"multipart/form-data",类型,才能完成传递文件数据。
<form action="/upload" enctype="multipart/form-data" method="post">
    Username: <input type="text" name="username">
    Password: <input type="password" name="password">
    File: <input type="file" name="file">
    <input type="submit">
</form>

4、表单用post提交,并且编码用 multipart/form-data 的情况

这种情况下,除了上传的文件直接采用二进制流没有再编码,其他的表单信息编码方式和x-www-form-urlencoded一样。举例如下:
表单信息:【name-desc 填写  春节  二字,上传的文件为   春节.txt】

<form action="testfileupload" method="post" enctype="multipart/form-data" accept-charset="gb2312">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

待上传文件:春节.txt
文件内容:

good春节OK
hello

文件二进制代码:
【备注在Windows的记事本软件中如果用utf-8编码保存时,会自动在保存信息中添加EF BB BF 字节序,而mac系统下不会,这里实验采用mac系统】

676f 6f64 e698 a5e8 8a82 4f4b 0d0a 6865
6c6c 6f

提交请求时,fiddler2抓包结果:

POST http://192.168.1.131:8080/springmvc-2/testfileupload HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 410
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryo2ilS97ZdtneTvV5
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=A15ECBF73D297496D7785129A231A321

------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-file"; filename="    .txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-desc"

    
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundaryo2ilS97ZdtneTvV5--

http请求特别解析:

##### 下面的  filename="    .txt"   其实是   filename="春节.txt"
##### 之所有 出现空白是表单信息中 春节 2个字 二进制信息是 B4 BA BD DA ,采用了GBK编码
##### 而 fiddler2 显示信息 用的是 utf-8 编码 所有显示不出来
Content-Disposition: form-data; name="name-file"; filename="    .txt"

##### 下面第三行默认是空白,而第四行 也是 空白 但是 有二进制信息 是 B4 BA BD DA
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-desc"

如果把表单 删除gb2312编码,也就是用 网页内定 的utf-8 编码:

<form action="testfileupload" method="post" enctype="multipart/form-data" >
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

抓包结果:

POST http://192.168.1.131:8080/springmvc-2/testfileupload HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 414
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4v5NL53bCqC2d9fH
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=A15ECBF73D297496D7785129A231A321

------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-file"; filename="春节.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-desc"

春节
------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundary4v5NL53bCqC2d9fH--

从这里我们能够看到,所有 春节 两字 编码信息 都是 utf-8 编码,都能在fiddler2 抓包中,正确显示。

从结果比较来看, multipart/form-data 的请求体中,上传文件的表单单元,不需要再编码,直接传输文件二进制流,因为春节.txt是utf-8编码,而fiddler2默认是用utf-8解码,所有解码正确。至于其他的表单单元,根据表单环境提供的编码方式编码,然后传递。上面的例子,表单第一次编码是gb2312,而fiddler2是utf-8解码,导致抓包时,信息解码错误。表单第二次编码是utf-8,和fiddler2的解码一致,所以信息抓包解码正确。

======================================================

特别说明:如果改成了iso-8859-1的编码表单

<form action="testRequestBody" method="post" enctype="multipart/form-data" accept-charset="iso-8859-1">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

上传文件名还是春节.txt ,表单输入的name-desc 也还是 春节

这个时候fiddler2抓包二进制流,然后用utf-8解码结果:

POST http://192.168.1.131:8080/springmvc-2/testRequestBody HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 420
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3FCh7yh4z2wpPZo9
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=07D6AF477CEE3B7CDB6A1847E2ECB3A4

------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-file"; filename="??.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-desc"

&#26149;&#33410;
------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundary3FCh7yh4z2wpPZo9--

我们看到:请求体中
文件名为: filename=”??.txt”    查看二进制发现春节两字 编码成  3F 3F
而 表单中 name-desc 值为:  &#26149;&#33410;    【这个26149 33410 分别是春节的Unicode 十进制 值】【&#26149;&#33410; 这两个都可以用ASCII解码成二进制流比如 &#代表26 23】
从这里我们能够看出来:表单用 multipart/form-data 传递信息时,当遇到表单信息无法编码时,表单中的不同类型,应对结果可能不同。比如 表单中的文件名应对错误编码的结果与 表单中 name-desc 应对错误编码的结果不同。但是,如果表单能被正确编码,那么两者编码结果值相同(因为两者原码相同)。

后来,我们又将表单编码设置成立utf-16,但是抓包结果来看,表单信息还是被编码成了utf-8,所以感觉没有效果。

当我们将表单 用big5 编码时,上传文件名 改为:春節   ,表单填写name-desc时 也改为 春節 ,抓包结果看到这两处 春節 都被编码成了   AC 4B B8 60  二进制流。至于文件中的信息春节,因为文件用utf-8编码过的二进制流,直接放入请求体中,不需要二次编码,所以不受表单编码的影响。

我们知道的是表单信息提交时,会按照表单设置的编码进行发送。但是对于 上传文件,用的是原始二进制流,不用编码。现在有一个疑问,就是http请求中,默认格式字符信息,用的是ASCII编码  还是 用表单设置的编码,目前我看到的表单编码都是兼容ASCII码的(比如UTF-8,GBK,big5 兼容ASCII),所以无法实质区分。曾尝试用utf-16设置表单编码,但是抓包后发现,表单信息居然采用了utf-8 编码。所以区分不了。


四、url编码与javascript

url编码-JavaScript-函数

   escape 、 encodeURI 、encodeURIComponent

首先,无论网页的编码方式是什么,都可以正确解析,毕竟编码和解码是对应统一的。JavaScript引擎根据 已经编码好的网页二进制流 和 网页的编码方式 , 将网页二进制信息流正确转码成JavaScript引擎内部能处理的二进制流【内部编码要么是ucs-2 或者是 utf -16,待探究】

所以,JavaScript 中的  函数参数 也会 编码成 内部编码。【JavaScript内部编码可能是 ucs-2,utf-16,总之:两个字节内,编码的结果都是一样的,以后再探讨到底是哪种编码 】

javascript:escape("\u6625\u8282");  ==>  反斜杠 \ 代表Unicode转义字符。以Unicode字符序的方式接收
javascript:escape("春节");  ==>  春节 二字,代表默认的字符 接收
#这里的函数参数  春节  或者 Unicode排序值  都将内部都编码成了 UCS-2 或者 UTF-16  
#这里的函数返回值 就是 将内部编码后的数据 进行运算,并将运算结果转码成 Unicode排序值,然后输出

编码问题归纳:记录网页信息的网页编码 和 JavaScript引擎处理网页信息的内部编码。

下面列出了这三个函数的安全字符(即函数不会对这些字符进行编码)

escape(69个):*/@+-._0-9a-zA-Z
encodeURI(82个):!#$&'()*+,/:;=?@-._~0-9a-zA-Z
encodeURIComponent(71个):!'()*-._~0-9a-zA-Z
encodeURI('https://www.baidu.com/ a b c')
// "https://www.baidu.com/%20a%20b%20c"
encodeURIComponent('https://www.baidu.com/ a b c')
// "https%3A%2F%2Fwww.baidu.com%2F%20a%20b%20c"
 
//而 escape 会编码成下面这样,eocode 了冒号却没 encode 斜杠,十分怪异,故废弃之
escape('https://www.baidu.com/ a b c')
// "https%3A//www.baidu.com/%20a%20b%20c"

实际上,escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode排序值。比如”春节”的返回结果是%u6625%u8282,也就是说在Unicode字符集中,”春”是第6625个(十六进制)字符,”节”是第8282个(十六进制)字符。

春的Unicode排序值为:6625(十六进制)   ; 26149(十进制)   

春的utf-8编码值为:E6 98 A5(十六进制) ;URL编码采用的是utf-8 编码!!!

Javascript:escape("春节");
"%u6625%u8282"

Javascript:escape("Hello World");
"Hello%20World"

###  反斜杠 \ 代表Unicode转义字符 ,以Unicode字符序的方式接收。

javascript:escape("\u6625\u8282");
"%u6625%u8282"

javascript:unescape("%u6625%u8282");
"春节"

javascript:unescape("\u6625\u8282");
"春节"

 

escape()的具体规则是,除了ASCII字母、数字、标点符号”@ * _ + – . /”以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。

escape()不对”+”编码。但是我们知道,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。所以,使用的时候要小心。

escape函数已经被W3C废弃了。因为 escape函数 返回的是Unicode 排序值,不是 utf-8 编码值。不符合RFC3986 标准。但是在ECMA-262标准中仍然保留着escape的这种编码语法。


encodeURI()是Javascript中真正用来对URL编码的函数。

它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号”; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%。

encodeURI("http://www.cnblogs.com/season-huang/some other thing");
//"http://www.cnblogs.com/season-huang/some%20other%20thing";

编码后变为上述结果,可以看到空格被编码成了%20,而斜杠 / ,冒号 : 并没有被编码。需要注意的是,它不对单引号’编码。


encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。

因此,”; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

使用场景:当我们的 URL 长这样子,请求参数中带了另一个 URL :

var URL = "http://www.a.com?foo=http://www.b.com?t=123&s=456";
//直接对它进行 encodeURI 显然是不行的。
//因为 encodeURI 不会对冒号 : 及斜杠 / 进行转义,
//那么就会出现上述所说的服务器接受到之后解析会有歧义。

encodeURI(URL);
// "http://www.a.com?foo=http://www.b.com?t=123&b=456"
//这个时候,就该用到 encodeURIComponent() 。
//它的作用是对 URL 中的参数进行编码,记住是对参数,而不是对整个 URL 进行编码。

//正确的用法:encodeURIComponent() 着眼于对单个的参数进行编码:
var param = "http://www.b.com?t=123&s=456"; // 要被编码的参数
URL = "http://www.a.com?foo="+encodeURIComponent(param);
//"http://www.a.com?foo=http%3A%2F%2Fwww.b.com%3Ft%3D123%26s%3D456"

url编码-JavaScript-AJAX

GBK编码的gbk-get.html

<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
    <script type="text/javascript"	src="jquery.js"></script>
</head>
<body>
    <center>
      <a href="gbk-response/春节?name=春节&psw=123">go</a>
        <form action="gbk-http-get-response/春节" method="get">
            <p>用户名:<input type="text" id="name"  name="name"/></p>
            <p>密码:<input type="password" id="pwd" name="pwd"/></p>
            <input type="submit" value="登录">
            <input type="button" value="ajax-get" id="ajax-get-btn"/>
            <input type="button" value="ajax-post" id="ajax-post-btn"/>

        </form>
    </center>

<script>

    $("#ajax-get-btn").click(function(){
    			//发送ajax请求,弹出部门信息,显示在下拉列表中
          $.ajax({
            url:"gbk-ajax-get-response/春节",
            type:"get",
            data:"name="+$("#name").val(),
            success:function(result){

            }

          })
    		});


        $("#ajax-post-btn").click(function(){

              //获取DOM对象的 value 属性
              var name = $("#name")[0].value;

          		//发送ajax请求,弹出部门信息,显示在下拉列表中
              $.ajax({
                url:"gbk-ajax-post-response/春节",
                type:"post",
                //下面这个 ${"#name"}.value 写错了,难怪一直报错。 应该是 $("#name").value
                //data:"name="+${"#name"}.value,
                data:"name="+name,
                success:function(result){

                }

              })
        		});

            // $("#ajax-get-btn").click(function(){
            //
            //     var name = ${"#name"}.value;
            //
            //       //发送ajax请求,弹出部门信息,显示在下拉列表中
            //       $.ajax({
            //         url:"control",
            //         type:"get",
            //       //  下面的写法是错误的,正确的是 1、获取DOM属性  data:"name="+$("#name")[0].value,  或者 2、获取JQuery属性   data:"name="+$("#name").val(),
            //       //  data:"name="+${"#name"}.value,
            //         data:"name="+name,
            //         success:function(result){
            //
            //         }
            //
            //       })
            //     });

</script>

</body>
</html>

utf-8编码的文件大致相同:

测试结果:

fiddler2 用 utf-8 解码显示 http请求信息

IE浏览器,gbk网页,用get请求

GET http://192.168.1.131/~sky/http_encode/gbk-ajax-get-response/%B4%BA%BD%DA?name=     HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/gbk-get.html
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Connection: Keep-Alive

/%B4%BA%BD%DA?name=
上面用utf-8显示的文字,用16进制表示如下:
2F 25 42 34 25 42 41 25 42 44 25 44 41 3F 6E 61 6D 65 3D B4 BA BD DA

IE浏览器,gbk网页,用post请求
POST http://192.168.1.131/~sky/http_encode/gbk-ajax-post-response/%B4%BA%BD%DA HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/gbk-get.html
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Content-Length: 11
Connection: Keep-Alive
Pragma: no-cache

name=春节

fiddler2 用utf-8 解码http信息:
请求体: name=春节  的二进制码为 6e 61 6d 65 3d e6 98 a5 e8 8a 82


IE浏览器 utf-8 网页 ,用get请求

GET http://192.168.1.131/~sky/http_encode/utf8-ajax-get-response/%B4%BA%BD%DA?name=     HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/utf-8-get.html
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Connection: Keep-Alive

/%B4%BA%BD%DA?name=
上面用utf-8显示的文字,用16进制表示如下:
2F 25 42 34 25 42 41 25 42 44 25 44 41 3F 6E 61 6D 65 3D B4 BA BD DA


IE浏览器 utf-8 网页 ,用post请求

POST http://192.168.1.131/~sky/http_encode/utf8-ajax-post-response/%B4%BA%BD%DA HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/utf-8-get.html
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Content-Length: 11
Connection: Keep-Alive
Pragma: no-cache

name=春节

fiddler2 用utf-8 解码http信息:
请求体: name=春节  的二进制码为 6e 61 6d 65 3d e6 98 a5 e8 8a 82
=================================================================

chrome gbk网页 get请求

GET /~sky/http_encode/gbk-ajax-get-response/%E6%98%A5%E8%8A%82?name=%B4%BA%BD%DA HTTP/1.1
Host: localhost
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Referer: http://localhost/~sky/http_encode/gbk-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

chrome gbk网页 post请求

POST /~sky/http_encode/gbk-ajax-post-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 11
Accept: */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost/~sky/http_encode/gbk-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

name=春节

抓包发现:请求体的二进制代码为:
6e 61 6d 65 3d e6 98 a5 e8 8a 82



chrome utf-8 get请求

GET /~sky/http_encode/utf8-ajax-get-response/%E6%98%A5%E8%8A%82?name=%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Referer: http://localhost/~sky/http_encode/utf-8-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

chrome utf-8 post请求

POST /~sky/http_encode/utf8-ajax-post-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 11
Accept: */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost/~sky/http_encode/utf-8-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

name=春节
抓包发现:请求体的二进制代码为:
6e 61 6d 65 3d e6 98 a5 e8 8a 82

也就是说,在Ajax调用中,path路径调用不同,IE是%B4%BA%BD%DA,chrome是%E6%98%A5%E8%8A%82querystring 又和网页的编码有关系,IE 是 一步到位的编码。chrome是两步到位的编码,中间经历了百分号编码。


五、url编码与服务器响应

Apache服务器返回的   Response Headers :

HTTP/1.1 200 OK
Date: Wed, 15 Aug 2018 07:49:34 GMT
Server: Apache/2.4.28 (Unix) PHP/5.5.38
Last-Modified: Wed, 15 Aug 2018 03:41:28 GMT
ETag: "1c2-xxxxxxxxxxxx"  【一串字符串】
Accept-Ranges: bytes
Content-Length: 450
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html【我看到百度Apache服务器,这里是 text/html;charset=utf-8】

百度Apache 服务器 Request Headers

HTTP/1.1 200 OK
Bdpagetype: 3
Bdqid: XXXXXXXXXXXXXXXX
Cache-Control: private
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Cxy_all: news+XXXXXXXXXXXXXXXXXXXXXXXXX
Cxy_ex: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Date: Wed, 15 Aug 2018 07:43:23 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: Apache
Tracecode: XXXXXXXXXXXXXXXXXXXXXXXXX
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1

Nginx 服务器 Request Headers:

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Wed, 15 Aug 2018 07:56:25 GMT
Content-Type: text/html
【上面是欢迎页的响应,我个人网站和Nginx官网服务器的响应都是text/html;charset=utf-8】
Content-Length: 612
Last-Modified: Tue, 17 Apr 2018 15:48:00 GMT
Connection: keep-alive
ETag: "XXXXXXXXXX-XXX"  【一串字符串】
Accept-Ranges: bytes

nginx官网服务器:Response Headers

content-encoding: gzip
content-type: text/html; charset=UTF-8
date: Wed, 15 Aug 2018 08:22:48 GMT
link: <https://www.nginx.com/wp-json/>; rel="https://api.w.org/"
link: <https://www.nginx.com/>; rel=shortlink
link: <https://www.nginx.com/wp-json>; rel="https://github.com/WP-API/WP-API"
server: nginx
status: 200
vary: Accept-Encoding, User-Agent
x-cache-config: 0 0
x-cache-status: HIT
x-cache-status: HIT
x-pingback: https://www.nginx.com/xmlrpc.php
x-user-agent: standard

Tomcat 服务器解析 http 请求过程

1、开发人员必须清楚的servlet规范:

(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没有说明这一点。
(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。
(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。
(4) POST提交的数据是作为request body的一部分。
(5) 网页的Http头中ContentType(“text/html; charset=GBK”)的作用:
(a) 告诉浏览器网页中数据是什么编码;
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。

2、Java中的uri 和 url 【和IETF设置的标准不同】

浏览器请求:http://localhost:8080/springmvc-1/春节.jsp?p=春节

写了一个 春节.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page language="java" import="java.util.*" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

测试完成 uri 与 url
<br>


<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

out.println("basePath:"+basePath);
out.println("<br/>");
out.println("getContextPath:"+request.getContextPath());
out.println("<br/>");
out.println("getServletPath:"+request.getServletPath());
out.println("<br/>");
out.println("getRequestURI:"+request.getRequestURI());
out.println("<br/>");
out.println("getRequestURL:"+request.getRequestURL());
out.println("<br/>");
out.println("getRealPath:"+request.getRealPath("/"));
out.println("<br/>");
out.println("getServletContext().getRealPath:"+getServletContext().getRealPath("/"));
out.println("<br/>");
out.println("getQueryString:"+request.getQueryString());

%>
	
</body>
</html>

结果显示:chrome,Safari,Firefox,Edge结果

测试完成 uri 与 url 
basePath:http://localhost:8080/springmvc-1/ 
getContextPath:/springmvc-1 
getServletPath:/春节.jsp 
getRequestURI:/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRequestURL:http://localhost:8080/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getServletContext().getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getQueryString:p=%E6%98%A5%E8%8A%82

IE浏览器显示结果【浏览器地址栏 编码,query string  用了GB2312 导致乱码】

测试完成 uri 与 url 
basePath:http://192.168.1.133:8080/springmvc-1/ 
getContextPath:/springmvc-1 
getServletPath:/春节.jsp 
getRequestURI:/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRequestURL:http://192.168.1.133:8080/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getServletContext().getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getQueryString:p=´º½Ú

Tomcat 服务器 response 报文:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 849
Date: Thu, 16 Aug 2018 09:50:30 GMT

java中 request.getServletPath() 和 request.getPathInfo() 的区别 {映射问题造成}

在 Web 中,我们通常需要获取 URL 相对于 Webapp 的路径,主要是下面的几个方法:

request.getServletPath()
request.getPathInfo()
request.getContextPath()
request.getRequestURI()

其中 request.getRequestURI() 的返回值包含了 request.getContextPath(),所以是相对于网站的根目录的。

下面我们分析 request.getServletPath() 和 request.getPathInfo()

1. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>*.jetx</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == "/templates/index.jetx"

request.getPathInfo() == <null>

2. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>/*</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == ""

request.getPathInfo() == "/templates/index.jetx"

3. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>/template/*</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == "/templates"
request.getPathInfo() == "/index.jetx"
3、tomcat 解析http 流的过程

解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

protected void convertURI(MessageBytes uri, Request request) 
throws Exception { 
       ByteChunk bc = uri.getByteChunk(); 
       int length = bc.getLength(); 
       CharChunk cc = uri.getCharChunk(); 
       cc.allocate(length, -1); 
       String enc = connector.getURIEncoding(); 
       if (enc != null) { 
           B2CConverter conv = request.getURIConverter(); 
           try { 
               if (conv == null) { 
                   conv = new B2CConverter(enc); 
                   request.setURIConverter(conv); 
               } 
           } catch (IOException e) {...} 
           if (conv != null) { 
               try { 
                   conv.convert(bc, cc, cc.getBuffer().length - 
cc.getEnd()); 
                   uri.setChars(cc.getBuffer(), cc.getStart(), 
cc.getLength()); 
                   return; 
               } catch (IOException e) {...} 
           } 
       } 
       // Default encoding: fast conversion 
       byte[] bbuf = bc.getBuffer(); 
       char[] cbuf = cc.getBuffer(); 
       int start = bc.getStart(); 
       for (int i = 0; i < length; i++) { 
           cbuf[i] = (char) (bbuf[i + start] & 0xff); 
       } 
       uri.setChars(cbuf, 0, length); 
}

从上面的代码中可以知道对 URL 的 URI【java标准的uri,上节已提到过】 部分进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。

QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1,要使用 ContentType 中定义的编码就要设置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true。这个配置项的名字有点让人产生混淆,它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码,这一点还要特别注意。

关于post表单提交的数据,存放在请求体中,至于如何解码,需要以后探讨。


六、请求头编码与网页内置编码优先级

设置网页编码的方法有:

1.利用php header()函数声明,这个header()函数的作用是把括号里面的信息发到http标头。

header(“Content-type: text/html; charset=xxx”); 

2.利用HTML <meta >进行声明,HTML <meta >这个标签的作用是声明客户端的浏览器用什么字符集编码显示该页面

<META http-equiv=”content-type” content=”text/html; charset=xxx”>

3.服务器在response 网页时,也可以 设置 服务器默认响应 编码。就相当于给每个文件都 加了一行header(“content-type:text/html; charset=xxx”)。如果网页里有header(“content-type:text/html; charset=xxx”),就把默认的字符集改为你设置的字符集,所以这个函数永远有用。

所以,网页编码的优先级是:

header(“content-type:text/html; charset=xxx”) 
AddDefaultCharset xxx 
<META http-equiv=”content-type” content=”text/html; charset=xxx”> 

如果你是web程序员,给你的每个页面都加个header(“content-type:text/html; charset=xxx”),保证它在任何服务器都能正确显示,可移植性强。 

1,2点 在服务器和浏览器上都可以使用,第3点只能在服务器上使用。

七、其它需要编码的地方

除了 URL 和参数编码问题外,在服务端还有很多地方可能存在编码,如可能需要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。

xml 文件可以通过设置头来制定编码格式

<?xml version="1.0" encoding="UTF-8"?>

Velocity 模版设置编码格式:

services.VelocityService.input.encoding=UTF-8

JSP 设置编码格式:

<%@page contentType="text/html; charset=UTF-8"%>

访问数据库都是通过客户端 JDBC 驱动来完成,用 JDBC 来存取数据要和数据的内置编码保持一致,可以通过设置 JDBC URL 来制定如 MySQL:url=”jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK”。


PHP程序在查询数据库之前,首先执行 mysql_query(“SET NAMES xxxx”);其中 xxxx 是你网页的编码(charset=xxxx),如果网页中 charset=utf8,则 xxxx=utf8,如果网页中 charset=gb2312,则xxxx=gb2312,几乎所有WEB程序,都有一段连接数据库的公共代码,放在一个文件里,在这文件里,加入 mysql_query(“set names”)就可以了。

SET NAMES 显示客户端发送的 SQL 语句中使用什么字符集。因此,SET NAMES ‘utf-8’语句告诉服务器“将来从这个客户端传来的信息采用字符集utf-8”。它还为服务器发送回客户端的结果指定了字符集。(例如,如果你使用一 个SELECT语句,它表示列值使用了什么字符集。)


八、乱码出现的常见原因

在了解了 Java Web 中可能需要编码的地方后,下面看一下,当我们碰到一些乱码时,应该怎么处理这些问题?出现乱码问题唯一的原因都是在 char 到 byte 或 byte 到 char 转换中编码和解码的字符集不一致导致的,由于往往一次操作涉及到多次编解码,所以出现乱码时很难查找到底是哪个环节出现了问题,下面就几种常见的现象进行分析。

1、中文变成了看不懂的字符

例如,字符串“淘!我喜欢!”变成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”编码过程如下图所示

字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码,而且是一个汉字字符变成两个乱码字符。

2、一个汉字变成一个问号

例如,字符串“淘!我喜欢!”变成了“??????”编码过程如下图所示

将中文和中文符号经过不支持中文的 ISO-8859-1 编码后,所有字符变成了“?”,这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示,这也就是通常所说的“黑洞”,所有 ISO-8859-1 不认识的字符都变成了“?”。

3、一个汉字变成两个问号

例如,字符串“淘!我喜欢!”变成了“????????????”编码过程如下图所示

这种情况比较复杂,中文经过多次编码,但是其中有一次编码或者解码不对仍然会出现中文字符变成“?”现象,出现这种情况要仔细查看中间的编码环节,找出出现编码错误的地方。

4、网页中head标签设置的编码和保存网页的编码不同

我用  gb2312 编码 保存了一个网页【大白话,里面的中文都是gb2312编码】。然后我在该网页中,<META http-equiv=”content-type” content=”text/html; charset=xxx”>设置了utf-8编码。  于是出现乱码了。

5、一种不正常的正确编码

还有一种情况是在我们通过 request.getParameter 获取参数值时,当我们直接调用

String value = request.getParameter(name);

会出现乱码,但是如果用下面的方式

String value = String(request.getParameter(name).getBytes("
ISO-8859-1"), "GBK");

解析时取得的 value 会是正确的汉字字符,这种情况是怎么造成的呢?

看下如所示:

这种情况是这样的,ISO-8859-1 字符集的编码范围是 0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值“不变”。虽然中文字符在经过网络传输时,被错误地“拆”成了两个欧洲字符,但由于输出时也是用 ISO-8859-1,结果被“拆”开的中文字的两半又被合并在一起,从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字,但是还是不建议用这种不正常的方式取得参数值,因为这中间增加了一次额外的编码与解码,这种情况出现乱码时因为 Tomcat 的配置文件中 useBodyEncodingForURI 配置项没有设置为”true”,从而造成第一次解析式用 ISO-8859-1 来解析才造成乱码的。


九、http编码优先级及编码别名的宽容性

1、编码优先级问题

根据 HTML4.01 规范中的描述,服务端应该提供给用户端文档的字符编码(character encoding)信息,最直接的方式为通过 HTTP 协议([RFC2616], 3.4 及 14.17) “Content-Type” 头字段的 “charset” 将文档的字符编码告诉用户端。例如以下 HTTP 头声明了字符编码为 ISO-8859-1:

Content-Type: text/html; charset=ISO-8859-1

处于某种情况无法访问服务器时,HTML 文档可以包含有关文档的字符编码的明确信息,META 元素可以用来为用户端提供这些信息。例如指定当前文档的字符编码为 ISO-8859-1,文档中应包含如下 META 声明:

<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

当 HTTP 协议与 META 元素均没有提供有关一个文档的字符编码信息时,HTML 还为一些元素提供了 charset 属性。结合这些机制,作者可以在很大程度上提高当用户获取资源时用户端识别字符编码的机会。

针对如何确定一个文档的字符编码,用户代码必须遵守下面的优先级顺序(优先级由高至低):

(1)HTTP “Content-Type” 字段中的 “charset” 参数。
(2)META 声明中 “http-equiv” 为 “Content-Type” 对应的值中的 “charset” 的值。
(3)元素的 charset 属性。

关于 字符编码 的详细信息,请参考 HTML4.01 规范 5.2 Character encodings 以及 W3C Internationalization 关于 Character encodings 中的内容。

2、编码别名宽容性问题

补充知识:做个测试,我们就能明白各浏览器对页面的默认字符编码不尽相同。
当页面没有设置任何字符编码信息或者浏览器无法识别 HTTP 头字段以及 META 元素中所声明的字符编码信息时,所有浏览器均会以各自在当前语言版本下的默认字符编码显示页面。

编码别名宽容性举例:

<?php
  header("Content-Type: text/html; charset=maccyrillic");
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=b.i.g+5"/>
</head>
<body style="font:24px Tahoma;">
字符編碼 --- 
<script>
  document.write((document.charset || document.characterSet).toUpperCase());
</script>
</body>
</html>

上面的动态页面自身的编码为 BIG5,HTTP “Content-Type” 头字段设置了字符编码为 maccyrillic,页面中的 META 元素设置了字符编码为 b.i.g+5。

各浏览器中运行效果如下:

IE6 IE7 IE8 Firefox Chrome Safari Opera
才絪絏 — GB2312 ¶r≤≈љsљX — X-MAC-CYRILLIC 字符編碼 — BIG5

(1)     在 IE6 IE7 IE8 Firefox 中,浏览器无法识别 maccyrillic 这种字符编码别名,所以寻找更低优先级的 META 元素声明的字符编码,但发现也无法识别 b.i.g+5 这种字符编码别名,继而采用了当前语言版本的默认编码 GB2312,与页面自身的字符编码 BIG5 不相符,导致页面中的文字显示异常。
(2)     在 Chrome Safari 中,浏览器将 maccyrillic 识别为合法的 X-MAC-CYRILLIC,则不再理会更低优先级的编码设置,页面采用 X-MAC-CYRILLIC 作为字符的编码,与页面自身的 BIG5 编码不符,导致页面中的文字显示异常。
(3)     在 Opera 中,浏览器无法识别 maccyrillic 这种字符编码别名,所以寻找更低优先级的 META 元素声明的字符编码,将 b.i.g+5 这种字符编码别名识别为 BIG5,刚好与页面自身的 BIG5 字符编码相符,所以页面中的文字显示正常。

出现上述现象的原因主要有三点:

(1)      各浏览器的字符编码别名表不尽相同,对同一种编码下的各种别名支持的宽泛程度不一样。像 maccyrillic 这种别名在 Chrome Safari 可以识别为通用的 X-MAC-CYRILLIC1,但其他浏览器则会将其视作错误的字符编码信息处理。
(2)     各浏览器在匹配页面的字符编码与别名表中的字符编码时,匹配的方式不同。Chrome Safari Opera 会将编码类型的字符串做一个转换,过滤了除英文大小写字符、数字字符之外的字符(isASCIIAlphanumeric)。如 ISO8859_5 会以转换后的 ISO88595 与别名表中的 ISO-8859-5 转换后的 ISO88595 做比较,b.i.g+5 也会转换为 big5 与别名表中的做比较,所以浏览器可以正确识别这些设置的字符编码为浏览器内部的别名。
(3)     各浏览器的默认字符编码不同。
注:各浏览器均可以识别 X-MAC-CYRILLIC 这种通用的字符编码别名。


附录:http 抓包工具 和  测试网页代码

1、http抓包工具fiddler2

2、测试网页代码下载

GBK编码的文件:
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
        <form action="gbk-response/春节" method="get">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
        <form action="gbk-response/春节"  method="post">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
       
        gb2312 响应页
        
    </center>
</body>
</html>
UTF-8编码的文件:
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        <form action="utf-8-response/春节" method="get">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试post提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        <form action="utf-8-response/春节" method="post">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        utf-8 响应页
    </center>
</body>
</html>

查看 网页的请求和响应,可以用 fiddler2 或者 浏览器开发者工具 –> network –>网页–>headers


参考:

http://www.w3help.org/zh-cn/causes/HR9001
https://blog.csdn.net/xiaokuikey/article/details/50517015
http://aub.iteye.com/blog/763117
https://www.cnblogs.com/JemBai/archive/2010/11/10/1873764.html
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
http://kaozjlin.iteye.com/blog/1038802
https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html

网络原理-计算机网络详解-因特网与中国网络发展史

计算机网络与Internet发展历史

  • 1957年:苏联发射了人类第一颗人造地球卫星“ Sputnik”
  • 1958年:美国国防部(DoD)组建了高级研究计划局(ARPA)

1961-1972:早期分组交换原理的提出与应用

  • 1961: Kleinrock – 排队论证实分组交换的有效性
  • 1964: Baran – 分组交换应用于军事网络
  • 1967: ARPA(Advanced
    Research ProjectsAgency)提出ARPAnet构想
  • 1969: 第一个ARPAnet 结点运行:美国国防部委托开发ARPANET,进行联网研究
  • 1972:
    • ARPAnet公开演示
    • 第一个主机-主机协议NCP(Network Control Protocol)
    • 第一个e-mail程序
    • ARPAnet拥有15个结点
  • 1972年:招开计算机通信国际会议,称为ICCC(NCP网络协议)

1972-1980:网络互连,大量新型、私有网络的涌现

  • 1970:在夏威夷构建了ALOHAnet卫星网络
  • 1974: Cerf 与 Kahn – 提出网络互连体系结构
  • 1976: Xerox设计了以太网
  • 70’s后期:
    • 私有网络体系结构: DECnet,SNA, XNA
    • 固定长度分组交换 (ATM 先驱)
  • 1975: ARPAnet移交给美国国防部通信局管理
  • 1979: ARPAnet拥有200结点

1980-1990:新型网络协议与网络的激增

  • 1983年1月1日:所有连入ARPANET的主机向TCP/IP转变,即部署TCP/IP
  • 同年ARPANET分成MILNET和NSFNET
  • 1983: 部署TCP/IP
  • 1982: 定义了smtp电子邮件协议
  • 1983: 定义了DNS
  • 1985: 定义了FTP协议
  • 1988: TCP拥塞控制
  • 1990年,NSFNET彻底代替了ARPANET成为Internet的主干

新型国家级网络:CSnet, BITnet, NSFnet,Minitel(法国)

  • 1986: NSFnet初步形成了一个由骨干网、区域网和校园网组成的三级网络
  • 100,000台主机连接公
    共网络

1990, 2000’s: 商业化, Web, 新应用

  • 1990’s早期: ARPAnet退役
  • 1991: NSF解除NSFnet的商业应用限制(1995年退役),由私营企业经营
  • 1992:因特网协会ISOC成立
  • 1990s后期: Web应用
    • 超文本(hypertext) [Bush1945, Nelson 1960’s]
    • HTML, HTTP: Berners-Lee
    • 1994: Mosaic、 Netscape浏览器
    • 1990’s后期:Web开始商业应用
  • 1990’s后期 – 2000’s:
    • 更多极受欢迎的网络应用:即时消息系统(如QQ),P2P文件共享
  • 网络安全引起重视
  • 网络主机约达50000, 网络用户达1亿以上
  • 网络主干链路带宽达到Gbps

2005-今

  • ~7.5亿主机
    • 智能手机和平板电脑
  • 宽带接入的快速部署
  • 无处不在的高速无线接入快速增长
  • 出现在线社交网络:
    • Facebook: 很快拥有10亿用户
  • 服务提供商 (如Google, Microsoft)创建其自己的专用网络
    • 绕开Internet,提供“即时”接入搜索、 email等服务
    • 电子商务、大学、企业等开始在“云”中运行自己的服务(如, Amazon EC2)

中国最早期拨号访问国际互联网

从1986年开始,国内一些科研单位,通过长途电话拨号到欧洲的一些国家,进行联机数据库检索。不久,利用这些国家与Internet的连接,进行E-mail通信。
从1990年开始,国内的北京市计算机应用研究所、中科院高能物理研究所、电子部华北计算所、电子部石家庄第54研究所等科研单位,先后将自己的计算机与CNPAC(X.25)相连接。同时,利用欧洲国家的计算机作为网关,在X.25网与Internet之间进行转接,使得中国的CNPC科技用户可以与Internet用户进行E-mail通信。
1993年3月,中国科学院高能物理研究所(IHEP)为了支持国外科学家使用北京正负电子对撞机做高能物理实验,开通了一条64kbps国际数据信道,连接北京西郊的中科院高能所和美国斯坦福线性加速器中心(SLAC),运行DECnet协议,虽然不能提供完全的Internet功能,但经SAC机器的转接,可以与Internet进行Email通信。

 


一、建设NCFC时代背景

1989年,中国的改革开放进入了第二个十年。

美国在计算机网络领域已经经过了20年的发展,美国国防部高级研究计划局计算机网APAR网络于1968年就开始组建,APAR网络面向软件、硬件和数据库资源共享,采用分层的网络协议、包交换技术和分布式控制架构建设,这些技术思路影响着今天的网络体系结构。随着APAR网络向大学和商业的开放,信息技术和信息产业成为促进经济发展的引擎,美国国家科学基金会大力组织建设了科研网络环境和超级计算环境,使美国保持着信息科技和产业发展的领先地位。

1989年的圣诞假期,在欧洲核物理中心(CERN)工作的蒂姆·伯纳斯-李继续坚持着万维网梦想,为方便研究人员分享及更新讯息,制作完成了第一个万维网浏览器(同时也是编辑器)和第一个网页服务器。这一开发就是WWW技术的原型,当1993年4月30日CERN宣布万维网对任何人免费开放,并不收取任何费用时,WWW真正引爆了互联网的普及。

相比之下,我国电信市场还没有形成,连私人安装电话仍是颇为困难和奢侈的事情,计算机网络也仅有零星建设,没有互联,科研工作就更谈不上实质的数据资源和计算资源的共享。我国科研人员不得不通过传统邮政和邮电服务与国际同行进行交流,无法及时获取国际科技资料和最新成果,信息手段和时效短板严重影响着科技人员的研究视野、研究选题、计算效率和知识更新,严重制约了国家科技发展。在科研教育单位密集的中关村地区建设高性能的科学计算环境和高速计算机网络,并与国际Internet互联的需求十分迫切。

二、面向科研与教育信息化的NCFC项目建设

在信息化时代的大背景和我国科技与教育工作者对建设计算机网络的迫切需求形势下,当时的国家计委将NCFC列入“世界银行贷款重点学科发展项目”,并于1989年8月组织项目招标,确定中国科学院负责承担工程项目的建设任务。1989年10月,中国科学院按照国家计委的要求组建了NCFC管理委员会,时任中国科学院副院长的胡启恒同志担任管委会主任,国家计委、国家自然科学基金委、国家教委、清华大学和北京大学选派有关领导担任管委会成员,于1990年4月完成了工程建设的各项准备工作。

世界银行对NCFC项目内容界定为主干网,不包括大学和研究机构内部的园区网络,其建设目的是在中关村地区建立一个示范性的超级计算中心,并用高速网络将该地区的中国科学院院网、北京大学校园网、清华大学校园网与NCFC互联。使该地区的科技网人员能使用高速网络并通过网络使用超级计算机资源。

中国科学院院网(CASnet)、北京大学校园网(PUnet)和清华大学校园网(TUnet)则分别由三个单位各自出资、各自建设,国家计委对三个园区级网络进行了资金补助。

在世界银行、国家计委的支持下,该建设项目完成了NCFC主干网建设和三个单位园区网络建设,覆盖了中国科学院中关村地区附件的40多个研究所;部署和运行了64亿次浮点计算能力的超级计算机资源;更重要的是,NCFC项目衍生了计划外的重大成果——1994年4月20日,在国家计委的补助资金支持下,一条64K的国际卫星专线从中科院计算机网络中心通过美国Sprint公司连入Internet,首次实现了中国与Internet的全功能连接。从此中国被国际上正式承认为第77个真正拥有全功能Internet的国家。

在当时的历史条件下,NCFC工程解决了若干技术问题和政策问题,为后来的互联网络环境建设奠定了基础。

NCFC工程确定了以TCP/IP协议为主的技术路线。当时我国的计算机,较为常见的网络协议体系有TCP/IP、DECnet和OSI。要把使用不同网络协议体系的计算机互联,必须在网络通信协议上达成共识才能实现计算机间的信息互通。中科院计算机网络中心总体组坚持实用、开放的原则,明确了以TCP/IP协议为主,以OSI为发展方向,兼顾现有的其他协议的技术路线。这一设计思路,为NCFC网络顺利接入Internet做好了水到渠成的技术准备。

NCFC工程首次使用光纤线路建设计算机主干网络,通过网桥将中国科学院院网(CASnet)、北京大学校园网(PUnet)和清华大学校园网(TUnet)进行联网。综合多样性的技术手段和装备解决网络接入问题,对于校园和城域网络分别采用光纤和微波手段解决;对于长途网络采用X.25、卫星通信等通信手段接入;对于个人用户采用拨号方式接入,为用户接入工作提出了技术先进、综合、全面的解决方案。

=========================================================

CASnet 历史

CASnet 是中国科学院的全国性网络建设工程。该工程分为两部分,一部分为分院区域网络工程,另一部分是用远程信道将各分院区域网络及零星分布在其他城市的研究所互联到NFC网络中心的广域网工程。
CASnet的远程连接,又分为两期工程,第一期用X.25信道将全国3个城市连到北京,第二期以高速卫星信道代替X.25信道,并把更多的城市连到北京。1995年底,CASnet完成了将12个分院区域网及其他城市的研究所连到北京的广域网工程,连接了24个城市(包括北京)。作为第一步,早期的连接通过CHINAPAC实现,速率从9.6kbps到64kbps不等。目前,已将各主要分院的地区网络,用高速的卫星信道连到北京。零星分布的研究所仍暂时沿用ChinaPAC信道,不久将也用卫星连到北京。

=========================================================

技术人员很快发现,NCFC工程采用的网桥技术解决了CASnet、PUnet和TUnet之间的光纤联网,但是无法解决网络中广播风暴的影响,路由器的引入和部署变得十分必要和迫切。由于当时巴黎统筹委员会的限制使我国无法进口路由器,因此中科院计算机网络中心组织了技术人员自行开发路由器,并在NCFC主干网和中科院院网中部署应用,为提升网络可靠性发挥出关键作用,从而使NCFC与当时国外先进的网络同样可以高可靠性运行。

三、突破壁垒,实现与国际互联网全功能接入

Internet对当时的中国来说还是一个新生事物,和传统的电信业务有很大的不同。为了取得国家电信部门在国际专线租用方面的批准和支持,NCFC管委会和邮电部进行了多次的沟通,终于取得了国际专线资源支持。

当NCFC启动与国际联网的时候,美国方面处于政治和安全方面的原因,不接纳我国接入Internet。1993年中科院计算机网络中心钱华林等同志赴美与sprint公司商谈落实了与Internet联网方案,并根据公共网络服务要求,制定了NCFC“准用政策”(AUP),在报经国务院邹家华副总理等有关领导同意后,NCFC管委会主任胡启恒同志在参加美国华盛顿举行的中美科技合作联合委员会第六次会议期间,就NCFC与Internet联网问题,与NSF官员进行商谈,美国同意遵守NSFNET(美国科研与教育骨干网)和NCFC“准用政策”的条件下,实现NCFC与美国NSF主干网连接。随后,在1994年3月开通了国际卫星专线,4月中美两侧路由器开通,同时,在中科院计算机网络中心建立了代表Internet中国顶级域名的.cn服务器、邮件服务器、文件服务器等一系列网络服务器。

  

  中国第一台.CN域名服务器

  随后,中科院计算机网络中心进一步完成了在InterNIC的注册,建立了与国际IneterNIC和APNIC规范的业务联系,并于1994年10月建成了NIC(网络信息中心)和NOC(网络运行中心),对国内外用户服务。自此,中国互联网系统地、整体地展现于世界。

四、NCFC建设的后续演进和发展

NCFC项目圆满完成了网络建设目标和超级计算机建设目标,并实现了与Internet联网。

中国科学院在NCFC和CASnet的基础上,以服务科研信息化为定位和发展方向,建设中国科技网(CSTNET)。经过不断的建设,中国科技网已成为承载国家超级计算资源、科学数据资源、服务国家科技创新的科研信息化技术设施,取得了令人瞩目的应用成果和社会效益。中国科技网具有向全国提供IPv4和IPv6双栈接入的7×24服务能力,提供电子邮件、视频会议、科研协同环境、网络管理、网络安全等丰富的互联网服务产品,在我国嫦娥工程、国际联合天文观测、高能物理海量数据传输、大型科研装置数据传输、野外台站联网观测和应急救灾等方面提供高性能网络支撑和技术支持。

=======================================================

CSTNet历史

CSTNet 是以中国科学院的NCFC及CASnet为基础,连接了中国科学院以外的一批中国科技单位而构成的网络。目前接入CSTNet的单位有农业、林业、医学、电力、地震、气象、铁道、电子、航空航天、环境保护等近30个科研部门及国家自然科学基金委、国家专利局等科技管理部门。目前,CSTNet有2Mbps的信道连到美国,64Kbps的信道连到法国,64Kbps的信道连到日本。【很早之前的历史资料了】
CSTNet为非盈利、公益性的网络,主要为科技用户、科技管理部门及与科技有关的政府部门服务。

=======================================================

由NCFC发展起来的中国互联网络信息中心(CNNIC)行使着国家互联网络信息中心的职责。经过长期的建设和开拓,CNNIC已经成为国家网络基础资源的运行管理和服务机构、国家网络基础资源的技术研发和安全中心、互联网发展研究力量、互联网开放合作和技术交流平台,促进科研成果转化和孵化,服务中国互联网事业发展。

更重要的是,NCFC项目的建成吹响了中国互联网时代的号角。NCFC项目1998年获得中国科学院科技进步特等奖;国家统计局将“国家计算与网络设施(NCFC)实现国内国际联网”列为1994年度国家重大科技成就之一;人民日报将“中国国家计算与网络设施(NCFC)实现国内国际联网成功”列为1994年中国十大科技新闻之一。作为一个示范性网络,NCFC建设在中国掀起了一个发展Internet的热潮,国内先后建设成了中国科技网CSTNET、中国公用计算机互联网CHINANET、中国教育和科研计算机网CERNET和中国金桥信息网CHINAGBN,也就是早期的中国四大互联网络。

今天,生活在互联网时代的我们重新梳理20年前的历史,不仅要向当时的建设者们致以崇高的敬意,更要有同样的勇气和智慧迎接互联网时代的新挑战。作为中国互联网发源地的中科院计算机网络信息中心,现今已经承担起了历史赋予的新使命:致力于中国科学院科研信息化和管理信息化的研发、支撑与服务。如果您到中科院计算机网络信息中心来,依然可以看到当初接入Internet的机房,也可看到我国第一台路由器、第一台CN域名服务器。面对这些历史物件,再转身看看中科院信息化基础设施机房里成排的服务器、大型的高性能计算机,我们无法不慨叹信息时代发展的“火箭速度”,并且暗自庆幸我们正置身于这个“一切皆有可能”的信息社会。

 

 


参考:

http://www.cas.cn/kxcb/kpwz/201404/t20140419_4093686.shtml

https://baike.baidu.com/item/CSTNET

网络原理-计算机网络详解-传输层之TCP协议

一、TCP 协议的作用

互联网由TCP/IP协议族构成。TCP 只是其中的一层,有着自己的分工。

(图片说明:TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议。)

最底层的以太网协议(Ethernet)规定了电子信号如何组成数据帧(frame),解决了子网内部的点对点通信。

(图片说明:以太网协议解决了局域网的点对点通信。)

但是,以太网协议不能解决多个局域网如何互通,这由 IP 协议解决。

(图片说明:IP 协议可以连接多个局域网。)

IP 协议定义了一套自己的地址规则,称为 IP 地址。它实现了路由功能,允许某个局域网的 A 主机,向另一个局域网的 B 主机发送消息。

(图片说明:路由器就是基于 IP 协议。局域网之间要靠路由器连接。)

路由的原理很简单。市场上所有的路由器,背后都有很多网口,要接入多根网线。路由器内部有一张路由表,规定了 A 段 IP 地址走出口一,B 段地址走出口二,……通过这套”指路牌”,实现了数据包的转发。

(图片说明:本机的路由表注明了不同 IP 目的地的数据包,要发送到哪一个网口(interface)。)

IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议。

简单说,TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包。

二、TCP 报文段的大小

MTU最大传输单元,这个最大传输单元实际上和链路层协议有着密切的关系,EthernetII帧的结构DMAC+SMAC+Type+Data+CRC由于以太网传输电气方面的限制,每个以太网帧都有最小的大小64bytes最大不能超过1518bytes,对于小于或者大于这个限制的以太网帧我们都可以视之为错误的数据帧,一般的以太网转发设备会丢弃这些数据帧。

由于以太网EthernetII最大的数据帧是1518Bytes这样,刨去以太网帧的帧头(DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾CRC校验部分4Bytes那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值我们就把它称之为MTU。

以太网数据包(packet)由于环境限制,最大值是固定的,最初协议是1518字节,后来新协议是1522字节。但是MTU 仍是 1500Bytes。

为了允许一些使用以太II版本的数据报和一些使用802.3封装的最初版本的数据包能够在同一个以太网段使用,以太类型值必须大于等于1536(0x0600)。这个值比802.3数据包的最大长度1500byte (0x05DC)要更大。因此如果这个字段的值大于等于1536,则这个帧是以太II帧,而那个字段是类型字段。否则(小于1500而大于46字节),他是一个IEEE 802.3帧,而那个字段是长度字段。1500~1536(不包含)的数值未定义。因为网络环境 MTU选择是1500【即以太网帧最大负载是1500字节】,所以我们上网用的以太网帧 应该是 802.3帧。

来自维基百科

802.3 以太网帧结构
前导码 帧开始符 MAC 目标地址 MAC 源地址 802.1Q 标签 (可选) 以太类型 负载 冗余校验 帧间距
10101010 7个octet 10101011 1个octet 6 octets 6 octets (4 octets) 2 octets 46–1500 octets 4 octets 12 octets
64–1522 octets
72–1530 octets
84–1542 octets

IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。

(图片说明:IP 数据包在以太网数据包里面,TCP 报文段在 IP 数据包里面。)

TCP 报文段在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 报文段的最大负载是 1480 – 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。

因此,一条1500字节的信息需要两个 TCP报文段。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 报文段里面,而不是分成多个,这样就提高了速度。

(图片说明:以太网数据帧的负载是1500字节,TCP 报文段的负载在1400字节左右。)

三、TCP报文段的格式

图释:

抓包过后才发现,ip数据包首部没有添加可选项,就直接装载 tcp 数据了。

各个段位说明:

  • 源端口和目的端口:  各占 2 字节.端口是传输层与应用层的服务接口.传输层的复用和分用功能都要通过端口才能实现
  • 序号:  占 4 字节。用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
  • 确认号:  占 4 字节,期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
  • 数据偏移/首部长度:  占 4 位,指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。“数据偏移”的单位是 32 位字(以 4 字节为计算单位)
  • 保留:  占 6 位,保留为今后使用,但目前应置为 0
  • 紧急URG:  当 URG=1 时,表明紧急指针字段有效.它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)
  • 确认ACK:  只有当 ACK=1 时确认号字段才有效。当 ACK=0 时,确认号无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
  • PSH(PuSH):  接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付
  • RST (ReSeT):  当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接
  • 同步 SYN:  在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
  • 终止 FIN:  用来释放一个连接。FIN=1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接
  • 窗口:      窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
  • 检验和:  占 2 字节.检验和字段检验的范围包括首部和数据这两部分.在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部
  • 紧急指针:  占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)
  • 选项:  长度可变.TCP 最初只规定了一种选项,即最大报文段长度 MSS.MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节.” [MSS(Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度.数据字段加上 TCP 首部才等于整个的 TCP 报文段]
  • 填充:  这是为了使整个首部长度是 4 字节的整数倍
  • 其他选项:
    • 窗口扩大:  占 3 字节,其中有一个字节表示移位值 S.新的窗口值等于TCP 首部中的窗口位数增大到(16 + S),相当于把窗口值向左移动 S 位后获得实际的窗口大小
    • 时间戳:  占10 字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节)
    • 选择确认:  接收方收到了和前面的字节流不连续的两2字节.如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据

四、TCP报文段特点

数据单位

TCP 传送的数据单位协议是 TCP 报文段(segment)

特点

TCP 是面向连接的传输层协议
每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一)
TCP 提供可靠交付的服务
TCP 提供全双工通信
面向字节流

注意

TCP 对应用进程一次把多长的报文发送到TCP 的缓存中是不关心的
TCP 根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节(UDP 发送的报文长度是应用进程给出的)
TCP 可把太长的数据块划分短一些再传送.TCP 也可等待积累有足够多的字节后再构成报文段发送出去
每一条 TCP 连接有两个端点
TCP 连接的端点不是主机,不是主机的IP 地址,不是应用进程,也不是传输层的协议端口.TCP 连接的端点叫做套接字(socket)或插口


五、TCP报文段的编号(SEQ)

一个报文段1400字节,那么一次性发送大量数据,就必须分成多个报文段(segment)。比如,一个 10MB 的文件,需要发送7100多个包。

发送的时候,TCP 协议为每个报文段编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢分段,也可以知道丢失的是哪一个报文段。

第一个报文段的编号是一个随机数。为了便于理解,这里就把它称为1号报文段。假定这个报文段的负载长度是100字节,那么可以推算出下一个报文段的编号应该是101。这就是说,每个报文段都可以得到两个编号:自身的编号,以及下一个报文段的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。

(图片说明:当前分段的编号是45943,下一个数据分段的编号是46183,由此可知,这个分段的负载是240字节。)

六、TCP 报文段的组装

收到 TCP 报文段以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 报文段。

对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 报文段里面,有自己的格式(比如 HTTP 协议)。

TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 报文段,将它们按照顺序组装好,一个分段都不少。

操作系统不会去处理 TCP 报文段里面的数据。一旦组装好 TCP 报文段,就把它们转交给应用程序。TCP 报文段里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。

(图片说明:系统根据 TCP 报文段里面的端口,将组装好的数据转交给相应的应用程序。上图中,21端口是 FTP 服务器,25端口是 SMTP 服务,80端口是 Web 服务器。)

应用程序收到组装好的原始数据,以浏览器为例,就会根据 HTTP 协议的Content-Length字段正确读出一段段的数据。这也意味着,一次 TCP 通信可以包括多个 HTTP 通信。


七、建立连接和断开连接过程

报文段的发送时机

TCP 维持一个变量,它等于最大报文段长度 MSS.只要缓存中存放的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去
由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送(push)操作
发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过 MSS)发送出去

发送TCP请求客户端

三次连接四次挥手

三个阶段:

  • 连接建立:
    • 图释:

    • 步骤:
      • A 的 TCP 向 B 发出连接请求报文段,其首部中的同步位 SYN = 1,并选择序号 seq = x,表明传送数据时的第一个数据字节的序号是 x
      • B 的 TCP 收到连接请求报文段后,如同意,则发回确认(B 在确认报文段中应使 SYN = 1,使 ACK = 1,其确认号ack = x﹢1,自己选择的序号 seq = y)
      • A 收到此报文段后向 B 给出确认,其 ACK = 1,确认号 ack = y﹢1(A 的 TCP 通知上层应用进程,连接已经建立,B 的 TCP 收到主机 A 的确认后,也通知其上层应用进程:TCP 连接已经建立)
  • 数据传送
  • 连接释放:
    • 图释:

    • 步骤:
      • 数据传输结束后,通信的双方都可释放连接.现在 A 的应用进程先向其 TCP 发出连接释放报文段,并停止再发送数据,主动关闭 TCP 连接(A 把连接释放报文段首部的 FIN = 1,其序号seq = u,等待 B 的确认)
      • B 发出确认,确认号 ack = u+1,而这个报文段自己的序号 seq = v(TCP 服务器进程通知高层应用进程.从 A 到 B 这个方向的连接就释放了,TCP 连接处于半关闭状态.B 若发送数据,A 仍要接收)
      • 若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接
      • A 收到连接释放报文段后,必须发出确认,在确认报文段中 ACK = 1,确认号 ack=w﹢1,自己的序号 seq = u + 1
    • 注意:

TCP 连接必须经过时间 2MSL 后才真正释放掉(2MSL 的时间的用意 — 为了保证 A 发送的最后一个 ACK 报文段能够到达 B.防止 “已失效的连接请求报文段”出现在本连接中.A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段,都从网络中消失.这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段)

    • 发现丢失确认时候的处理:

三个问题:

  • 要使每一方能够确知对方的存在
  • 要允许双方协商一些参数(如最大报文段长度,最大窗口大小,服务质量等)
  • 能够对运输实体资源(如缓存大小,连接表中的项目等)进行分配

四次挥手 :

A:我这边传完了,你那边收接收完了没?B:我这边接收完了。A就不传输信息了。

B:我这边传完了,你那边收接收完了没?A:我这边接收完了。B就不传输信息了。


七、滑动窗口(发送窗口-接收窗口-拥塞窗口)

滑动窗口

图释:

特点:

  • 以字节为单位的滑动窗口
  • A 的发送窗口并不总是和 B 的接收窗口一样大(因为有一定的时间滞后)

要求:

  • TCP 标准没有规定对不按序到达的数据应如何处理.通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程
  • TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销

具体实现: 


七、拥塞避免

服务器发送数据包,当然越快越好,最好一次性全发出去。但是,发得太快,就有可能丢包。带宽小、路由器过热、缓存溢出等许多因素都会导致丢包。线路不好的话,发得越快,丢得越多。

最理想的状态是,在线路允许的情况下,达到最高速率。但是我们怎么知道,对方线路的理想速率是多少呢?

1、慢开始算法

TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动(slow start)机制。开始的时候,发送得较慢,然后根据丢报文段的情况,调整速率:如果不丢报文段,就加快发送速度;如果丢报文段,就降低发送速度。

Linux 内核里面设定了(常量TCP_INIT_CWND),刚开始通信的时候,发送方一次性发送10个报文段,即”发送窗口”的大小为10。然后停下来,等待接收方的确认,再继续发送。

默认情况下,接收方每收到两个 TCP 报文段,就要发送一个确认消息。”确认”的英语是 acknowledgement,所以这个确认消息就简称 ACK。

ACK 携带两个信息。

  • 期待要收到下一个数据包的编号
  • 接收方的接收窗口的剩余容量

发送方有了这两个信息,再加上自己已经发出的报文段的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。这被称为”发送窗口”,这个窗口的大小是可变的。

(图片说明:每个 ACK 都带有下一个报文段的编号,以及接收窗口的剩余容量。双方都会发送 ACK。)

注意,由于 TCP 通信是双向的,所以双方都需要发送 ACK。两方的窗口大小,很可能是不一样的。而且 ACK 只是很简单的几个字段,通常与数据合并在一个报文段里面发送。

(图片说明:上图一共4次通信。第一次通信,A 主机发给B 主机的报文段编号是1,长度是100字节,因此第二次通信 B 主机的 ACK 编号是 1 + 100 = 101,第三次通信 A 主机的报文段编号也是 101。同理,第二次通信 B 主机发给 A 主机的报文段编号是1,长度是200字节,因此第三次通信 A 主机的 ACK 是201,第四次通信 B 主机的报文段编号也是201。)

即使对于带宽很大、线路很好的连接,TCP 也总是从10个报文段开始慢慢试,过了一段时间以后,才达到最高的传输速率。这就是 TCP 的慢启动。


慢开始算法:

  • 在主机刚刚开始发送报文段时可先设置拥塞窗口 cwnd = 1,即设置为一个最大报文段 MSS 的数值
  • 在每收到一个对新的报文段的确认后,将拥塞窗口加 1,即增加一个 MSS 的数值
  • 使用慢开始算法后,每经过一个传输轮次(往返时间 RTT),拥塞窗口 cwnd 就加倍

2、拥塞避免算法:

拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,使拥塞窗口 cwnd 按线性规律缓慢增长

3、慢开始门限 ssthresh 的用法:

  • 当 cwnd < ssthresh 时,使用慢开始算法
  • 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法
  • 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法

4、网络出现拥塞时(其根据就是没有按时收到确认):

  • 就要把慢开始门限 ssthresh 设置为出现拥塞时的发送方窗口值的一半(但不能小于2)
  • 然后把拥塞窗口 cwnd 重新设置为 1.执行慢开始算法

八、拥塞处理

拥塞窗口:

含义:

拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化.发送方让自己的发送窗口等于拥塞窗口.如再考虑到接收方的接收能力,则发送窗口还可能小于拥塞窗口

发送方控制拥塞窗口的原则:

只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去.但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数

乘法减小:

是指不论在慢开始阶段还是拥塞避免阶段,只要出现一次超时(即出现一次网络拥塞),就把慢开始门限值 ssthresh 设置为当前的拥塞窗口值乘以 0.5

加法增大:

是指执行拥塞避免算法后,在收到对所有报文段的确认后(即经过一个往返时间),就把拥塞窗口 cwnd增加一个 MSS 大小,使拥塞窗口缓慢增大,以防止网络过早出现拥塞

快重传:

每一个TCP报文段都带有下一个报文段的编号。如果下一个报文段没有收到,那么 ACK 的编号就不会发生变化。

举例来说,现在收到了4号报文段,但是没有收到5号报文段。ACK 就会记录,期待收到5号报文段。过了一段时间,5号报文段收到了,那么下一轮 ACK 会更新编号。如果5号报文段还是没收到,但是收到了6号报文段或7号报文段,那么 ACK 里面的编号不会变化,总是显示5号报文段。这会导致大量重复内容的 ACK。

如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢失报文段,即5号报文段遗失了,从而再次发送这个报文段。通过这种机制,TCP 保证了不会有报文段丢失。

(图片说明:Host B 没有收到100号报文段,会连续发出相同的 ACK,触发 Host A 重发100号报文段。)

快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认.这样做可以让发送方及早知道有报文段没有到达接收方,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段

快恢复:

当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢开始门限 ssthresh 减半.但接下去不执行慢开始算法

发送窗口的上限值:

发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个,即应按以下公式确定:
发送窗口的上限值 Min [rwnd, cwnd]

    • 当 rwnd < cwnd 时,是接收方的接收能力限制发送窗口的最大值
    • 当 cwnd < rwnd 时,则是网络的拥塞限制发送窗口的最大值

九、报文段的遗失处理-自动重传

TCP 协议可以保证数据通信的完整性,这是怎么做到的?

自动重传请求ARQ

定义:

可靠传输协议常称为自动重传请求ARQ (Automatic Repeat reQuest)

累积确认:

  • 定义:  接收方一般采用累积确认的方式.即不必对收到的分组逐个发送确认,而是对按序到达的最后一个分组发送确认,这样就表示:到这个分组为止的所有分组都已正确收到了
  • 优点:  容易实现,即使确认丢失也不必重传
  • 缺点:  不能向发送方反映出接收方已经正确收到的所有分组的信息

Go-back-N(回退N):

如果发送方发送了前 5 个分组,而中间的第 3 个分组丢失了.这时接收方只能对前两个分组发出确认.发送方无法知道后面三个分组的下落,而只好把后面的三个分组都再重传一次

具体实现

说明:

  • TCP 连接的每一端都必须设有两个窗口 一个发送窗口和一个接收窗口
  • TCP 可靠传输机制用字节的序号进行控制.TCP 所有的确认都是基于序号而不是基于报文段
  • TCP 两端的四个窗口经常处于动态变化之中
  • TCP连接的往返时间 RTT 也不是固定不变的.需要使用特定的算法估算较为合理的重传时间

图释:

确认丢失和确认迟到

超时重传时间选择

具体实现:

TCP 每发送一个报文段,就对这个报文段设置一次计时器.只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段

加权平均往返时间:

做法:

TCP 保留了 RTT 的一个加权平均往返时间 RTTS(这又称为平滑的往返时间),第一次测量到 RTT 样本时,RTTS 值就取为所测量到的 RTT 样本值.以后每测量到一个新的 RTT 样本,就按下式重新计算一次 RTTS:

公式:

新的 RTTS = ( 1 – α)×(旧的 RTTS)+α(新的 RTT 样本)

说明:

式中,0 ≤ α< 1.若α很接近于零,表示 RTT 值更新较慢若选择 α 接近于1,则表示 RTT 值更新较快
RFC 2988 推荐的 α 值为 1/8,即 0.125

超时重传时间RTO:

RTO 应略大于上面得出的加权平均往返时间 RTTS.
RFC 2988 建议使用下式计算 RTO:

RTO=RTTS + 4×RTTD

RTTD 是 RTT 的偏差的加权平均值
RFC 2988 建议这样计算 RTTD.第一次测量时,RTTD 值取为测量到的 RTT 样本值的一半.在以后的测量中,则使用下式计算加权平均的 RTTD:

新的 RTTD = (1-β)×(旧的RTTD)+β×|RTTS﹣新的 RTT 样本|

β是个小于 1 的系数,其推荐值是 1/4,即 0.25
在计算平均往返时间 RTT 时,只要报文段重传了,就不采用其往返时间样本

修正的Karn算法:

报文段每重传一次,就把 RTO 增大一些:

新的 RTO= γ×(旧的 RTO)

系数γ 的典型值是 2
当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延 RTT 和超时重传时间 RTO 的数值

持续计时器

  • TCP 为每一个连接设有一个持续计时器
  • 只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器
  • 若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值
  • 若窗口仍然是零,则收到这个报文段的一方就重新设置持续计时器
  • 若窗口不是零,则死锁的僵局就可以打破了

九、缓存控制

发送缓存

发送缓存用来暂时存放:

  • 发送应用程序传送给发送方 TCP 准备发送的数据
  • TCP 已发送出但尚未收到确认的数据

图释:

接收缓存

接收缓存用来暂时存放:

  • 按序到达的、但尚未被接收应用程序读取的数据;
  • 不按序到达的数据

 图释:

 

 


参考:

http://www.cnblogs.com/kzang/articles/2582957.html

http://www.ruanyifeng.com/blog/2017/06/tcp-protocol.html

https://blog.csdn.net/u014222687/article/details/55002177

网络原理-计算机网络详解-传输层概述

我们这里只讲TCP/IP 网络模型的传输层协议,传输层协议有TCP协议和UDP协议。

一、传输层

传输层的作用

TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。

传输层定义

IP首部有一个协议字段,用来标识网络层(IP)的上一层所采用的是哪一种传输层协议。根据这个字段的协议号,就可以识别IP传输的数据部分究竟是TCP的内容还是UDP的内容。

同样,传输层的TCP和UDP,为了识别自己所传输的数据部分究竟应该发给哪个应用,也设定了这样一个编号。

在TCP/IP通信中需要指定“应用程序”。而传输层必须指出这个具体的程序,为了实现这一功能,使用端口号这样一种识别码。根据端口号可以识别在传输层上一层的应用层中所要进行处理的具体程序。

通信处理

服务端程序在UNIX系统当中叫做守护进程。例如HTTP的服务端程序是httpd(HTTP守护进程),而ssh的服务端程序是sshd(SSH守护进程)。UNIX中并不需要将这些守护进程逐个启动,而是启动一个可以代表它们接收客户端请求的inetd(互联网守护进程)服务程序即可。它是一种超级守护进程。该超级守护进程收到客户端请求以后会创建(fork)新的进程并转换(exec)为sshd等各个守护进程。

确认一个请求究竟发给的是哪个服务端(守护进程),可以通过所收到数据包的目标端口号轻松识别。当收到TCP的建立连接请求时,如果目标端口为22,则转给sshd,如果是80则转给httd。然后,这些守护进程会继续对该连接上的通信传输进行处理。

传输协议TCP、UDP通过接收数据中的目标端口识别目标处理程序。以下图为例,传输协议的数据将被传递给HTTP、TELNET以及FTP等应用层协议。

HTTP连接请求:

通过IP地址、端口号、协议号进行通信识别

TCP/IP或UDP/IP通信中通常采用5个信息来识别一个通信。它们是“源IP地址”、“目标IP地址”、“协议号”、“源端口号”、“目标端口号”。只要其中某一项不同,则被认为是其他通信。

 

二、SOCKET的用处

Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

大白话:我们编写的应用层协议:http 、ftp、smtp 都是调用socket 接口实现的。

三、TCP与UDP简介

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。我们来看看这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

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

四、TCP与UDP的区别

TCP与UDP基本区别
1.基于连接与无连接
2.TCP要求系统资源较多,UDP较少;
3.UDP程序结构较简单
4.流模式(TCP)与数据报模式(UDP);
5.TCP保证数据正确性,UDP可能丢包
6.TCP保证数据顺序,UDP不保证

UDP应用场景:
1.面向数据报方式
2.网络数据大多为短消息
3.拥有大量Client
4.对数据安全性无特殊要求
5.网络负担非常重,但对响应速度要求高

具体编程时的区别
1.socket()的参数不同
2.UDP Server不需要调用listen和accept
3.UDP收发数据用sendto/recvfrom函数
4.TCP:地址信息在connect/accept时确定
5.UDP:在sendto/recvfrom函数中每次均 需指定地址信息
6.UDP:shutdown函数无效

编程区别
通常我们在说到网络编程时默认是指TCP编程,即用前面提到的socket函数创建一个socket用于TCP通讯,函数参数我们通常填为SOCK_STREAM。即socket(PF_INET, SOCK_STREAM, 0),这表示建立一个socket用于流式网络通讯。
SOCK_STREAM这种的特点是面向连接的,即每次收发数据之前必须通过connect建立连接,也是双向的,即任何一方都可以收发数据,协议本身提供了一些保障机制保证它是可靠的、有序的,即每个包按照发送的顺序到达接收方。

而SOCK_DGRAM这种是User Datagram Protocol协议的网络通讯,它是无连接的,不可靠的,因为通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据。任何一方建立一个socket以后就可以用sendto发送数据,也可以用recvfrom接收数据。根本不关心对方是否存在,是否发送了数据。它的特点是通讯速度比较快。大家都知道TCP是要经过三次握手的,而UDP没有。

基于上述不同,UDP和TCP编程步骤也有些不同,如下:

TCP: 
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;

TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;

UDP:
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;

UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;

TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。

UDP补充:
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。

TCP补充:
TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。

TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保   证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

 


参考:
https://blog.csdn.net/Li_Ning_/article/details/52117463
https://blog.csdn.net/sinat_37138973/article/details/72822229

网络原理-计算机网络详解-传输层之UDP协议

数据单位

UDP 传送的数据单位协议是 UDP 报文或用户数据报


特点

UDP 是无连接的,即发送数据之前不需要建立连接

UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制

UDP 是面向报文的.UDP 没有拥塞控制,很适合多媒体通信的要求

UDP 支持一对一、一对多、多对一和多对多的交互通信

UDP 的首部开销小,只有 8 个字节


具体实现

发送方 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层.UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界

应用层交给 UDP 多长的报文,UDP 就照样发送,即一次发送一个报文

接收方 UDP 对 IP 层交上来的 UDP 用户数据报,在去除首部后就原封不动地交付上层的应用进程,一次交付一个完整的报文


要求

应用程序必须选择合适大小的报文


UDP首部格式

说明:

  • 户数据报 UDP 有两个字段:数据字段和首部字段.首部字段有 8 个字节,由 4 个字段组成,每个字段都是两个字节
  • 在计算检验和时,临时把“伪首部”和UDP用户数据报连接在一起,伪首部仅仅是为了计算检验和

图释:

首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。【也就是说伪首部不会添加到数据包里面】

抓包过后才发现,ip数据包首部没有添加可选项,就直接装载 udp数据了。


发送UDP请求的客户端图释

参考:

http://www.cnblogs.com/kzang/articles/2582879.html

网络原理-家用路由器常识介绍

常见的家用路由器:如tp-link  以此举例

大白话:家用路由器 本质是   1个 三层路由器   +  1个 无线交换机。
三层路由器 一端连外网,另一端连 无线交换机。
无线交换机,一端连三层交换机,剩余端口 连 各种上网主机。

1、路由器的LAN口工作在第二层协议还是第三层协议上?

这个lan只是低端路由器,你说tp-link那种,简单说LAN是二次,WAN是三层,但一般说路由器,所有口都是三层的。

发送到网关的数据包转发出去是从LAN口出去的还是从WAN口出去的?
内部的数据就是LAN,上外网就是WAN,看情况的。

=======================================================

企业路由器通常有多个(两个以上)带有公网ip地址的端口 ,当路由器从内网收到一个数据包时, 他要做出决定 从哪个端口发送出去这个数据包到外网, 当他决定并且转发出去这个数据包时 他就完成了一次路由。
而对于家用路由器来说 ,它通常指带有一个公网ip【WAN口公网IP】(LAN口 是两层交换机 模式, 内网IP  互相 访问 是 mac 层的 交换,不算路由)。当内网数据从LAN口 进入路由器内部,从 WAN口 出去时,内网数据包进行了一次路由 到外网去。因为只有 一个 WAN口即一个公网IP,所以ip数据包 发送到外网是 一次没有选择的 路由选择。

2、家用路由器的路由协议

家用路由器是中低端路由器,采用的是静态路由协议。

3、家用路由器上面的wifi功能

wifi是 802.11 协议标准,包括 物理层和数据链路层,无线上网的wifi 信号最终会进入 路由器里面,这个时候,参考路由器的LAN端口。wifi 和LAN 是属于同一个网段 的,我这里推测  wifi 在 该子网内 互相访问时,只用到了路由器的 两层交换机功能。

当wifi 经WAN端口,访问外网时,才算经过了三层路由。【毕竟 子网 和 外网 是两个网段了,必须要在 三层上 修改IP地址,在外网看来,路由器的子网是透明的,只有一个路由器IP地址。】(所以难怪我在traceroute 内网 ip时,直连内网IP,中途没有经过路由,所以家用路由器的内网之间访问只需两层交换机就行)

网络原理-计算机网络详解-mac表、arp表、路由表

大白话:记住内网 也可能包括了多个子网,所以用同一网段表示更精确。

MAC地址表(FDB表,2层):MAC地址——》交换机接口地址;
【只存在于交换机,用于帮助交换机指明mac帧应从哪个接口发出去】

补充说明:现在的家用路由器 ,是三层的,也有二层的交换机功能。


ARP缓存表(2.5层):IP地址——》MAC地址;
【存在于主机和路由器中,用于帮助主机或路由器,查询对应IP的mac地址,以便组建mac帧发送出去】

同一网段内通信,原mac地址和目标mac地址 与 原ip地址和目标ip地址一一对应 。不同网段通信,mac地址与ip地址不关联,mac地址从本处地址指向下一个接力地址这样可以区分,信息是从网关发来的,还是从另一个网段发来的。

从这里可以看出,arp缓存表只缓存本机网段内的ip与mac地址的关系。

arp请求是广播,因为不知道目标的mac地址,所以只能进行广播。广播只能在同网段内进行。这里广播的意思是,交换机收到广播请求时,会对其他剩余交换机端口全部转发【至于以前的hub时代,每台主机收到的mac帧,会查看是不是和自己的mac帧相同,或者收到了arp广播帧,这样会继续拆开数据包,检查IP数据,否则直接丢弃mac帧】

=======================================================

理论上可以设置两个不同的网段,然后用交换机连接。两个网段都可以指定一个(虚假的)网关,两个网段内的ip都设置静态ip。【这个时候,按照操作系统的逻辑,查路由表若目标IP属于同一网段内(不同网段只会先发给网关,mac帧目标地址是网关地址,但是目标IP不是网关IP而是真正的目标主机ip地址),查询arp缓存表,没有就发送arp请求,这个时候arp请求会经过交换机,交换机看到广播 mac帧 会 进行广播,这个时候两个网段都有arp请求了。但是arp响应还是在同一个网段内出现的。两个网段后面的mac帧信息,看交换机的mac帧–端口表,进行分发了。如果把交换机换成路由器,路由器收到arp广播,查看ip地址是不是自己的 ,是就发个arp响应,不是就丢弃,不会转发arp广播,因为同网段内有交换机 连接着其他主机,负责将arp广播到其他主机。唯一的问题是路由器收到这个错网段arp请求,应该是直接丢弃吧,应该不会出现路由器报警神马的,毕竟多一事不如少一事,路由器开发人员估计也是这个心态】【家用路由器就是个 三层路由器+无线交换机】《如果我用代码单独发一个arp包,里面的目标IP是另一个网段的,那么另一网段的目标主机能收到,但是发现不同网段的,这时候,不清楚操作系统的arp协议怎么解决,如果正常返回,操作系统中的arp缓存表也不会记录,如果想要通信,操作系统认为不属于同一网段,只会先发包给虚假的网关》

总结:从网络设计上讲,arp 广播请求 只针对 同一个网段内的目标主机。

=======================================================

arp应答是单播,因为知道请求者的mac地址。所以可以直接采用点对点的单播方式回答对方。

为何要缓存arp?因为如果不缓存,那么每次通信都需要广播,不仅费时且减小了广播消息对同网段的影响。 当然arp缓存也存在一定的有效时长。


路由表(FIB表,3层)【存在于主机和路由器上 ,就是用来决策和转发 IP数据包的表】

=======================================================

上网时信息过程

1、本机 开机联网  用dhcp获得 IP 或者 自己设置静态IP,获得本机IP后,会发一个??? 广播,询问 局域网内 是否 和本机 有相同 IP。

2、dns访问网络,其实就是访问 IP。

3、对   待访问IP  进行判断,

如果属于同一个子网,查询ARP缓存表,如果用就租金组建 mac帧,若没有,就发送一个 arp广播,询问IP持有者的 mac地址。等对方arp应答,发回mac地址。然后再组件 mac帧。

mac帧组建好后,就用物理信号,直接发给内网中的目标主机。内网中一般就只有交换机,不会再路由了,因为路由是三层了,是用来跨网段的。【路由表就是根据不同网段,来进行路由的,所以路由器就是用来跨网段的,如果不跨网段的话(记住,内网也可以划分成好多网段不同的子网,不同子网间就需要路由器连接),用二层交换机就够了 】

********************************************************

如果不属于一个子网,就查询网关的mac地址,如果有就组建mac帧,发给网关。如果没有网关的mac地址,就发arp查询,获得网关mac地址,然后组建mac帧。发给网关。

发给网关的mac帧,经过网关检查,发现mac帧中的IP地址不是自己的,于是查询路由表,重新组建mac帧,发给下一个目标。重新组建的mac帧,源mac地址为本网关mac地址,目标mac地址为下一个目标的mac地址。ip数据包里面的内容不变。

就这样,在不同的网段内不断转发,mac帧终于抵达了目标主机。

=======================================================

一、路由表

1、路由表概述

在计算机网络中,路由表或称路由择域信息库(RIB)是一个存储在路由器或者联网计算机中的电子表格(文件)或类数据库。

每个路由器中都有一个路由表和FIB(Forward Information Base)表:路由表用来决策路由,FIB用来转发分组。FIB强调的是作为转发的路由表,RIB是用来做路由管理的表。通常有了动态路由协议的参与才能理解这个问题。RIP、OSPF、BGP、ISIS都是动态路由协议,它们学习到的路由首先要通告给RIB表。RIB表把所有路由协议学习到的路由汇总到一起,经过优选,把优选结果的路由加入到FIB表,供转发使用。所以FIB是RIB的一个子集。

2、路由表中路由有三类:

直连路由:
(1)链路层协议发现的路由
非直连路由:
(1)静态路由
(2)动态路由协议发现的路由。

高档路由器可以运行动态路由选择协议,而中低档的不可以.只有运行了动态路由选择协议的路由器才可以自动生成和更新路由表.

一般的家用型路由器如tp-link都是运行的静态路由,静态路由是由管理员手工的逐条的输入的,不能自动适应网络的拓扑变化.静态路由里面有缺省路由条目,路由器收到的ip包,都会默认转发给上层路由器。

Pc电脑上的 路由表,是 静态路由。我做了一下测试:

(1)电脑上的网线网卡每换一个接入点,都会重新重新初始化PC路由表。

路由表上面的 0.0.0.0 网段,127网络, localhost网段 ,169.254网段【这个是电脑未联网时自己设置的所在网段】,224.0.0 组播网段,255.255.255.255【有限广播地址也称为本地广播地址】

广播地址分为两种:直接广播地址和有限广播地址  。TCP/IP协议规定32比特全为1的IP地址(255.255.255.255)用于本网广播。 在主机不知道本机所处的网络时(如主机的启动过程中),只能采用有限广播方式,通常由无盘工作站启动时使用,希望从网络IP地址服务器处获得一个IP地址。当广播地址包含一个有效的网络号和主机号,技术上就称为直接广播地址。

上面这些都是网络连接成功时,自动生成的路由表。其中 0.0.0.0是缺省路由,如果你是动态获取的IP地址,就是从DHCP服务器学来的;如果是手工静态分配的IP地址,则是静态分配来的;

(2)当别的内网计算机ping或访问本机时,本机路由表就会 添加 该内网计算机的路由。当本机访问别的内网主机或ping内网主机【即使ping的不存在】,本机也会将该内网主机添加到路由表中。当然,这些添加的路由只是暂时性的 ,重启或者切换网络都会路由记录都会被删除。本机自动添加的路由,只限于内网的计算机,外网的计算机路由,都不会添加到本机中。

下面 mac主机的路由表中:192.168.1.133 是另一台 内网主机。该内网的网段是192.168.1.128/25,网关是 192.168.1.129/32,本机IP是  192.168.1.131/32

3、路由表查看

Windows linux 通用: netstat -r  
Windows 上面 还可以用:route print

xp下的 路由表:

===========================================================================
Interface List
0x1 ........................... MS TCP Loopback interface
0x2 ...08 00 27 d7 b7 a9 ...... Intel(R) PRO/1000 T Server Adapter - 数据包计划程序微型端口
===========================================================================
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0         10.0.2.2       10.0.2.15	  10
         10.0.2.0    255.255.255.0        10.0.2.15       10.0.2.15	  10
        10.0.2.15  255.255.255.255        127.0.0.1       127.0.0.1	  10
   10.255.255.255  255.255.255.255        10.0.2.15       10.0.2.15	  10
        127.0.0.0        255.0.0.0        127.0.0.1       127.0.0.1	  1
        224.0.0.0        240.0.0.0        10.0.2.15       10.0.2.15	  10
  255.255.255.255  255.255.255.255        10.0.2.15       10.0.2.15	  1
Default Gateway:          10.0.2.2
===========================================================================
Persistent Routes:
  None

Route Table

mac下面的路由表:

Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            192.168.1.129      UGSc          512        0     en1
127                localhost          UCS             1        0     lo0
localhost          localhost          UH              2  1425190     lo0
169.254            link#5             UCS             1        0     en1
192.168.1.128/25   link#5             UCS             3        0     en1
192.168.1.129/32   link#5             UCS             2        0     en1
192.168.1.129      20:dc:e6:c1:ee:24  UHLWIir       513       25     en1    973
192.168.1.131/32   link#5             UCS             1        0     en1
192.168.1.133      5c:c3:7:4f:62:91   UHLWIi          1        9     en1    726
192.168.1.255      link#5             UHLWbI          1       12     en1
224.0.0            link#5             UmCS            2        0     en1
224.0.0.251        1:0:5e:0:0:fb      UHmLWI          1        0     en1
255.255.255.255/32 link#5             UCS             2        0     en1
broadcasthost      link#5             UHLWbI          1      139     en1

Internet6:
Destination        Gateway            Flags         Netif Expire
localhost          localhost          UHL             lo0
fe80::%lo0         fe80::1%lo0        UcI             lo0
fe80::1%lo0        link#1             UHLI            lo0
fe80::%en1         link#5             UCI             en1
cooldemacbook-pro. 38:c9:86:e7:7c:e   UHLI            lo0
fe80::%awdl0       link#9             UCI           awdl0
cooldemacbook-pro. 46:28:cb:55:19:75  UHLI            lo0
ff01::%lo0         localhost          UmCI            lo0
ff01::%en1         link#5             UmCI            en1
ff01::%awdl0       link#9             UmCI          awdl0
ff02::%lo0         localhost          UmCI            lo0
ff02::%en1         link#5             UmCI            en1
ff02::%awdl0       link#9             UmCI          awdl0

先百度了一下:mac 网络接口(Netif),的各种名字

lo0 = loopback
gif0 = Software Network Interface
stf0 = 6to4 tunnel interface
en0 = Ethernet 0
fw0 = Firewire
en1 = Ethernet 1
vmnet1 = Virtual Interface

补充:在运行ifconfig时,会看到en0 en1 en2 en3 en4 怎么这么多???

运行一下:networksetup -listallhardwareports

Hardware Port: Ethernet
Device: en0
Ethernet Address: xx:xx:xx:xx:xx:xx

Hardware Port: FireWire
Device: fw0
Ethernet Address: xx:xx:xx:xx:xx:xx:xx:xx

Hardware Port: Wi-Fi
Device: en1
Ethernet Address: xx:xx:xx:xx:xx:xx

Hardware Port: Bluetooth PAN
Device: en3
Ethernet Address: xx:xx:xx:xx:xx:xx

Hardware Port: Thunderbolt 1
Device: en2
Ethernet Address: xx:xx:xx:xx:xx:xx

Hardware Port: Thunderbolt Bridge
Device: bridge0
Ethernet Address: xx:xx:xx:xx:xx:xx

VLAN Configurations
===================

原来是Wi-Fi,蓝牙,thunderbolt

4、路由表讲解

举例:

(1)、通俗点说就是:接口指硬件网卡上的网线口,一个接口可以设置N个IP。

市面上有不止一个接口的网卡,2口、4口都有。

接口是你拥有的IP,因为允许一个网卡设置多个IP,所以接口的IP是数据包出去时的IP,网关是数据包从接口出去后,第一个访问的地址,不一定是路由器的…有可能是电信的服务器、有可能是公司的代理服务器,主要是看这个网关地址是哪里才能初步判断是什么东西。

(2)、网关是指数据从你接口出去先走哪里。比如就是你要出门,先要走你家大门一样,有些家庭不止一个大门的,所以要指定。

(3)、“在链路上”即指你电脑访问网络的链路中存在多个网关,VISTA以上的系统支持配置多个网关的多重网络。

(4)、mac上面的路由表:目标 指的是网段号;xp上面的路由表 ,目标指的是,网络地址,后面还跟着子网掩码,这样就可以计算出 网段号。

(5)、权值英语:Metrics),又称路由度量(routing metric),是电脑网络上,路由的参数之一,这个参数被用来决定某个特定路径是否应该被选择。权值主要在动态选径时使用。权值包含了被路由算法使用来决定哪一条路径较另一条路径好的所有数值。度量可能包括许多资讯,例如带宽、延迟、经过节点数、路径成本、负载、最大传输单元(MTU)、可靠性及传输成本等。路由表只储存最佳的可能路径,但连线状态或拓扑数据库可能储存其他相关的资讯。【来自维基百科】

路由会选择最低权值的闸道器路径(预设路径,default gateway)前进。如果权值为0,代表该路径的目的地,与本地端界面是连接在同一个网络上。如果权值大于零,该路径的目的地会被认为外部位址,必须透过外部闸道器才能抵达目的地。

5、路由表实例

如下图:详细介绍路由器的工作原理

1)HostA在网络层将来自上层的报文封装成IP数据包,其中源IP地址为自己,目标IP地址是HostB,HostA会用本机配置的24位子网掩码与目标地址进行“与”运算,得出目标地址与本机不是同一网段,因此发送HostB的数据包需要经过网关路由A的转发。

2)HostA通过ARP请求获取网关路由A的E0口的MAC地址,并在链路层将路由器E0接口的MAC地址封装成目标MAC地址,源MAC地址是自己。

3)路由器A从E0可接收到数据帧,把数据链路层的封装去掉,并检查路由表中是否有目标IP地址网段(即192.168.2.2的网段)相匹配的的项,根据路由表中记录到192.168.2.0网段的数据请发送给下一跳地址10.1.1.2,因此数据在路由器A的E1口重新封装,此时,源MAC地址是路由器A的E1接口的MAC地址,封装的目标MAC地址则是路由器2的E1接口的MAC地址。

4)路由B从E1口接收到数据帧,同样会把数据链路层的封装去掉,对目标IP地址进行检测,并与路由表进行匹配,此时发现目标地址的网段正好是自己E0口的直连网段,路由器B通过ARP广播,获知HostB的MAC地址,此时数据包在路由器B的E0接口再次封装,源MAC地址是路由器B的E0接口的MAC地址,目标MAC地址是HostB的MAC地址。封装完成后直接从路由器的E0接口发送给HostB。

5)此时HostB才会收到来自HostA发送的数据。

总结:路由表负责记录一个网络到另一个网络的路径,因此路由器是根据路由表工作的。


二、mac地址表

说到MAC地址表,就不得不说一下交换机的工作原理了,因为交换机是根据MAC地址表转发数据帧的。在交换机中有一张记录着局域网主机MAC地址与交换机接口的对应关系的表,交换机就是根据这张表负责将数据帧传输到指定的主机上的。

交换机的工作原理

交换机在接收到数据帧以后,首先、会记录数据帧中的源MAC地址和对应的接口到MAC表中,接着、会检查自己的MAC表中是否有数据帧中目标MAC地址的信息,如果有则会根据MAC表中记录的对应接口将数据帧发送出去(也就是单播),如果没有,则会将该数据帧从非接受接口发送出去(也就是广播)。

如下图:详细讲解交换机传输数据帧的过程

 

1)主机A会将一个源MAC地址为自己,目标MAC地址为主机B的数据帧发送给交换机。

2)交换机收到此数据帧后,首先将数据帧中的源MAC地址和对应的接口(接口为f 0/1) 记录到MAC地址表中。

3)然后交换机会检查自己的MAC地址表中是否有数据帧中的目标MAC地址的信息,如果有,则从MAC地址表中记录的接口发送出去,如果没有,则会将此数据帧从非接收接口的所有接口发送出去(也就是除了f 0/1接口)。

4)这时,局域网的所有主机都会收到此数据帧,但是只有主机B收到此数据帧时会响应这个广播,并回应一个数据帧,此数据帧中包括主机B的MAC地址。

5)当交换机收到主机B回应的数据帧后,也会记录数据帧中的源MAC地址(也就是主机B的MAC地址),这时,再当主机A和主机B通信时,交换机根据MAC地址表中的记录,实现单播了。

如下图:当局域网存在多个交换机互联的时候,交换机的MAC地址表是怎么记录的呢?

 

1)主机A将一个源MAC地址为自己,目标MAC地址主机C的数据帧发送给交换机

2)交换机1收到此数据帧后,会学习源MAC地址,并检查MAC地址表,发现没有目标MAC地址的记录,则会将数据帧广播出去,主机B和交换机2都会收到此数据帧。

3)交换机2收到此数据帧后也会将数据帧中的源MAC地址和对应的接口记录到MAC地址表中,并检查自己的MAC地址表,发现没有目标MAC地址的记录,则会广播此数据帧。

4)主机C收到数据帧后,会响应这个数据帧,并回复一个源MAC地址为自己的数据帧,这时交换机1和交换机2都会将主机C的MAC地址记录到自己的MAC地址表中,并且以单播的形式将此数据帧发送给主机A。

5)这时,主机A和主机C通信就是一单播的形式传输数据帧了,主机B和主机C通信如上述过程一样,因此交换机2的MAC地址表中记录着主机A和主机B的MAC地址都对应接口f 0/1。

总结:从上面的两幅图可以看出,交换机具有动态学习源MAC地址的功能,并且交换机的一个接口可以对应多个MAC地址,但是一个MAC地址只能对应一个接口。

注意:交换机动态学习的MAC地址默认只有300S的有效期,如果300S内记录的MAC地址没有通信,则会删除此记录。


三、ARP缓存表

上面我们讲解了交换机的工作原理,知道交换机是通过MAC地址通信的,但是我们是如何获得目标主机的MAC地址呢?这时我们就需要使用ARP协议了,在每台主机中都有一张ARP表,它记录着主机的IP地址和MAC地址的对应关系。

ARP协议:ARP协议是工作在网络层的协议,它负责将IP地址解析为MAC地址。

如下图:详细讲解ARP的工作原理。

 

1)如果主机A想发送数据给主机B,主机A首先会检查自己的ARP缓存表,查看是否有主机B的IP地址和MAC地址的对应关系,如果有,则会将主机B的MAC地址作为源MAC地址封装到数据帧中。如果没有,主机A则会发送一个ARP请求信息,请求的目标IP地址是主机B的IP地址,目标MAC地址是MAC地址的广播帧(即FF-FF-FF-FF-FF-FF),源IP地址和MAC地址是主机A的IP地址和MAC地址。

2)当交换机接受到此数据帧之后,发现此数据帧是广播帧,因此,会将此数据帧从非接收的所有接口发送出去。

3)当主机B接受到此数据帧后,会校对IP地址是否是自己的,并将主机A的IP地址和MAC地址的对应关系记录到自己的ARP缓存表中,同时会发送一个ARP应答,其中包括自己的MAC地址。

4)主机A在收到这个回应的数据帧之后,在自己的ARP缓存表中记录主机B的IP地址和MAC地址的对应关系。而此时交换机已经学习到了主机A和主机B的MAC地址了。

 


参考:

mac表 arp表 路由表 http://blog.51cto.com/dengqi/1223132

en0 en1 问题      https://blog.csdn.net/yangziluomu?t=1

traceroute  原理及实现  https://www.jianshu.com/p/75a5822d0eec

Traceroute原理解析  https://www.jianshu.com/p/9465fa3abe47

Metrics 问题  https://en.wikipedia.org/wiki/Metrics_(networking)

网络原理-一张网卡设置两个IP的作用

一块网卡,设置两个IP的 作用

1、一个网卡设置两个或者多个IP地址,作用就是可以连接多个网段(就是可以访问多个网段),但前提是这些网段物理层是连接在一起。
2、举个例子:有两个LAN(局域网)通过交换机连接在一起,LAN1为192.168.0.0/24 网段
LAN2 为192.168.1.0/24网段,在没有三层设备的情况下LAN1与LAN2下的主机是不可以相互访问的,因为LAN1与LAN2的网络号分别为192.168.0.0、192.168.1.0,网络号不同即不在同一网段。虽然两个LAN通过交换机连接在一起了,但在没有三层设备时是无法进行通信的。但若是LAN1下的一台主机PC1设置两个IP地址如 192.168.0.100/24、192.168.1.100/24(不可与LAN2下的主机IP冲突) 则PC1就可以访问LAN2下的主机,因为此时PC1使用IP 192.168.1.100/24进行LAN2的访问。


设置双IP流程

首先打开“本地连接 属性”页

 

第二步,选择“Internet 协议版本4”点属性或双击它。打开“Internet 协议版本4 属性”页

 

第三步,设置其中一个IP段的IP地址、网关、DNS服务器地址,点“高级”,打开“高级TCP/IP设置”页。

 

第四步,点“添加”,输入另一个网段分配的IP地址,以及网关,如下图

 

第五步,点“确定”完成设置。

网络原理-网络拓扑之两条宽带叠加上网问题

一、宽带叠加概述

一直觉得上网,就需要一条宽带就行了。
偶尔想到,如果电脑同时接有线和无线宽带会怎么样?

当电脑连接两条宽带时,且两条宽带的公网ip不同。这时电脑上网会采用,有线宽带的IP。下载也是有线宽带。因为有线宽带的优先级比无线宽带高,我们需要修改有线网卡和无线网卡的跃点数,修改成一样【需要进入网络连接属性 /高级 TCP/IP 属性 修改 跃点数】。这样有线和无线宽带的优先级就一样了。平时上网只会用一条,但是用迅雷等多进程下载时,可以用两条宽带下载。

或者买一个双WAN口的 路由器或者 买两个路由器 且 组合成一个局域网,当电脑连接上路由器时,相当于两条宽带上网了。平时上网只用一条,多进程下载时可以用两条宽带。


无线和有线 同时连接时,电脑的路由表 (命令行 netstat -r) 可以看到 本机 无线网卡的 网段 和 有线网卡的网段 不一样,但是 电脑 同时可以访问 两个网段。【我测试时,无线连的是手机热点,有线连的是 路由器的 宽带】


二、两条宽带连PC操作

第一,右击右下角的网络图标,打开网络和共享中心,在弹出来的对话框中选择更改网络配置。

第二,在弹出来的对话框中可以找到我们现在使用的网络,右击击其中的一个网络适配器,选择属性。

第三,在弹出来的对话框中双击Internet 协议版本4,此项是用来设置我们当前使用的网络。

第四,此时会弹出常规选项卡,点击高级,在弹出来的对话框中我们去掉自动跃点的勾选,自己任意设置成任意数字,20 40都行,点击确定,这样我们就完成了一个网络适配器的更改。

 

第五,我们需要进行第二个网络适配器的更改,和上面的步骤一样,接口跃点数一定要设置成和第一个适配器的跃点数一样的数字,否则就不会网络叠加的成功。

此时,完成是配置的设置之后,我们就可以进行网络速度的测试了,很多软件均集成了这个功能,如360安全卫士,以前我的网速最大下载速度是500k左右,进行网络的叠加使用后就变成了1M下载,很强悍吧!

为什么非要跃点数设置成一样的呢?传输过程中需要经过多个网络,每个被经过的网络设备点叫做一个跃点,地址就是它的ip,跃点数是经过了多少个跃点的累加器,为了防止无用的数据包在网上流散。将跃点数设置成一样的数值,系统会认定为这是同一个网络,所以两个网络会合并为一个网络供我们使用。

=========================================================

命令行模式,查看 修改 效果前后 图

然后进入命令模式,输入route print 回车查看IP的跃点数,这做是为了寻找两者的差值以达到平衡
最后进入网卡修改跃点数

最后再route print一次两者跃点数相同就可以了,叠加成功,测速,下载,在线电影均有效。

三、双WAN口路由器或两个路由器操作

这里讲个三网叠加的 :

  1. 设置路由器C拨号,lan IP与路由器A、B同一网段PC端设置网关:顺序为路由器C、A、B,

  2. 看看速度,牛X,迅雷下载稳定3.2M/S

    遗憾的是手机端设置不了多个网关,不过10M宽带也够用了

 


参考:

双路由器实现宽带叠加 https://jingyan.baidu.com/article/1974b289a238d6f4b1f7743c.html
有线+无线叠加教程 http://tieba.baidu.com/p/2589595477
win8.1有线+无线同时上叠加上网设置 http://tieba.baidu.com/p/3309314007