Blog

字符编码-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

wordpress-优化php-fpm和mysql

个人小站,用VPS 搭建了  wordpress,因为系统配置低,所以需要优化一下。

一、优化 php-fpm

#用于查找配置文件
 find / -name php.ini
#结果  /etc/php.ini
ls /etc
#发现  /etc/php-fpm.conf  里面 调用了 /etc/php-fpm.d  这个是文件夹
# 进入   cd /etc/php-fpm.d
修改  pm.max_children 等参数

php-fpm进程设置多少合适,设成动态还是静态?

lnmp一键安装包》中会根据你服务器内存调整php-fpm进程数。

下面是摘自Google讨论话题:《 PHP-FPM on highload tips 》如果你的高负载网站使用PHP-FPM管理FastCGI,也许下面这些技巧对你有用

1、系统上优化


1. Increase Linux “max open files”, using the following command (must be root):

Linux下增加文件打开数,命令如下:

cat >> /etc/security/limits.conf <<EOF
* soft nproc 65535
* hard nproc 65535
* soft nofile 65535
* hard nofile 65535
EOF

可以用 ulimit -n 命令查看。

2. Using SOCKET PHP FastCGI, and put into /dev/shm on Linux;

socket连接FastCGI,/dev/shm是内存文件系统,socket放在内存中肯定会快些。

nginx 下的配置
&nbsp;location ~ \.php$ {
   #fastcgi_pass 127.0.0.1:9000;
    fastcgi_pass unix:/dev/shm/php-fcgi.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
}


systemctl restart nginx [这里可能会报错 unknown directive "fastcgi_pass" 其实是因为配置包含的空格中有一些 dirty EOL(end of line) characters,重新删除添加即可]

##################################### 

修改php-fpm.conf
;listen = 127.0.0.1:9000
listen = /dev/shm/php-fcgi.sock

# 执行下面这句话,会自动生成一个 /dev/shm/php-fcgi.sock 但是文件权限不对
systemctl restart php-fpm # 所以我又添加了权限 chmod 777 /dev/shm/php-fcgi.sock


# 原因是一开始忘记添加了下面两句话,导致生成的 php-fcgi.sock 是root所有,存在权限问题
listen.owner = apache # 这里要设置网站所有者,目标权限统一
listen.group = apache # 这里要设置网站所有者,目标权限统一 
# 但是又出问题了,于sock 文件是 nginx 和 php-fpm共享的,所以nginx和php-fpm的进程需要设置成同一个用户,权限统一

##################################### 
ps aux|grep nginx     # 发现 是 nginx 用户所有
ps aux|grep php-fpm   # 发现 是 apache 用户所有

nginx的用户名在/etc/nginx/nginx.conf配置文件中,有一个user参数,查看对应的就可以。
后来浏览器访问时,发现页面加载不全,net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)
百度后知道,nginx目录下fastcgi_temp属于nginx用户和用户组,还需要修改成 apache
chown  -R  apache:apache /etc/nginx/fastcgi_params

查看 php-fpm 配置文件
#vi /etc/php-fpm.d/www.conf
 
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache Choosed to be able to access some dir as httpd
;user = nobody
user=apache
; RPM: Keep a group allowed to write in log dir.
;group = nobody
group=apache

# 后来发现 nginx 日志中:open() "/var/cache/nginx/fastcgi_temp/3/00/0000000003" failed (13: Permission denied) while reading
# 于是我 rm -rf /var/cache/nginx/  然后发现重启 nginx 报错,mkdir() "/var/cache/nginx/client_temp" failed (2: No such file or directory)
# 于是我 mkdir /var/cache/nginx

mkdir /var/cache/nginx

 

Unix域Socket因为不走网络,的确可以提高Nginx和php-fpm通信的性能,但在高并发时会不稳定。
Nginx会频繁报错:
     connect() to unix:/dev/shm/php-fcgi.sock failed (11: Resource temporarily unavailable) while connecting to upstream

可以通过下面两种方式提高稳定性:
1)调高nginx和php-fpm中的backlog
     配置方法为:在nginx配置文件中这个域名的server下,在listen 80后面添加default backlog=1024。
     同时配置php-fpm.conf中的listen.backlog为1024,默认为128。
2)增加sock文件和php-fpm实例数
     再新建一个sock文件,在Nginx中通过upstream模块将请求负载均衡到两个sock文件背后的两套php-fpm实例上。

 

3. Compile PHP’s modules as less as possible, the simple the best (fast);

尽量少安装PHP模块,最简单是最好(快)的

妈蛋的 一个 php-fpm 占了80M
# 查看装了哪些 php 模块 ;卸载只要  yum remove 模块名
rpm -qa | grep php

# 下面是 必须要装的模块
php71w-common-7.1.22-1.w7.x86_64 【这个才是真正的主模块】
php71w-cli-7.1.22-1.w7.x86_64
php71w-mysql-7.1.22-1.w7.x86_64
php71w-pdo-7.1.22-1.w7.x86_64
php71w-fpm-7.1.22-1.w7.x86_64

# 下面是没有必要装的
mod_php71w-7.1.22-1.w7.x86_64  【这个模块应该是独立于其他模块的,可以删除,因为卸载7.1版本时,这个模块根本就没有删除】
php71w-xml-7.1.22-1.w7.x86_64
php71w-soap-7.1.22-1.w7.x86_64
php71w-mcrypt-7.1.22-1.w7.x86_64
php71w-xmlrpc-7.1.22-1.w7.x86_64
php71w-gd-7.1.22-1.w7.x86_64  // 作用是 png 转成 jpg

4. Using PHP code accelerator, e.g eAccelerator, XCache. And set “cache_dir” to /dev/shm on Linux.

使用php代码加速器,有那些PHP Opcode缓存插件?
Optimizer+(Optimizer+于2013年3月中旬改名为Opcache,PHP 5.5集成Opcache,其他的会不会消失?)、eAccelerator、xcache、APC …,例如 eAccelerator, XCache.在Linux平台上可以把`cache_dir`指向 /dev/shm

Opcache 缓存默认就存放在内存中,所以不需要指定目录了。

yum install php-opcache
# 相关配置
# vim /etc/php.d/10-opcache.ini

# 安装完后,直接用wordpress 的 W3 Total Cache 插件设置缓存 到这里就行了。

===============================
可以安装 redis 服务,启动服务端后,需要 php的 驱动
yum list | grep php72w*  ## 从网上查找所有的 php 插件
yum install php72w-pecl-redis  
## PECL is a repository for PHP Extensions, providing a directory of all known extensions and hosting facilities for downloading and development of PHP extensions.
安装完 redis 驱动后 ,直接用wordpress 的 W3 Total Cache 插件设置缓存 到这里就行了

2、php-fpm 配置文件优化


1. Increas PHP FastCGI child number to 100 and even more. Sometime, 200 is OK! ( On 4GB memory server);

把你的PHP FastCGI子进程数调到100或以上,在4G内存的服务器上200就可以(建议压力测试来得出自己服务器合理的值)其实就是 php-fpm 进程数
配置文件/etc/php-fpm.conf --> /etc/php-fpm.d/www.conf
这个配置文件的里面的池名[www] 就对应网站的域名 ,两者互相对应。这个配置文件即可以用来设置 php-fpm 运行时的用户名和用户组,也可以生成 sock文件,并规定用户名和用户组。

; Start a new pool named 'www'.
[www]

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache Choosed to be able to access some dir as httpd
user = apache
; RPM: Keep a group allowed to write in log dir.
group = apache

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
;                            a specific port;
;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses
;                            (IPv6 and IPv4-mapped) on a specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
;listen = 127.0.0.1:9000
listen = /dev/shm/php-fcgi.sock

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
;                 mode is set to 0660
;listen.owner = nobody
;listen.group = nobody
;listen.mode = 0660
listen.owner = apache
listen.group = apache

# 查看php-fpm的进程个数
ps -fe |grep "php-fpm"|grep "pool"|wc -l

# 查看每个php-fpm占用的内存大小
ps -ylC php-fpm --sort:rss

# 配置php-fpm参数
pm = dynamic #动态适合小内存机器,灵活分配进程,省内存。静态适用于大内存机器,动态创建回收进程对服务器资源也是一种消耗。
#如何控制子进程,选项有static和dynamic。如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:

pm.max_children #子进程最大数
pm.start_servers #启动时的进程数
pm.min_spare_servers #保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
pm.max_spare_servers #保证空闲进程数最大值,如果空闲进程大于此值,此进行清理

###  我的 1G 主机,测试使用发现  一个 php-fpm 占了80M,所以进程配置了最多10个

2.其他参数

rlimit_files = 51200
Increase PHP-FPM open file description rlimit:增加 PHP-FPM 打开文件描述符的限制。

pm.max_requests = 10240;
每个请求完成后php-cgi会回收内存,但是不会释放给操作系统,这样就会导致大量内存被php-cgi占用。官方的解决办法是降低PHP_FCGI_MAX_REQUESTS的值,如果用的是php-fpm,对应的php-fpm.conf中的就是max_requests,该值的意思是发送多少个请求后会重启该线程,我们需要适当降低这个值,用以让php-fpm自动的释放内存,不是大部分网上说的51200等等。

request_terminate_timeout = 30;
最大执行时间, 在php.ini中也可以进行配置(max_execution_time)默认值为 0 秒,也就是说,PHP 脚本会一直执行下去。这样,当所有的 php-cgi 进程都卡在 file_get_contents() 函数时,这台 Nginx+PHP 的 WebServer 已经无法再处理新的 PHP 请求了,Nginx 将给用户返回“502 Bad Gateway”。可以使用 request_terminate_timeout = 30s,但是如果发生 file_get_contents() 获取网页内容较慢的情况,这就意味着 150 个 php-cgi 进程,每秒钟只能处理 5 个请求,WebServer 同样很难避免“502 Bad Gateway”。php-cgi进程数不够用、php执行时间长、或者是php-cgi进程死掉,都会出现502错误。

用strace跟踪 超时进程:
利用nohup将strace转为后台执行,直到attach上的php-fpm进程死掉为止:
nohup strace -T -p 13167 > 13167-strace.log &

参数说明:

-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-o filename,则所有进程的跟踪结果输出到相应的filename
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认为40.
-e execve 只记录 execve 这类系统调用
-p 主进程号

request_slowlog_timeout = 2; 配置输出php-fpm慢日志,阀值为2秒:
slowlog =/tmp/www.slow.log; 慢日志路径,可以自己定路径。
利用sort/uniq命令分析汇总php-fpm慢日志:

[root@b28-12 log]# grep -v "^$" www.log.slow.tmp | cut -d " " -f 3,2 | sort | uniq -c | sort -k1,1nr | head -n 50

   5181 run() /www/test.net/framework/web/filters/CFilter.php:41

   5156 filter() /www/test.net/framework/web/filters/CFilterChain.php:131

   2670 = /www/test.net/index.php

   2636 run() /www/test.net/application/controllers/survey/index.php:665

   2630 action() /www/test.net/application/controllers/survey/index.php:18

   2625 run() /www/test.net/framework/web/actions/CAction.php:75

   2605 runWithParams() /www/test.net/framework/web/CController.php:309

   2604 runAction() /www/test.net/framework/web/filters/CFilterChain.php:134

   2538 run() /www/test.net/framework/web/CController.php:292

   2484 runActionWithFilters() /www/test.net/framework/web/CController.php:266

   2251 run() /www/test.net/framework/web/CWebApplication.php:276

   1799 translate() /www/test.net/application/libraries/Limesurvey_lang.php:118

   1786 load_tables() /www/test.net/application/third_party/php-gettext/gettext.php:254

   1447 runController() /www/test.net/framework/web/CWebApplication.php:135

参数解释:
sort:  对单词进行排序
uniq -c:  显示唯一的行,并在每行行首加上本行在文件中出现的次数
sort -k1,1nr:  按照第一个字段,数值排序,且为逆序
head -10:  取前10行数据

3、PHP性能监控


常用的方法就是开启xdebug的性能监控功能,将xdebug输出结果通过WinCacheGrind软件分析。
xdebug的安装和配合IDE调试的方法参见:Vim+XDebug调试PHP

php.ini中配置的这几项是输出性能信息的:

xdebug.auto_trace = on

xdebug.auto_profile = on
xdebug.collect_params = on
xdebug.collect_return = on
xdebug.profiler_enable = on
xdebug.trace_output_dir = "/tmp"
xdebug.profiler_output_dir ="/tmp"

这样XDebug会输出所有执行php函数的性能数据,但产生的文件也会比较大。可以关闭一些选项如collect_params、collect_return,来减少输出的数据量。或者关闭自动输出,通过在想要监控的函数首尾调用xdebug函数来监控指定的函数。输出的文件名类似cachegrind.out.1277560600和trace.3495983249.txt,可以拿到Windows平台下用WinCacheGrind进行图形化分析。

在线监测:

# nginx 配置
location ~ ^/status$ {
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
}

# php-fpm配置
pm.status_path = /status

###############################
这样的话通过http://域名/status就可以看到当前的php情况

  下面介绍每个参数的作用:
  pool:php-fpm池的名称,一般都是应该是www
  process manage:进程的管理方法,php-fpm支持三种管理方法,分别是static,dynamic和ondemand,一般情况下都是dynamic
  start time:php-fpm启动时候的时间,不管是restart或者reload都会更新这里的时间
  start since:php-fpm自启动起来经过的时间,默认为秒
  accepted conn:当前接收的连接数
  listen queue:在队列中等待连接的请求个数,如果这个数字为非0,那么最好增加进程的fpm个数
  max listen queue:从fpm启动以来,在队列中等待连接请求的最大值
  listen queue len:等待连接的套接字队列大小
  idle processes:空闲的进程个数
  active processes:活动的进程个数
  total processes:总共的进程个数
  max active processes:从fpm启动以来,活动进程的最大个数,如果这个值小于当前的max_children,可以调小此值
  max children reached:当pm尝试启动更多的进程,却因为max_children的限制,没有启动更多进程的次数。如果这个值非0,那么可以适当增加fpm的进程数
  slow requests:慢请求的次数,一般如果这个值未非0,那么可能会有慢的php进程,一般一个不好的mysql查询是最大的祸首。

二、优化mysql

my.cnf  【经过使用,目前mysql5.7版本,使用默认配置,内存占用稳定在20%左右,我也懒得优化了】

MySQL 5.6版本适合在1GB内存VPS上的my.cnf配置文件(点击这里下载文件):

[client]  
port = 3306  
socket = /tmp/mysql.sock  
  
[mysqld]  
port = 3306  
socket = /tmp/mysql.sock  
  
basedir = /usr/local/mysql  
datadir = /data/mysql  
pid-file = /data/mysql/mysql.pid  
user = mysql  
bind-address = 0.0.0.0  
server-id = 1 #表示是本机的序号为1,一般来讲就是master的意思  
  
skip-name-resolve  
# 禁止MySQL对外部连接进行DNS解析,使用这一选项可以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,  
# 则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求  
  
#skip-networking  
  
back_log = 600  
# MySQL能有的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用,  
# 然后主线程花些时间(尽管很短)检查连接并且启动一个新线程。back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。  
# 如果期望在一个短时间内有很多连接,你需要增加它。也就是说,如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,  
# 以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。  
# 另外,这值(back_log)限于您的操作系统对到来的TCP/IP连接的侦听队列的大小。  
# 你的操作系统在这个队列大小上有它自己的限制(可以检查你的OS文档找出这个变量的最大值),试图设定back_log高于你的操作系统的限制将是无效的。  
  
max_connections = 1000  
# MySQL的最大连接数,如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量,当然这建立在机器能支撑的情况下,因为如果连接数越多,介于MySQL会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值,不能盲目提高设值。可以过'conn%'通配符查看当前状态的连接数量,以定夺该值的大小。  
  
max_connect_errors = 6000  
# 对于同一主机,如果有超出该参数值个数的中断错误连接,则该主机将被禁止连接。如需对该主机进行解禁,执行:FLUSH HOST。  
  
open_files_limit = 65535  
# MySQL打开的文件描述符限制,默认最小1024;当open_files_limit没有被配置的时候,比较max_connections*5和ulimit -n的值,哪个大用哪个,  
# 当open_file_limit被配置的时候,比较open_files_limit和max_connections*5的值,哪个大用哪个。  
  
table_open_cache = 128  
# MySQL每打开一个表,都会读入一些数据到table_open_cache缓存中,当MySQL在这个缓存中找不到相应信息时,才会去磁盘上读取。默认值64  
# 假定系统有200个并发连接,则需将此参数设置为200*N(N为每个连接所需的文件描述符数目);  
# 当把table_open_cache设置为很大时,如果系统处理不了那么多文件描述符,那么就会出现客户端失效,连接不上  
  
max_allowed_packet = 4M  
# 接受的数据包大小;增加该变量的值十分安全,这是因为仅当需要时才会分配额外内存。例如,仅当你发出长查询或MySQLd必须返回大的结果行时MySQLd才会分配更多内存。  
# 该变量之所以取较小默认值是一种预防措施,以捕获客户端和服务器之间的错误信息包,并确保不会因偶然使用大的信息包而导致内存溢出。  
  
binlog_cache_size = 1M  
# 一个事务,在没有提交的时候,产生的日志,记录到Cache中;等到事务提交需要提交的时候,则把日志持久化到磁盘。默认binlog_cache_size大小32K  
  
max_heap_table_size = 8M  
# 定义了用户可以创建的内存表(memory table)的大小。这个值用来计算内存表的最大行数值。这个变量支持动态改变  
  
tmp_table_size = 16M  
# MySQL的heap(堆积)表缓冲大小。所有联合在一个DML指令内完成,并且大多数联合甚至可以不用临时表即可以完成。  
# 大多数临时表是基于内存的(HEAP)表。具有大的记录长度的临时表 (所有列的长度的和)或包含BLOB列的表存储在硬盘上。  
# 如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。还可以通过设置tmp_table_size选项来增加临时表的大小。也就是说,如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果  
  
read_buffer_size = 2M  
# MySQL读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。  
# 如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能  
  
read_rnd_buffer_size = 8M  
# MySQL的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,  
# MySQL会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大  
  
sort_buffer_size = 8M  
# MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。  
# 如果不能,可以尝试增加sort_buffer_size变量的大小  
  
join_buffer_size = 8M  
# 联合查询操作所能使用的缓冲区大小,和sort_buffer_size一样,该参数对应的分配内存也是每连接独享  
  
thread_cache_size = 8  
# 这个值(默认8)表示可以重新利用保存在缓存中线程的数量,当断开连接时如果缓存中还有空间,那么客户端的线程将被放到缓存中,  
# 如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,那么这个线程将被重新创建,如果有很多新的线程,  
# 增加这个值可以改善系统性能.通过比较Connections和Threads_created状态的变量,可以看到这个变量的作用。(–>表示要调整的值)  
# 根据物理内存设置规则如下:  
# 1G  —> 8  
# 2G  —> 16  
# 3G  —> 32  
# 大于3G  —> 64  
  
query_cache_size = 8M  
#MySQL的查询缓冲大小(从4.0.1开始,MySQL提供了查询缓冲机制)使用查询缓冲,MySQL将SELECT语句和查询结果存放在缓冲区中,  
# 今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。根据MySQL用户手册,使用查询缓冲最多可以达到238%的效率。  
# 通过检查状态值'Qcache_%',可以知道query_cache_size设置是否合理:如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,  
# 如果Qcache_hits的值也非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,  
# 这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲  
  
query_cache_limit = 2M  
#指定单个查询能够使用的缓冲区大小,默认1M  
  
key_buffer_size = 4M  
#指定用于索引的缓冲区大小,增加它可得到更好处理的索引(对所有读和多重写),到你能负担得起那样多。如果你使它太大,  
# 系统将开始换页并且真的变慢了。对于内存在4GB左右的服务器该参数可设置为384M或512M。通过检查状态值Key_read_requests和Key_reads,  
# 可以知道key_buffer_size设置是否合理。比例key_reads/key_read_requests应该尽可能的低,  
# 至少是1:100,1:1000更好(上述状态值可以使用SHOW STATUS LIKE 'key_read%'获得)。注意:该参数值设置的过大反而会是服务器整体效率降低  
  
ft_min_word_len = 4  
# 分词词汇最小长度,默认4  
  
transaction_isolation = REPEATABLE-READ  
# MySQL支持4种事务隔离级别,他们分别是:  
# READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.  
# 如没有指定,MySQL默认采用的是REPEATABLE-READ,ORACLE默认的是READ-COMMITTED  
  
log_bin = mysql-bin  
binlog_format = mixed  
expire_logs_days = 30 #超过30天的binlog删除  
  
log_error = /data/mysql/mysql-error.log #错误日志路径  
slow_query_log = 1  
long_query_time = 1 #慢查询时间 超过1秒则为慢查询  
slow_query_log_file = /data/mysql/mysql-slow.log  
  
performance_schema = 0  
explicit_defaults_for_timestamp  
  
#lower_case_table_names = 1 #不区分大小写  
  
skip-external-locking #MySQL选项以避免外部锁定。该选项默认开启  
  
default-storage-engine = InnoDB #默认存储引擎  
  
innodb_file_per_table = 1  
# InnoDB为独立表空间模式,每个数据库的每个表都会生成一个数据空间  
# 独立表空间优点:  
# 1.每个表都有自已独立的表空间。  
# 2.每个表的数据和索引都会存在自已的表空间中。  
# 3.可以实现单表在不同的数据库中移动。  
# 4.空间可以回收(除drop table操作处,表空不能自已回收)  
# 缺点:  
# 单表增加过大,如超过100G  
# 结论:  
# 共享表空间在Insert操作上少有优势。其它都没独立表空间表现好。当启用独立表空间时,请合理调整:innodb_open_files  
  
innodb_open_files = 500  
# 限制Innodb能打开的表的数据,如果库里的表特别多的情况,请增加这个。这个值默认是300  
  
innodb_buffer_pool_size = 64M  
# InnoDB使用一个缓冲池来保存索引和原始数据, 不像MyISAM.  
# 这里你设置越大,你在存取表里面数据时所需要的磁盘I/O越少.  
# 在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的80%  
# 不要设置过大,否则,由于物理内存的竞争可能导致操作系统的换页颠簸.  
# 注意在32位系统上你每个进程可能被限制在 2-3.5G 用户层面内存限制,  
# 所以不要设置的太高.  
  
innodb_write_io_threads = 4  
innodb_read_io_threads = 4  
# innodb使用后台线程处理数据页上的读写 I/O(输入输出)请求,根据你的 CPU 核数来更改,默认是4  
# 注:这两个参数不支持动态改变,需要把该参数加入到my.cnf里,修改完后重启MySQL服务,允许值的范围从 1-64  
  
innodb_thread_concurrency = 0  
# 默认设置为 0,表示不限制并发数,这里推荐设置为0,更好去发挥CPU多核处理能力,提高并发量  
  
innodb_purge_threads = 1  
# InnoDB中的清除操作是一类定期回收无用数据的操作。在之前的几个版本中,清除操作是主线程的一部分,这意味着运行时它可能会堵塞其它的数据库操作。  
# 从MySQL5.5.X版本开始,该操作运行于独立的线程中,并支持更多的并发数。用户可通过设置innodb_purge_threads配置参数来选择清除操作是否使用单  
# 独线程,默认情况下参数设置为0(不使用单独线程),设置为 1 时表示使用单独的清除线程。建议为1  
  
innodb_flush_log_at_trx_commit = 2  
# 0:如果innodb_flush_log_at_trx_commit的值为0,log buffer每秒就会被刷写日志文件到磁盘,提交事务的时候不做任何操作(执行是由mysql的master thread线程来执行的。  
# 主线程中每秒会将重做日志缓冲写入磁盘的重做日志文件(REDO LOG)中。不论事务是否已经提交)默认的日志文件是ib_logfile0,ib_logfile1  
# 1:当设为默认值1的时候,每次提交事务的时候,都会将log buffer刷写到日志。  
# 2:如果设为2,每次提交事务都会写日志,但并不会执行刷的操作。每秒定时会刷到日志文件。要注意的是,并不能保证100%每秒一定都会刷到磁盘,这要取决于进程的调度。  
# 每次事务提交的时候将数据写入事务日志,而这里的写入仅是调用了文件系统的写入操作,而文件系统是有 缓存的,所以这个写入并不能保证数据已经写入到物理磁盘  
# 默认值1是为了保证完整的ACID。当然,你可以将这个配置项设为1以外的值来换取更高的性能,但是在系统崩溃的时候,你将会丢失1秒的数据。  
# 设为0的话,mysqld进程崩溃的时候,就会丢失最后1秒的事务。设为2,只有在操作系统崩溃或者断电的时候才会丢失最后1秒的数据。InnoDB在做恢复的时候会忽略这个值。  
# 总结  
# 设为1当然是最安全的,但性能页是最差的(相对其他两个参数而言,但不是不能接受)。如果对数据一致性和完整性要求不高,完全可以设为2,如果只最求性能,例如高并发写的日志服务器,设为0来获得更高性能  
  
innodb_log_buffer_size = 2M  
# 此参数确定些日志文件所用的内存大小,以M为单位。缓冲区更大能提高性能,但意外的故障将会丢失数据。MySQL开发人员建议设置为1-8M之间  
  
innodb_log_file_size = 32M  
# 此参数确定数据日志文件的大小,更大的设置可以提高性能,但也会增加恢复故障数据库所需的时间  
  
innodb_log_files_in_group = 3  
# 为提高性能,MySQL可以以循环方式将日志文件写到多个文件。推荐设置为3  
  
innodb_max_dirty_pages_pct = 90  
# innodb主线程刷新缓存池中的数据,使脏数据比例小于90%  
  
innodb_lock_wait_timeout = 120   
# InnoDB事务在被回滚之前可以等待一个锁定的超时秒数。InnoDB在它自己的锁定表中自动检测事务死锁并且回滚事务。InnoDB用LOCK TABLES语句注意到锁定设置。默认值是50秒  
  
bulk_insert_buffer_size = 8M  
# 批量插入缓存大小, 这个参数是针对MyISAM存储引擎来说的。适用于在一次性插入100-1000+条记录时, 提高效率。默认值是8M。可以针对数据量的大小,翻倍增加。  
  
myisam_sort_buffer_size = 8M  
# MyISAM设置恢复表之时使用的缓冲区的尺寸,当在REPAIR TABLE或用CREATE INDEX创建索引或ALTER TABLE过程中排序 MyISAM索引分配的缓冲区  
  
myisam_max_sort_file_size = 10G  
# 如果临时文件会变得超过索引,不要使用快速排序索引方法来创建一个索引。注释:这个参数以字节的形式给出  
  
myisam_repair_threads = 1  
# 如果该值大于1,在Repair by sorting过程中并行创建MyISAM表索引(每个索引在自己的线程内)    
  
interactive_timeout = 28800  
# 服务器关闭交互式连接前等待活动的秒数。交互式客户端定义为在mysql_real_connect()中使用CLIENT_INTERACTIVE选项的客户端。默认值:28800秒(8小时)  
  
wait_timeout = 28800  
# 服务器关闭非交互连接之前等待活动的秒数。在线程启动时,根据全局wait_timeout值或全局interactive_timeout值初始化会话wait_timeout值,  
# 取决于客户端类型(由mysql_real_connect()的连接选项CLIENT_INTERACTIVE定义)。参数默认值:28800秒(8小时)  
# MySQL服务器所支持的最大连接数是有上限的,因为每个连接的建立都会消耗内存,因此我们希望客户端在连接到MySQL Server处理完相应的操作后,  
# 应该断开连接并释放占用的内存。如果你的MySQL Server有大量的闲置连接,他们不仅会白白消耗内存,而且如果连接一直在累加而不断开,  
# 最终肯定会达到MySQL Server的连接上限数,这会报'too many connections'的错误。对于wait_timeout的值设定,应该根据系统的运行情况来判断。  
# 在系统运行一段时间后,可以通过show processlist命令查看当前系统的连接状态,如果发现有大量的sleep状态的连接进程,则说明该参数设置的过大,  
# 可以进行适当的调整小些。要同时设置interactive_timeout和wait_timeout才会生效。  
  
[mysqldump]  
quick  
max_allowed_packet = 16M #服务器发送和接受的最大包长度  
  
[myisamchk]  
key_buffer_size = 8M  
sort_buffer_size = 8M  
read_buffer = 4M  
write_buffer = 4M

 

参考:

https://blog.csdn.net/dc_726/article/details/12340349

https://blog.csdn.net/dc_726/article/details/9336193

https://blog.linuxeye.cn/380.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,中途没有经过路由,所以家用路由器的内网之间访问只需两层交换机就行)

windows命令行-文件-命令行输出重定向

无论是shell 还是 dos ,用法是一样的。

一共有4个输出到文件的命令,现以jar命令打war包举例说明:

命令 说明 举例
> 正常输出覆盖指定文件  jar -xvf my.war @select.txt  > output.txt
2> 正常输出尾部追加到指定文件  jar -xvf my.war @select.txt  2> output.txt
>> 异常输出覆盖指定文件  jar -xvf my.war @select.txt  >> error.txt
2>> 异常输出尾部追加到指定文件  jar -xvf my.war @select.txt  2>> error.txt

 

# 我这里用到 的 重定向 测试
netstat -r >/Users/cool/Desktop/test.txt

 

网络原理-计算机网络详解-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)