4、网络编程基础:Socket编程入门、TCP与UDP在交易中的选择、非阻塞I/O与epoll模型

做量化交易系统,尤其是高频做市,网络编程是绕不开的坎。说白了,你的策略再牛,如果网络层拉胯,订单发不出去或者收不到行情,一切都是白搭。今天我们就来聊聊网络编程里最核心的几个东西。

Socket编程入门:交易员和交易所的“电话线”

Socket,翻译过来叫“套接字”,但我觉得叫它“通信管道”更形象。你想想看,你的交易程序和交易所服务器之间要说话,总得有个通道吧?Socket就是这个通道的两端。

我刚开始写交易程序时,觉得Socket很神秘。后来发现,它其实就是一套API。你调用它,就能在网络上发数据。核心流程就四步:

  • 创建Socket:就像申请一根电话线。
  • 绑定地址:告诉系统,我这根线接在哪个端口上。
  • 监听/连接:服务端等着别人打进来,客户端主动拨号。
  • 收发数据:开始通话,传订单和行情。

一个最简单的TCP客户端代码长这样:

#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("192.168.1.100");

    connect(sock, (struct sockaddr*)&addr, sizeof(addr));
    // 发送订单
    char order[] = "BUY 100 AAPL @ 150.00";
    send(sock, order, strlen(order), 0);
    return 0;
}

嗯,这里要注意,htonsinet_addr这些函数,是网络编程的“基本功”。我见过不少新手直接写死端口号,结果换了台机器就崩了。建议你把这些配置抽出来,放到配置文件里。

TCP与UDP在交易中的选择:一个都不能少

很多新手会问:“做交易到底用TCP还是UDP?”我的答案是:看场景,而且往往是两者都用。

特性 TCP UDP
可靠性 保证送达,顺序一致 不保证,可能丢包
速度 较慢(有重传机制) 极快(无握手)
适用场景 订单发送、风控指令 行情推送、心跳检测

我个人习惯是:订单走TCP,行情走UDP。为什么?

  • 订单必须可靠:你下了一笔买单,如果丢了,可能就错过了最佳成交价。TCP的重传机制虽然慢一点,但能保证交易所一定收到。
  • 行情可以容忍丢包:行情数据每秒成千上万笔,丢了一两笔,下一笔马上补上。UDP的延迟更低,更适合做市商抢速度。

我在项目中遇到过一个问题:某次用UDP推行情,结果网络波动导致丢包率飙升,策略直接“瞎了”。后来我加了一个序列号校验,如果发现跳号,就主动请求重传。这算是一个折中方案。

避坑指南:我曾经在UDP里试图自己实现可靠传输,结果搞出了一个“四不像”,既没有TCP可靠,又没有UDP快。后来我学乖了——不要重复造轮子。如果场景需要可靠,直接用TCP;如果追求极致速度,就用UDP加简单的校验。

非阻塞I/O与epoll模型:让程序“忙而不乱”

做高频交易,最怕的就是程序“卡住”。比如你用阻塞式Socket收行情,如果网络延迟,整个线程就挂在那了。这在高频场景下是致命的。

非阻塞I/O,说白了就是:“我发个请求,不等你回复,我先干别的”。你调用recv,如果没有数据,它立刻返回一个错误码,而不是傻等。

但非阻塞I/O有个问题:你怎么知道什么时候有数据来了?总不能一直轮询吧?那太浪费CPU了。

这时候,epoll就登场了。它是Linux下最高效的I/O多路复用机制。你可以把它想象成一个“前台接待员”:你把所有Socket都告诉它,然后去忙别的。一旦某个Socket有数据了,它立刻通知你。

核心流程:

  1. 创建epoll实例epoll_create()
  2. 注册事件:把需要监听的Socket加进去,告诉它“有数据读就通知我”
  3. 等待事件epoll_wait(),阻塞等待,直到有事件发生
  4. 处理事件:遍历返回的事件列表,挨个处理

一个简单的epoll示例:

int epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;  // 监听可读事件
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

struct epoll_event events[1024];
while (1) {
    int n = epoll_wait(epfd, events, 1024, -1);
    for (int i = 0; i < n; i++) {
        if (events[i].data.fd == listen_fd) {
            // 新连接来了
        } else {
            // 有数据可读
        }
    }
}

你想想看,如果不用epoll,而是每个连接开一个线程,那1000个连接就是1000个线程。线程切换的开销,足以让你的交易延迟飙升。而epoll只用几个线程,就能管理成千上万个连接。

个人经验:我建议你在epoll里使用边缘触发(ET)模式。水平触发(LT)虽然简单,但容易重复通知,增加不必要的开销。边缘触发只在状态变化时通知一次,效率更高。不过要注意,ET模式下必须一次性把数据读完,否则会丢数据。

知识体系总览

下面这张图,是我对本章核心逻辑的总结。你可以把它当作一个“地图”,随时回来对照。

网络编程核心知识体系 Socket编程 TCP vs UDP 非阻塞I/O & epoll 创建 → 绑定 → 监听/连接 → 收发 订单走TCP,行情走UDP epoll_create → epoll_ctl → epoll_wait 核心原则 可靠性与速度的平衡 | 非阻塞避免线程阻塞 | epoll实现高并发

本章小结:网络编程是量化交易系统的“血管”。Socket是基础,TCP和UDP是工具,epoll是效率保障。记住:没有银弹,每种技术都有它的适用场景。多在实践中积累经验,慢慢你就能找到最适合自己系统的方案。

专注资料整理