WebSocket协议详解文章如何撰写?
- 内容介绍
- 文章标签
- 相关推荐
本文共计3621个文字,预计阅读时间需要15分钟。
WebSocket协议运行在TCP协议之上,与HTTP协议同属于应用层网络数据传输协议。WebSocket相比HTTP协议最大的特点是:允许服务器主动向客户端推送数据(从服务器端主动推送数据到客户端)。因此,WebSocket解决了HTTP+1.1协议中客户端被动等待数据的问题。
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:`允许服务端主动向客户端推送数据`(从而解决Http 1.1协议实现中客户端只能通过轮询方式获取服务端推送数据造成的资源消耗和消息延时等问题)。 WebSocket 协议诞生于2008年6月,并在2011年12月成为 [RFC6455] 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。 WebSocket协议定义中客户端和服务端只需要`完成一次握手`,两者之间就可以建立持久性的连接,并进行双向数据传输。而且WebSocket 握手阶段采用的是HTTP 协议;完成协议握手后,后续的数据通信采用WebSocket数据格式通信。WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据(从而解决Http 1.1协议实现中客户端只能通过轮询方式获取服务端推送数据造成的资源消耗和消息延时等问题)。
WebSocket 协议诞生于2008年6月,并在2011年12月成为 RFC6455
www.rfc-editor.org/rfc/rfc6455.txt 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。
WebSocket协议定义中客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。而且WebSocket 握手阶段采用的是HTTP 协议;完成协议握手后,后续的数据通信采用WebSocket数据格式通信。
这篇文章,我们按照如下顺序,学习一下 WebSocket 全向数据传输协议:
- WebSocket 诞生;
- WebSocket 简介;
- WebSocket 使用举例;
- WebSocket 协议握手;
- WebSocket 数据帧格式详解;
- WebSocket 关闭连接;
- WebSocket 心跳消息;
早期的很多网站为具备数据推送能力,所在用的技术基本都是HTTP轮询。
轮询是由由客户端每隔一段时间(如每隔5s)向服务器发出HTTP请求,服务端接收到请求后向客户端返回最新的数据。
客户端的轮询方式一般为短轮询或长轮询。
短轮询:
一般是由客户端每隔一段时间(如每隔5s)向服务器发起一次普通 HTTP 请求。服务端查询当前接口是否有数据更新,若有数据更新则向客户端返回最新数据,若无则提示客户端无数据更新。长轮询:
一般是由客户端向服务端发出一个设置较长网络超时时间的 HTTP 请求,并在Http连接超时前,不主动断开连接;待客户端超时或有数据返回后,再次建立一个同样的Http请求,重复以上过程。
以上两种轮询方式也带来了很明显的缺点:
- 首先,客户端需要不断的向服务器发出请求,在消耗较多客户端资源的情况下,服务端并不一定有新的数据下发;
- 其次,HTTP协议请求与回复消息中,需包含较长的头部信息,其中真正有效的数据有可能只占较小的一部分,带来较多的带宽资源消耗。
- 另外,若服务端在同一时间存在连续频繁的数据变化(例如:聊天室场景中),客户端获知数据更新相对较慢(可能存在时间的滞后性)无法保证客户端的用户体验。
因此,工程师们一直在思考,有没有更好的方法,可以减少资源的消耗,同时提高客户端的用户体验 !,在以上情况下 WebSocket 孕育而生。
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据。
WebSocket 协议诞生于2008年6月,并在2011年12月成为 RFC6455
www.rfc-editor.org/rfc/rfc6455.txt 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。
WebSocket协议定义中客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
建立在 TCP 协议之上,属于应用层协议;与 HTTP 协议有着良好的兼容性:
WebSocket 默认端口是80,WebSocket Over SSL的默认端口是443;
WebSocket 握手阶段采用的是 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。数据格式比较轻量,性能开销小:
WebSocket数据帧格式相对简单,用于协议控制的数据包头部相对较小(在不包含扩展的情况下,对于服务端到客户端的内容,此头部大小只有2至10字节)。数据实时性较高:
由于WebSocket是全双工的,所以服务端可以随时主动给客户端下发数据,相对于HTTP请求需要待客户端发起请求服务端才能响应,延迟明显更少,用户体验更高。- 可以发送的载荷数据可以是
文本数据,也可以是二进制数据;
WebSocket协议的数据载荷可以是普通的文本数据,也可以是二进制数据,数据量相对较大时,还可以分片多帧进行数据发送与接收。
WebSocket 的协议标识符是ws(如果Over SSL,则为wss):
/**
* WebSocketAgent
*
* @author blog.csdn.net/xiaxl
*/
public class WebSocketAgent {
//
private static final String TAG = "WebSocketAgent";
// OkHttpClient
private OkHttpClient mOkHttpClient;
// WebSocket
private WebSocket mWebSocket;
private WebSocketCallback mWebSocketCallback;
public WebSocketAgent() {
mOkHttpClient = new OkHttpClient.Builder()
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
// 每隔 30s 客户端主动发送ping保活消息
.pingInterval(30, TimeUnit.SECONDS)
.build();
Log.d(TAG, "newWebSocket connection");
}
public void connect() {
//建立连接
Request request = new Request.Builder()
.url(AgentConstant.WEBSOCKET_APP_URL)
.build();
mWebSocket = mOkHttpClient.newWebSocket(request, new OkHttpWebSocketListener());
}
public void sendMessage(String message) {
Log.d(TAG, "sendMessage text: " + message);
if (mWebSocket != null) {
mWebSocket.send(message);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void sendMessage(byte... data) {
Log.d(TAG, "sendMessage byte: " + data);
if (mWebSocket != null) {
ByteString bs = ByteString.of(data);
mWebSocket.send(bs);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void close(int code, String reason) {
if (mWebSocket != null) {
mWebSocket.close(code, reason);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void setWebSocketCallback(WebSocketCallback callback) {
mWebSocketCallback = callback;
}
public class OkHttpWebSocketListener extends WebSocketListener {
@Override
public void onOpen(WebSocket webSocket, Response response) {
// 注意:这里回到的是异步线程
if (response.code() == 101) {
// 连接成功
}
}
// 当收到文本(类型{@code 0x1})消息时调用
@Override
public void onMessage(WebSocket webSocket, String text) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketMessage(text);
}
}
// 当收到二进制(类型为{@code 0x2})消息时调用。
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// 注意:这里回到的是异步线程
}
// 当远程对等体指示不再有传入的消息将被传输时调用。
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketClosing(code, reason);
}
}
// 当两个对等方都表示不再传输消息并且连接已成功释放时调用。 没有进一步的电话给这位听众。
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketClosed(code, reason);
}
}
// 由于从网络读取或向网络写入错误而关闭Web套接字时调用。
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketError(t, response);
}
// TODO 切换到主线程中后,考虑一下断开连接后的重试逻辑
}
}
public abstract static class WebSocketCallback {
public void onWebsocketConnected() {}
public void onWebSocketMessage(String text) {}
public void onWebSocketClosing(int code, String reason) {}
public void onWebSocketClosed(int code, String reason) {}
public void onWebSocketError(Throwable t, Response response) {}
}
}
四、协议握手
WebSocket 协议握手复用了HTTP协议:
客户端通过HTTP请求与WebSocket服务端协商升级协议到 Websocket 协议。服务端通过HTTP响应数据回应客户端的升级请求,完成协议握手。
完成协议握手后,后续的数据交换则遵照 WebSocket 的协议进行。
4.1 握手请求客户端通过 HTTP/1.1 协议 GET 请求发起协议升级请求:
本文共计3621个文字,预计阅读时间需要15分钟。
WebSocket协议运行在TCP协议之上,与HTTP协议同属于应用层网络数据传输协议。WebSocket相比HTTP协议最大的特点是:允许服务器主动向客户端推送数据(从服务器端主动推送数据到客户端)。因此,WebSocket解决了HTTP+1.1协议中客户端被动等待数据的问题。
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:`允许服务端主动向客户端推送数据`(从而解决Http 1.1协议实现中客户端只能通过轮询方式获取服务端推送数据造成的资源消耗和消息延时等问题)。 WebSocket 协议诞生于2008年6月,并在2011年12月成为 [RFC6455] 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。 WebSocket协议定义中客户端和服务端只需要`完成一次握手`,两者之间就可以建立持久性的连接,并进行双向数据传输。而且WebSocket 握手阶段采用的是HTTP 协议;完成协议握手后,后续的数据通信采用WebSocket数据格式通信。WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据(从而解决Http 1.1协议实现中客户端只能通过轮询方式获取服务端推送数据造成的资源消耗和消息延时等问题)。
WebSocket 协议诞生于2008年6月,并在2011年12月成为 RFC6455
www.rfc-editor.org/rfc/rfc6455.txt 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。
WebSocket协议定义中客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。而且WebSocket 握手阶段采用的是HTTP 协议;完成协议握手后,后续的数据通信采用WebSocket数据格式通信。
这篇文章,我们按照如下顺序,学习一下 WebSocket 全向数据传输协议:
- WebSocket 诞生;
- WebSocket 简介;
- WebSocket 使用举例;
- WebSocket 协议握手;
- WebSocket 数据帧格式详解;
- WebSocket 关闭连接;
- WebSocket 心跳消息;
早期的很多网站为具备数据推送能力,所在用的技术基本都是HTTP轮询。
轮询是由由客户端每隔一段时间(如每隔5s)向服务器发出HTTP请求,服务端接收到请求后向客户端返回最新的数据。
客户端的轮询方式一般为短轮询或长轮询。
短轮询:
一般是由客户端每隔一段时间(如每隔5s)向服务器发起一次普通 HTTP 请求。服务端查询当前接口是否有数据更新,若有数据更新则向客户端返回最新数据,若无则提示客户端无数据更新。长轮询:
一般是由客户端向服务端发出一个设置较长网络超时时间的 HTTP 请求,并在Http连接超时前,不主动断开连接;待客户端超时或有数据返回后,再次建立一个同样的Http请求,重复以上过程。
以上两种轮询方式也带来了很明显的缺点:
- 首先,客户端需要不断的向服务器发出请求,在消耗较多客户端资源的情况下,服务端并不一定有新的数据下发;
- 其次,HTTP协议请求与回复消息中,需包含较长的头部信息,其中真正有效的数据有可能只占较小的一部分,带来较多的带宽资源消耗。
- 另外,若服务端在同一时间存在连续频繁的数据变化(例如:聊天室场景中),客户端获知数据更新相对较慢(可能存在时间的滞后性)无法保证客户端的用户体验。
因此,工程师们一直在思考,有没有更好的方法,可以减少资源的消耗,同时提高客户端的用户体验 !,在以上情况下 WebSocket 孕育而生。
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据。
WebSocket 协议诞生于2008年6月,并在2011年12月成为 RFC6455
www.rfc-editor.org/rfc/rfc6455.txt 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。
WebSocket协议定义中客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
建立在 TCP 协议之上,属于应用层协议;与 HTTP 协议有着良好的兼容性:
WebSocket 默认端口是80,WebSocket Over SSL的默认端口是443;
WebSocket 握手阶段采用的是 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。数据格式比较轻量,性能开销小:
WebSocket数据帧格式相对简单,用于协议控制的数据包头部相对较小(在不包含扩展的情况下,对于服务端到客户端的内容,此头部大小只有2至10字节)。数据实时性较高:
由于WebSocket是全双工的,所以服务端可以随时主动给客户端下发数据,相对于HTTP请求需要待客户端发起请求服务端才能响应,延迟明显更少,用户体验更高。- 可以发送的载荷数据可以是
文本数据,也可以是二进制数据;
WebSocket协议的数据载荷可以是普通的文本数据,也可以是二进制数据,数据量相对较大时,还可以分片多帧进行数据发送与接收。
WebSocket 的协议标识符是ws(如果Over SSL,则为wss):
/**
* WebSocketAgent
*
* @author blog.csdn.net/xiaxl
*/
public class WebSocketAgent {
//
private static final String TAG = "WebSocketAgent";
// OkHttpClient
private OkHttpClient mOkHttpClient;
// WebSocket
private WebSocket mWebSocket;
private WebSocketCallback mWebSocketCallback;
public WebSocketAgent() {
mOkHttpClient = new OkHttpClient.Builder()
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
// 每隔 30s 客户端主动发送ping保活消息
.pingInterval(30, TimeUnit.SECONDS)
.build();
Log.d(TAG, "newWebSocket connection");
}
public void connect() {
//建立连接
Request request = new Request.Builder()
.url(AgentConstant.WEBSOCKET_APP_URL)
.build();
mWebSocket = mOkHttpClient.newWebSocket(request, new OkHttpWebSocketListener());
}
public void sendMessage(String message) {
Log.d(TAG, "sendMessage text: " + message);
if (mWebSocket != null) {
mWebSocket.send(message);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void sendMessage(byte... data) {
Log.d(TAG, "sendMessage byte: " + data);
if (mWebSocket != null) {
ByteString bs = ByteString.of(data);
mWebSocket.send(bs);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void close(int code, String reason) {
if (mWebSocket != null) {
mWebSocket.close(code, reason);
} else {
Log.e(TAG, "mWebSocket is null, please call connect first.", null);
}
}
public void setWebSocketCallback(WebSocketCallback callback) {
mWebSocketCallback = callback;
}
public class OkHttpWebSocketListener extends WebSocketListener {
@Override
public void onOpen(WebSocket webSocket, Response response) {
// 注意:这里回到的是异步线程
if (response.code() == 101) {
// 连接成功
}
}
// 当收到文本(类型{@code 0x1})消息时调用
@Override
public void onMessage(WebSocket webSocket, String text) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketMessage(text);
}
}
// 当收到二进制(类型为{@code 0x2})消息时调用。
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// 注意:这里回到的是异步线程
}
// 当远程对等体指示不再有传入的消息将被传输时调用。
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketClosing(code, reason);
}
}
// 当两个对等方都表示不再传输消息并且连接已成功释放时调用。 没有进一步的电话给这位听众。
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketClosed(code, reason);
}
}
// 由于从网络读取或向网络写入错误而关闭Web套接字时调用。
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
// 注意:这里回到的是异步线程
if (mWebSocketCallback != null) {
mWebSocketCallback.onWebSocketError(t, response);
}
// TODO 切换到主线程中后,考虑一下断开连接后的重试逻辑
}
}
public abstract static class WebSocketCallback {
public void onWebsocketConnected() {}
public void onWebSocketMessage(String text) {}
public void onWebSocketClosing(int code, String reason) {}
public void onWebSocketClosed(int code, String reason) {}
public void onWebSocketError(Throwable t, Response response) {}
}
}
四、协议握手
WebSocket 协议握手复用了HTTP协议:
客户端通过HTTP请求与WebSocket服务端协商升级协议到 Websocket 协议。服务端通过HTTP响应数据回应客户端的升级请求,完成协议握手。
完成协议握手后,后续的数据交换则遵照 WebSocket 的协议进行。
4.1 握手请求客户端通过 HTTP/1.1 协议 GET 请求发起协议升级请求:

