[Tomcat] Tomcat 7中的Websocket

1. WebSocket介绍

  WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:1.WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;2.WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。简单的建立握手的时序图如下:

握手过程:

  1. Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。
  2. 在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。
  3. WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。
  4. Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。

2. Tomcat 7中的Websocket架构

如图所示,因为Websocket通信分为握手和数据传输两个过程,两个过程中需要用到的处理方式是不一样的,握手过程是基于HTTP 1.1基础上的,而数据传输是直接基于TCP的流传输。

       握手过程中,在HttpServletRequest的基础上,封装了WsHttpServletRequest类,添加了对Request的失效操作函数invalidate()。而在数据通信时,接受和处理数据过程中,基于org.apache.coyote.http11.upgrade.UpgradeInbound重新封装了用于处理数据输入流的类StreamInbound,并在StreamInbound的基础上扩展生成了用于消息处理的类MessageInbound。在这两个数据处理类中均留有onData,onTextData/onBinaryData,onOpen,onClose等事件操作函数接口,这些接口将在载入的代码类中实现业务逻辑。在用于数据输出流的类WsOutbound则是封装了UpgradeOutbound对象实例,基于UpgradeOutbound对象的基础上,添加了websocket响应有关的处理逻辑。这里处理函数均为同步调用的函数,保证websocket响应的时序性。

       Tomcat中Websocket的处理流程如下:

  1. 接收客户端发来的握手请求,Coyote.http11连接器对socket进行解析,形成HttpServletRequest发送给Container。
  2. Container中的相应WebsocketServlet处理请求,如不接受连接请求,则返回,如接受连接请求,则对请求作出响应,建立起客户端和服务器的socket连接。
  3. 服务器此时可以通过WsOutbound发送数据给客户端,同时通过StreamInbound监听socket。
  4. 如果接收到客户端发来的数据,则将socket数据解析成frame,判断frame类型,通过事件分发数据到不同的逻辑处理流程。
  5. 数据返回时调用WsOutbound对返回的数据进行封装处理,发送给客户端

3. 代码分析

  1. WebSocketServlet

这个类负责WS的握手过程,通过对HTTP请求头的判断确定是否接受连接请求。接受连接请求后则建立websocket数据连接,连接建立过程如下所示:

WsHttpServletRequestWrapper wrapper = new WsHttpServletRequestWrapper(req); //将HttpServletRequest封装可进行失效操作的WsHttpServletRequestWrapper
StreamInbound inbound = createWebSocketInbound(subProtocol, wrapper); //建立数据连接,监听对应的端口
wrapper.invalidate(); //握手完,对这个Request进行invalidate处理

  2. StreamInbound

这个类最关键的是onData()函数,即接收到数据后的处理函数。这个函数里对接受的数据进行解析,并根据操作码分发给不同的处理函数。

WsInputStream wsIs = new WsInputStream(processor, getWsOutbound()); //根据当前的Processor和定制的WsOutbound输出流对象,构建输入流的解析对象
        try {
            WsFrame frame = wsIs.nextFrame(true); //查找数据中的下一个Frame
            while (frame != null) {
                byte opCode = frame.getOpCode(); //查找Frame中的操作码
                if (opCode == Constants.OPCODE_BINARY) {
                    doOnBinaryData(wsIs); //处理Binary数据
                } else if (opCode == Constants.OPCODE_TEXT) {
                    InputStreamReader r =
                            new InputStreamReader(wsIs, new Utf8Decoder());
                    doOnTextData(r); //处理文本数据
                } else if (opCode == Constants.OPCODE_CLOSE){
                    closeOutboundConnection(frame); //数据发送完毕,发送close frame
                    return SocketState.CLOSED;
                } else if (opCode == Constants.OPCODE_PING) {
                    getWsOutbound().pong(frame.getPayLoad()); //发送pong frame
                } else if (opCode == Constants.OPCODE_PONG) {
                } else {
                    closeOutboundConnection(
                            Constants.STATUS_PROTOCOL_ERROR, null);
                    return SocketState.CLOSED;
                }
                frame = wsIs.nextFrame(false);
            }
        } 

  3. MessageInbound

该类是StreamInbound的扩展类,实现了对文本数据的解析函数。文本处理过程中,主要用到了ByteBuffer和CharBuffer,通过对Buffer的操作实现文本数据的解析。

  4. WsOutbound

该类是处理Websocket输出流的类,实现了Websocket几个close,pong,ping和正常数据响应frame。比如在输出文本数据的处理函数里:

public synchronized void writeTextData(char c) throws IOException {
        if (closed) { //数据流已关闭
            throw new IOException(sm.getString("outbound.closed"));
        }
        if (cb.position() == cb.capacity()) { //没有数据可以返回
            doFlush(false);
        }
        if (text == null) {
            text = Boolean.TRUE;
        } else if (text == Boolean.FALSE) { //如果已经写好数据准备传输
            flush(); //输出数据
            text = Boolean.TRUE;
        }
        cb.append(c); //将添加到CharBuffer中
    } 

  5. WsInputStream

这个类主要用于输入流的解析,将数据从InputStream中解析成websocket的frame。类的关键逻辑在read()函数中:

public int read(byte b[], int off, int len) throws IOException {
        makePayloadDataAvailable(); //确保有Payload数据可供读取
        if (remaining == 0) { //frame数据已经读到尾
            return -1;
        }
        if (len > remaining) { //重置可读取数据长度
            len = (int) remaining;
        }
        int result = processor.read(true, b, off, len); //调用Processor进行数据读取
        if(result == -1) {
            return -1;
        }
        for (int i = off; i < off + result; i++) {
            b[i] = (byte) (b[i] ^
                    frame.getMask()[(int) ((readThisFragment + i - off) % 4)]); //获取帧的Mask
        }
        remaining -= result;
        readThisFragment += result;  //已读帧数据
        return result;
    }

 

 

posted @ 2013-01-15 00:03  MySirius  阅读(2943)  评论(1编辑  收藏  举报