You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 09:15:03