本文原做者:“竹千代”,原文由“玉刚说”写做平台提供写做赞助,原文版权归“玉刚说”微信公众号全部,即时通信网收录时有改动。php
不管是即时通信应用仍是传统的信息系统,Http协议都是咱们最常打交道的网络应用层协议之一,它的重要性可能不须要再强调(有鉴于此,即时通信网整理了大量的有关http协议的文章,若有必要可从本文的相关连接处查阅)。可是实际上不少人(包括我本身),虽然天天都会跟http的代码打交道,但对http了解的并不够深刻。本文就我本身的学习心得,分享一下我认为须要知道的http常见的相关知识点。
HTTP协议所处的TCP/IP协议层级:
<ignore_js_op>
(▲ 上图来自《计算机网络通信协议关系图(中文珍藏版)[附件下载]》,您可下载此图的完整清晰版)html
本文是系列文章中的第3篇,本系列大纲以下:
java
首先咱们来点基础的,看看http报文具体的格式。http报文能够分为请求报文和响应报文,格式大同小异。
主要分为三个部分:
git
请求报文格式:github
1
2
3
4
|
<
method
> <
request-url
> <
version
>
<
headers
>
<
entity-body
>
|
响应报文格式:面试
1
2
3
4
|
<
version
> <
status
> <
reason-phrase
>
<
headers
>
<
entity-body
>
|
从请求报文格式和响应报文格式能够看出,二者主要在起始行上有差别。
这里稍微解释一下各个标签:算法
1
2
3
4
5
|
<
method
> 指请求方法,经常使用的主要是Get、 Post、Head 还有其余一些咱们这里就不说了,有兴趣的能够本身查阅一下
<
version
> 指协议版本,如今一般都是Http/1.1了
<
request-url
> 请求地址
<
status
> 指响应状态码, 咱们熟悉的200、404等等
<
reason-phrase
> 缘由短语,200 OK 、404 Not Found 这种后面的描述就是缘由短语,一般没必要太关注。
|
咱们知道请求方法最经常使用的有Get 和Post两种,面试时也经常会问到这二者有什么区别,一般什么状况下使用。这里咱们来简单说一说。
两个方法之间在传输形式上有一些区别,经过Get方法发起请求时,会将请求参数拼接在request-url尾部,格式是url?param1=xxx¶m2=xxx&[…]。
咱们须要知道,这样传输参数会使得参数都暴露在地址栏中。而且因为url是ASCII编码的,因此参数中若是有Unicode编码的字符,例如汉字,都会编码以后传输。另外值得注意的是,虽然http协议并无对url长度作限制,可是一些浏览器和服务器可能会有限制,因此经过GET方法发起的请求参数不可以太长。而经过POST方法发起的请求是将参数放在请求体中的,因此不会有GET参数的这些问题。
另一点差异就是方法自己的语义上的。GET方法一般是指从服务器获取某个URL资源,其行为能够看做是一个读操做,对同一个URL进行屡次GET并不会对服务器产生什么影响。而POST方法一般是对某个URL进行添加、修改,例如一个表单提交,一般会往服务器插入一条记录。屡次POST请求可能致使服务器的数据库中添加了多条记录。因此从语义上来说,二者也是不能混为一谈的。
数据库
常见的状态码主要有:
编程
在请求报文和响应报文中均可以携带一些信息,经过与其余部分配合,可以实现各类强大的功能。这些信息位于起始行之下与请求实体之间,以键值对的形式,称之为首部。每条首部以回车换行符结尾,最后一个首部额外多一个换行,与实体分隔开。
这里咱们重点关注一下:小程序
01
02
03
04
05
06
07
08
09
10
|
Date
Cache-Control
Last-Modified
Etag
Expires
If-Modified-Since
If-None-Match
If-Unmodified-Since
If-Range
If-Match
|
Http的首部还有不少,但限于篇幅咱们不一一讨论。这些首部都是Http缓存会涉及到的,在下文中咱们会来讲说各自的做用。
请求发送的资源,或是响应返回的资源。
由于篇幅缘由,本文只是对http的相关知识作出简要介绍,若是须要详细了解,则建议阅下如下文章:
当咱们发起一个http请求后,服务器返回所请求的资源,这时咱们能够将该资源的副本存储在本地,这样当再次对该url资源发起请求时,咱们能快速的从本地存储设备中获取到该url资源,这就是所谓的缓存。缓存既能够节约没必要要的网络带宽,又能迅速对http请求作出响应。
先摆出几个概念:
咱们知道,有些url所对应的资源并非一成不变的,服务器中该url的资源可能在必定时间以后会被修改。这时本地缓存中的资源将与服务器一侧的资源有差别。
既然在必定时间以后可能资源会改变,那么在某个时间以前咱们能够认为这个资源没有改变,从而放心大胆的使用缓存资源,当请求时间超过来该时间,咱们认为这个缓存资源可能再也不与服务器端一致了。因此当咱们发起一个请求时,咱们须要先对缓存的资源进行判断,看看究竟咱们是否能够直接使用该缓存资源,这个就叫作新鲜度检测。即每一个资源就像一个食品同样,拥有一个过时时间,咱们吃以前须要先看看有没有过时。
若是发现该缓存资源已经超过了必定的时间,咱们再次发起请求时不会直接将缓存资源返回,而是先去服务器查看该资源是否已经改变,这个就叫作再验证。若是服务器发现对应的url资源并无发生变化,则会返回304 Not Modified,而且再也不返回对应的实体。这称之为再验证命中。相反若是再验证未命中,则返回200 OK,并将改变后的url资源返回,此时缓存能够更新以待以后请求。
咱们看看具体的实现方式。
新鲜度检测:
咱们须要经过检测资源是否超过必定的时间,来判断缓存资源是否新鲜可用。那么这个必定的时间怎么决定呢?实际上是由服务器经过在响应报文中增长Cache-Control:max-age,或是Expire这两个首部来实现的。值得注意的是Cache-Control是http1.1的协议规范,一般是接相对的时间,即多少秒之后,须要结合last-modified这个首部计算出绝对时间。而Expire是http1.0的规范,后面接一个绝对时间。
再验证:
若是经过新鲜度检测发现须要请求服务器进行再验证,那么咱们至少须要告诉服务器,咱们已经缓存了一个什么样的资源了,而后服务器来判断这个缓存资源究竟是不是与当前的资源一致。逻辑是这样没错。那怎么告诉服务器我当前已经有一个备用的缓存资源了呢?咱们能够采用一种称之为条件请求的方式实现再验证。
Http定义了5个首部用于条件请求:
If-Modified-Since 能够结合Last-Modified这个服务器返回的响应首部使用,当咱们发起条件请求时,将Last-Modified首部的值做为If-Modified-Since首部的值传递到服务器,意思是查询服务器的资源自从咱们上一次缓存以后是否有修改。
If-None-Match 须要结合另外一个Etag的服务器返回的响应首部使用。Etag首部实际上能够认为是服务器对文档资源定义的一个版本号。有时候一个文档被修改了,可能所作的修改极为微小,并不须要全部的缓存都从新下载数据。或者说某一个文档的修改周期极为频繁,以致于以秒为时间粒度的判断已经没法知足需求。这个时候可能就须要Etag这个首部来代表这个文档的版号了。发起条件请求时可将缓存时保存下来的Etag的值做为If-None-Match首部的值发送至服务器,若是服务器的资源的Etag与当前条件请求的Etag一致,代表此次再验证命中。
其余三个与断点续传涉及到的相关知识有关,本文暂时不讨论。待我以后写一篇文章来说讲断点续传。
缓存的Http理论知识大体就是这么些。咱们从OkHttp的源码来看看(iOS端能够读一读著名的AFNetworking库的代码),这些知名的开源库是如何利用Http协议实现缓存的。这里咱们假设读者对OkHttp的请求执行流程有了大体的了解,而且只讨论缓存相关的部分。对于OkHttp代码不熟悉的同窗,建议先看看相关代码或是其余文章。
咱们知道OkHttp的请求在发送到服务器以前会通过一系列的Interceptor,其中有一个CacheInterceptor便是咱们须要分析的代码。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
final
InternalCache cache;
@Override
public
Response intercept(Chain chain)
throws
IOException {
Response cacheCandidate = cache !=
null
? cache.get(chain.request())
:
null
;
long
now = System.currentTimeMillis();
CacheStrategy strategy =
new
CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
......
}
|
方法首先经过InternalCache 获取到对应请求的缓存。这里咱们不展开讨论这个类的具体实现,只须要知道,若是以前缓存了该请求url的资源,那么经过request对象能够查找到这个缓存响应。
将获取到的缓存响应,当前时间戳和请求传入CacheStrategy,而后经过执行get方法执行一些逻辑最终能够获取到strategy.networkRequest,strategy.cacheResponse。若是经过CacheStrategy的判断以后,咱们发现此次请求没法直接使用缓存数据,须要向服务器发起请求,那么咱们就经过CacheStrategy为咱们构造的networkRequest来发起此次请求。咱们先来看看CacheStrategy作了哪些事情。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
CacheStrategy.Factory.java
public
Factory(
long
nowMillis, Request request, Response cacheResponse) {
this
.nowMillis = nowMillis;
this
.request = request;
this
.cacheResponse = cacheResponse;
if
(cacheResponse !=
null
) {
this
.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this
.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for
(
int
i =
0
, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if
(
"Date"
.equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
}
else
if
(
"Expires"
.equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
}
else
if
(
"Last-Modified"
.equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
}
else
if
(
"ETag"
.equalsIgnoreCase(fieldName)) {
etag = value;
}
else
if
(
"Age"
.equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -
1
);
}
}
}
}
|
CacheStrategy.Factory的构造方法首先保存了传入的参数,并将缓存响应的相关首部解析保存下来。以后调用的get方法以下
01
02
03
04
05
06
07
08
09
10
|
public
CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if
(candidate.networkRequest !=
null
&& request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return
new
CacheStrategy(
null
,
null
);
}
return
candidate;
}
|
get方法很简单,主要逻辑在getCandidate中,这里的逻辑是若是返回的candidate所持有的networkRequest不为空,表示咱们此次请求须要发到服务器,此时若是请求的cacheControl要求本次请求只使用缓存数据。那么此次请求恐怕只能以失败了结了,这点咱们等会儿回到CacheInterceptor中能够看到。接着咱们看看主要getCandidate的主要逻辑。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
private
CacheStrategy getCandidate() {
// No cached response.
if
(cacheResponse ==
null
) {
return
new
CacheStrategy(request,
null
);
}
// Drop the cached response if it's missing a required handshake.
if
(request.isHttps() && cacheResponse.handshake() ==
null
) {
return
new
CacheStrategy(request,
null
);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if
(!isCacheable(cacheResponse, request)) {
return
new
CacheStrategy(request,
null
);
}
CacheControl requestCaching = request.cacheControl();
if
(requestCaching.noCache() || hasConditions(request)) {
return
new
CacheStrategy(request,
null
);
}
......
}
|
上面这段代码主要列出四种状况下须要忽略缓存,直接想服务器发起请求的状况:
这些状况下直接构造一个包含networkRequest,可是cacheResponse为空的CacheStrategy对象返回。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
private
CacheStrategy getCandidate() {
......
CacheControl responseCaching = cacheResponse.cacheControl();
if
(responseCaching.immutable()) {
return
new
CacheStrategy(
null
, cacheResponse);
}
long
ageMillis = cacheResponseAge();
long
freshMillis = computeFreshnessLifetime();
if
(requestCaching.maxAgeSeconds() != -
1
) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long
minFreshMillis =
0
;
if
(requestCaching.minFreshSeconds() != -
1
) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long
maxStaleMillis =
0
;
if
(!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -
1
) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if
(!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if
(ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader(
"Warning"
,
"110 HttpURLConnection \"Response is stale\""
);
}
long
oneDayMillis =
24
*
60
*
60
* 1000L;
if
(ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader(
"Warning"
,
"113 HttpURLConnection \"Heuristic expiration\""
);
}
return
new
CacheStrategy(
null
, builder.build());
}
......
}
|
若是缓存响应的Cache-Control首部包含immutable,那么说明该资源不会改变。客户端能够直接使用缓存结果。值得注意的是immutable并不属于http协议的一部分,而是由facebook提出的扩展属性。
以后分别计算ageMills、freshMills、minFreshMills、maxStaleMills这四个值。
若是响应缓存没有经过Cache-Control:No-Cache 来禁止客户端使用缓存,而且:
ageMillis + minFreshMillis < freshMillis + maxStaleMillis
这个不等式成立,那么咱们进入条件代码块以后最终会返回networkRequest为空,而且使用当前缓存值构造的CacheStrtegy。
这个不等式到底是什么含义呢?咱们看看这四个值分别表明什么:
minFreshMills 和maxStatleMills都是由请求首部取出的,请求能够根据本身的须要,经过设置:
Cache-Control:min-fresh=xxx、Cache-Control:max-statle=xxx
来控制缓存,以达到对缓存使用严格性的收紧与放松。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private
CacheStrategy getCandidate() {
......
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if
(etag !=
null
) {
conditionName =
"If-None-Match"
;
conditionValue = etag;
}
else
if
(lastModified !=
null
) {
conditionName =
"If-Modified-Since"
;
conditionValue = lastModifiedString;
}
else
if
(servedDate !=
null
) {
conditionName =
"If-Modified-Since"
;
conditionValue = servedDateString;
}
else
{
return
new
CacheStrategy(request,
null
);
// No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return
new
CacheStrategy(conditionalRequest, cacheResponse);
}
|
若是以前的条件不知足,说明咱们的缓存响应已通过期了,这时咱们须要经过一个条件请求对服务器进行再验证操做。接下来的代码比较清晰来,就是经过从缓存响应中取出的Last-Modified,Etag,Date首部构造一个条件请求并返回。
接下来咱们返回CacheInterceptor。
01
02
03
04
05
06
07
08
09
10
11
12
|
// If we're forbidden from using the network and the cache is insufficient, fail.
if
(networkRequest ==
null
&& cacheResponse ==
null
) {
return
new
Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(
504
)
.message(
"Unsatisfiable Request (only-if-cached)"
)
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
|
能够看到,若是咱们返回的networkRequest和cacheResponse都为空,说明咱们即没有可用的缓存,同时请求经过Cache-Controlnly-if-cached只容许咱们使用当前的缓存数据。这个时候咱们只能返回一个504的响应。接着往下看,
1
2
3
4
5
6
|
// If we don't need the network, we're done.
if
(networkRequest ==
null
) {
return
cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
|
若是networkRequest为空,说明咱们不须要进行再验证了,直接将cacheResponse做为请求结果返回。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
Response networkResponse =
null
;
try
{
networkResponse = chain.proceed(networkRequest);
}
finally
{
// If we're crashing on I/O or otherwise, don't leak the cache body.
if
(networkResponse ==
null
&& cacheCandidate !=
null
) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if
(cacheResponse !=
null
) {
if
(networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return
response;
}
else
{
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if
(cache !=
null
) {
if
(HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return
cacheWritingResponse(cacheRequest, response);
}
if
(HttpMethod.invalidatesCache(networkRequest.method())) {
try
{
cache.remove(networkRequest);
}
catch
(IOException ignored) {
// The cache cannot be written.
}
}
}
return
response;
|
若是networkRequest存在不为空,说明此次请求是须要发到服务器的。此时有两种状况,一种cacheResponse不存在,说明咱们没有一个可用的缓存,此次请求只是一个普通的请求。若是cacheResponse存在,说明咱们有一个可能过时了的缓存,此时networkRequest是一个用来进行再验证的条件请求。
无论哪一种状况,咱们都须要经过networkResponse=chain.proceed(networkRequest)获取到服务器的一个响应。不一样的只是若是有缓存数据,那么在获取到再验证的响应以后,须要cache.update(cacheResponse, response)去更新当前缓存中的数据。若是没有缓存数据,那么判断这次请求是否能够被缓存。在知足缓存的条件下,将响应缓存下来,并返回。
OkHttp缓存大体的流程就是这样,咱们从中看出,整个流程是遵循了Http的缓存流程的。
最后咱们总结一下缓存的流程:
OAUTH协议为用户资源的受权提供了一个安全的、开放而又简易的标准。与以往的受权方式不一样之处是OAUTH的受权不会使第三方触及到用户的账号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就能够申请得到该用户资源的受权,所以OAUTH是安全的。oAuth是Open Authorization的简写。
基本上如今主流的第3方登录接口都是使用或者相似于OAuth的实现原理,好比:QQ开放给第3方的登录API、微信登录API、新浪微博帐号登录API等。
OAuth定义的几个角色:
<ignore_js_op>
OAuth受权流程基本流程以下:
<ignore_js_op>
受权流程以有道云笔记为例(以下图):
<ignore_js_op>
从上图能够看出,一个典型的OAuth受权的流程主要分为6步:
苹果已从去年开始强制新上线的APP必须使用HTTPS(详见《苹果即将强制实施 ATS,你的APP准备好切换到HTTPS了吗?》),谷歌的Chrome浏览器也已宣布不支持https的网络将被标记不“不安全”,作为开发者,咱们能感受到HTTPS愈来愈被重视,因此了解https也是必须的。
简单的说:Http + 加密 + 认证 + 完整性保护 = Https。
传统的Http协议是一种应用层的传输协议,Http直接与TCP协议通讯。
其自己存在一些缺点:
所以,在一些须要保证安全性的场景下,好比涉及到银行帐户的请求时,Http没法抵御这些攻击。 Https则能够经过增长的SSL\TLS,支持对于通讯内容的加密,以及对通讯双方的身份进行验证。
近代密码学中加密的方式主要有两类:
对称秘钥加密是指加密与解密过程使用同一把秘钥。这种方式的优势是处理速度快,可是如何安全的从一方将秘钥传递到通讯的另外一方是一个问题。
非对称秘钥加密是指加密与解密使用两把不一样的秘钥。这两把秘钥,一把叫公开秘钥,能够随意对外公开。一把叫私有秘钥,只用于自己持有。获得公开秘钥的客户端可使用公开秘钥对传输内容进行加密,而只有私有秘钥持有者自己能够对公开秘钥加密的内容进行解密。这种方式克服了秘钥交换的问题,可是相对于对称秘钥加密的方式,处理速度较慢。
SSL\TLS的加密方式则是结合了两种加密方式的优势。首先采用非对称秘钥加密,将一个对称秘钥使用公开秘钥加密后传输到对方。对方使用私有秘钥解密,获得传输的对称秘钥。以后双方再使用对称秘钥进行通讯。这样即解决了对称秘钥加密的秘钥传输问题,又利用了对称秘钥的高效率来进行通讯内容的加密与解密。
安全方面的文章,能够详细阅读如下几篇:
SSL\TLS采用的混合加密的方式仍是存在一个问题,即怎么样确保用于加密的公开秘钥确实是所指望的服务器所分发的呢?也许在收到公开秘钥时,这个公开秘钥已经被别人篡改了。所以,咱们还须要对这个秘钥进行认证的能力,以确保咱们通讯的对方是咱们所指望的对象。
目前的作法是使用由数字证书认证机构颁发的公开秘钥证书。服务器的运营人员能够向认证机构提出公开秘钥申请。认证机构在审核以后,会将公开秘钥与共钥证书绑定。服务器就能够将这个共钥证书下发给客户端,客户端在收到证书后,使用认证机构的公开秘钥进行验证。一旦验证成功,便可知道这个秘钥是能够信任的秘钥。
Https的通讯流程:
[1] 更多网络编程资料汇总:
《TCP/IP详解 - 第11章·UDP:用户数据报协议》
《TCP/IP详解 - 第17章·TCP:传输控制协议》
《TCP/IP详解 - 第18章·TCP链接的创建与终止》
《TCP/IP详解 - 第21章·TCP的超时与重传》
《技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)》
《通俗易懂-深刻理解TCP协议(上):理论基础》
《通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》
《理论经典:TCP协议的3次握手与4次挥手过程详解》
《理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程》
《计算机网络通信协议关系图(中文珍藏版)》
《UDP中一个包的大小最大能多大?》
《P2P技术详解(一):NAT详解——详细原理、P2P简介》
《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解》
《P2P技术详解(三):P2P技术之STUN、TURN、ICE详解》
《通俗易懂:快速理解P2P技术中的NAT穿透原理》
《高性能网络编程(一):单台服务器并发TCP链接数到底能够有多少》
《高性能网络编程(二):上一个10年,著名的C10K并发链接问题》
《高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了》
《高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索》
《鲜为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》
《鲜为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》
《鲜为人知的网络编程(三):关闭TCP链接时为何会TIME_WAIT、CLOSE_WAIT》
《鲜为人知的网络编程(四):深刻研究分析TCP的异常关闭》
《鲜为人知的网络编程(五):UDP的链接性和负载均衡》
《鲜为人知的网络编程(六):深刻地理解UDP协议并用好它》
《鲜为人知的网络编程(七):如何让不可靠的UDP变的可靠?》
《网络编程懒人入门(一):快速理解网络通讯协议(上篇)》
《网络编程懒人入门(二):快速理解网络通讯协议(下篇)》
《网络编程懒人入门(三):快速理解TCP协议一篇就够》
《网络编程懒人入门(四):快速理解TCP和UDP的差别》
《网络编程懒人入门(五):快速理解为何说UDP有时比TCP更有优点》
《网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门》
《网络编程懒人入门(七):深刻浅出,全面理解HTTP协议》
《网络编程懒人入门(八):手把手教你写基于TCP的Socket长链接》
《技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解》
《让互联网更快:新一代QUIC协议在腾讯的技术实践分享》
《现代移动端网络短链接的优化手段总结:请求速度、弱网适应、安全保障》
《聊聊iOS中网络编程长链接的那些事》
《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》
《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》
《IPv6技术详解:基本概念、应用现状、技术实践(上篇)》
《IPv6技术详解:基本概念、应用现状、技术实践(下篇)》
《从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路》
《脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》
《脑残式网络编程入门(二):咱们在读写Socket时,究竟在读写什么?》
《脑残式网络编程入门(三):HTTP协议必知必会的一些知识》
>> 更多同类文章 ……
[2] Web端即时通信资料汇总:
《新手入门贴:史上最全Web端即时通信技术原理详解》
《Web端即时通信技术盘点:短轮询、Comet、Websocket、SSE》
《SSE技术详解:一种全新的HTML5服务器推送事件技术》
《Comet技术详解:基于HTTP长链接的Web端实时通讯技术》
《新手快速入门:WebSocket简明教程》
《WebSocket详解(一):初步认识WebSocket技术》
《WebSocket详解(二):技术原理、代码演示和应用案例》
《WebSocket详解(三):深刻WebSocket通讯协议细节》
《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》
《WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)》
《WebSocket详解(六):刨根问底WebSocket与Socket的关系》
《socket.io实现消息推送的一点实践及思路》
《LinkedIn的Web端即时通信实践:实现单机几十万条长链接》
《Web端即时通信技术的发展与WebSocket、Socket.io的技术实践》
《Web端即时通信安全:跨站点WebSocket劫持漏洞详解(含示例代码)》
《开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器》
《使用WebSocket和SSE技术实现Web端消息推送》
《详解Web端通讯方式的演进:从Ajax、JSONP 到 SSE、Websocket》
《MobileIMSDK-Web的网络层框架为什么使用的是Socket.io而不是Netty?》
《理论联系实际:从零理解WebSocket的通讯原理、协议格式、安全性》
《微信小程序中如何使用WebSocket实现长链接(含完整源码)》
>> 更多同类文章 ……