最近在使用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参数,目前我也没找到在哪里可以进行设置值,可以自定义。所以只能去减少数据长度。看看后面会不会有时间去找一下这个东西。