<!DOCTYPE html>
<html>
<head>
<title>查看源</title>
<link rel="canonical" href="/pages/viewpage.action?pageId=$action.page.id" />
<script>
window.WRM=window.WRM||{};window.WRM._unparsedData=window.WRM._unparsedData||{};window.WRM._unparsedErrors=window.WRM._unparsedErrors||{};
WRM._unparsedData["com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path.context-path"]="\u0022\u0022";
if(window.WRM._dataArrived)window.WRM.dataArrived();</script>
<link type="text/css" rel="stylesheet" href="/s/2b107a7e58b6a6cc647e0c6632cd043c-CDN/zh_CN/6441/3dfb154e647c41466ff19113f0197feded41ba10/ae872ac6c9ab3e34265d6fd71347d61f//download/contextbatch/css/_super/batch.css?atlassian.aui.raphael.disabled=true" data-wrm-key="_super" data-wrm-batch-type="context" media="all">
<link type="text/css" rel="stylesheet" href="/s/d41d8cd98f00b204e9800998ecf8427e-CDN/zh_CN/6441/3dfb154e647c41466ff19113f0197feded41ba10/665c8bb8944fcb9bdcc0d6508200760e/_/download/contextbatch/css/plugin.viewsource,-_super/batch.css?atlassian.aui.raphael.disabled=true" data-wrm-key="plugin.viewsource,-super" data-wrm-batch-type="context" media="all">
<link type="text/css" rel="stylesheet" href="/s/3c1de4a43daa0fe17b5080559cff640e-CDN/zh_CN/6441/3dfb154e647c41466ff19113f0197feded41ba10/0ca31745b1ca288788fbf9e3bb602c3e//download/contextbatch/css/page,-_super/batch.css?atlassian.aui.raphael.disabled=true&build-number=6441" data-wrm-key="page,-super" data-wrm-batch-type="context" media="all">
<link type="text/css" rel="stylesheet" href="/s/3c1de4a43daa0fe17b5080559cff640e-CDN/zh_CN/6441/3dfb154e647c41466ff19113f0197feded41ba10/0ca31745b1ca288788fbf9e3bb602c3e//download/contextbatch/css/page,-_super/batch.css?atlassian.aui.raphael.disabled=true&build-number=6441&media=print" media="print" data-wrm-key="page,-super" data-wrm-batch-type="context">
<link type="text/css" rel="stylesheet" href="/s/504290b912ad9ed1e8dbba11f193d045-CDN/zh_CN/6441/3dfb154e647c41466ff19113f0197feded41ba10/b8812ebb1e08c662c8a081352dabf379//download/contextbatch/css/editor-content,-_super/batch.css?atlassian.aui.raphael.disabled=true&confluence.table.resizable=true&confluence.view.edit.transition=true" data-wrm-key="editor-content,-_super" data-wrm-batch-type="context" media="all">
</head>
<body class="mceContentBody aui-theme-default wiki-content fullsize">
<p> </p> <div class="contentLayout2">
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h2>1、技术方案</h2></div>
</div>
</div>
<div class="columnLayout two-equal" data-layout="two-equal">
<div class="cell normal" data-type="normal">
<div class="innerCell">
</div>
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h3>b、长轮询 Long-Polling</h3><p>
</div>
</div>
<div class="columnLayout two-equal" data-layout="two-equal">
<div class="cell normal" data-type="normal">
<div class="innerCell">
</div>
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h3>d、Flash开放自己的Socket接口</h3><p>缺点:</p><ol><li>移动设备覆盖度差,<span style="color: rgb(50,50,50);">2012 年 Adobe 官方宣布不再支持 Android4.1+系统。</span></li></ol></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h2>2、案例</h2><p>给大家找了几个真实案例,大家辨别下各自属于哪种方案。</p><p> </p></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h3><span style="font-size: 16.0px;font-weight: bold;">案例1 </span><a style="font-size: 16.0px;font-weight: bold;" href="http://stock.finance.sina.com.cn/usstock/quotes/.DJI.html">Web版新浪股票</a><span style="font-size: 16.0px;font-weight: bold;">普通版 </span></h3><p>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h1>三、基于Okhttp3.8版本的WebSocket小Demo</h1><table class="wysiwyg-macro" data-macro-name="code" data-macro-id="2ad41927-cfdf-4e10-a53d-ee322e6bc71a" data-macro-parameters="collapse=true|language=java|linenumbers=true|title=WebSocket客户端代码" data-macro-schema-version="1" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9amF2YXx0aXRsZT1XZWJTb2NrZXTlrqLmiLfnq6_ku6PnoIF8bGluZW51bWJlcnM9dHJ1ZXxjb2xsYXBzZT10cnVlfQ&locale=zh_CN&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>//初始化 WebSocket通道
private void initWebSocket() {
mOkHttpClient = new OkHttpClient.Builder()
.build();
mRequest = new Request.Builder()
.url("ws://10.4.227.205:8418")
.build();
mOkHttpClient.newWebSocket(mRequest, mWebSocketListener);
}
// WebSocket通道状态变化监听器
private WebSocketListener mWebSocketListener = new WebSocketListener() {
//通道已建立
public void onOpen(WebSocket webSocket, Response response) {
//向服务端发送消息
webSocket.send("服务端,服务端,收到请回答,收到请回答");
}
//接受来自服务端的String消息
public void onMessage(WebSocket webSocket, String text) {
}
//接受来自服务端的二进制消息
public void onMessage(WebSocket webSocket, ByteString bytes) {
}
//通道关闭中
public void onClosing(WebSocket webSocket, int code, String reason) {
}
//通道已关闭,由于客户端或服务端主动关闭
public void onClosed(WebSocket webSocket, int code, String reason) {
}
//通道初始化失败、发送失败、读取失败
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
//失败重连
tryReconnect();
}
};</pre></td></tr></table><p> </p><p> </p></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h1>四、WebSocket协议</h1><p>学习别人的网络协议好处就是,在自己定义协议时有很好的借鉴作用,更利于业务协议完善性,比如鉴权、扩展性、稳定性。WebSocket协议比较简单,建议每个人get到协议中每个字段的作用。</p><p>看完协议之后,我来问大家两个问题:</p><ol><li>WebSocket和Socket有什么区别?</li><li>WebSocket和Http有什么区别?</li></ol><h2>1、建立WebSocket通道过程交互图</h2><p>
</div>
</div>
<div class="columnLayout two-equal" data-layout="two-equal">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h2>2、Http请求切换到WebSocket协议</h2><p>
</div>
<div class="cell normal" data-type="normal">
<div class="innerCell">
<p> </p><p>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<table class="wysiwyg-macro" data-macro-name="code" data-macro-id="b3e6e602-b007-4796-9510-047bdc0ddada" data-macro-parameters="collapse=true|linenumbers=true|title=Http请求切换WebSocket协议解释" data-macro-schema-version="1" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6dGl0bGU9SHR0cOivt-axguWIh-aNoldlYlNvY2tldOWNj-iuruino-mHinxsaW5lbnVtYmVycz10cnVlfGNvbGxhcHNlPXRydWV9&locale=zh_CN&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>/General/
Request URL: wss://dx.neixin.cn:8610/ //wss, ws代表WebSocket请求
Request Method: GET //必须是GET请求
Status Code: 101 Switching Protocols //101 代表服务端已成功完成协议切换
/Request Headers/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cache-Control: no-cache
Host: dx.neixin.cn:8610
Origin: https://x.sankuai.com
Pragma: no-cache
Upgrade: websocket //请求升级到WebSocket 协议
Connection: Upgrade //通道类型,keep-alive:通道长连,close:请求完毕后通道断开,Upgrade:升级协议
Sec-WebSocket-Key: DXra0t8WFqGcEhUUDXhWgg== //客户端随机生成的Key,校验服务器合法性,生成方式:随机16字节再被base64编码
Sec-WebSocket-Version: 13 //版本号
Sec-WebSocket-Extensions:x-webkit-deflate-frame //可选,希望采用的扩展协议
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36
/Response Headers/
Upgrade: websocket //服务端协议已切换到WebSocket
Connection: upgrade
Sec-WebSocket-Accept: +vYpVaR7s3RSl9YgaL3U2EYRz5o= //用于校验WebSocket服务端是否合法,生成方式:客户端请求参数中的 Sec-WebSocket-Key值+258EAFA5-E914-47DA-95CA-C5AB0DC85B11,再进行base64
Date: Tue, 01 Aug 2017 16:19:37 GMT
Server: Tengine</pre></td></tr></table></div>
</div>
</div>
<div class="columnLayout two-equal" data-layout="two-equal">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h2>3、WebSocket底层通道协议</h2><p>如何向服务端发送一帧消息帧,消息内容是String文本</p><p>如何向服务端发送一帧控制帧,结束WebSocket通道</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-id="19d67453-16a4-4efe-990e-58ca83e71295" data-macro-parameters="language=java|linenumbers=true|theme=Eclipse|title=WebSocket底层通道协议" data-macro-schema-version="1" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9amF2YXx0aGVtZT1FY2xpcHNlfHRpdGxlPVdlYlNvY2tldOW6leWxgumAmumBk-WNj-iurnxsaW5lbnVtYmVycz10cnVlfQ&locale=zh_CN&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>WebSocket Frame Format:
+---------------------------------------------------------------+
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
- - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+</pre></td></tr></table></div>
</div>
<div class="cell normal" data-type="normal">
<div class="innerCell">
<p> </p><p> </p><p> </p><p> </p><ol><li><p>FIN: 1 bit 。表示此帧是否是消息的最后帧,第一帧也可能是最后帧。</p></li><li><p>RSV1,RSV2,RSV3: 各1 bit 。必须是0,除非协商了扩展定义了非0的意义。</p></li><li><p>opcode:4 bit。表示被传输帧的类型:</p><ol><li><p>x0 表示一个后续帧;</p></li><li><p>x1 表示一个文本帧;</p></li><li><p>x2 表示一个二进制帧;</p></li><li><p>x3-7 为以后的非控制帧保留;</p></li><li><p>x8 表示一个连接关闭close;</p></li><li><p>x9 表示一个ping;</p></li><li><p>xA 表示一个pong;</p></li><li><p>xB-F 为以后的控制帧保留。</p></li></ol></li><li><p>Mask: 1 bit。表示净荷是否有掩码(只适用于客户端发送给服务器的消息)。</p></li><li><p>Payload length: 7 bit, 7 + 16 bit, 7 + 64 bit。 净荷长度由可变长度字段表示: 如果是 0~125,就是净荷长度;如果是 126,则接下来 2 字节表示的 16 位无符号整数才是这一帧的长度; 如果是 127,则接下来 8 字节表示的 64 位无符号整数才是这一帧的长度。</p></li><li><p>Masking-key:0或4 Byte。 用于给净荷加掩护,客户端到服务器标记。</p></li><li><p>Extension data: x Byte。默认为0 Byte,除非通过字段Sec-WebSocket-Extensions协商了扩展协议。</p></li><li><p>Application data: y Byte。 要发送的消息,在”Extension data”之后,占据了帧的剩余部分。</p></li><li><p>Payload data: (x + y) Byte。”extension data” + “application data”。</p></li></ol></div>
</div>
</div>
<div class="columnLayout two-equal" data-layout="two-equal">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h2>4、WebSocket应用层协议</h2><p>由业务方自定义</p><table class="relative-table wrapped confluenceTable" style="width: 47.0301%;"><colgroup><col style="width: 30.9343%;" /><col style="width: 26.1995%;" /><col style="width: 42.9293%;" /></colgroup><tbody><tr><th class="confluenceTh"><pre>protocolType协议类型</pre></th><th class="confluenceTh">说明</th><th colspan="1" class="confluenceTh"><pre>operationType操作码</pre></th></tr><tr><td colspan="1" class="confluenceTd">3</td><td colspan="1" class="confluenceTd">PushOrderOperation 推送订单相关操作</td><td colspan="1" class="confluenceTd"><table class="wrapped confluenceTable"><tbody><tr><th class="confluenceTh"><pre>operationType</pre></th><th class="confluenceTh">说明</th></tr><tr><td class="confluenceTd"><p>7</p></td><td class="confluenceTd">接单</td></tr><tr><td class="confluenceTd">2</td><td class="confluenceTd">获取新订单信息</td></tr></tbody></table></td></tr><tr><td class="confluenceTd">11</td><td class="confluenceTd">.BindPoiRequest 门店绑定</td><td colspan="1" class="confluenceTd">无</td></tr><tr><td class="confluenceTd">12</td><td class="confluenceTd">.BindPoiResponse 门店绑定结果</td><td colspan="1" class="confluenceTd">无</td></tr><tr><td class="confluenceTd">13</td><td class="confluenceTd">.Error 主动推送的错误</td><td colspan="1" class="confluenceTd">无</td></tr></tbody></table><p> </p><p> </p></div>
</div>
<div class="cell normal" data-type="normal">
<div class="innerCell">
<p> </p><p> </p><table class="wysiwyg-macro" data-macro-name="code" data-macro-id="6e419c71-ff6c-4997-9d8a-0fb3220ff8f3" data-macro-parameters="title=云端下发接单命令" data-macro-schema-version="1" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6dGl0bGU95LqR56uv5LiL5Y-R5o6l5Y2V5ZG95LukfQ&locale=zh_CN&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>{
"protocolId":"61874bc5-d84e-4b8b-a778-87a3d10e4aae",
"protocolType":3, //协议类型
"protocolName":"PushOrderOperation",
"timestamp":11111,
"data":
{
"platformId":1,
"platformName":"elm",
"operationType":7, //操作码
"operationName":"confirmOrder",
"pushContent":[
{
"name":"orderId_placeholder",
"value":"123456789"
}
]
},
"error":null
}</pre></td></tr></table></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h1>五、OkHttp3.8源码实现核心流程</h1><p>
- - - - - - - +-------------------------------+
@Override public WebSocket newWebSocket(Request request, WebSocketListener listener) {
//1
RealWebSocket webSocket = new RealWebSocket(request, listener, new Random());
webSocket.connect(this);
return webSocket;
}
public void connect(OkHttpClient client) {
client = client.newBuilder()
.protocols(ONLY_HTTP1)
.build();
//2
final Request request = originalRequest.newBuilder()
.header("Upgrade", "websocket")
.header("Connection", "Upgrade")
.header("Sec-WebSocket-Key", key)
.header("Sec-WebSocket-Version", "13")
.build();
call = Internal.instance.newWebSocketCall(client, request);
call.enqueue(new Callback() {
@Override public void onResponse(Call call, Response response) {
try {
//3
checkResponse(response);
} catch (ProtocolException e) {
failWebSocket(e, response);
closeQuietly(response);
return;
}
try {
listener.onOpen(RealWebSocket.this, response);
//4
loopReader();
} catch (Exception e) {
failWebSocket(e, null);
}
}
});
}
void checkResponse(Response response) throws ProtocolException {
if (response.code() != 101) {
throw new ProtocolException("Expected HTTP 101 response but was '"
+ response.code() + " " + response.message() + "'");
}
String headerConnection = response.header("Connection");
if (!"Upgrade".equalsIgnoreCase(headerConnection)) {
throw new ProtocolException("Expected 'Connection' header value 'Upgrade' but was '"
+ headerConnection + "'");
}
String headerUpgrade = response.header("Upgrade");
if (!"websocket".equalsIgnoreCase(headerUpgrade)) {
throw new ProtocolException(
"Expected 'Upgrade' header value 'websocket' but was '" + headerUpgrade + "'");
}
String headerAccept = response.header("Sec-WebSocket-Accept");
String acceptExpected = ByteString.encodeUtf8(key + WebSocketProtocol.ACCEPT_MAGIC)
.sha1().base64();
if (!acceptExpected.equals(headerAccept)) {
throw new ProtocolException("Expected 'Sec-WebSocket-Accept' header value '"
+ acceptExpected + "' but was '" + headerAccept + "'");
}
}
/读取控制帧、消息帧/
public void loopReader() throws IOException {
while (receivedCloseCode == -1) {
reader.processNextFrame();
}
}
void processNextFrame() throws IOException {
readHeader();
if (isControlFrame) {
readControlFrame();
} else {
readMessageFrame();
}
}
private void readHeader() throws IOException {
if (closed) throw new IOException("closed");
int b0;
long timeoutBefore = source.timeout().timeoutNanos();
source.timeout().clearTimeout();
try {
b0 = source.readByte() & 0xff;
} finally {
source.timeout().timeout(timeoutBefore, TimeUnit.NANOSECONDS);
}
opcode = b0 & B0_MASK_OPCODE;
isFinalFrame = (b0 & B0_FLAG_FIN) != 0;
isControlFrame = (b0 & OPCODE_FLAG_CONTROL) != 0;
boolean reservedFlag1 = (b0 & B0_FLAG_RSV1) != 0;
boolean reservedFlag2 = (b0 & B0_FLAG_RSV2) != 0;
boolean reservedFlag3 = (b0 & B0_FLAG_RSV3) != 0;
if (reservedFlag1 || reservedFlag2 || reservedFlag3) {
throw new ProtocolException("Reserved flags are unsupported.");
}
int b1 = source.readByte() & 0xff;
isMasked = (b1 & B1_FLAG_MASK) != 0;
if (isMasked == isClient) {
// Masked payloads must be read on the server. Unmasked payloads must be read on the client.
throw new ProtocolException(isClient
? "Server-sent frames must not be masked."
: "Client-sent frames must be masked.");
}
// Get frame length, optionally reading from follow-up bytes if indicated by special values.
frameLength = b1 & B1_MASK_LENGTH;
if (frameLength == PAYLOAD_SHORT) {
frameLength = source.readShort() & 0xffffL; // Value is unsigned.
} else if (frameLength == PAYLOAD_LONG) {
frameLength = source.readLong();
if (frameLength < 0) {
throw new ProtocolException(
"Frame length 0x" + Long.toHexString(frameLength) + " > 0x7FFFFFFFFFFFFFFF");
}
}
frameBytesRead = 0;
}
private void readMessageFrame() throws IOException {
int opcode = this.opcode;
if (opcode != OPCODE_TEXT && opcode != OPCODE_BINARY) {
throw new ProtocolException("Unknown opcode: " + toHexString(opcode));
}
Buffer message = new Buffer();
readMessage(message);
//不要在onReadMessage做耗时操作,因为当前是读线程,影响服务端Push消息时效性
if (opcode == OPCODE_TEXT) {
frameCallback.onReadMessage(message.readUtf8());
} else {
frameCallback.onReadMessage(message.readByteString());
}
}
/发送消息/
private synchronized boolean send(ByteString data, int formatOpcode) {
//写缓存队列容量有限制 最大16M
if (queueSize + data.size() > MAX_QUEUE_SIZE) {
close(CLOSE_CLIENT_GOING_AWAY, null);
return false;
}
// Enqueue the message frame.
queueSize += data.size();
messageAndCloseQueue.add(new Message(formatOpcode, data));
//5
runWriter();
return true;
}
private void runWriter() {
assert (Thread.holdsLock(this));
if (executor != null) {
executor.execute(writerRunnable);
}
}
this.writerRunnable = new Runnable() {
@Override public void run() {
try {
while (writeOneFrame()) {
}
} catch (IOException e) {
failWebSocket(e, null);
}
}
};
}
//发送到服务端的消息通过掩码处理进行加密
public static void toggleMask() {
//原始数据
byte[] data = new String("我是demo,我要4字节掩码加密").getBytes();
//四个字节的掩码
byte[] mask = {0x24, 0x48, 0x23, 0x54};
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (data[i] ^ mask[i % 4]);
}
//data即可变成 加密或解密后的数据
}</pre></td></tr></table><p>除了Okhttp3.5+支持WebSocket,还有<a href="https://github.com/koush/AndroidAsync">AndroidAsync</a> 、<a href="https://github.com/socketio/socket.io-client-java">socket.io-client-java</a></p><p> </p></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h1>六、全家桶项目实践</h1><ol><li>需要建立断开后自动重连机制。<ol><li>检测断开,由协议底层定时ping和pong检测,当写失败代表断开,可能是网络等问题造成</li><li>重连时机,通路状态WebSocketListener回调onFailure()</li><li>重连策略,无限次重连,首次断开立刻重连,重连间隔步进值10s,最大重连间隔120s</li></ol></li><li>通过回传ACK统计关键消息到达率,以及服务端消息重传机制。</li><li>一帧的最大传输量是多少? 协议上,最大传输 2^63 ,实际上,取决于两端的消息缓存区容量,Okhttp定义的发送缓存区为16M。</li><li>在客户端onMessage回调中,不要做耗时操作,影响Push消息时效性。</li><li>在Android中运行WebSocketServer ,Android4.4+版本WebView中的H5可以通过WebSocket与本地Server直连通信。</li><li>WebSocket如何把异步调用 转化为 同步调用?实现同步的HTTP请求-响应模型?比如通过WebSocket完成通过OrderId同步查询OrderInfo的过程。<br /><ol><li>查询OrderInfo的消息体里加一个唯一消息ID,另外有一个与此ID对应的CallBack,并由Map维护关系</li><li>服务器端回复的消息里也加上同样的ID,客户端收到后找到ID对应的CallBack回调即可。</li><li>查询线程在未收到结果前一直阻塞,直到CallBack回调setResult</li></ol></li></ol><p> </p></div>
</div>
</div>
<div class="columnLayout single" data-layout="single">
<div class="cell normal" data-type="normal">
<div class="innerCell">
<h1>七、总结</h1><ol><li>服务端推送消息功能技术方案汇总</li><li>WebSocket协议和源码实现</li><li>WebSocket实践经验</li></ol><p>服务端与客户端之间,存在频繁的双向通信、要求高时效性,用于交换数据 或 相互控制的场景,可选用WebSocket。</p><h1 class="t">八、留给大家一个问题</h1><p>不管HTTP协议还是WebSocket协议都是人制订的。</p><p><a href="https://www.baidu.com/link?url=atV73bKs5neq5ErUDNBQ0VkmiHe663I4hVDXgGePGLFSKSuoUVN4-4rLtLSK3jrk-WP_Vo_jx5hGm2DjN5Oe0a&wd=&eqid=821f036400034d5e00000003596b866c">为什么HTTP协议要求完成一次请求后,WebServer要主动断开Tcp连接呢 ? 为什么HttpWebServer不能主动推送消息呢?</a></p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p> </p><p>备注</p><ol><li>WebSocket协议地址 <a href="https://tools.ietf.org/html/rfc6455">https://tools.ietf.org/html/rfc6455</a></li><li>各方案的实现 <a href="http://blog.zhangruipeng.me/2015/10/22/Web-Connectivity/">http://blog.zhangruipeng.me/2015/10/22/Web-Connectivity/</a></li><li>后端均衡负载<a href="http://www.cnblogs.com/549294286/p/5067865.html">http://www.cnblogs.com/549294286/p/5067865.html</a></li><li>各方案对比</li></ol><table class="relative-table wrapped confluenceTable" style="width: 99.7559%;"><colgroup><col style="width: 12.6263%;" /><col style="width: 15.2258%;" /><col style="width: 14.1117%;" /><col style="width: 19.7564%;" /><col style="width: 38.0273%;" /></colgroup><thead><tr><th class="confluenceTh">对比项</th><th class="confluenceTh">传统轮询</th><th class="confluenceTh">长轮询</th><th class="confluenceTh">服务器发送事件</th><th class="confluenceTh">WebSocket</th></tr></thead><tbody><tr><td class="confluenceTd">浏览器支持</td><td class="confluenceTd">几乎所有现代浏览器</td><td class="confluenceTd">几乎所有现代浏览器</td><td class="confluenceTd">Firefox 6+ Chrome 6+ Safari 5+ Opera 10.1+</td><td class="confluenceTd">IE 10+ Edge Firefox 4+ Chrome 4+ Safari 5+ Opera 11.5+</td></tr><tr><td class="confluenceTd">服务器负载</td><td class="confluenceTd">较少的CPU资源,较多的内存资源和带宽资源</td><td class="confluenceTd">与传统轮询相似,但是占用带宽较少</td><td class="confluenceTd">与长轮询相似,除非每次发送请求后服务器不需要断开连接</td><td class="confluenceTd">无需循环等待(长轮询),CPU和内存资源不以客户端数量衡量,而是以客户端事件数衡量。四种方式里性能最佳。</td></tr><tr><td class="confluenceTd">客户端负载</td><td class="confluenceTd">占用较多的内存资源与请求数。</td><td class="confluenceTd">与传统轮询相似。</td><td class="confluenceTd">浏览器中原生实现,占用资源很小。</td><td class="confluenceTd">同Server-Sent Event。</td></tr><tr><td class="confluenceTd">延迟</td><td class="confluenceTd">非实时,延迟取决于请求间隔。</td><td class="confluenceTd">同传统轮询。</td><td class="confluenceTd">非实时,默认3秒延迟,延迟可自定义。</td><td class="confluenceTd">实时。</td></tr><tr><td class="confluenceTd">实现复杂度</td><td class="confluenceTd">非常简单。</td><td class="confluenceTd">需要服务器配合,客户端实现非常简单。</td><td class="confluenceTd">需要服务器配合,而客户端实现甚至比前两种更简单。</td><td class="confluenceTd"><p>需要Socket程序实现和额外端口,客户端实现简单。</p><p> </p></td></tr></tbody></table></div>
</div>
</div>
</div>
<p> </p>
</body>
</html>