SockJS Java 客户端自动重新连接
SockJS Java Client auto reconnect
我正在使用 SockJs Java 客户端连接不同服务器上的 websocket 运行。一切正常,比如服务器发布消息,我的 Java 客户端收到它,但是如果服务器重新启动,那么当我重新启动客户端时我无法收到任何 reply.But 然后一切正常。所以我想通过重新启动 SockJs Java 客户端来实现重新连接逻辑。我的代码如下:
@SpringBootApplication
public class Application {
private final static WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
private static Logger logger = Logger.getLogger(Application.class);
public static void main(String[] args) throws InterruptedException, ExecutionException {
SpringApplication.run(Application.class, args);
Transport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
List<Transport> transports = Collections.singletonList(webSocketTransport);
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
String url = "ws://{host}:{port}/hello";
ListenableFuture<StompSession> f = stompClient.connect(url, headers, new MyWebSocketHandler(), "localhost", 9090);
StompSession stompSession = f.get();
logger.info("Subscribing to greeting topic using session " + stompSession);
subscribeGreetings(stompSession);
}
public static void subscribeGreetings(StompSession stompSession) throws ExecutionException, InterruptedException {
stompSession.subscribe("/topic/greetings", new StompFrameHandler() {
public Type getPayloadType(StompHeaders stompHeaders) {
return byte[].class;
}
public void handleFrame(StompHeaders stompHeaders, Object o) {
logger.info("Received greeting " + new String((byte[]) o));
}
});
}
}
您可以实施代理模式。我使用 Java 模式重新连接。
但是我的实现是针对原始 Websocket java 客户端的。
代理模式涉及原始对象的功能。您需要将 reconnect() 和 onReconnect() 方法添加到 websocket 的生命周期中。 Websocket 只有 onError、onConnect、OnMessage、OnClose 方法。
我将为 SockJS Java 客户端实现一个重新连接器,然后我会 post 如果您需要代码。
我的一些 java 代码,但用于原始 websocket,不使用 STOMP。
基于此 java 脚本重新连接实现:https://github.com/joewalnes/reconnecting-websocket
Java代码:
/**
*
* Problemas conocidos:
*
* Si se realiza la instancia dos veces consecutivas, se generan dos conexiones. Esto es debido a que el Executor
* crea dos tareas y no limita.
* Una posible solucion seria convertir esta API de reconexion en un Singleton, sin embargo hacer eso significa solo
* poder crear una coneccion.
* Una forma de solucionar esto es crear una fabrica de websockets. Y por otro lado tener la API como un singleton.
*
* Para reproducir el problema:
*
* ReconnectWebsocket ws = new ReconnectWebsocket(new LogicWsExternal() {
@Override
public void onOpen(Session session) {
} // This is myPersonalEndPoint
},uri, settings
);
ws = new ReconnectWebsocket(new LogicWsExternal() {
@Override
public void onOpen(Session session) {
} // This is myPersonalEndPoint
},uri, settings
);
// .. dos conexiones...
*
*
*
* @author gas
*
*/
public class ReconnectWebsocket implements ReconnectObserver{
private final Logger LOG = LoggerFactory.getLogger(ReconnectWebsocket.class);
//
public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_WHITE = "\u001B[37m";
// private static ReconnectWebsocketTest instance = null;
Boolean debug;
Boolean automaticOpen;
Integer reconnectInterval;
Integer maxReconnectionInterval;
Float reconnectDecay;
Integer timeoutInterval;
Integer maxConnectAttempts;
String binaryType;
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Reas only. */
URI path;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
int connectAttemptsCount;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
WebSocketStates readyState = WebSocketStates.CLOSED;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
// TODO
//this.protocol = null;
// Private state variables
//ReconnectWebsocket self = this;
Session session; // In Javascript implementation is ws variable.
boolean forcedClose = false;
boolean timeOutFlag = false;
Future<Boolean> timerReconnectionFuture; // Usado para controlar el timer para las reconexnes.
//private ReconnectObservable observable;
LogicWsExternal logicExternal;
ClientManager client;
LogicWs wsLogic;
static ScheduledExecutorService executor;
static ScheduledExecutorService executor2;
Future openFuture;
AtomicBoolean openFlag;
/*
*
* Tyrus estates:
* org.glassfish.tyrus.core.TyrusWebSocket.State
*
* */
public static enum WebSocketStates { // Tyrus:
CONNECTING("CONNECTING",0) // NEW
,OPEN("OPEN",1) // CONNECTED
,CLOSING("CLOSING",2) // CLOSING
,CLOSED("CLOSED",3) // CLOSED
;
String desc;
Integer statusInt;
WebSocketStates(String desc,Integer statusInt){
this.desc=desc;
this.statusInt=statusInt;
}
public String getDescription() {
return desc;
}
public Integer getStatusInt() {
return statusInt;
}
public void printDescription(){
System.out.println("PrinterStatus: "+desc);
}
}
public ReconnectWebsocket(URI path) throws DeploymentException, IOException {
this(new LogicWsExternal(){
@Override
public void onOpen(Session session) { }},path, null);
}
// Consturctor whith Only package visivility
public ReconnectWebsocket(LogicWsExternal logicWsExernal, URI path) throws DeploymentException, IOException {
this(logicWsExernal,path, null);
}
public ReconnectWebsocket(LogicWsExternal logicWsExternal, URI path, ReconnectSettings settings) {
// Default setting
// Overwrite and define settings with options if they exist.
/** Wheter this instance should log debug mesages. */
this.debug = settings.getDebug()!=null ? settings.getDebug() : true;
/** Wheter or not the websocket should attempt to connect immediately upon instantiation. */
this.automaticOpen = settings.getAutomaticOpen() !=null ? settings.getAutomaticOpen() : true;
/** The number of milliseconds to delay before attempting to reconnect. */
this.reconnectInterval = settings.getReconnectInterval()!=null ? settings.getReconnectInterval() : 1000;
/** The maximum number of milliseconds to delay a reconnection attempt. Timeout to reconnect */
this.maxReconnectionInterval = settings.getMaxReconnectionInterval()!=null ? settings.getMaxReconnectionInterval() : 10000;
/** The rate of increase of the reconnect delay. Allows reconnect attemps to back off when problems persist. */
this.reconnectDecay = settings.getReconnectDecay()!=null ? settings.getReconnectDecay() : (float) 1.3;
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying */
this.timeoutInterval = settings.getTimeoutInterval()!=null ? settings.getTimeoutInterval() : 5000;
/** The number of connection attempts to make before to stop. Unlimited if value is zero.
**/
this.maxConnectAttempts = settings.getMaxConnectAttempts()!=null ? settings.getMaxConnectAttempts() : 0;
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
this.binaryType = settings.getBinaryType()!=null ? settings.getBinaryType() : "blob";
//settings.put("idStateEvenbusChannel", "false");
//settings.put("idStateEvenbusChannel", "false");
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Reas only. */
this.path = path;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
this.connectAttemptsCount = 0;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
this.readyState = WebSocketStates.CLOSED;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
// TODO
// "has a" rather than "is a" observable
//observable = new ReconnectObservable();
this.logicExternal = logicWsExternal;
wsLogic = new LogicWs();
//client = ClientManager.createClient();//GLiszli lient by default
// Java 7 cient.
client = ClientManager.createClient(JdkClientContainer.class.getName());
wsLogic.addObserver(this);
// By default initialize the executors.
executor = Executors.newScheduledThreadPool(1);
executor2 = Executors.newScheduledThreadPool(1);
openFlag = new AtomicBoolean(true);
// Wheher or not to create a websocket upon instantiation
if (this.automaticOpen) {
//this.open();
this.open();
}
}
public void open() {
if (readyState == WebSocketStates.CONNECTING || readyState == WebSocketStates.OPEN ) {
return;
}
if (executor.isShutdown()) {
executor = Executors.newScheduledThreadPool(1);
}
if (executor2.isShutdown()) {
/*
* Este poolthread se apaga cuando se manda a llamar la funcion close() de la API.
* El apagado se realiza porque se considera que ya no se va o volver a conectar.
*/
executor2 = Executors.newScheduledThreadPool(1);
}
/*
* Resetear variables
*/
AtomicInteger counter = new AtomicInteger(0);
connectAttemptsCount = 0;
readyState = WebSocketStates.CONNECTING;
// Ejecutar funciones en metodo OnConnecting
//String reconnectReason = e.getMessage();
//self.update(new InternalMessageWs(WsEventType.ONCONNECTING,new OnConnectingEvent(reconnectReason)));
update(new InternalMessageWs(WsEventType.ONCONNECTING,new OnConnectingEvent("First Connect")));
Runnable openRun = () -> {
do{
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket attempt-connect# "+(connectAttemptsCount+1)+" of "+(maxConnectAttempts==0?"infinite":maxConnectAttempts)+" URI="+path.getPath());
}
Callable<Session> task1 = new Callable<Session>() {
@Override
public Session call() throws Exception {
// Avizar al API que se esta intentando realizar una reconexión. Patron productor-consumidor.
// TODO Deberia de ser un hilo?? ESto ya que podria se r que algo en el metodo externo sea de tipo bloqueante.
watcherReconnectionTry();
Session session = client.connectToServer(wsLogic,path);
//self.session = client.connectToServer(wsLogic,self.path);
//System.out.println("ReconnectWebsocket:: client.connectToServer(...) is null:"+(session==null?"true":"false"));
//System.out.println("ReconnectWebsocket:: client.connectToServer(...) is open:"+session.isOpen());
return session;
}
};
Future<Session> future = executor.submit(task1);
try {
// Tiempo de espera antes de interrumpir volver a intentarlo.
//Session s = future.get(self.timeoutInterval,TimeUnit.MILLISECONDS);
//Session s = future.get(30,TimeUnit.SECONDS);
//return s;
//self.session = future.get(30,TimeUnit.SECONDS);
session = future.get(timeoutInterval,TimeUnit.MILLISECONDS);
break;
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO Auto-generated catch block
//e.printStackTrace(); //For debug only
// Calculate Back off time.
/*
*
*/
float timeout = (float) (reconnectInterval * Math.pow(reconnectDecay,connectAttemptsCount));
connectAttemptsCount++;
if (maxConnectAttempts > 0 && ( connectAttemptsCount >= maxConnectAttempts )) {
break;
}
int maxTimeReconnect = (int) (timeout > maxReconnectionInterval ? maxReconnectionInterval : timeout);
counter.set(maxTimeReconnect/1000);
Callable<Boolean> timerReconnection = new Callable<Boolean>() {
@Override
public Boolean call() {
System.out.println("counter.get()="+counter.get());
while(counter.get() >= 0) {
System.out.println("Time next reconection: "+counter.get()+" seconds");
System.out.println("ThreadId: "+Thread.currentThread().getId() );
// Avizar a la API el tiempo para la sig. reconexión.. Patron productor-consumidor.
// TODO Deberia de ser un hilo?? ESto ya que podria se r que algo en el metodo externo sea de tipo bloqueante.
watcherTimeLeft(counter.get());
if (counter.get() == 0 && debug ) {
System.out.println("DEBUG: ReconnectingWebSocket connection-timeout: "+path.getPath());
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
counter.decrementAndGet();
}
return false;
}
};
/*
* scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS)
*
* 0 -> initialDelay, this time is includen in the backoff algorithm calc, then this value is zero.
* timeout -> the delay between the termination of one execution and the commencement of the next
*/
timerReconnectionFuture = executor.submit(timerReconnection);
try {
Boolean delayTime = timerReconnectionFuture.get();
} catch (InterruptedException | ExecutionException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}while (!Thread.currentThread().isInterrupted()); //END while
//while (openFlag.get()); //END while
};// END Runnable
openFuture = executor2.submit(openRun);
}//
public void send(String str) {
// Convertir el texto a objeto Message para enviarlo.
if (readyState == WebSocketStates.OPEN) {
if (debug) {
LOG.debug("Sending to URL:\"{}\", Data:\n\"{}\" ",path.getPath(),JsonWriter.formatJson(str));
//System.out.println("DEBUG: ReconnectingWebSocket sending to "+path.getPath()+": "+str);
}
//session.getBasicRemote().sendText(data);
try {
session.getBasicRemote().sendText(str);
} catch (IOException e) {
LOG.error("Sending to URL:\"{}\", Data:\"{}\" {}",path.getPath(),str,e);
//e.printStackTrace();
}
}
}
/**
*
* @param j objeto Json a enviar por Websocket.
* @throws EncodeException
* @throws IOException
*/
public void send(Json j) {
//System.out.println("ReconnectWebsocket:: send(Json INI)");
send(new Message(j));
//System.out.println("ReconnectWebsocket:: send(Json END)");
}
/**
* Transmits data to the server over Websocket connection.
*
* @param data a text string, ArrayBuffer or Blob to send to the server.
* @throws IOException
* @throws EncodeException
*/
public void send(Message data) {
//System.out.println("ReconnectWebsocket:: send(Message INI)");
if (readyState == WebSocketStates.OPEN) {
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket send "+path.getPath()+": "+data);
}
//System.out.println("ReconnectWebSocket::send(Message msg - Before)" );
//System.out.println("ReconnectWebSocket::send(Message msg - Before - session is null="+(session==null?"true":"false" ));
//System.out.println("ReconnectWebSocket::send(Message msg - Before - session is open="+session.isOpen());
//session.getBasicRemote().sendText(data);
try {
session.getBasicRemote().sendObject(data);
LOG.debug("Sending to URL:\"{}\", Data:\"{}\" ",path.getPath(),data);
//System.out.println("REconnectWebSocket::send(Message msg - After)" );
} catch (IOException | EncodeException e) {
//e.printStackTrace();
LOG.error("Sending to URL:\"{}\", Data:\"{}\" {}",path.getPath(),data,e);
}
}
// Deberia de detenerse la reconeccion en estos casos?, es decir detener despues de intentar enviar una
// cadena de texto pero que ha fallado.
//System.out.println("ReconnectWebsocket:: send(Message END)");
}
public void close(String reason) {
close(CloseReason.CloseCodes.NORMAL_CLOSURE,reason);
}
public void close() {
close(CloseReason.CloseCodes.NORMAL_CLOSURE,null);
}
public void close(CloseReason.CloseCodes code) {
close(code,null);
}
/** Closes the Websocket connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
public void close(CloseReason.CloseCodes code, String reason) {
if (readyState == WebSocketStates.CLOSED) {
return;
}
CloseReason closeReason;
forcedClose = true;
/*
* Status code: 1000
* Name: CLOSE_NORMAL
* Description: The connection successfully completed whatever purpose for
* which it was created.
* https://developer.mozilla.org/es/docs/Web/API/CloseEvent
*/
// Default CLOSE_NORMAL code
if (code==null) {
closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"ReconnectingWebSocket STOP");
} else if (reason!=null) {
closeReason = new CloseReason(code,reason);
} else {
closeReason = new CloseReason(code,"ReconnectingWebSocket STOP");
}
if ( (readyState == WebSocketStates.OPEN || readyState == WebSocketStates.CONNECTING) ) {
// Change readyState status:
readyState = WebSocketStates.CLOSED;
if (session==null) {
/*
* readyState == WebSocketStates.CONNECTING && session == null
*
* This ocurr when the server is off and the client is in a loop trying to connect.
*/
timerReconnectionFuture.cancel(true);
//openFuture.cancel(true);
} else {
/*
* readyState == WebSocketStates.OPEN && session != null
* or
* readyState == WebSocketStates.CONNECTING && session != null
*/
try { // Permanent close. Called via the Close method.
if (session.isOpen()) {
/*
* Session is previously closed when has connected at less one time, after the server shutdown
* and the reconnection beging. During the reconnection if you try to close (forced close)
* then session.close will thorwn a error.
* To fix we have verificate if the session is closed.
*/
session.close(closeReason);
}
executor.shutdown();
executor2.shutdown();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
LOG.error("Error cerrando sesion. {}",e);
}
}
//openFuture.cancel(true);
}
}
/**
* Additional public API method to refresh the connection if still is open.
* After close, the websocket will try to reconnect.
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
public void refresh() {
if (readyState == WebSocketStates.OPEN) {
try {
session.close(new CloseReason(CloseCodes.SERVICE_RESTART, "refresh!!"));
} catch (IOException e) {
//e.printStackTrace();
LOG.error("Error cerrando sesion. {}",e);
}
} else {
// Stop timer of reconnection.
if (readyState == WebSocketStates.CONNECTING) {
//timerReconnectionFuture.cancel(true);
close(CloseCodes.SERVICE_RESTART, "refresh!!");
// Reset variables.
connectAttemptsCount = 0;
open();
}
}
}
public Session getSession(){
return session;
}
public WebSocketStates getReadyState() {
return readyState;
}
/**
* El observador de los cambios en el clente Websocket interno.
*/
@Override
public void update(InternalMessageWs msg) {
switch (msg.getType()) {
case ONOPEN:
// Cambiar estado de la conexión
readyState = WebSocketStates.OPEN;
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onOpen: "+path.getPath());
}
// Ejecutar las funciones onOPen que el usuario ha definido.
logicExternal.onOpen( ((OnOpenEvent)msg.getEvent()).getSession() );
break;
case ONMESSAGE:
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket onMessage: "+path.getPath());
}
OnMessageEvent evtMsg = (OnMessageEvent)msg.getEvent();
// Ejecutar las funciones OnMessage que el usuario ha definido.
logicExternal.onMessage(evtMsg.getSession(),evtMsg.getMessage());
break;
case ONCLOSE:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onClose: "+path.getPath()+" forcedClose="+forcedClose);
}
// Cambiar estado de la conexión
readyState = WebSocketStates.CLOSED;
OnCloseEvent evtClose = (OnCloseEvent)msg.getEvent();
// Ejecutar las funciones OnClose que el usuario ha definido.
logicExternal.onClose(evtClose.getSession(),evtClose.getReason());
/*
* Determinar si se debe vlver a conectar o no.
* Si forcedClose = true, entonces detener.
* Si forcedClose = false, entonces reconectar.
*/
if (!forcedClose) {
this.open();
}
else{
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket STOP Reconnectiing: "+path.getPath());
}
forcedClose = false;
}
break;
case ONERROR:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onError: "+path.getPath());
}
// Cambiar estado de la conexión
readyState = WebSocketStates.CLOSED;
OnErrorEvent evtError = (OnErrorEvent)msg.getEvent();
// Ejecutar las funciones OnError que el usuario ha definido.
logicExternal.onError(evtError.getSession(),evtError.getT());
// Volver a iniciar secuencia de conectar.
this.open();
// Algunos prfieren cerrar la conexion.
//this.close(CloseCodes.CLOSED_ABNORMALLY,evtError.getT().getMessage());
break;
case ONCONNECTING:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onConnecting: "+path.getPath());
}
OnConnectingEvent evtConnecting = (OnConnectingEvent)msg.getEvent();
// Ejecutar las funciones OnConnecting que el usuario ha definido.
logicExternal.onConnecting(evtConnecting.getReason());
break;
default:
break;
}
}
@Override
public void watcherReconnectionTry() {
logicExternal.watcherReconnectionTry();
}
@Override
public void watcherTimeLeft(int timeLeft) {
logicExternal.watcherTimeLeft(timeLeft);
}
}
一个接口:
public interface ReconnectObserver {
public void update(InternalMessageWs msg);
public void watcherReconnectionTry();
public void watcherTimeLeft(int timeLeft);
}
ReconnectObservable class:
import java.util.ArrayList;
public class ReconnectObservable implements ReconnectSubject {
private ArrayList<ReconnectObserver> observers;
public ReconnectObservable() {
observers = new ArrayList<ReconnectObserver>();
}
@Override
public void addObserver(ReconnectObserver observer) {
observers.add(observer);
}
@Override
public void notifyObservers(InternalMessageWs msg) {
for(ReconnectObserver observer : observers) {
observer.update(msg);
}
}
}
ReconnectSubject 接口:
public interface ReconnectSubject {
public void addObserver(ReconnectObserver observer);
//public void notifyObservers();
public void notifyObservers(InternalMessageWs msg);
}
InternalMessageWs class:
import javax.websocket.CloseReason;
import javax.websocket.Session;
public class InternalMessageWs {
WsEventType type;
Object event;
InternalMessageWs(WsEventType type) {
this.type = type;
this.event = null;
}
InternalMessageWs(WsEventType type, Object event) {
this.type = type;
this.event=event;
}
public WsEventType getType() {
return type;
}
public void setType(WsEventType type) {
this.type = type;
}
public Object getEvent() {
return event;
}
public void setEvent(Object event) {
this.event = event;
}
}
class OnOpenEvent {
Session session;
public OnOpenEvent(Session session) {
this.session = session;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnMessageEvent {
Message message;
Session session;
public OnMessageEvent(Session session, Message message) {
this.message = message;
this.session = session;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnCloseEvent {
Session session;
CloseReason reason;
public OnCloseEvent(Session session, CloseReason reason) {
this.session = session;
this.reason = reason;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public CloseReason getReason() {
return reason;
}
public void setReason(CloseReason reason) {
this.reason = reason;
}
}
class OnErrorEvent {
Session session;
Throwable t;
public OnErrorEvent(Session session,Throwable t) {
this.t = t;
this.session=session;
}
public Throwable getT() {
return t;
}
public void setT(Throwable t) {
this.t = t;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnConnectingEvent {
String reason;
public OnConnectingEvent(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}
enum WsEventType {
ONOPEN,ONMESSAGE,ONCLOSE,ONERROR,
ONCONNECTING // Using in Reconnecting state of the Websocket client.
}
您需要使用 Java 8 JDK 因为我使用可调用文件等
我正在使用 SockJs Java 客户端连接不同服务器上的 websocket 运行。一切正常,比如服务器发布消息,我的 Java 客户端收到它,但是如果服务器重新启动,那么当我重新启动客户端时我无法收到任何 reply.But 然后一切正常。所以我想通过重新启动 SockJs Java 客户端来实现重新连接逻辑。我的代码如下:
@SpringBootApplication
public class Application {
private final static WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
private static Logger logger = Logger.getLogger(Application.class);
public static void main(String[] args) throws InterruptedException, ExecutionException {
SpringApplication.run(Application.class, args);
Transport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
List<Transport> transports = Collections.singletonList(webSocketTransport);
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
String url = "ws://{host}:{port}/hello";
ListenableFuture<StompSession> f = stompClient.connect(url, headers, new MyWebSocketHandler(), "localhost", 9090);
StompSession stompSession = f.get();
logger.info("Subscribing to greeting topic using session " + stompSession);
subscribeGreetings(stompSession);
}
public static void subscribeGreetings(StompSession stompSession) throws ExecutionException, InterruptedException {
stompSession.subscribe("/topic/greetings", new StompFrameHandler() {
public Type getPayloadType(StompHeaders stompHeaders) {
return byte[].class;
}
public void handleFrame(StompHeaders stompHeaders, Object o) {
logger.info("Received greeting " + new String((byte[]) o));
}
});
}
}
您可以实施代理模式。我使用 Java 模式重新连接。 但是我的实现是针对原始 Websocket java 客户端的。
代理模式涉及原始对象的功能。您需要将 reconnect() 和 onReconnect() 方法添加到 websocket 的生命周期中。 Websocket 只有 onError、onConnect、OnMessage、OnClose 方法。
我将为 SockJS Java 客户端实现一个重新连接器,然后我会 post 如果您需要代码。
我的一些 java 代码,但用于原始 websocket,不使用 STOMP。 基于此 java 脚本重新连接实现:https://github.com/joewalnes/reconnecting-websocket
Java代码:
/**
*
* Problemas conocidos:
*
* Si se realiza la instancia dos veces consecutivas, se generan dos conexiones. Esto es debido a que el Executor
* crea dos tareas y no limita.
* Una posible solucion seria convertir esta API de reconexion en un Singleton, sin embargo hacer eso significa solo
* poder crear una coneccion.
* Una forma de solucionar esto es crear una fabrica de websockets. Y por otro lado tener la API como un singleton.
*
* Para reproducir el problema:
*
* ReconnectWebsocket ws = new ReconnectWebsocket(new LogicWsExternal() {
@Override
public void onOpen(Session session) {
} // This is myPersonalEndPoint
},uri, settings
);
ws = new ReconnectWebsocket(new LogicWsExternal() {
@Override
public void onOpen(Session session) {
} // This is myPersonalEndPoint
},uri, settings
);
// .. dos conexiones...
*
*
*
* @author gas
*
*/
public class ReconnectWebsocket implements ReconnectObserver{
private final Logger LOG = LoggerFactory.getLogger(ReconnectWebsocket.class);
//
public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_WHITE = "\u001B[37m";
// private static ReconnectWebsocketTest instance = null;
Boolean debug;
Boolean automaticOpen;
Integer reconnectInterval;
Integer maxReconnectionInterval;
Float reconnectDecay;
Integer timeoutInterval;
Integer maxConnectAttempts;
String binaryType;
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Reas only. */
URI path;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
int connectAttemptsCount;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
WebSocketStates readyState = WebSocketStates.CLOSED;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
// TODO
//this.protocol = null;
// Private state variables
//ReconnectWebsocket self = this;
Session session; // In Javascript implementation is ws variable.
boolean forcedClose = false;
boolean timeOutFlag = false;
Future<Boolean> timerReconnectionFuture; // Usado para controlar el timer para las reconexnes.
//private ReconnectObservable observable;
LogicWsExternal logicExternal;
ClientManager client;
LogicWs wsLogic;
static ScheduledExecutorService executor;
static ScheduledExecutorService executor2;
Future openFuture;
AtomicBoolean openFlag;
/*
*
* Tyrus estates:
* org.glassfish.tyrus.core.TyrusWebSocket.State
*
* */
public static enum WebSocketStates { // Tyrus:
CONNECTING("CONNECTING",0) // NEW
,OPEN("OPEN",1) // CONNECTED
,CLOSING("CLOSING",2) // CLOSING
,CLOSED("CLOSED",3) // CLOSED
;
String desc;
Integer statusInt;
WebSocketStates(String desc,Integer statusInt){
this.desc=desc;
this.statusInt=statusInt;
}
public String getDescription() {
return desc;
}
public Integer getStatusInt() {
return statusInt;
}
public void printDescription(){
System.out.println("PrinterStatus: "+desc);
}
}
public ReconnectWebsocket(URI path) throws DeploymentException, IOException {
this(new LogicWsExternal(){
@Override
public void onOpen(Session session) { }},path, null);
}
// Consturctor whith Only package visivility
public ReconnectWebsocket(LogicWsExternal logicWsExernal, URI path) throws DeploymentException, IOException {
this(logicWsExernal,path, null);
}
public ReconnectWebsocket(LogicWsExternal logicWsExternal, URI path, ReconnectSettings settings) {
// Default setting
// Overwrite and define settings with options if they exist.
/** Wheter this instance should log debug mesages. */
this.debug = settings.getDebug()!=null ? settings.getDebug() : true;
/** Wheter or not the websocket should attempt to connect immediately upon instantiation. */
this.automaticOpen = settings.getAutomaticOpen() !=null ? settings.getAutomaticOpen() : true;
/** The number of milliseconds to delay before attempting to reconnect. */
this.reconnectInterval = settings.getReconnectInterval()!=null ? settings.getReconnectInterval() : 1000;
/** The maximum number of milliseconds to delay a reconnection attempt. Timeout to reconnect */
this.maxReconnectionInterval = settings.getMaxReconnectionInterval()!=null ? settings.getMaxReconnectionInterval() : 10000;
/** The rate of increase of the reconnect delay. Allows reconnect attemps to back off when problems persist. */
this.reconnectDecay = settings.getReconnectDecay()!=null ? settings.getReconnectDecay() : (float) 1.3;
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying */
this.timeoutInterval = settings.getTimeoutInterval()!=null ? settings.getTimeoutInterval() : 5000;
/** The number of connection attempts to make before to stop. Unlimited if value is zero.
**/
this.maxConnectAttempts = settings.getMaxConnectAttempts()!=null ? settings.getMaxConnectAttempts() : 0;
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
this.binaryType = settings.getBinaryType()!=null ? settings.getBinaryType() : "blob";
//settings.put("idStateEvenbusChannel", "false");
//settings.put("idStateEvenbusChannel", "false");
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Reas only. */
this.path = path;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
this.connectAttemptsCount = 0;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
this.readyState = WebSocketStates.CLOSED;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
// TODO
// "has a" rather than "is a" observable
//observable = new ReconnectObservable();
this.logicExternal = logicWsExternal;
wsLogic = new LogicWs();
//client = ClientManager.createClient();//GLiszli lient by default
// Java 7 cient.
client = ClientManager.createClient(JdkClientContainer.class.getName());
wsLogic.addObserver(this);
// By default initialize the executors.
executor = Executors.newScheduledThreadPool(1);
executor2 = Executors.newScheduledThreadPool(1);
openFlag = new AtomicBoolean(true);
// Wheher or not to create a websocket upon instantiation
if (this.automaticOpen) {
//this.open();
this.open();
}
}
public void open() {
if (readyState == WebSocketStates.CONNECTING || readyState == WebSocketStates.OPEN ) {
return;
}
if (executor.isShutdown()) {
executor = Executors.newScheduledThreadPool(1);
}
if (executor2.isShutdown()) {
/*
* Este poolthread se apaga cuando se manda a llamar la funcion close() de la API.
* El apagado se realiza porque se considera que ya no se va o volver a conectar.
*/
executor2 = Executors.newScheduledThreadPool(1);
}
/*
* Resetear variables
*/
AtomicInteger counter = new AtomicInteger(0);
connectAttemptsCount = 0;
readyState = WebSocketStates.CONNECTING;
// Ejecutar funciones en metodo OnConnecting
//String reconnectReason = e.getMessage();
//self.update(new InternalMessageWs(WsEventType.ONCONNECTING,new OnConnectingEvent(reconnectReason)));
update(new InternalMessageWs(WsEventType.ONCONNECTING,new OnConnectingEvent("First Connect")));
Runnable openRun = () -> {
do{
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket attempt-connect# "+(connectAttemptsCount+1)+" of "+(maxConnectAttempts==0?"infinite":maxConnectAttempts)+" URI="+path.getPath());
}
Callable<Session> task1 = new Callable<Session>() {
@Override
public Session call() throws Exception {
// Avizar al API que se esta intentando realizar una reconexión. Patron productor-consumidor.
// TODO Deberia de ser un hilo?? ESto ya que podria se r que algo en el metodo externo sea de tipo bloqueante.
watcherReconnectionTry();
Session session = client.connectToServer(wsLogic,path);
//self.session = client.connectToServer(wsLogic,self.path);
//System.out.println("ReconnectWebsocket:: client.connectToServer(...) is null:"+(session==null?"true":"false"));
//System.out.println("ReconnectWebsocket:: client.connectToServer(...) is open:"+session.isOpen());
return session;
}
};
Future<Session> future = executor.submit(task1);
try {
// Tiempo de espera antes de interrumpir volver a intentarlo.
//Session s = future.get(self.timeoutInterval,TimeUnit.MILLISECONDS);
//Session s = future.get(30,TimeUnit.SECONDS);
//return s;
//self.session = future.get(30,TimeUnit.SECONDS);
session = future.get(timeoutInterval,TimeUnit.MILLISECONDS);
break;
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO Auto-generated catch block
//e.printStackTrace(); //For debug only
// Calculate Back off time.
/*
*
*/
float timeout = (float) (reconnectInterval * Math.pow(reconnectDecay,connectAttemptsCount));
connectAttemptsCount++;
if (maxConnectAttempts > 0 && ( connectAttemptsCount >= maxConnectAttempts )) {
break;
}
int maxTimeReconnect = (int) (timeout > maxReconnectionInterval ? maxReconnectionInterval : timeout);
counter.set(maxTimeReconnect/1000);
Callable<Boolean> timerReconnection = new Callable<Boolean>() {
@Override
public Boolean call() {
System.out.println("counter.get()="+counter.get());
while(counter.get() >= 0) {
System.out.println("Time next reconection: "+counter.get()+" seconds");
System.out.println("ThreadId: "+Thread.currentThread().getId() );
// Avizar a la API el tiempo para la sig. reconexión.. Patron productor-consumidor.
// TODO Deberia de ser un hilo?? ESto ya que podria se r que algo en el metodo externo sea de tipo bloqueante.
watcherTimeLeft(counter.get());
if (counter.get() == 0 && debug ) {
System.out.println("DEBUG: ReconnectingWebSocket connection-timeout: "+path.getPath());
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
counter.decrementAndGet();
}
return false;
}
};
/*
* scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS)
*
* 0 -> initialDelay, this time is includen in the backoff algorithm calc, then this value is zero.
* timeout -> the delay between the termination of one execution and the commencement of the next
*/
timerReconnectionFuture = executor.submit(timerReconnection);
try {
Boolean delayTime = timerReconnectionFuture.get();
} catch (InterruptedException | ExecutionException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}while (!Thread.currentThread().isInterrupted()); //END while
//while (openFlag.get()); //END while
};// END Runnable
openFuture = executor2.submit(openRun);
}//
public void send(String str) {
// Convertir el texto a objeto Message para enviarlo.
if (readyState == WebSocketStates.OPEN) {
if (debug) {
LOG.debug("Sending to URL:\"{}\", Data:\n\"{}\" ",path.getPath(),JsonWriter.formatJson(str));
//System.out.println("DEBUG: ReconnectingWebSocket sending to "+path.getPath()+": "+str);
}
//session.getBasicRemote().sendText(data);
try {
session.getBasicRemote().sendText(str);
} catch (IOException e) {
LOG.error("Sending to URL:\"{}\", Data:\"{}\" {}",path.getPath(),str,e);
//e.printStackTrace();
}
}
}
/**
*
* @param j objeto Json a enviar por Websocket.
* @throws EncodeException
* @throws IOException
*/
public void send(Json j) {
//System.out.println("ReconnectWebsocket:: send(Json INI)");
send(new Message(j));
//System.out.println("ReconnectWebsocket:: send(Json END)");
}
/**
* Transmits data to the server over Websocket connection.
*
* @param data a text string, ArrayBuffer or Blob to send to the server.
* @throws IOException
* @throws EncodeException
*/
public void send(Message data) {
//System.out.println("ReconnectWebsocket:: send(Message INI)");
if (readyState == WebSocketStates.OPEN) {
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket send "+path.getPath()+": "+data);
}
//System.out.println("ReconnectWebSocket::send(Message msg - Before)" );
//System.out.println("ReconnectWebSocket::send(Message msg - Before - session is null="+(session==null?"true":"false" ));
//System.out.println("ReconnectWebSocket::send(Message msg - Before - session is open="+session.isOpen());
//session.getBasicRemote().sendText(data);
try {
session.getBasicRemote().sendObject(data);
LOG.debug("Sending to URL:\"{}\", Data:\"{}\" ",path.getPath(),data);
//System.out.println("REconnectWebSocket::send(Message msg - After)" );
} catch (IOException | EncodeException e) {
//e.printStackTrace();
LOG.error("Sending to URL:\"{}\", Data:\"{}\" {}",path.getPath(),data,e);
}
}
// Deberia de detenerse la reconeccion en estos casos?, es decir detener despues de intentar enviar una
// cadena de texto pero que ha fallado.
//System.out.println("ReconnectWebsocket:: send(Message END)");
}
public void close(String reason) {
close(CloseReason.CloseCodes.NORMAL_CLOSURE,reason);
}
public void close() {
close(CloseReason.CloseCodes.NORMAL_CLOSURE,null);
}
public void close(CloseReason.CloseCodes code) {
close(code,null);
}
/** Closes the Websocket connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
public void close(CloseReason.CloseCodes code, String reason) {
if (readyState == WebSocketStates.CLOSED) {
return;
}
CloseReason closeReason;
forcedClose = true;
/*
* Status code: 1000
* Name: CLOSE_NORMAL
* Description: The connection successfully completed whatever purpose for
* which it was created.
* https://developer.mozilla.org/es/docs/Web/API/CloseEvent
*/
// Default CLOSE_NORMAL code
if (code==null) {
closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"ReconnectingWebSocket STOP");
} else if (reason!=null) {
closeReason = new CloseReason(code,reason);
} else {
closeReason = new CloseReason(code,"ReconnectingWebSocket STOP");
}
if ( (readyState == WebSocketStates.OPEN || readyState == WebSocketStates.CONNECTING) ) {
// Change readyState status:
readyState = WebSocketStates.CLOSED;
if (session==null) {
/*
* readyState == WebSocketStates.CONNECTING && session == null
*
* This ocurr when the server is off and the client is in a loop trying to connect.
*/
timerReconnectionFuture.cancel(true);
//openFuture.cancel(true);
} else {
/*
* readyState == WebSocketStates.OPEN && session != null
* or
* readyState == WebSocketStates.CONNECTING && session != null
*/
try { // Permanent close. Called via the Close method.
if (session.isOpen()) {
/*
* Session is previously closed when has connected at less one time, after the server shutdown
* and the reconnection beging. During the reconnection if you try to close (forced close)
* then session.close will thorwn a error.
* To fix we have verificate if the session is closed.
*/
session.close(closeReason);
}
executor.shutdown();
executor2.shutdown();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
LOG.error("Error cerrando sesion. {}",e);
}
}
//openFuture.cancel(true);
}
}
/**
* Additional public API method to refresh the connection if still is open.
* After close, the websocket will try to reconnect.
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
public void refresh() {
if (readyState == WebSocketStates.OPEN) {
try {
session.close(new CloseReason(CloseCodes.SERVICE_RESTART, "refresh!!"));
} catch (IOException e) {
//e.printStackTrace();
LOG.error("Error cerrando sesion. {}",e);
}
} else {
// Stop timer of reconnection.
if (readyState == WebSocketStates.CONNECTING) {
//timerReconnectionFuture.cancel(true);
close(CloseCodes.SERVICE_RESTART, "refresh!!");
// Reset variables.
connectAttemptsCount = 0;
open();
}
}
}
public Session getSession(){
return session;
}
public WebSocketStates getReadyState() {
return readyState;
}
/**
* El observador de los cambios en el clente Websocket interno.
*/
@Override
public void update(InternalMessageWs msg) {
switch (msg.getType()) {
case ONOPEN:
// Cambiar estado de la conexión
readyState = WebSocketStates.OPEN;
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onOpen: "+path.getPath());
}
// Ejecutar las funciones onOPen que el usuario ha definido.
logicExternal.onOpen( ((OnOpenEvent)msg.getEvent()).getSession() );
break;
case ONMESSAGE:
if (debug) {
System.out.println("DEBUG: ReconnectingWebSocket onMessage: "+path.getPath());
}
OnMessageEvent evtMsg = (OnMessageEvent)msg.getEvent();
// Ejecutar las funciones OnMessage que el usuario ha definido.
logicExternal.onMessage(evtMsg.getSession(),evtMsg.getMessage());
break;
case ONCLOSE:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onClose: "+path.getPath()+" forcedClose="+forcedClose);
}
// Cambiar estado de la conexión
readyState = WebSocketStates.CLOSED;
OnCloseEvent evtClose = (OnCloseEvent)msg.getEvent();
// Ejecutar las funciones OnClose que el usuario ha definido.
logicExternal.onClose(evtClose.getSession(),evtClose.getReason());
/*
* Determinar si se debe vlver a conectar o no.
* Si forcedClose = true, entonces detener.
* Si forcedClose = false, entonces reconectar.
*/
if (!forcedClose) {
this.open();
}
else{
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket STOP Reconnectiing: "+path.getPath());
}
forcedClose = false;
}
break;
case ONERROR:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onError: "+path.getPath());
}
// Cambiar estado de la conexión
readyState = WebSocketStates.CLOSED;
OnErrorEvent evtError = (OnErrorEvent)msg.getEvent();
// Ejecutar las funciones OnError que el usuario ha definido.
logicExternal.onError(evtError.getSession(),evtError.getT());
// Volver a iniciar secuencia de conectar.
this.open();
// Algunos prfieren cerrar la conexion.
//this.close(CloseCodes.CLOSED_ABNORMALLY,evtError.getT().getMessage());
break;
case ONCONNECTING:
if (debug ) {
System.out.println("DEBUG: ReconnectingWebSocket onConnecting: "+path.getPath());
}
OnConnectingEvent evtConnecting = (OnConnectingEvent)msg.getEvent();
// Ejecutar las funciones OnConnecting que el usuario ha definido.
logicExternal.onConnecting(evtConnecting.getReason());
break;
default:
break;
}
}
@Override
public void watcherReconnectionTry() {
logicExternal.watcherReconnectionTry();
}
@Override
public void watcherTimeLeft(int timeLeft) {
logicExternal.watcherTimeLeft(timeLeft);
}
}
一个接口:
public interface ReconnectObserver {
public void update(InternalMessageWs msg);
public void watcherReconnectionTry();
public void watcherTimeLeft(int timeLeft);
}
ReconnectObservable class:
import java.util.ArrayList;
public class ReconnectObservable implements ReconnectSubject {
private ArrayList<ReconnectObserver> observers;
public ReconnectObservable() {
observers = new ArrayList<ReconnectObserver>();
}
@Override
public void addObserver(ReconnectObserver observer) {
observers.add(observer);
}
@Override
public void notifyObservers(InternalMessageWs msg) {
for(ReconnectObserver observer : observers) {
observer.update(msg);
}
}
}
ReconnectSubject 接口:
public interface ReconnectSubject {
public void addObserver(ReconnectObserver observer);
//public void notifyObservers();
public void notifyObservers(InternalMessageWs msg);
}
InternalMessageWs class:
import javax.websocket.CloseReason;
import javax.websocket.Session;
public class InternalMessageWs {
WsEventType type;
Object event;
InternalMessageWs(WsEventType type) {
this.type = type;
this.event = null;
}
InternalMessageWs(WsEventType type, Object event) {
this.type = type;
this.event=event;
}
public WsEventType getType() {
return type;
}
public void setType(WsEventType type) {
this.type = type;
}
public Object getEvent() {
return event;
}
public void setEvent(Object event) {
this.event = event;
}
}
class OnOpenEvent {
Session session;
public OnOpenEvent(Session session) {
this.session = session;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnMessageEvent {
Message message;
Session session;
public OnMessageEvent(Session session, Message message) {
this.message = message;
this.session = session;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnCloseEvent {
Session session;
CloseReason reason;
public OnCloseEvent(Session session, CloseReason reason) {
this.session = session;
this.reason = reason;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public CloseReason getReason() {
return reason;
}
public void setReason(CloseReason reason) {
this.reason = reason;
}
}
class OnErrorEvent {
Session session;
Throwable t;
public OnErrorEvent(Session session,Throwable t) {
this.t = t;
this.session=session;
}
public Throwable getT() {
return t;
}
public void setT(Throwable t) {
this.t = t;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
class OnConnectingEvent {
String reason;
public OnConnectingEvent(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}
enum WsEventType {
ONOPEN,ONMESSAGE,ONCLOSE,ONERROR,
ONCONNECTING // Using in Reconnecting state of the Websocket client.
}
您需要使用 Java 8 JDK 因为我使用可调用文件等