第二章:网络编程基础(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 一张图看懂本章核心
下面这张图,是我自己画的知识体系图。你看一遍,基本就明白这章在讲什么了。
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+非阻塞。这样既保证了可靠性,又兼顾了速度。
好了,这一章就到这。代码不多,但都是基础。你最好自己动手敲一遍,感受一下阻塞和非阻塞的区别。光看是学不会的。