为什么我的 tomcat websocket OnMessage 注释方法从未被调用?



我正在尝试将websocket与tomcat 8.5一起使用来监视日志文件,但是我的代码无法按预期工作:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Writer;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/ws/log")
public class LogEndPoint extends Endpoint {
private Thread tailer = null;
@Override
public void onOpen(Session session, EndpointConfig config) {
System.out.println("---------------- Connection Established ----------------");
System.out.println("----message handlers's size in current size=" + session.getMessageHandlers().size());
//      session.addMessageHandler(new MessageHandler.Whole<String>() {
//
//          @Override
//          public void onMessage(String message) {
//              System.out.println("<<<< " + message);
//              try {
//                  tailer = new Thread(new FileTailer(getFilePath(message), session.getBasicRemote().getSendWriter(), 200));
//              } catch (IOException e) {
//                  e.printStackTrace();
//                  try {
//                      session.close();
//                  } catch (IOException e1) {
//                      e1.printStackTrace();
//                  }
//              }
//          }
//      });
}
@OnMessage
public void doOnMessage(String message, Session session) {
System.out.println("<<<< " + message);
try {
tailer = new Thread(new FileTailer(getFilePath(message), session.getBasicRemote().getSendWriter(), 200));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onClose(Session session, CloseReason closeReason) {
System.out.println("---------------- Connection Closed ----------------");
if(tailer != null)
tailer.interrupt();
}
private String getFilePath(String name){
switch (name) {
case "app": {
return "d:\myApp.log";
}
case "server": {
return "d:\myServer.log";
}
}
throw new RuntimeException("invalid log type");
}
@Override
public void onError(Session session, Throwable throwable) {
System.out.println("---------------- Error Occured ----------------");
throwable.printStackTrace();
if(tailer != null)
tailer.interrupt();
}
class FileTailer implements Runnable{
private int interval = 200;
private long lastKnownPosition;
private RandomAccessFile file;
private Writer writer;
FileTailer(String file, Writer writer, int interval){
this.interval = interval;
this.writer = writer;
try(RandomAccessFile tmp = new RandomAccessFile(file, "r")){
this.file = tmp;
lastKnownPosition = file.length();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
followFile();
}
private void followFile(){
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(interval);
if(file.length() > lastKnownPosition){
file.seek(lastKnownPosition);
String line = null;
while((line = file.readLine()) != null){
writer.write(line);
}
lastKnownPosition = file.getFilePointer();
}else{
System.out.println("after " +  interval + " ms, no new data has been written into the file");
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}finally{
try {
if(file != null)
file.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}

}

我使用 javascript 向此端点发送一些文本消息,但doOnMessage方法从未被调用。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Logging Demo</title>
<style>
#shell{
background-color : #000;
color : #fff;
}
</style>
<script>
var socket = null;
function start(){
console.log('start');
socket = new WebSocket('ws://localhost:9080/labws/ws/log');
socket.onopen = function(event) {
console.log('onopen');
var label = document.getElementById('shell');
label.value = label.value + '-------------Connection Established-------------n';
socket.send('app');
label.value = label.value + '>>>> appn';
};
socket.onmessage = function(event) {
console.log('onmessage: ' + event.data);
var label = document.getElementById('shell');
label.value = label.value + '<<<< ' + event.data + 'n';
};

}
function stop(){
console.log('stop');
var label = document.getElementById('shell');
label.value =  label.value + '-------------Connection Closed-------------n';
//  if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, 'byebye');
//  }
}
</script>
</head>
<body>
<div>
<h3>WebSocket Logging Demo</h3>
<input type="button" value="start"  onclick="start()"/>&nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="stop"  onclick="stop()" /><br />
<textarea id="shell" rows="10" cols="300"></textarea>
</div>
</body>
</html>

感兴趣的是onOpen方法中的注释部分,如果我手动添加消息处理程序,它会按预期工作。我刚开始学习 websocket,我错过了什么吗?

编辑

对于其他人面临同样的问题,被接受的 anwser 提出了一个很好的观点,但我想加上我的两分钱:

JSR 356 第 2 章是这样说的:

There are two main means by which an endpoint can be created. The first means is to implement certain of
the API classes from the Java WebSocket API with the required behavior to handle the endpoint lifecycle,
consume and send messages, publish itself, or connect to a peer. Often, this specification will refer to this
kind of endpoint as a programmatic endpoint. The second means is to decorate a Plain Old Java Object
(POJO) with certain of the annotations from the Java WebSocket API. The implementation then takes these
annotated classes and creates the appropriate objects at runtime to deploy the POJO as a websocket endpoint.
Often, this specification will refer to this kind of endpoint as an annotated endpoint. The specification will
refer to an endpoint when it is talking about either kind of endpoint: programmatic or annotated.

基本上,这是接受的答案提到的注释驱动模式和界面模式,但分离并没有说我们不能使用混合模式! 所以这取决于实现,例如,我上面的代码适用于码头。

我认为您在这里混合了作为JSR 356的一部分存在的两个编程模型(它指定了Java开发人员在想要将WebSockets集成到他们的应用程序中时可以使用的API):注释驱动方法和接口驱动方法。

使用注释驱动的方法,您可以创建一个使用注释@ServerEndpoint注释作为端点的 POJO。它的生命周期也由注释决定:@onOpen@OnMessage等。

使用接口驱动的方法,您应该扩展javax.websocket.Endpoint class并覆盖onOpenonCloseonError方法。消息处理程序仅在onOpen方法中注册,就像在注释代码中一样。

因此,在您的情况下,我建议不要扩展 Endpoint 类并添加所有生命周期注释,它应该可以工作。

这是一篇关于JSR 365的更详细的文章

编辑

隔离并没有说我们不能使用混合模式! 所以这取决于实现,例如,问题中的代码适用于码头。

最新更新