我正在尝试实现一些基于websockets的应用程序,它将与JS客户端密切沟通。
发送消息的代码非常原始:
synchronized (session) {
if (session.isOpen()) {
session.getBasicRemote().sendText(message);
}
}
对于罕见的发送它可以正常工作,但是当几个线程试图通过同一个会话(套接字)发送一些消息时,抛出下一个异常(请注意它不是多线程问题,因为代码块是由会话同步的) :
java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1015)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:978)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendString(WsRemoteEndpointImplBase.java:161)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendText(WsRemoteEndpointBasic.java:37)
谷歌目前并没有这种类型的例外,在这个问题上打了几个小时之后,仍然没有解决方案。
Java 7.0.21,在Tomcat 7.0.52和Tomcat 8.0.3上测试。
任何答案都非常感谢! 提前谢谢。
更新3/11/2014:我使用Jetty 9.1测试了我的应用程序,但没有发生此异常。 我假设这是Tomcat实现错误。
答案 0 :(得分:11)
好的,这不是Tomcat问题,而是我的错。
我的onMessage函数返回了一个字符串,这意味着我回复了该消息。结果,这部分代码没有同步。
为:
@OnMessage
public String onMessage(String message, Session session) {
...
return message;
}
好:
@OnMessage
public void onMessage(String message, Session session) {
...
}
答案 1 :(得分:1)
我今天刚遇到这个问题,被接受的答案不是我的解决方案。我尝试将每个调用同步到代码中的远程端点,该远程端点只有4个实例。那也没有解决。我还尝试过更新到最新的tomcat版本,这是9.0.24,但没有修复。
这似乎是我的问题的根源,因为在一个传入的Websocket消息请求中,我在请求期间碰巧发送了两条不同的消息(故意)。我验证了两个sendText调用均已正确同步,它们在不同的块中被调用的时间大约为0.001毫秒或更短。
我真正快速解决的解决方案是使用远程端点的异步版本,并确保在请求发送下一个味精时已完成最后一个味精。我对此并不感到兴奋,但是它确实解决了这个问题……这是我编写的类,我现在只要我想通过websocket发送某些东西而无需同步块中的代码,就可以引用该对象。 *该类上的方法已经同步。希望这可以帮助某人。
注意:除了send *之外,我没有进行任何同步,因此不确定Ping / Pong是否会遇到相同的问题,我从未使用过它们。
public class WebSocketEndpointAsync implements RemoteEndpoint.Async {
private final Session _session;
private final Async _ep;
private Future<Void> _lastFuture = null;
public WebSocketEndpointAsync(Session session, Async ep)
{
_session = session;
_ep = ep;
}
@Override public long getSendTimeout() { return _ep.getSendTimeout(); }
@Override public void setSendTimeout(long timeout) { _ep.setSendTimeout(timeout); }
@Override public void setBatchingAllowed(boolean allowed) throws IOException { _ep.setBatchingAllowed(allowed); }
@Override public boolean getBatchingAllowed() { return _ep.getBatchingAllowed(); }
@Override public void flushBatch() throws IOException { _ep.flushBatch(); }
@Override public void sendPing(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPing(byteBuffer); }
@Override public void sendPong(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPong(byteBuffer); }
@Override public void sendText(String s, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
@Override public void sendBinary(ByteBuffer byteBuffer, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
@Override public void sendObject(Object o, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
protected synchronized void checkLastSendComplete() {
if (_lastFuture != null) {
try {
if (!_lastFuture.isDone()) {
// Only one write to the websocket can happen at a time, so we need to make sure the last one completed
// else we get ...
// java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
do { Thread.sleep(1); }
while (!_lastFuture.isDone());
}
// Get the result to ensure
var ignore = _lastFuture.get();
}
catch (InterruptedException ie) { }
catch (ExecutionException ee) { }
}
}
@Override
public synchronized Future<Void> sendText(String text) {
checkLastSendComplete();
return (_lastFuture = _ep.sendText(text));
}
@Override
public synchronized Future<Void> sendBinary(ByteBuffer byteBuffer) {
checkLastSendComplete();
return (_lastFuture = _ep.sendBinary(byteBuffer));
}
@Override
public synchronized Future<Void> sendObject(Object obj) {
checkLastSendComplete();
return (_lastFuture = _ep.sendObject(obj));
}
}
答案 2 :(得分:0)
我发现了这个:https://bz.apache.org/bugzilla/show_bug.cgi?id=56026
似乎tomcat做了一些意外的事情,作为一种解决方法,你必须同步所有session.sendxxx调用,无论它是否异步。