第二章:网络编程基础(Socket)——TCP/UDP协议回顾、Python Socket编程入门、阻塞与非阻塞IO

2.1 先聊聊TCP和UDP——这两个老伙计

做量化交易,说白了就是跟网络打交道。你下的每一笔订单,都是通过网线飞出去的。那这数据到底怎么飞的?靠的就是TCP和UDP这两个协议。

TCP,我习惯叫它“靠谱老大哥”。它保证你的数据一定送到,顺序不乱。你想想看,下了一笔买入100手的单子,结果网络丢了一半,那不就乱套了?TCP会帮你重传、排序,确保交易所收到的就是你发的那条指令。

UDP呢,就是个“快枪手”。它不保证送达,也不管顺序。但速度极快,延迟极低。我在做行情数据接入时,就特别喜欢用UDP。为什么?因为行情数据是流式的,丢一帧无所谓,下一帧马上就来。但如果你用TCP去收行情,万一丢包重传,那延迟就上去了。

核心区别一句话总结:

  • TCP:可靠、有序、有连接——适合交易下单、登录认证
  • UDP:快速、无连接、不可靠——适合行情推送、心跳检测

嗯,这里要注意。很多新手会以为UDP完全不可靠,其实不是。UDP只是不保证,但实际网络中UDP丢包率很低,尤其是在局域网内。我曾在机房做过测试,千兆网络下UDP丢包率不到0.01%。所以别怕,大胆用。

2.2 Python Socket编程——从零开始写一个“小电话”

Python的socket模块,就是操作系统网络接口的封装。说白了,你调用的就是系统底层的API。

先看一个最简单的TCP服务端和客户端。我当年第一次写的时候,还闹过笑话——忘了绑端口,结果一直连不上。

TCP服务端示例

import socket

# 创建socket对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定地址和端口
server.bind(('0.0.0.0', 8888))

# 开始监听,最大连接数设为5
server.listen(5)
print('服务端已启动,等待连接...')

# 接受客户端连接
conn, addr = server.accept()
print(f'客户端已连接:{addr}')

# 接收数据
data = conn.recv(1024)
print(f'收到消息:{data.decode()}')

# 发送响应
conn.send('你好,客户端!'.encode())

# 关闭连接
conn.close()
server.close()

TCP客户端示例

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))

client.send('你好,服务端!'.encode())
response = client.recv(1024)
print(f'服务端回复:{response.decode()}')

client.close()

你看,就这么几行代码,一个完整的TCP通信就搭起来了。但实际项目中,没人这么写。为什么?因为它是阻塞的。

2.3 阻塞与非阻塞IO——别让你的程序“卡死”

刚才那个例子,accept()recv()send()都是阻塞的。什么意思?就是程序执行到recv()时,如果对方没发数据,它就停在那等着,啥也不干。

我遇到过最惨的一次,生产环境下的行情网关,就因为一个recv阻塞,导致整个线程挂起,行情延迟了整整3秒。那3秒里,我们的策略还在按旧数据交易,亏了不少钱。

所以,做量化交易系统,必须用非阻塞IO。

怎么设置非阻塞?

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)  # 设置为非阻塞模式
server.bind(('0.0.0.0', 8888))
server.listen(5)

try:
    conn, addr = server.accept()
except BlockingIOError:
    # 没有连接时,不会阻塞,直接抛出异常
    print('当前没有新连接,继续做其他事情...')

非阻塞模式下,如果没有数据,recv()会立刻返回一个异常。你可以捕获这个异常,然后去做别的事。比如去处理另一个连接,或者去计算策略信号。

我的小技巧:

非阻塞IO配合select/poll/epoll使用,效果最佳。select就像个“门卫”,帮你盯着哪些socket有数据来了,你再挨个去处理。这样就不会漏掉任何一个连接。

2.4 一张图看懂本章核心

下面这张图,是我自己画的知识体系图。你看一遍,基本就明白这章在讲什么了。

网络编程基础(Socket)知识体系 Socket编程 TCP协议 可靠、有序、有连接 适合:下单、登录 UDP协议 快速、无连接、不可靠 适合:行情、心跳 阻塞IO 简单但效率低,会卡住线程 非阻塞IO 高效,配合select/epoll使用

2.5 避坑指南——我踩过的那些坑

做网络编程,坑真的不少。我挑几个最常见的说说。

我曾经踩过的坑:

  • 粘包问题:TCP是流式协议,没有边界。你发两次数据,对方可能一次全收了。解决办法:自定义协议头,比如前4个字节表示数据长度。
  • 端口占用:服务端挂了再重启,提示“Address already in use”。加个setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)就能解决。
  • recv返回空字节:对方关闭连接时,recv会返回b''。很多人没判断这个,导致死循环。记住:收到空字节,就该关闭连接了。

嗯,这些坑说起来简单,但真遇到了,排查起来很费时间。我建议你写代码时,就把这些边界情况都处理掉。别等到线上出问题了再补。

2.6 小结

这一章,我们聊了TCP和UDP的区别,写了最简单的Socket代码,也搞懂了阻塞和非阻塞IO。说白了,网络编程就是两件事:怎么连,怎么传。连的方式选TCP还是UDP,传的方式选阻塞还是非阻塞,全看你的业务场景。

做量化交易,我个人的建议是:交易指令用TCP+非阻塞,行情数据用UDP+非阻塞。这样既保证了可靠性,又兼顾了速度。

好了,这一章就到这。代码不多,但都是基础。你最好自己动手敲一遍,感受一下阻塞和非阻塞的区别。光看是学不会的。


专注资料整理