JPOS服务端如何在客户端建立连接时直接保存ISOSource,实现主动向客户端发送消息
JPOS服务端如何在客户端建立连接时直接保存ISOSource,实现主动向客户端发送消息
这个场景我之前做JPOS主动推送业务的时候也碰到过,你现在的写法确实只能在收到客户端的请求后才能捕获到ISOSource,想要客户端一连接上就拿到这个连接通道,得用上JPOS专门的SessionListener接口——它就是用来监听会话全生命周期事件的,比如连接建立、断开这些,刚好能解决你的问题。
下面给你一步步说怎么改:
1. 写一个会话监听器,捕获连接建立事件
首先我们要实现SessionListener接口,在客户端连接建立的时候(也就是sessionCreated方法触发时),把对应的ISOSource保存起来。这里要注意线程安全和多客户端的情况,别用单个static变量存,不然多个客户端连上来会覆盖,建议用ConcurrentHashMap来管理,按会话标识区分不同客户端:
@Slf4j @Component public class ClientConnectionListener implements SessionListener { // 用线程安全的Map管理多个客户端连接,key用会话名/客户端IP+端口当唯一标识 private static final ConcurrentHashMap<String, ISOSource> clientConnections = new ConcurrentHashMap<>(); @Override public void sessionCreated(Session session) { ISOSource source = session.getSource(); String sessionKey = session.getName(); // 或者用session.getSocketAddress()拿客户端地址当key clientConnections.put(sessionKey, source); log.info("客户端连接建立,已保存会话:{}", sessionKey); } @Override public void sessionClosed(Session session) { String sessionKey = session.getName(); clientConnections.remove(sessionKey); log.info("客户端连接断开,已清理会话:{}", sessionKey); } // 对外提供获取连接的方法,单客户端用这个拿活跃连接就行 public ISOSource getActiveClientConnection() { return clientConnections.values().stream() .filter(ISOSource::isConnected) .findFirst() .orElse(null); } // 多客户端场景下,根据会话key获取指定连接 public ISOSource getClientConnection(String sessionKey) { return clientConnections.get(sessionKey); } }
2. 修改QServer配置,注册会话监听器
接下来要把这个监听器加到你的QServer配置里,让QServer在会话创建/销毁时通知我们的监听器:
<server class="org.jpos.q2.iso.QServer" logger="Q2" name="simulator_10000"> <attr name="port" type="java.lang.Integer">10000</attr> <channel class="org.jpos.iso.channel.XMLChannel" logger="Q2" packager="org.jpos.iso.packager.XMLPackager"/> <request-listener class="com.example.jpos.services.HandleMsg" logger="Q2"/> <!-- 把我们的会话监听器加进来 --> <listener class="com.example.jpos.listeners.ClientConnectionListener" logger="Q2"/> <in>network-out</in> <out>network-in</out> </server>
3. 改造原有的HandleMsg服务,使用统一的连接管理
最后把你原来的HandleMsg里的static连接变量去掉,改成依赖我们的ClientConnectionListener来获取连接,这样不管客户端有没有先发请求,只要连接建立了,就能拿到ISOSource:
@Slf4j @Service public class HandleMsg implements ISORequestListener { private final ClientConnectionListener connectionListener; // 用构造注入的方式把监听器传进来(Spring Boot下会自动装配) public HandleMsg(ClientConnectionListener connectionListener) { this.connectionListener = connectionListener; } @Override public boolean process(ISOSource source, ISOMsg m) { try { if ("0800".equals(m.getMTI())) { ISOMsg r = (ISOMsg) m.clone(); r.setResponseMTI(); r.set(33, "Good Message"); source.send(r); return true; } } catch (ISOException | IOException e) { log.error("处理请求出错:{}", e.getMessage()); } return false; } public void sendMessageToService(String message) { // 拿活跃的客户端连接 ISOSource clientConnection = connectionListener.getActiveClientConnection(); try { if (Objects.isNull(clientConnection) || !clientConnection.isConnected()) { log.error("没有可用的客户端连接!"); return; } ISOMsg serverPush = new ISOMsg(); serverPush.setMTI("0210"); serverPush.set(33, message); clientConnection.send(serverPush); log.info("主动推送消息给客户端成功:{}", message); } catch (IOException | ISOException e) { throw new RuntimeException("主动推送消息失败", e); } } }
一些额外注意点
- 如果你是多客户端连接的场景,记得在调用
sendMessageToService时,要指定具体的sessionKey,用getClientConnection(sessionKey)来获取对应客户端的连接,别直接拿第一个活跃连接。 - 要确保
ClientConnectionListener被Spring正确扫描到(比如加了@Component注解,且所在包在Spring的扫描范围内),或者如果是纯Q2容器管理的话,要把它注册到Q2的组件里。 - 别忘了处理连接断开的情况,我们在
sessionClosed里已经清理了无效连接,避免后续调用时拿到已断开的连接。
备注:内容来源于stack exchange,提问作者shaxa 2000
相关产品推荐
相关产品推荐

