WebSocket 之 Java API

  • 本文目的
最近项目中使用到WebSocket 需要java 对WebSocket 进行一个封装,来回折腾了几次,最后发现xlightweb 比自己封装的代码 NB 多了(哎,功底不行!),但是,值得庆幸的时原理都差不多,起码没偏离。接下来介绍WebSocket 和 Java 怎么调用以及工具。希望对大家有用。
  • WebSocket 简介
WebSockets是在一个(TCP)接口进行双向通信的技术,PUSH技术类型。同时WebSockets仍将基于W3C标准,目前为止,Chrome和Safari的最新版本浏览器已经支持WebSockets了。(更多关于websocket请上google)
  • WebSocket  Handshake
我重点结合xSocket 和 xlightweb 来说明WebSocket 的握手过程。
最开始开发是也没太在意这个握手其实一般,要它何用,不用去管它。 但是,有一个场景是服务器需要在握手是返回动态加密字符串,在握手后其他的消息交互中使用到。这就很有必要对 Handshake 成功后返回的 response header 进行解析了。
  • xSocket 和 xlightweb
xSocket 是 对NIO 进行的封装非常好用, xlightweb 是在xSocket 基础上面扩展了对web协议的支持,比如Http, websocket ,如果这两个不太清楚也请上google搜索。
  • WebSocket 之 Java API
大家都知道WebSocket 是以 0x00, 0xFF 开头和结尾来包住有效数据的,每个消息都是如此。 使用xSocket 的时候开始的时候 只知道 readByteBufferByDelimiter(String)或者 readByteBufferByDelimiter(length),还有一个就是按字节读取,很 2 的想法是怎么把0x00 转成字符串(非常2)。

好吧,既然xSocket 没有提供按字节 Delimiter 的,那就按字节读取,存入ByteBuffer[] 中,然后在遍历ByteBuffer[] ,使用 startWith == 0x00 && endWith == 0xFF 进行处理。大致原理如下:
  1. onData 中开启一个读取线程,有数据就 connect.getByte()
  2. 读取的数据存入 ByteBuffer[] 中
  3. 定期匹配 0x00, 0xFF ,匹配成功后 返回可用message,剩余的ByteBuffer 缓存后,等待下一次匹配。
基本是这样,但是后面发现自己代码没办法想HttpClient一样有效的处理 requestHeader 或者 responseHeader,虽然可以根据文本进行解析,分割。但是还是觉得不脱,代码不美观。到目前为止自己用xSocket 封装的代码已经基本够用了。再一次寻觅下看见了 xlightweb 已经封装了 websocket 的交互,查看代码和测试之后就毅然改用xlightweb 了。

xlightweb 的 websocket 交互代码:
HttpClient httpClient = new HttpClient();			
httpClient.setConnectTimeoutMillis(60 * 1000);
IWebSocketConnection webSocketConnection = httpClient.openWebSocketConnection("ws://localhost:8080" , "Sample", new WebSocketHandler());
responseHeader = webSocketConnection.getUpgradeResponseHeader();
3,4行代码就已经处理了 websocket 的握手和返回的 responseHeader 了。 这个只是开始,我想要看见的是它怎么解析 0x00,0xFF 最后得到有效数据的。那么在看看 WebSocketHandler (自己取的类名)
	public class WebSocketHandler implements IWebSocketHandler, IHttpRequestHandler {
		
		@Override
		public void onConnect(IWebSocketConnection webStream) throws IOException {
			Log.i(TAG, "on connect, id : " + webStream.getId());
			reconnect.set(false);
		}

		@Override
		public void onDisconnect(IWebSocketConnection webStream) throws IOException {
			Log.e(TAG, "on disconnect, id : " + webStream.getId());
			reconnect.set(true);
		}

		@Override
		public void onMessage(IWebSocketConnection webStream) throws IOException {
			Log.i(TAG, "on message, id : " + webStream.getId());
			
			TextMessage msg = webStream.readTextMessage();
		     taskExecutor.execute(new WebSocketTask( msg.toString() ));
		}

		@Override
		public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {

		}
	}

重要的就是加粗的两行代码,其实就是  readTextMessage 就可以了,下面一行是我使用的多线程来分发message 避免阻塞通道。 好了,到目前为止着急使用Java API 来操作 websocket 的已经够用了:资源下载地址: http://xsocket.sourceforge.net/download.htm
想知道 readTextMessage 我们继续看看和学习 xlightweb 的代码
TestMessage 是 extends WebSocketMessage,  WebSocketMessage 派生出来的有三个类:TextMessage,BinaryMessage,CloseMessage。
webStream.readTextMessage() 调用后返回的也是 WebSocketMessage,代码如下:
public WebSocketMessage readMessage() throws BufferUnderflowException, SocketTimeoutException, ClosedChannelException, IOException {
        long start = System.currentTimeMillis();
        long remainingTime = receiveTimeoutSec;

        do {
            synchronized (inQueue) {
                IOException ioe = exceptionRef.getAndSet(null); 
                if (ioe != null) {
                    throw ioe;
                }
                
                if (isDisconnected.get()) {
                    throw new ClosedChannelException();
                }
                
                if (inQueue.isEmpty()) {
                    try {
                        inQueue.wait(remainingTime);
                    } catch (InterruptedException ie) { 
                        // Restore the interrupted status
                        Thread.currentThread().interrupt();
                    }
                } else {
                    inQueueVersion++;
                    return inQueue.remove(0);
                }
            }

            remainingTime = HttpUtils.computeRemainingTime(start, receiveTimeoutSec);
        } while (remainingTime > 0);
        

        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
        }

        throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
    }
其实就是开了一个循环在   remainngTime > 0 内有效。超过时间就抛出异常。 但是,就看见从inQueue 里面获取东西,什么时候写入到 inQueue 呢。 大家要记住xSocket是封装的NIO,xlightweb是借助xSocket 扩展的web 协议内容。 那么好吧,肯定有一个 Handler 来对应处理接收的数据,在WebSocketConnection 类中找到 WebSocketProtocolHandler,代码如下:
 private final class WebSocketProtocolHandler implements IConnectHandler, IDataHandler, IDisconnectHandler {
        // network data
        private ByteBuffer rawBuffer = null;
        
        public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
            ........
            return true;
        }
        
        public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
            
            if (connection.isOpen()) {
                // copying available network data into raw data buffer
                int available = connection.available();
                
                ByteBuffer[] data = null;
                if (available > 0) {
                    data = connection.readByteBufferByLength(available);
                }
                onData(data);
            } 
            
            return true;
        }
        
        
        void onData(ByteBuffer[] data) throws IOException {
            if (data == null) {
                if (rawBuffer == null) {
                    rawBuffer = ByteBuffer.allocate(0);
                }
            } else {
                if (rawBuffer == null) {
                    rawBuffer = HttpUtils.merge(data);
                } else {
                    rawBuffer = HttpUtils.merge(rawBuffer, data);
                }
            }  
            
            parse(rawBuffer);
            
            if (!rawBuffer.hasRemaining()) {
                rawBuffer = null;
            }
        }
        
        
        void parse(ByteBuffer buffer) throws IOException {

            while (buffer.hasRemaining()) {
                
                WebSocketMessage msg = WebSocketMessage.parse(buffer);
                
                if (msg == null) {
                    return;
                    
                } else {
                    
                    if (msg.isTextMessage()) {
                        synchronized (inQueue) {
                            inQueueVersion++;
                            inQueue.add(msg);
                            inQueue.notifyAll();
                        }
                        
                        synchronized (webSocketHandlerGuard) {
                            if (webSocketHandlerAdapter != null) {
                                webSocketHandlerAdapter.onMessage(WebSocketConnection.this);
                            }
                        }
                    } else if (msg.isCloseMessage()) {
                        if (isCloseMsgSent.get()) {
                            if (LOG.isLoggable(Level.FINE)) {
                                LOG.fine("[" + getId() + "] echo close msg reveived. Destroying connection");
                            }
                            writeMessageIgnoreClose(msg);
                            destroy();
                            
                        // peer initiated close    
                        } else {
                            if (LOG.isLoggable(Level.FINE)) {
                                LOG.fine("[" + getId() + "] close msg reveived. echoing it and destroying connection");
                            }
                            isCloseMsgSent.set(true);
                            writeMessageIgnoreClose(msg);
                            destroy();
                        }
                        
                    } else {
                        if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("[" + getId() + "] binary message received. The ws draft does not longer allow binary messages. Ignoring it");
                        }
                    }
                }
            }
        }

在这里把数据从 onData 中收取下了并且进行转换后存入到  inQueue 中。这里已经知道它是怎么收取数据了,就差最后一步,怎么转化数据,分离有效信息的。看到这几行代码,回想下自己原来写的也可以这么做就行了。( 委屈
 if (connection.isOpen()) {
                // copying available network data into raw data buffer
                int available = connection.available(); //这里还没有仔细查看,如果谁有时间查看了,希望也转告我原理
                
                ByteBuffer[] data = null;
                if (available > 0) {
                    data = connection.readByteBufferByLength(available);
                }
                onData(data);
 } 

收到数据时还有一个 合并ByteBuffer 的过程,这个和我之前自己实现的大同小异,如果有不明白的可以自己查看代码。 收到数据后进行 parse,我们可以看到是调用了WebSocketMessage 的parse 方法,上面使用的是 TextMessage,所以对应调用的是 TextMessage 的parser 方法,代码如下:
    private static final byte START_BYTE_TEXTFRAME = (byte) 0x00;
    private static final byte END_BYTE = (byte) 0xFF;    
static TextMessage parse(ByteBuffer buffer) throws IOException {
        
        int savePos =  buffer.position();
        int saveLimit =  buffer.limit();

        while (buffer.hasRemaining()) {
            byte b = buffer.get();
            if ((b & END_BYTE) == END_BYTE) {
                int pos = buffer.position();
                
                buffer.limit(buffer.position() - 1);
                buffer.position(savePos);
                
                ByteBuffer msg = buffer.slice();
                
                buffer.limit(saveLimit);
                buffer.position(pos);
                
                return new TextMessage(msg);
            }
         }
        
        buffer.position(savePos);        
        buffer.limit(saveLimit);
        
        return null;
    }

到这里算是明白,清楚了。 就是遍历 ByteBuffer 然后匹配 END_BYTE 就可以 返回  new TextMessage 了。
 
xSocket 和 xlightweb 还可以做很多事情,我做得也就是基本功能,如果有更深入了解的人可以互相讨论,希望对大家有用。

补充一句:为什么直接用 OxFF可可以进行分包?
                    因为协议规定使用 UTF-8 进行编码,0xFF 是不会出现在数据 data 中的。

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值