QuickFIXJ 2.3使用问题:如何捕获特定异常及手动控制登出逻辑
QuickFIXJ 2.3 Initiator 异常捕获与重连逻辑定制方案
问题1:特定异常场景捕获方案
你需要结合SessionStateListener的异常回调和Application接口的管理消息拦截能力,覆盖所有目标异常场景:
- 首先实现
SessionStateListener的onException方法,所有会话层级的运行时异常都会通过该方法抛出,可直接通过类型判断捕获特定异常:EndOfStream异常:直接判断异常是否为quickfix.EndOfStream实例即可捕获流终止场景- 序列号无效异常:捕获
quickfix.SequenceResetException类型异常即可识别本地和对端上报的序列号不匹配场景 - 会话数据错误:捕获
quickfix.FieldNotFound/IncorrectDataFormat/IncorrectTagValue等协议解析类异常即可覆盖
- 单会话登录冲突场景一般是对端返回登出消息时携带自定义提示,需要实现
Application接口的fromAdmin方法拦截管理消息,读取Text(58)字段的内容判断是否为重复登录提示
示例代码如下:
// 线程安全的会话错误标记Map private final ConcurrentHashMap<SessionID, ErrorType> sessionErrorMap = new ConcurrentHashMap<>(); @Override public void onException(SessionID sessionID, Throwable cause) { if (cause instanceof EndOfStream) { log.error("会话{} 触发流终止异常", sessionID, cause); sessionErrorMap.put(sessionID, ErrorType.END_OF_STREAM); } else if (cause instanceof SequenceResetException) { log.error("会话{} 序列号不匹配", sessionID, cause); sessionErrorMap.put(sessionID, ErrorType.SEQUENCE_INVALID); } else if (cause instanceof IncorrectDataFormat || cause instanceof IncorrectTagValue) { log.error("会话{} 协议数据解析错误", sessionID, cause); sessionErrorMap.put(sessionID, ErrorType.DATA_ERROR); } } @Override public void fromAdmin(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon { MsgType msgType = new MsgType(); message.getHeader().getField(msgType); // 拦截登出消息判断错误类型 if (MsgType.LOGOUT.equals(msgType.getValue())) { Text logoutText = new Text(); if (message.isSetField(logoutText)) { String text = message.getString(logoutText.getField()); if (text.contains("Session already logged on")) { sessionErrorMap.put(sessionID, ErrorType.DUPLICATE_SESSION); } } } } // 自定义错误类型枚举 enum ErrorType { UNKNOWN, END_OF_STREAM, SEQUENCE_INVALID, DUPLICATE_SESSION, DATA_ERROR, NETWORK_ERROR }
问题2:重连逻辑定制实现
resetOnLogon=Y仅会在序列号不匹配的登录场景触发自动序列号重置,框架默认的重连逻辑由ReconnectInterval配置控制,你可以在onDisconnect回调里判断断开原因,拦截非序列号场景的重连:
- 会话断开时读取之前存储的错误类型,如果为
SEQUENCE_INVALID且重试次数未超过阈值,直接放通框架默认的自动重连逻辑 - 如果是其他错误类型,主动调用会话登出方法、停止对应会话的调度,即可避免框架自动重连,触发手动登出流程
示例代码如下:
// 序列号异常最大重试次数,可按需调整 private static final int MAX_SEQ_RETRY = 5; // 序列号异常重试计数Map private final ConcurrentHashMap<SessionID, Integer> seqRetryCountMap = new ConcurrentHashMap<>(); // 你初始化的Initiator实例 private Initiator initiator; @Override public void onDisconnect(SessionID sessionID) { ErrorType errorType = sessionErrorMap.getOrDefault(sessionID, ErrorType.UNKNOWN); // 仅序列号异常场景放通自动重连 if (ErrorType.SEQUENCE_INVALID.equals(errorType)) { int retryCount = seqRetryCountMap.getOrDefault(sessionID, 0) + 1; seqRetryCountMap.put(sessionID, retryCount); if (retryCount <= MAX_SEQ_RETRY) { log.info("会话{} 序列号不匹配,第{}次自动重连", sessionID, retryCount); return; } log.error("会话{} 序列号重试超过{}次,触发手动登出", sessionID, MAX_SEQ_RETRY); } // 非序列号异常/重试超限,停止会话避免自动重连 Session session = Session.lookupSession(sessionID); if (session != null && session.isLoggedOn()) { session.logout("Manual logout for non-sequence error"); } if (initiator != null) { try { initiator.stopSession(sessionID, true); } catch (Exception e) { log.error("停止会话{}失败", sessionID, e); } } // 清理会话缓存数据 sessionErrorMap.remove(sessionID); seqRetryCountMap.remove(sessionID); // 这里可以加你自己的手动登出通知逻辑 }
注意事项
- 原有
resetOnLogon=Y和ReconnectInterval配置不需要修改,仅在非序列号场景主动停止会话即可 - 如果供应商存在直接断连不返回登出消息的场景,可以在
onConnectException回调里补充异常信息判断,标记对应错误类型
内容的提问来源于stack exchange,提问作者S Shah
相关产品推荐
相关产品推荐

