转载请注明出处:http://blog.csdn.net/llew2011/article/details/72987090
在上篇文章Android 源码系列之<十五>,深入浅出WebSocket,打造自己的即时聊天交互系统<上>中主要讲解了WebSocket协议,然后通过实战方式展示了WebSocket的通信过程,这篇文章我将从源码的角度带领小伙伴们深入理解一下autobahn以及okhttp的Socket通信源码,如果你对上述项目的WebSocket实现比较清楚了,请跳过本文(*^__^*) ……
我们首先从GitHub上clone来下autobahn的源码,运行一下该项目,效果如下:
autobahn给出的demo我稍微做了修改,IP和端口号是我从网上找的一个在线测试地址,当点击Connect按钮后就会链接输入框给定的IP地址和端口号,然后进行链接操作,链接成功后通过Toast弹出服务器端返回的消息,表示Socket链接已经建立,可以进行通信了,然后我们可以在下方的输入框中输入任意内容给服务器,为了弄清楚通信过程,我们通过分析点击Connect按钮看看发生的流程,点击CONNECT按钮会调用start()方法,源码如下:
private void start() {
final String wsuri = "ws://" + mHostname.getText() + ":" + mPort.getText();
mStatusline.setText("Status: Connecting to " + wsuri + " ..");
setButtonDisconnect();
try {
mConnection.connect(wsuri, new WebSocketConnectionHandler() {
@Override
public void onOpen() {
mStatusline.setText("Status: Connected to " + wsuri);
savePrefs();
mSendMessage.setEnabled(true);
mMessage.setEnabled(true);
}
@Override
public void onTextMessage(String payload) {
alert("Got echo: " + payload);
Log.e("---WebSocket---", payload);
}
@Override
public void onClose(int code, String reason) {
alert("Connection lost.");
mStatusline.setText("Status: Ready.");
setButtonConnect();
mSendMessage.setEnabled(false);
mMessage.setEnabled(false);
}
});
} catch (WebSocketException e) {
Log.d(TAG, e.toString());
}
}
start()方法就是进行链接Socket的核心,首先根据输入框输入的IP地址和端口生成一个wsuri地址(该处值为:ws://121.40.165.18:8088),然后设置Connect按钮为不可点击状态,最后通过mConnection的connect()方法进行Socket链接,mConnection的定义如下:
private final WebSocket mConnection = new WebSocketConnection();
mConnection是WebSocket类型,该类是抽象类,它的实现类是WebSocketConnection,那也就是说mConnection调用的connect()方法走的是WebSocketConnection的connect()方法,其源码如下:
public void connect(String wsUri, WebSocket.ConnectionHandler wsHandler) throws WebSocketException {
connect(wsUri, null, wsHandler, new WebSocketOptions(), null);
}
connect()方法又调用了其重载方法connect(),源码如下:
public void connect(String wsUri, String[] wsSubprotocols, WebSocket.ConnectionHandler wsHandler, WebSocketOptions options, List headers) throws WebSocketException {
// don't connect if already connected .. user needs to disconnect first
if (isConnected()) {
throw new WebSocketException("already connected");
}
// parse WebSockets URI
try {
mWsUri = new URI(wsUri);
if (!mWsUri.getScheme().equals("ws") && !mWsUri.getScheme().equals("wss")) {
throw new WebSocketException("unsupported scheme for WebSockets URI");
}
mWsScheme = mWsUri.getScheme();
if (mWsUri.getPort() == -1) {
if (mWsScheme.equals("ws")) {
mWsPort = 80;
} else {
mWsPort = 443;
}
} else {
mWsPort = mWsUri.getPort();
}
if (mWsUri.getHost() == null) {
throw new WebSocketException("no host specified in WebSockets URI");
} else {
mWsHost = mWsUri.getHost();
}
if (mWsUri.getRawPath() == null || mWsUri.getRawPath().equals("")) {
mWsPath = "/";
} else {
mWsPath = mWsUri.getRawPath();
}
if (mWsUri.getRawQuery() == null || mWsUri.getRawQuery().equals("")) {
mWsQuery = null;
} else {
mWsQuery = mWsUri.getRawQuery();
}
} catch (URISyntaxException e) {
throw new WebSocketException("invalid WebSockets URI");
}
mWsSubprotocols = wsSubprotocols;
mWsHeaders = headers;
mWsHandler = wsHandler;
// make copy of options!
mOptions = new WebSocketOptions(options);
// set connection active
mActive = true;
// reset value
onCloseCalled = false;
// use async connector on short-lived background thread
new WebSocketConnector().start();
}
该方法首先判断链接是否建立,如果建立则直接抛了一个异常,因为链接一旦建立了就不需要再次建立,然后检测给定的URI协议和端口是否符合规范,如果都符合WebSocket协议规范则启动后台线程WebSocketConnector来进行消息通信,该类源码如下:
/**
* Asynchronous socket connector.
*/
private class WebSocketConnector extends Thread {
public void run() {
Thread.currentThread().setName("WebSocketConnector");
/*
* connect TCP socket
*/
try {
if (mWsScheme.equals("wss")) {
mSocket = SSLSocketFactory.getDefault().createSocket();
} else {
mSocket = SocketFactory.getDefault().createSocket();
}
// the following will block until connection was established or
// an error occurred!
mSocket.connect(new InetSocketAddress(mWsHost, mWsPort), mOptions.getSocketConnectTimeout());
// before doing any data transfer on the socket, set socket
// options
mSocket.setSoTimeout(mOptions.getSocketReceiveTimeout());
mSocket.setTcpNoDelay(mOptions.getTcpNoDelay());
} catch (IOException e) {
onClose(WebSocketConnectionHandler.CLOSE_CANNOT_CONNECT,
e.getMessage());
return;
}
if (isConnected()) {
try {
// create & start WebSocket reader
createReader();
// create & start WebSocket writer
createWriter();
// start WebSockets handshake
WebSocketMessage.ClientHandshake hs = new WebSocketMessage.ClientHandshake(
mWsHost + ":" + mWsPort);
hs.mPath = mWsPath;
hs.mQuery = mWsQuery;
hs.mSubprotocols = mWsSubprotocols;
hs.mHeaderList = mWsHeaders;
mWriter.forward(hs);
mPrevConnected = true;
} catch (Exception e) {
onClose(WebSocketConnectionHandler.CLOSE_INTERNAL_ERROR,
e.getMessage());
}
} else {
onClose(WebSocketConnectionHandler.CLOSE_CANNOT_CONNECT,
"Could not connect to WebSocket server");
}
}
}
WebSocketConnector线程启动后先根据传递的scheme判断创建哪种类型的Socket,Socket对象创建完成后根据IP地址和端口号调用其connec()方法和服务器建立链接,然后设置超时等属性,接下来判断该链接是否建立成功,如果链接创建成功,则分别调用createReader()和createWriter()方法创建读写线程进行消息的接收和发送,先看一下createReader()方法,源码如下:
/**
* Create WebSockets background reader.
*/
protected void createReader() throws IOException {
mReader = new WebSocketReader(mMasterHandler, mSocket, mOptions, "WebSocketReader");
mReader.start();
if (DEBUG) Log.d(TAG, "WS reader created and started");
}
createReader()方法启动了WebSocketReader线程并把相关参数传递给了该线程,我们直接进入该线程的run()方法看一下,源码如下:
@Override
public void run() {
try {
do {
// blocking read on socket
int len = mBufferedStream.read(mMessageData, mPosition, mMessageData.length - mPosition);
mPosition += len;
if (len > 0) {
// process buffered data
while (consumeData()) {
}
} else if (mState == STATE_CLOSED) {
mStopped = true;
} else if (len < 0) {
notify(new WebSocketMessage.ConnectionLost());
mStopped = true;
}
} while (!mStopped);
} catch (WebSocketException e) {
notify(new WebSocketMessage.ProtocolViolation(e));
} catch (SocketException e) {
// BufferedInputStream throws when the socket is closed,
// eat the exception if we are already in STATE_CLOSED.
if (mState != STATE_CLOSED && !mSocket.isClosed()) {
// wrap the exception and notify master
notify(new WebSocketMessage.ConnectionLost());
}
} catch (Exception e) {
// wrap the exception and notify master
notify(new WebSocketMessage.Error(e));
} finally {
mStopped = true;
}
}
run()方法里边是个do while循环,因为接收端要接收对方发送的消息,因此需要一直不断循环的来读取消息。首先通过mBufferedStream的read()方法往mMessageData的byte数组中读取数据,mBufferedStream是通过Socket来创建的,mBufferedStream = new BufferedInputStream(mSocket.getInputStream(), mOptions.getMaxFramePayloadSize() + 14);如果读取到了数据,这调用consumeData()方法,该方法如下所示:
/**
* Consume data buffered in mFrameBuffer.
*/
private boolean consumeData() throws Exception {
if (mState == STATE_OPEN || mState == STATE_CLOSING) {
return processData();
} else if (mState == STATE_CONNECTING) {
return processHandshake();
} else if (mState == STATE_CLOSED) {
return false;
} else {
// should not arrive here
return false;
}
}
该方法主要是根据Socket的链接状态进行不同的消息处理,Socket链接建立后首先是进行握手验证,验证服务器端是否支持Websocket协议等,此时调用了processHandshake()方法,该方法验证完成后会返回true或false。接下来握手验证通过后就是调用processData()方法了,该方法源码如下:
/**
* Process incoming WebSockets data (after handshake).
*/
private boolean processData() throws Exception {
// outside frame?
if (mFrameHeader == null) {
// need at least 2 bytes from WS frame header to start processing
if (mPosition >= 2) {
byte b0 = mMessageData[0];
boolean fin = (b0 & 0x80) != 0;
int rsv = (b0 & 0x70) >> 4;
int opcode = b0 & 0x0f;
byte b1 = mMessageData[1];
boolean masked = (b1 & 0x80) != 0;
int payload_len1 = b1 & 0x7f;
// now check protocol compliance
if (rsv != 0) {
throw new WebSocketException("RSV != 0 and no extension negotiated");
}
if (masked) {
// currently, we don't allow this. need to see whats the final spec.
throw new WebSocketException("masked server frame");
}
if (opcode > 7) {
// control frame
if (!fin) {
throw new WebSocketException("fragmented control frame");
}
if (payload_len1 > 125) {
throw new WebSocketException("control frame with payload length > 125 octets");
}
if (opcode != 8 && opcode != 9 && opcode != 10) {
throw new WebSocketException("control frame using reserved opcode " + opcode);
}
if (opcode == 8 && payload_len1 == 1) {
throw new WebSocketException("received close control frame with payload len 1");
}
} else {
// message frame
if (opcode != 0 && opcode != 1 && opcode != 2) {
throw new WebSocketException("data frame using reserved opcode " + opcode);
}
if (!mInsideMessage && opcode == 0) {
throw new WebSocketException("received continuation data frame outside fragmented message");
}
if (mInsideMessage && opcode != 0) {
throw new WebSocketException("received non-continuation data frame while inside fragmented message");
}
}
int mask_len = masked ? 4 : 0;
int header_len;
if (payload_len1 < 126) {
header_len = 2 + mask_len;
} else if (payload_len1 == 126) {
header_len = 2 + 2 + mask_len;
} else if (payload_len1 == 127) {
header_len = 2 + 8 + mask_len;
} else {
// should not arrive here
throw new Exception("logic error");
}
// continue when complete frame header is available
if (mPosition >= header_len) {
// determine frame payload length
int i = 2;
long payload_len;
if (payload_len1 == 126) {
payload_len = ((0xff & mMessageData[i]) << 8) | (0xff & mMessageData[i + 1]);
if (payload_len < 126) {
throw new WebSocketException("invalid data frame length (not using minimal length encoding)");
}
i += 2;
} else if (payload_len1 == 127) {
if ((0x80 & mMessageData[i]) != 0) {
throw new WebSocketException("invalid data frame length (> 2^63)");
}
payload_len = ((long) (0xff & mMessageData[i]) << 56) |
((long) (0xff & mMessageData[i + 1]) << 48) |
((long) (0xff & mMessageData[i + 2]) << 40) |
((long) (0xff & mMessageData[i + 3]) << 32) |
((long) (0xff & mMessageData[i + 4]) << 24) |
((long) (0xff & mMessageData[i + 5]) << 16) |
((long) (0xff & mMessageData[i + 6]) << 8) |
((long) (0xff & mMessageData[i + 7]));
if (payload_len < 65536) {
throw new WebSocketException("invalid data frame length (not using minimal length encoding)");
}
i += 8;
} else {
payload_len = payload_len1;
}
// immediately bail out on frame too large
if (payload_len > mOptions.getMaxFramePayloadSize()) {
throw new WebSocketException("frame payload too large");
}
// save frame header metadata
mFrameHeader = new FrameHeader();
mFrameHeader.mOpcode = opcode;
mFrameHeader.mFin = fin;
mFrameHeader.mReserved = rsv;
mFrameHeader.mPayloadLen = (int) payload_len;
mFrameHeader.mHeaderLen = header_len;
mFrameHeader.mTotalLen = mFrameHeader.mHeaderLen + mFrameHeader.mPayloadLen;
if (masked) {
mFrameHeader.mMask = new byte[4];
for (int j = 0; j < 4; ++j) {
mFrameHeader.mMask[i] = (byte) (0xff & mMessageData[i + j]);
}
i += 4;
} else {
mFrameHeader.mMask = null;
}
// continue processing when payload empty or completely buffered
return mFrameHeader.mPayloadLen == 0 || mPosition >= mFrameHeader.mTotalLen;
} else {
// need more data
return false;
}
} else {
// need more data
return false;
}
} else {
/// \todo refactor this for streaming processing, incl. fail fast on invalid UTF-8 within frame already
// within frame
// see if we buffered complete frame
if (mPosition >= mFrameHeader.mTotalLen) {
// cut out frame payload
byte[] framePayload = null;
if (mFrameHeader.mPayloadLen > 0) {
framePayload = new byte[mFrameHeader.mPayloadLen];
System.arraycopy(mMessageData, mFrameHeader.mHeaderLen, framePayload, 0, mFrameHeader.mPayloadLen);
}
mMessageData = Arrays.copyOfRange(mMessageData, mFrameHeader.mTotalLen, mMessageData.length + mFrameHeader.mTotalLen);
mPosition -= mFrameHeader.mTotalLen;
if (mFrameHeader.mOpcode > 7) {
// control frame
if (mFrameHeader.mOpcode == 8) {
int code = 1005; // CLOSE_STATUS_CODE_NULL : no status code received
String reason = null;
if (mFrameHeader.mPayloadLen >= 2) {
// parse and check close code - see http://tools.ietf.org/html/rfc6455#section-7.4
code = (framePayload[0] & 0xff) * 256 + (framePayload[1] & 0xff);
if (code < 1000
|| (code >= 1000 && code <= 2999 &&
code != 1000 && code != 1001 && code != 1002 && code != 1003 && code != 1007 && code != 1008 && code != 1009 && code != 1010 && code != 1011)
|| code >= 5000) {
throw new WebSocketException("invalid close code " + code);
}
// parse and check close reason
if (mFrameHeader.mPayloadLen > 2) {
byte[] ra = new byte[mFrameHeader.mPayloadLen - 2];
System.arraycopy(framePayload, 2, ra, 0, mFrameHeader.mPayloadLen - 2);
Utf8Validator val = new Utf8Validator();
val.validate(ra);
if (!val.isValid()) {
throw new WebSocketException("invalid close reasons (not UTF-8)");
} else {
reason = new String(ra, "UTF-8");
}
}
}
onClose(code, reason);
// We have received a close, so lets set the state as early as possible.
// It seems that Handler() has a lag to deliver a message, so if the onClose
// is sent to master and just after that the other peer closes the socket,
// BufferedInputReader.read() will throw an exception which results in
// our code sending a ConnectionLost() message to master.
mState = STATE_CLOSED;
} else if (mFrameHeader.mOpcode == 9) {
// dispatch WS ping
onPing(framePayload);
} else if (mFrameHeader.mOpcode == 10) {
// dispatch WS pong
onPong(framePayload);
} else {
// should not arrive here (handled before)
throw new Exception("logic error");
}
} else {
// message frame
if (!mInsideMessage) {
// new message started
mInsideMessage = true;
mMessageOpcode = mFrameHeader.mOpcode;
if (mMessageOpcode == 1 && mOptions.getValidateIncomingUtf8()) {
mUtf8Validator.reset();
}
}
if (framePayload != null) {
// immediately bail out on message too large
if (mMessagePayload.size() + framePayload.length > mOptions.getMaxMessagePayloadSize()) {
throw new WebSocketException("message payload too large");
}
// validate incoming UTF-8
if (mMessageOpcode == 1 && mOptions.getValidateIncomingUtf8() && !mUtf8Validator.validate(framePayload)) {
throw new WebSocketException("invalid UTF-8 in text message payload");
}
// buffer frame payload for message
mMessagePayload.write(framePayload);
}
// on final frame ..
if (mFrameHeader.mFin) {
if (mMessageOpcode == 1) {
// verify that UTF-8 ends on codepoint
if (mOptions.getValidateIncomingUtf8() && !mUtf8Validator.isValid()) {
throw new WebSocketException("UTF-8 text message payload ended within Unicode code point");
}
// deliver text message
if (mOptions.getReceiveTextMessagesRaw()) {
// dispatch WS text message as raw (but validated) UTF-8
onRawTextMessage(mMessagePayload.toByteArray());
} else {
// dispatch WS text message as Java String (previously already validated)
String s = new String(mMessagePayload.toByteArray(), "UTF-8");
onTextMessage(s);
}
} else if (mMessageOpcode == 2) {
// dispatch WS binary message
onBinaryMessage(mMessagePayload.toByteArray());
} else {
// should not arrive here (handled before)
throw new Exception("logic error");
}
// ok, message completed - reset all
mInsideMessage = false;
mMessagePayload.reset();
}
}
// reset frame
mFrameHeader = null;
// reprocess if more data left
return mPosition > 0;
} else {
// need more data
return false;
}
}
}
processData()方法比较长,读起来也比较费力,这里就不再详细解读了,总之这块的代码的实现一定是严格按照WebSocket协议来解析数据的,如果有小伙伴对该协议还不太清楚,请参照上文。processData()方法解析到数据后会调用相关回调方法最终把数据通过Handler传递到主线程。这是createReader()方法的主要流程,那我们现在看一下createWriter()方法是如何操作的,源码如下:
/**
* Create WebSockets background writer.
*/
protected void createWriter() throws IOException {
mWriterThread = new HandlerThread("WebSocketWriter");
mWriterThread.start();
mWriter = new WebSocketWriter(mWriterThread.getLooper(), mMasterHandler, mSocket, mOptions);
}
createWriter()方法通过HandlerThread创建了WebSocketWriter对象,如果有对HandlerThread不熟悉的小伙伴可以阅读一下我之前的一篇文章: Android 源码系列之<七>从源码的角度深入理解IntentService及HandlerThread。createWriter()方法执行完毕后,当在测试demo中输入数据点击SEND按钮后辗转调用的是WebSocketConnection的sendTextMessage()方法,该方法中调用的是mWriter的forward()方法,源码如下:
/**
* Call this from the foreground (UI) thread to make the writer
* (running on background thread) send a WebSocket message on the
* underlying TCP.
*
* @param message Message to send to WebSockets writer. An instance of the message
* classes inside WebSocketMessage or another type which then needs
* to be handled within processAppMessage() (in a class derived from
* this class).
*/
public void forward(Object message) {
// We have already quit, we are no longer sending messages.
if (!mActive) {
if (DEBUG) Log.d(TAG, "We have already quit, not processing further messages");
return;
}
Message msg = obtainMessage();
msg.obj = message;
sendMessage(msg);
}
forward()方法仅仅是发送了一个Message,该方法目的是进行线程的调度,把发送操作切换到子线程中,我们看一下processMessage()方法,源码如下:
/**
* Sends a WebSockets frame. Only need to use this method in derived classes which implement
* more message types in processAppMessage(). You need to know what you are doing!
*
* @param opcode The WebSocket frame opcode.
* @param fin FIN flag for WebSocket frame.
* @param payload Frame payload or null.
* @param offset Offset within payload of the chunk to send.
* @param length Length of the chunk within payload to send.
*/
protected void sendFrame(int opcode, boolean fin, byte[] payload, int offset, int length) throws IOException {
// first octet
byte b0 = 0;
if (fin) {
b0 |= (byte) (1 << 7);
}
b0 |= (byte) opcode;
write(b0);
// second octet
byte b1 = 0;
if (mOptions.getMaskClientFrames()) {
b1 = (byte) (1 << 7);
}
long len = length;
// extended payload length
if (len <= 125) {
b1 |= (byte) len;
write(b1);
} else if (len <= 0xffff) {
b1 |= (byte) (126 & 0xff);
write(b1);
write(new byte[]{(byte) ((len >> 8) & 0xff),
(byte) (len & 0xff)});
} else {
b1 |= (byte) (127 & 0xff);
write(b1);
write(new byte[]{(byte) ((len >> 56) & 0xff),
(byte) ((len >> 48) & 0xff),
(byte) ((len >> 40) & 0xff),
(byte) ((len >> 32) & 0xff),
(byte) ((len >> 24) & 0xff),
(byte) ((len >> 16) & 0xff),
(byte) ((len >> 8) & 0xff),
(byte) (len & 0xff)});
}
byte mask[] = null;
if (mOptions.getMaskClientFrames()) {
// a mask is always needed, even without payload
mask = newFrameMask();
write(mask[0]);
write(mask[1]);
write(mask[2]);
write(mask[3]);
}
if (len > 0) {
if (mOptions.getMaskClientFrames()) {
/// \todo optimize masking
/// \todo masking within buffer of output stream
for (int i = 0; i < len; ++i) {
payload[i + offset] ^= mask[i % 4];
}
}
mBufferedOutputStream.write(payload, offset, length);
}
}
sendFrame()方法把我们发送的数据根据WebSocket协议封装成符合规范的协议帧然后通过mBufferedOutputStream的write()方法发送出去了。整套流程其实很简单,就是通过Socket管道然后按照WebSocket协议进行消息的发送和接收。当然在项目中如果有精力也可以自己约定一套协议,只要都遵循了该协议都是没有问题的。
原本打算把okhttp的WebSocket模块也讲解一下由于篇幅原因,这里就不再讲解了,okhttp的WebSocket模块同样遵循的是WebSocket协议,只是实现上和autobahn略有不同罢了,如果有小伙伴对okhttp的WebSocket模块感兴趣,请自行查阅。最后,感谢小伙伴们的收看(*^__^*) …