JAVA Stomp客户端无法接收太大的数据的解决和挣扎

最近在使用stomp java客户端,使用的是spring-websocket包。连接服务端的方法如下:

public ListenableFuture<StompSession> connect(String url, StompHeaders stompHeaders) throws InterruptedException, ExecutionException {
		stompHeaders.add("token", token);
		
		WebSocketContainer container = ContainerProvider.getWebSocketContainer();
		StandardWebSocketClient standardWebSocketClient=new StandardWebSocketClient(container);
		Transport webSocketTransport = new WebSocketTransport(standardWebSocketClient);
		List<Transport> transports = new ArrayList<Transport>();
		transports.add(webSocketTransport);
		SockJsClient sockJsClient = new SockJsClient(transports);
		WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
		stompClient.setReceiptTimeLimit(3000);
		stompClient.setDefaultHeartbeat(new long[] { 10000l, 10000l });
		ThreadPoolTaskScheduler task = new ThreadPoolTaskScheduler();
		task.initialize();
		stompClient.setTaskScheduler(task);
		client = stompClient;
		sockJsClient = sockJsClient;
		Ttask = task;
		return stompClient.connect(url, headers, stompHeaders, receiverMsgHandler);
	}

在接收订阅的数据时,如果数据长度过大,超过65536,就会报错。报错如下:

org.springframework.messaging.simp.stomp.StompConversionException: The configured STOMP buffer size limit of 65536 bytes has been exceeded

这个比较好解决,通过查看spring源码。发现是在WebSocketStompClient类下,有一个int型变量inboundMessageSizeLimit,默认为:64*1024。并提供了set方法。

private int inboundMessageSizeLimit = 64 * 1024;

在这个类中有一个私有类WebSocketTcpConnectionHandlerAdapter。

private class WebSocketTcpConnectionHandlerAdapter implements ListenableFutureCallback<WebSocketSession>,
			WebSocketHandler, TcpConnection<byte[]>

在连接服务器时实例化了该类的对象,

public ListenableFuture<StompSession> connect(URI url, @Nullable WebSocketHttpHeaders handshakeHeaders,
			@Nullable StompHeaders connectHeaders, StompSessionHandler sessionHandler) {

		Assert.notNull(url, "'url' must not be null");
		ConnectionHandlingStompSession session = createSession(connectHeaders, sessionHandler);
		WebSocketTcpConnectionHandlerAdapter adapter = new WebSocketTcpConnectionHandlerAdapter(session);
		getWebSocketClient().doHandshake(adapter, handshakeHeaders, url).addCallback(adapter);
		return session.getSessionFuture();
	}

处理消息的方法就在这个Adapter类中

@Override
		public void handleMessage(WebSocketSession session, WebSocketMessage<?> webSocketMessage) {
			this.lastReadTime = (this.lastReadTime != -1 ? System.currentTimeMillis() : -1);
			List<Message<byte[]>> messages;
			try {
				messages = this.codec.decode(webSocketMessage);
			}
			catch (Throwable ex) {
				this.connectionHandler.handleFailure(ex);
				return;
			}
			for (Message<byte[]> message : messages) {
				this.connectionHandler.handleMessage(message);
			}
		}

其中有一个codec变量,是WebSocketStompClient类中另一个静态的私有类的对象。在WebSocketTcpConnectionHandlerAdapter类中进行了实例化。

private final StompWebSocketMessageCodec codec = new StompWebSocketMessageCodec(getInboundMessageSizeLimit());

StompWebSocketMessageCodec类中,其中有一个方法decode(),

private static class StompWebSocketMessageCodec
public List<Message<byte[]>> decode(WebSocketMessage<?> webSocketMessage) {
			List<Message<byte[]>> result = Collections.emptyList();
			ByteBuffer byteBuffer;
			if (webSocketMessage instanceof TextMessage) {
				byteBuffer = ByteBuffer.wrap(((TextMessage) webSocketMessage).asBytes());
			}
			else if (webSocketMessage instanceof BinaryMessage) {
				byteBuffer = ((BinaryMessage) webSocketMessage).getPayload();
			}
			else {
				return result;
			}
			result = this.bufferingDecoder.decode(byteBuffer);
			if (result.isEmpty()) {
				if (logger.isTraceEnabled()) {
					logger.trace("Incomplete STOMP frame content received, bufferSize=" +
							this.bufferingDecoder.getBufferSize() + ", bufferSizeLimit=" +
							this.bufferingDecoder.getBufferSizeLimit() + ".");
				}
			}
			return result;
		}

    result = this.bufferingDecoder.decode(byteBuffer);

通过这一句查看源码。是在BufferingStompDecoder类中。

public List<Message<byte[]>> decode(ByteBuffer newBuffer) {
		this.chunks.add(newBuffer);
		checkBufferLimits();

		Integer contentLength = this.expectedContentLength;
		if (contentLength != null && getBufferSize() < contentLength) {
			return Collections.emptyList();
		}

		ByteBuffer bufferToDecode = assembleChunksAndReset();
		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
		List<Message<byte[]>> messages = this.stompDecoder.decode(bufferToDecode, headers);

		if (bufferToDecode.hasRemaining()) {
			this.chunks.add(bufferToDecode);
			this.expectedContentLength = StompHeaderAccessor.getContentLength(headers);
		}

		return messages;
	}

其中调用了checkBufferLimits()方法,此方法就是检查数据是否超过限制的。

private void checkBufferLimits() {
		Integer contentLength = this.expectedContentLength;
		if (contentLength != null && contentLength > this.bufferSizeLimit) {
			throw new StompConversionException(
					"STOMP 'content-length' header value " + this.expectedContentLength +
					"  exceeds configured buffer size limit " + this.bufferSizeLimit);
		}
		if (getBufferSize() > this.bufferSizeLimit) {
			throw new StompConversionException("The configured STOMP buffer size limit of " +
					this.bufferSizeLimit + " bytes has been exceeded");
		}
	}

抛出的异常也是在这个方法中抛出的,一层一层的看,这个方法中的bufferSizeLimit是在构造方法中初始化的,在StompWebSockerMessageCodec的构造方法中,进行BufferingDecoder对象初始化。在WebSocketTcpConnectionHandlerAdapter类中codec定义变量时进行了StompWebSocketMessageCodec对象初始化。并传入了参数,getInboundMessageSizeLimit().直接使用了WebSocketStompClient中的变量inboundMessageSizeLimit的get方法获取该参数的值。

public StompWebSocketMessageCodec(int messageSizeLimit) {
			this.bufferingDecoder = new BufferingStompDecoder(DECODER, messageSizeLimit);
		}
private final StompWebSocketMessageCodec codec = new StompWebSocketMessageCodec(getInboundMessageSizeLimit());

至此,spring在设置这个参数的源码大致就清楚了,当抛出这个异常时,只需要设置一下inboundMessageSizeLimit的值就可以解决了。

 stompClient.setInboundMessageSizeLimit(Integer.MAX_VALUE);

然而,这还没有完==

后来当数据长度比较大,超过4M的时候,发现报了另一个错误,连接直接关闭了:

org.springframework.messaging.simp.stomp.ConnectionLostException: Connection closed
	at org.springframework.messaging.simp.stomp.DefaultStompSession.afterConnectionClosed(DefaultStompSession.java:513)
	at org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter.afterConnectionClosed(WebSocketStompClient.java:349)
	at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.afterTransportClosed(AbstractClientSockJsSession.java:350)
	at org.springframework.web.socket.sockjs.client.WebSocketTransport$ClientSockJsWebSocketHandler.afterConnectionClosed(WebSocketTransport.java:173)
	at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onClose(StandardWebSocketHandlerAdapter.java:144)
	at org.glassfish.tyrus.core.TyrusEndpointWrapper.onClose(TyrusEndpointWrapper.java:1259)
	at org.glassfish.tyrus.core.TyrusWebSocket.onClose(TyrusWebSocket.java:130)
	at org.glassfish.tyrus.client.TyrusClientEngine$TyrusReadHandler.handle(TyrusClientEngine.java:756)
	at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientFilter$ProcessTask.execute(GrizzlyClientFilter.java:476)
	at org.glassfish.tyrus.container.grizzly.client.TaskProcessor.processTask(TaskProcessor.java:114)
	at org.glassfish.tyrus.container.grizzly.client.TaskProcessor.processTask(TaskProcessor.java:91)
	at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientFilter.handleRead(GrizzlyClientFilter.java:272)
	at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112)
	at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
	at org.glassfish

在stackflow上看到有人说是设置container的默认最大消息大小(地址:https://stackoverflow.com/questions/38962389/websocket-java-client-spring-stomp-transport-error-connectionlostexception


container.setDefaultMaxTextMessageBufferSize(Integer.MAX_VALUE);
container.setDefaultMaxBinaryMessageBufferSize(Integer.MAX_VALUE);
		 

我尝试了一下,不能解决。继续去看源码,从报错的位置进去看,这个报错之后Stomp连接就关闭了,那就看关闭之前的方法。报错位置关闭之前的最后一个方法是org.glassfish.tyrus.client包下的TyrusClientEngine类中的一个静态私有类TyrusReadHandler中。源码如下:

 @Override
        public void handle(ByteBuffer data) {
            try {
                if (data != null && data.hasRemaining()) {

                    if (buffer != null) {
                        data = Utils.appendBuffers(buffer, data, incomingBufferSize, BUFFER_STEP_SIZE);
                    } else {
                        int newSize = data.remaining();
                        if (newSize > incomingBufferSize) {
                            throw new IllegalArgumentException("Buffer overflow.");
                        } else {
                            final int roundedSize = (newSize % BUFFER_STEP_SIZE) > 0 ? ((newSize / BUFFER_STEP_SIZE)
                                    + 1) * BUFFER_STEP_SIZE : newSize;
                            final ByteBuffer result = ByteBuffer.allocate(roundedSize > incomingBufferSize ? newSize
                                                                                  : roundedSize);
                            result.flip();
                            data = Utils.appendBuffers(result, data, incomingBufferSize, BUFFER_STEP_SIZE);
                        }
                    }

                    do {
                        Frame frame = handler.unframe(data);
                        if (frame == null) {
                            buffer = data;
                            break;
                        } else {
                            for (Extension extension : negotiatedExtensions) {
                                if (extension instanceof ExtendedExtension) {
                                    try {
                                        frame = ((ExtendedExtension) extension)
                                                .processIncoming(extensionContext, frame);
                                    } catch (Throwable t) {
                                        LOGGER.log(
                                                Level.FINE,
                                                String.format(
                                                        "Extension '%s' threw an exception during processIncoming "
                                                                + "method invocation: \"%s\".",
                                                        extension.getName(), t.getMessage()), t);
                                    }
                                }
                            }

                            handler.process(frame, socket);
                        }
                    } while (true);
                }
            } catch (WebSocketException e) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
                socket.onClose(new CloseFrame(e.getCloseReason()));
            } catch (Exception e) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
                socket.onClose(new CloseFrame(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, e
                        .getMessage())));
            }
        }
    }

没有全部理解,只是大概去梳理了一下,应当是在这个位置出现了异常:

 final int roundedSize = (newSize % BUFFER_STEP_SIZE) > 0 ? ((newSize / BUFFER_STEP_SIZE)
                                    + 1) * BUFFER_STEP_SIZE : newSize;
                            final ByteBuffer result = ByteBuffer.allocate(roundedSize > incomingBufferSize ? newSize
                                                                                  : roundedSize);
                            result.flip();
                            data = Utils.appendBuffers(result, data, incomingBufferSize, BUFFER_STEP_SIZE);
                      

所以在handle方法中catch到异常,并关闭了连接。

catch (Exception e) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
                socket.onClose(new CloseFrame(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, e
                        .getMessage())));
            }

进入Utils.appendBuffers方法。

 public static ByteBuffer appendBuffers(ByteBuffer buffer, ByteBuffer buffer1, int incomingBufferSize,
                                           int BUFFER_STEP_SIZE) {

        final int limit = buffer.limit();
        final int capacity = buffer.capacity();
        final int remaining = buffer.remaining();
        final int len = buffer1.remaining();

        // buffer1 will be appended to buffer
        if (len < (capacity - limit)) {

            buffer.mark();
            buffer.position(limit);
            buffer.limit(capacity);
            buffer.put(buffer1);
            buffer.limit(limit + len);
            buffer.reset();
            return buffer;
            // Remaining data is moved to left. Then new data is appended
        } else if (remaining + len < capacity) {
            buffer.compact();
            buffer.put(buffer1);
            buffer.flip();
            return buffer;
            // create new buffer
        } else {
            int newSize = remaining + len;
            if (newSize > incomingBufferSize) {
                throw new IllegalArgumentException(LocalizationMessages.BUFFER_OVERFLOW());
            } else {
                final int roundedSize =
                        (newSize % BUFFER_STEP_SIZE) > 0 ? ((newSize / BUFFER_STEP_SIZE) + 1) * BUFFER_STEP_SIZE
                                : newSize;
                final ByteBuffer result = ByteBuffer.allocate(roundedSize > incomingBufferSize ? newSize : roundedSize);
                result.put(buffer);
                result.put(buffer1);
                result.flip();
                return result;
            }
        }
    }

一层一层理解下去,应当是newSize>incomingBufferSize,抛出了异常。其中incomingBufferSize来至于TyrusClientEngine类中,

    public static final int DEFAULT_INCOMING_BUFFER_SIZE = 4194315; // 4M (payload) + 11 (frame overhead)

newSize是当前空间剩余的空间+当前数据长度,当数据来的时候,分配的空间是默认的4M的空间,如果数据大于4M,则抛出异常:

if (newSize > incomingBufferSize) {
                throw new IllegalArgumentException(LocalizationMessages.BUFFER_OVERFLOW());
            } 

所以在handle方法中catch到了异常,并关闭了连接。但是tyrus-standalone-client-1.13.1.jar这个包中的DEFAULT_INCOMING_BUFFER_SIZE参数,目前我也没找到在哪里可以进行设置值,可以自定义。所以只能去减少数据长度。看看后面会不会有时间去找一下这个东西。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值