安卓连接Websocket问题
---------------------------------- 2020-04-22-11:20:00
前言:Websocket是方便客户端与服务器实现长连接、可以及时的收到服务器推送过来的消息。一般都用在web端与服务器的连接中。那么我们安卓应该如何连接Websocket呢?
首先了解一下什么是Websocket、Websocket与普通的Socket区别在哪里。先看一下百度百科给出的说法。
OK,了解了Websocket与Socket的区别之后、我们可以着手来连接了。github上有很多安卓连接Websocket的案例,大家也可以自己去找现成的轮子。我几乎每个轮子都用过了。其中遇到过很多的问题。还是觉得Google的OKHTTP最好用。OKHTTP的强大之处不仅在于支持HTTP/HTTPS、还支持Websocket。
Websocket包含两种、WS与WSS。这两个有什么区别呢?就相当于HTTP与HTTPS的区别。不懂的去自行百度哈。我们公司使用wss://xxx.xxx.com,wss开头的网址。其中遇到了很多错误。
最常见的就是timeout、连接超时。在网上搜了好多也没有能解决的例子。直到今天。我在CSDN发现了这条博客,简直是发现了救星一样。先感谢一波作者。
来谈谈我的过程。首先连接超时、其中也出现了HandShake,因为我使用的是WSS协议。所以我感觉是在请求第一次握手的时候、就已经失败了。所以连接不成功,后来又发现好像是证书验证的问题。所以找了好多案例来试,都无功而返。于是我使用OKHTTP来忽略证书验证。
X509TrustManager xtm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{xtm}, new SecureRandom());
HostnameVerifier DO_NOT_VERIFT = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
再使用OKHTTPBuilder进行连接。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(5000, TimeUnit.SECONDS)
.connectTimeout(5000, TimeUnit.SECONDS)
.sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(DO_NOT_VERIFT)
.build();
Request request = new Request.Builder()
.url(Constants.HOSTNAME)
.build();
webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
Log.e(TAG, "onOpen: ");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
Log.e(TAG, "onMessage: " + text);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
Log.e(TAG, "onMessage: ");
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
Log.e(TAG, "onClosing: code = " + code + " reason " + reason);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
Log.e(TAG, "onClosed: code = " + code + " reason " + reason);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
super.onFailure(webSocket, t, response);
Log.e(TAG, "onFailure: " + t.toString());
}
});
这时运行发现打印信息仍然是onFilure:timeOut。已经要发疯了。于是我跟进了一下源码。发现了一些端倪
/**
* Uses {@code request} to connect a new web socket.
*/
@Override public WebSocket newWebSocket(Request request, WebSocketListener listener) {
RealWebSocket webSocket = new RealWebSocket(request, listener, new Random(), pingInterval);
webSocket.connect(this);
return webSocket;
}
再跟进一下RealWebSocket()
public RealWebSocket(Request request, WebSocketListener listener, Random random,
long pingIntervalMillis) {
if (!"GET".equals(request.method())) {
throw new IllegalArgumentException("Request must be GET: " + request.method());
}
this.originalRequest = request;
this.listener = listener;
this.random = random;
this.pingIntervalMillis = pingIntervalMillis;
byte[] nonce = new byte[16];
random.nextBytes(nonce);
this.key = ByteString.of(nonce).base64();
this.writerRunnable = new Runnable() {
@Override public void run() {
try {
while (writeOneFrame()) {
}
} catch (IOException e) {
failWebSocket(e, null);
}
}
};
}
再跟进一下connect方法
public void connect(OkHttpClient client) {
client = client.newBuilder()
.eventListener(EventListener.NONE)
.protocols(ONLY_HTTP1)
.build();
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.timeout().clearTimeout();
call.enqueue(new Callback() {
@Override public void onResponse(Call call, Response response) {
try {
checkResponse(response);
} catch (ProtocolException e) {
failWebSocket(e, response);
closeQuietly(response);
return;
}
// Promote the HTTP streams into web socket streams.
StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);
streamAllocation.noNewStreams(); // Prevent connection pooling!
Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation);
// Process all web socket messages.
try {
listener.onOpen(RealWebSocket.this, response);
String name = "OkHttp WebSocket " + request.url().redact();
initReaderAndWriter(name, streams);
streamAllocation.connection().socket().setSoTimeout(0);
loopReader();
} catch (Exception e) {
failWebSocket(e, null);
}
}
@Override public void onFailure(Call call, IOException e) {
failWebSocket(e, null);
}
});
}
看着是没有什么问题的吧。但是问题就出在这里
final Request request = originalRequest.newBuilder()
.header("Upgrade", "websocket")
.header("Connection", "Upgrade")
.header("Sec-WebSocket-Key", key)
.header("Sec-WebSocket-Version", "13")
.build();
我好像发现了什么惊人的秘密!!
我用websocket在线验证的网址的开发者工具F12试着抓一下打开链接的包。比对一下Request Headers.
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: Upgrade
Host: 222.128.10.136:2224
Origin: http://www.websocket-test.com
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: DvuNBBqVEML+7xA6+71fdg==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3756.400 QQBrowser/10.5.4039.400
发现这根本不一样啊。难怪链接不上。
于是又回过头来修改自己的RequestBuilder。这次我把上面的Header全部都copy过来,添加在自己的请求头中。
Request request = new Request.Builder()
.url(Constants.HOSTNAME)
.addHeader("Accept-Encoding", "gzip, deflate")
.addHeader("Accept-Language", "zh-CN,zh;q=0.9")
.addHeader("Cache-Control", "no-cache")
.addHeader("Connection", "Upgrade")
.addHeader("Origin", "http://www.websocket-test.com")
.addHeader("Pragma", "no-cache")
.addHeader("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits")
.addHeader("Sec-WebSocket-Key", "DvuNBBqVEML+7xA6+71fdg==")
.addHeader("Sec-WebSocket-Version", "13")
.addHeader("Upgrade", "websocket")
.build();
试着运行一下。查看log信息:onOpen: !!!这是什么情况。我蒙了。我更加肯定问题是出在这里。
可能是与服务器端没有沟通好吧!
奇怪的知识又增加了呢~,如果有了解的大神们欢迎给予评价。有要批评的,我也会虚心接受。
提前祝大家五一节日快乐!!