3. FIX协议消息类型:管理类消息详解
管理类消息,说白了就是FIX会话的「交通警察」。它们不负责交易数据本身,而是负责连接怎么建立、怎么维持、怎么断开。我刚开始接触FIX时,觉得这些消息没啥技术含量,直到有一次线上环境因为心跳超时导致大量订单被拒……嗯,从那以后我再也不敢小看它们了。
3.1 Logon (35=A) —— 会话的握手礼
Logon消息是FIX会话的第一步。客户端向服务端发送Logon,服务端回复Logon,连接才算正式建立。你想想看,这就像两个人见面先握手,确认身份后才能谈正事。
Logon消息里最重要的几个字段:
| 标签 | 字段名 | 说明 |
|---|---|---|
| 98 | EncryptMethod | 加密方式,0表示不加密,1表示PKCS,2表示DES…… |
| 108 | HeartBtInt | 心跳间隔,单位秒。这个值双方必须一致 |
| 141 | ResetSeqNumFlag | 是否重置序列号,Y表示重置,N表示不重置 |
| 553 | Username | 用户名,用于身份认证 |
| 554 | Password | 密码,明文传输,建议配合加密方式使用 |
我个人习惯在Logon消息里把ResetSeqNumFlag设为Y。为什么?因为每次重连后序列号从1开始,排查问题方便很多。但要注意,生产环境如果频繁重连,序列号重置会导致对方丢消息判断困难——这是个取舍问题。
核心要点:Logon消息的HeartBtInt必须双方协商一致。我曾经遇到过一个坑:客户端设了30秒,服务端默认是60秒,结果服务端一直没收到心跳,直接踢掉了连接。后来我加了个配置校验,启动时先比对双方的心跳间隔,不一致就报错。
// Logon消息示例(FIX 4.4)
8=FIX.4.4|9=65|35=A|34=1|49=CLIENT1|52=20240101-00:00:00|56=BROKER1|
98=0|108=30|141=Y|553=trader01|554=pass123|10=123|
3.2 Heartbeat (35=0) —— 我还活着
Heartbeat消息,就是定时告诉对方「我还活着」。它的逻辑很简单:如果在HeartBtInt时间内没收到任何消息,就发一个Heartbeat。如果连续几个Heartbeat都没回应,那就认为连接断了。
这里有个细节:Heartbeat消息的MsgSeqNum是递增的。也就是说,即使你只是发心跳,序列号也在涨。这很重要——序列号是FIX消息可靠性的基石。
我的经验:心跳间隔不要设得太短。30秒是比较常见的值。设成5秒的话,网络抖动一下就会触发大量重连。设成120秒以上,断连后恢复太慢。我一般建议30-60秒之间。
3.3 TestRequest (35=1) —— 你还在吗?
TestRequest是Heartbeat的「加强版」。当一方怀疑连接可能断了,就主动发一个TestRequest。对方收到后必须回复一个Heartbeat,并且Heartbeat的TestReqID字段要和TestRequest里的保持一致。
为什么要用TestRequest?因为Heartbeat是定时发的,如果刚好在心跳间隔内断了,你要等下一个心跳才能发现。TestRequest可以主动探测,缩短了故障发现时间。
我遇到过一种情况:网络做了NAT,长时间没数据时NAT会话会被回收。这时候Heartbeat可能被中间设备拦截,但TestRequest因为带了TestReqID,能穿透一些防火墙。嗯,这算是个小技巧。
// TestRequest消息示例
8=FIX.4.4|9=56|35=1|34=5|49=CLIENT1|52=20240101-00:00:05|56=BROKER1|
112=TEST001|10=234|
3.4 ResendRequest (35=2) —— 我漏了,重发一下
ResendRequest是FIX协议里最「智能」的消息之一。它用于请求对方重发某个序列号范围内的消息。为什么需要这个?因为网络可能丢包,或者接收方处理不过来导致消息丢失。
ResendRequest的格式很简单:
- 7 (BeginSeqNo):起始序列号
- 16 (EndSeqNo):结束序列号,设为0表示「从起始号到最新」
对方收到后,会用SequenceReset (35=4)或Reject (35=3)来响应。如果消息还在缓存里,就逐个重发;如果已经过期了,就发一个Gap Fill(序列号跳跃填充)。
避坑指南:我曾经在生产环境遇到过一个问题:ResendRequest的EndSeqNo设成了0,结果对方把从断连开始到现在的所有消息都重发了一遍,整整几万条。我们的处理线程直接被打满,导致正常交易延迟。后来我加了个限制:ResendRequest最多请求1000条,超过就分批请求。
3.5 Logout (35=5) —— 再见,下次再聊
Logout消息用于正常关闭会话。发送方发Logout,接收方回复Logout,然后双方关闭Socket。Logout消息里可以带一个58 (Text)字段,说明退出原因。
常见的退出原因:
- 「Session expired」—— 会话过期
- 「User requested logout」—— 用户主动退出
- 「Trading session ended」—— 交易时段结束
注意:Logout后,双方应该重置序列号。如果不重置,下次重连时序列号可能对不上,导致ResendRequest风暴。我个人习惯在Logout后主动调用ResetSeqNumFlag=Y的Logon来重新建立会话。
// Logout消息示例
8=FIX.4.4|9=62|35=5|34=10|49=CLIENT1|52=20240101-00:00:10|56=BROKER1|
58=User requested logout|10=345|
3.6 管理类消息的交互流程
下面这张图展示了管理类消息的完整交互流程。我画这张图时特意把时序和异常情况都标出来了,方便你理解。
3.7 管理类消息的常见陷阱
最后,我总结几个实战中容易踩的坑:
- 序列号不同步:重连时如果序列号对不上,会触发大量ResendRequest。我建议每次重连都重置序列号,除非你有严格的审计需求。
- 心跳超时设置不合理:太短导致频繁重连,太长导致故障发现慢。30秒是个不错的起点。
- Logout后不清理资源:有些系统Logout后Socket还开着,导致资源泄漏。记得在Logout后关闭Socket并清理会话上下文。
- TestRequest的TestReqID重复:如果TestReqID重复,对方可能无法区分是新的探测还是旧的回复。我一般用时间戳+随机数来生成。
我的建议:如果你刚开始搭建FIX系统,先把管理类消息的测试用例写全。Logon/Logout的异常场景、心跳超时的恢复、ResendRequest的边界条件……这些测试通过了,你的会话层就稳了。交易数据出错还能补救,会话层崩了那就真的什么都干不了了。