Android WebSocket 编程

1、原理

什么是WebSocket

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通讯——容许服务器主动发送信息给客户端。 WebSocket通讯协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。通俗的讲就是服务器和客户端能够均可以主动的向对方发送消息,而不是请求-响应的模式了。html

  • 上面这段话是百度百科上描述的WebSocket,WebSocket是应用层的一种协议,是创建在TCP(传输层)协议基础上的,主要特色就是全双工通讯。
  • websocket通讯可以极大减轻网路拥塞。和传统的轮询操做保持长链接的方式相比也极大了减少了损耗。

WebSocket VS HTTP

相同点

  • 都是位于应用层的传输协议。
  • 都通过握手环节创建通讯。
  • 都是基于TPC协议基础之上。

不一样点

  • HTTP的通讯过程在经历握手创建链接以后的通讯都是请求响应的方式,服务端不能主动的向客户端发送消息;而WebSocket在服务端和客户端创建链接以后,服务端能够主动的向客户端发送消息。
  • websocket传输的数据是二进制流,是以帧为单位的;http传输的是明文传输,是字符串传输。
  • WebSockt是长链接,原则上除非主动关闭链接,在创建链接以后,双发能够互发消息;而HTTP1.0是客端户发起请求,服务端响应而后就结束了,若是客端户和服务端再有交流还要从新发起握手而后请求-响应。在HTTP1.1中有了改进(加了keep-alive),在一次链接容许屡次的“请求-应答”。
  • 再就是在HTTP和WebSocket的请求头部会有区别,这一点后文详解。且通讯过程当中通讯头部比较轻。
    image

2、Andoid中WebSocketd的实现

(一)Android 中使用java-webSocket

库文件

java-websocket-1.3.8.jar oracle官方APIjava

链接

private static WebSocketClient webSocketClient;
  private void connetToServer(final String str){
       try {
           webSocketClient = new WebSocketClient(new URI("ws://192.168.25.188:8080/websocket"), new Draft_6455() {},null,100000) {
               @Override
               public void onOpen(ServerHandshake handshakedata) {
               }
               @Override
               public void onMessage(String message) {
               }
               @Override
               public void onClose(int code, String reason, boolean remote) {
               }
               @Override
               public void onClosing(int code, String reason, boolean remote) {
                   super.onClosing(code, reason, remote);
               }
               @Override
               public void onError(Exception ex) {
               }
           };
       } catch (URISyntaxException e) {
           e.printStackTrace();
       }
       webSocketClient.connect();
   }

复制代码
  • 首先建立一个WebSocketClient实例,建立实例的时候,传入地址、Websocket Draft(协议版本)、头部、链接超时时间。
  • 建立实例时候,会有些回调,根据名字就能看出来,链接成功后打开状态回调open(),关闭时候调用onClosing(),出错时候调用onError()等等。
  • 最后就调用WebSocket的connet()进行链接。
WebSocket的握手

这里须要注意的是,WwebSocket在握手链接阶段借用了http协议,握手链接阶段的报文跟http报文同样。差很少以下形式:git

GET / HTTP/1.1
Host: ws://192.168.25.188:8080/
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: VR+OReqwhymoQ21dBtoIMQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
复制代码

跟http协议请求头部,主要不同的是Connetiong:Upgrad和Upgrade: websocket以及Sec-WebSocket-Version: 13这些就告诉服务器说我要握手的完成的是哪一个版本的websocket协议。github

发送消息

链接完成以后,能够向服务端发送消息,直接调用WebSocketClient中的发送方法。有下面两个发送消息的方法。web

public void send( String text ) 
}

public void send( byte[] data ) 
}
复制代码

关闭链接

正常关闭 调用webSocket的close()方法。数组

(二)java-websocket内部原理(源码分析)

image

WebSocketClient和WebSocketImpl使用了代理模式。浏览器

(1) 握手链接阶段
public void connect() {
    if( writeThread != null )
    		throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
    writeThread = new Thread( this );
    writeThread.start();
	}
复制代码

websocketclient的connet()方法,启动一个线程,这个线程里面的操做是什么呢?就查看WebSocketClient的继承在Runable实现的run()方法。bash

对run()方法作了适合的化简服务器

public void run() {
		InputStream istream;
	    
		socket = new Socket( proxy );
		socket.setTcpNoDelay( isTcpNoDelay() );
		socket.setReuseAddress( isReuseAddr() );

		istream = socket.getInputStream();
		ostream = socket.getOutputStream();
		sendHandshake();
		writeThread = new Thread( new WebsocketWriteThread() );
		writeThread.start();
		byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ];
		int readBytes;

		while ( !isClosing() && !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) {
			engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) );
		}
		engine.eot();
	}
复制代码

在run方法中作了五件事:websocket

  1. 建立socket
  2. 在socket中获取inputStream和outputStream两个流
  3. sendHandshake();这个方法就是往engine中的发送队列outQueue中放入挥手信息。
  4. 建立并启动WebsocketWriteThread线程,这个线程的工做就是再outQueue中取出信息写入ostream流中。
  5. 循环读流istream,取出字节交给engingDecode处理。

发送握手以后,在流中接收到服务端的返回,就交给engine的decode()方法,decode()方法最终会调用onWebsocketHandshakeReceivedAsClient()方法和onOpen() 至此完成握手链接。

(2)打开阶段

//NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED

a.发送消息

完成握手链接创建的通道以后,发送消息就很简单了。关键代码以下:

webSocketClient

public void send( String text ) throws NotYetConnectedException {
		engine.send( text );
	}
复制代码

WebsocketImpl:

private void send( Collection<Framedata> frames ) {
	ArrayList<ByteBuffer> outgoingFrames = new ArrayList<ByteBuffer>();
	for( Framedata f : frames ) {
		outgoingFrames.add( draft.createBinaryFrame( f ) );
	}
	write( outgoingFrames );
}
	
private void write( ByteBuffer buf ) {
    outQueue.put( buf );
    wsl.onWriteDemand( this );
}

复制代码

客户端向服务器发送消息,归根到底就是engin把要发送给的字节数组或者字符串转换成ByteBuffer,而后放在outQueue的队列中。等待WebSocketClient中启动的写线程在outQueue队列读出来,写入socket的流中。

b.接受消息

接受消息在websocketClient的run()方法中一直在流中读数据,交给engin的decode()处理。

websocket:

private void decodeFrames( ByteBuffer socketBuffer ) {
	List<Framedata> frames;

	frames = draft.translateFrame( socketBuffer );
	for( Framedata f : frames ) {
		if( DEBUG )
			System.out.println( "matched frame: " + f );
		draft.processFrame( this, f );
	}
}
复制代码

Draft_6455:

public void processFrame( WebSocketImpl webSocketImpl, Framedata frame ) throws InvalidDataException {
	Framedata.Opcode curop = frame.getOpcode();
	if( curop == Framedata.Opcode.CLOSING ) {
	}else if( curop == Framedata.Opcode.PING ) {
		webSocketImpl.getWebSocketListener().onWebsocketPing( webSocketImpl, frame );
	} else if( curop == Framedata.Opcode.PONG ) {
		webSocketImpl.updateLastPong();
		webSocketImpl.getWebSocketListener().onWebsocketPong( webSocketImpl, frame );
	} else if( !frame.isFin() || curop == Framedata.Opcode.CONTINUOUS ) {	
	} else if( current_continuous_frame != null ) {
	} else if( curop == Framedata.Opcode.TEXT ) {
		webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, Charsetfunctions.stringUtf8( frame.getPayloadData() ) );	
	} else if( curop == Framedata.Opcode.BINARY ) {
			webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, frame.getPayloadData() );
	}
}
复制代码

在Draft_draft_6455中首先检查接受的数据帧的类型,连续的帧、仍是字节、仍是text或或者心跳检测帧,而后作相应的处理,回调WebSocketListener相应的方法,接受到正常的String或者字节的时候,最终回调到onMessage()方法,用户就接受到信息了。

(3)关闭阶段

由于WebSocket是基于TCP协议的,而正常的TCP协议的断开须要通过“四次挥手”。为何呢?TCP是双工通讯的,客户端和服务端都正常的向对方推送消息,因此挥手的过程以下:

客户端:我要关闭了啊。
服务端:好的,我知道你要关闭了。
服务端:我要关闭了兄弟。
客户端:好的我知道你要关闭了
复制代码

通过以上过程,整个TCP协议就关闭了。

固然也会存在单项关闭的问题。客户端关了,这时候客户端不能向服务端发送消息了,可是服务端没有关,服务端能够向客户端发送消息。相反也是。

那么再java-websocket中是如何实现关闭的呢?

在WebsocketImpl中close方法向服务端发送一个关闭帧。

public synchronized void close( int code, String message, boolean remote ) {
    if( isOpen() ) {
		CloseFrame closeFrame = new CloseFrame();
		closeFrame.setReason( message );
		closeFrame.setCode( code );
		closeFrame.isValid();
		sendFrame( closeFrame );
	}
	setReadyState( READYSTATE.CLOSING );
	}
复制代码

服务端接受到关闭后会向客户端发送一个关闭确认帧,客户端接收到以后最会调用WebsocketImpl的closeConnetion()。

public synchronized void closeConnection( int code, String message, boolean remote ) {
		if( getReadyState() == READYSTATE.CLOSED ) {
			return;
		}
		if( getReadyState() == READYSTATE.OPEN ) {
			if( code == CloseFrame.ABNORMAL_CLOSE ) {
				setReadyState( READYSTATE.CLOSING );
			}
		}
		this.wsl.onWebsocketClose( this, code, message, remote );
		setReadyState( READYSTATE.CLOSED );
	}
复制代码

closeconnetion作的主要事情就是把客户端的状态设置为closed,而后回调WebSocketLisetener的onWebsocketClose()。 至此,关闭了。

可能会有疑问若是客户端已经处于关闭状态了,若是服务端发送来关闭消息,客户端就没有办法向服端关闭确认了。这个问题的解决,是使用超时机制,也就是服务端再发送关闭信息一段时间后没有接受到确认,他就默认是客户端收到了他的关闭信息。

关于断线重连

须要 特别提出的是,WebSocket可能会出现不可预知的断开链接了,可是这时候客户端还不知道,客端户依旧开心的处于“Open”的状态。这个问题怎么解决呢?

开心的是java-websocket中提供了心跳检测,一旦检测到不在线了链接断了就会启动从新链接机制。具体这个心跳检测多长时间检测一次咱们能够经过WebSocketClient的以下方法

public void setConnectionLostTimeout( int connectionLostTimeout )
复制代码

进行设置,系统默认的是60秒。经过判断ping和pong一旦检测到websocket断开了就会调用engine的closeConnetion()方法。

  • 具体的断重连是如何实现的呢?

当在监听close方法中监听到是ping不通的状态致使的关闭的时候,就启动一个线程,作五次链接操做,这部分的具体实现,能够在Demo中查看,再次具体不作详解了。

(二)使用OkHttp作WebSocket链接

基本使用

  • 使用OkHttpClient建立WebSocket,响应的闯入监听回调
private static OkHttpClient sClient;
    private static WebSocket  sWebSocket;
    public static  WebSocket startRequest(String url,WebSocketListener socketListener) {
        if (sClient == null) {
            sClient = new OkHttpClient();
        }
        if (sWebSocket == null) {
            Request request = new Request.Builder().url(url).build();
            sWebSocket = sClient.newWebSocket(request, socketListener);
        }
        return sWebSocket;
    }
复制代码
  • WebSocket有 链接中、打开中、关闭中、已关闭这几种状态、每种状态都会有响应的回调。以下:
webSocketClient =  WebSocketClient.startRequest("ws://192.168.25.188:8080/websocket", new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
            }
            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
            }
            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
            }
            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
            }
            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
            }
            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
            }
        });
复制代码

固然能够根据须要继承WebSocketListener而后本身实现,好比把onMessage(WebSocket webSocket, String text) 和onMessage(WebSocket webSocket, ByteString bytes) 方法合并同一回调onMessage(String msg);

  • 发送消息就经过webSocket.send()方法,接受消息在监听的onMessage方法中监听。

小结

  • WebSocket的生命周期链接中,打开,关闭中,已关闭。
  • 经过三次握手创建链接以后websocket处于open状态。
  • 处于open状态的websocket 能够向对方收发消息。
  • websocket的关闭,四次挥手,客户端向服务端挥手,服务端返回确认;服务端向客户端挥手,客户端返回确认。
  • websocket经过固定周期的ping、pong保链接。

本文的demo地址: github.com/poecook/And…

相关文章
相关标签/搜索