http大家肯定都不陌生了,入行就接触的老朋友了。
但是它的兄弟sse又是怎么一回事呢?
说它是http的兄弟,是因为它们同属应用层协议,具体一点,就是tcp的上一层。
然而,叫sse协议,只是一个口语上的习惯性表达,它本质上,只是一种特殊的http协议,而不是像websocket那样独立于http以外的协议。所以,可以叫他们亲兄弟也不为过。通过http本身就有的一系列规范,去做了一个有特定需求场景的协议。
sse是如何降临到世界上来的呢
起初,有了http,我们跟别人通信,我说一句,你回一句,很好很美满。
后来,大家在网络协议的基础上做出各种好玩的软件,游戏。发现了一个问题,就好像,当我要盯着股票软件看实时更新数据的时候。用http的话,那岂不是要自己不断地手点刷新盯盘?因为http只能一次一次地调用,调完这一次,下次它也不会主动发给你。
有的小聪明想到进一步的方法,那我就轮询呗,本地按照一定频率去访问服务端,那不就实现了自动刷新了吗。
然而这样做了一会儿,又暴露了新问题
第一,轮询的频率是多少,如果股票那段时间变动很慢,那么轮询快了是不是浪费网络资源。相反,轮询慢了,那么软件本身的信息功能性就大打折扣。
第二,轮询功能,大量频繁的连接创建和断开,你什么服务器能顶的起这样的压力?手机也顶不住这样的电力损耗吧。
第三,轮询一次要重新拉取所有信息,如果你需要的只是某一块信息,那么岂不是浪费带宽了。
既然从工程层面不好处理,那么网络大佬们开始动脑筋了。
压力主要是来自于轮询的反复创建销毁http连接,有没有可能,在用户用软件的这段时间里,就持续不断地送数据,但是不断开连接呢?
于是乎,sse闪亮登场了。
sse的本质
sse的本质,就是深入到tcp层面,跟传统的http做一个区分。当连接建立之后,不立刻断开,持续用这个连接推送数据,直到其中某一方主动断开连接,或者将数据发送完毕,再断开。这样不就解决了过度连接的资源损耗吗。
进一步,我们看到,sse并非是一个完全需要重新设计的逻辑,它与http非常相似,核心上只是多了一步特征,保持长连接。那么,在已有的http协议之上,我们使用keep-alive,是不是就能达到这个效果了呢?
sse的工作流程
1.请求端发出http请求,头部参数设置Accept: text/event-stream,告诉服务端,我要长连接。
2.服务端返还的第一个报文,响应头设置
yaml
HTTP/1.1 200 OK
Content-Type: text/event-stream // 告知浏览器,按照流式内容解析
Cache-Control: no-cache // 禁止缓存,保证实时推送
X-Accel-Buffering: no // 链路上的nginx/反向代理们不要攒包,直接转发报文。
Connection: keep-alive // 维持HTTP长连接不断开
3.保持长连接不断,服务端按照特有的sse数据帧,在tcp链路中持续发送消息。格式为:
kotlin
data: ...
4.重连机制。链路意外中断后,客户端感知,触发onerror。浏览器自动重新发一次get请求,额外带上Last-Event-ID这个请求头,服务端拿到这个ID,继续上次的传输。
5.连接关闭。客户端主动关闭,或者服务端发送204 N Content,否则客户端还是会重连。
注意:一次sse协议中,只有一次http的请求和响应,其他数据全在tcp链路层面传输。
自己实现简易的sse协议
只理解还是不过瘾的,想要更深入,那就自己实现一次sse协议。未完待续...
python
# sse_server.py
import socket
import threading
import time
def handle_client(conn):
try:
# 解析http请求
req = conn.recv(1024).decode("utf-8")
print("客户端请求头:\n", req)
# 构造个回复报文,返还回去,通知客户端和网络链路上的节点
sse_response = (
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/event-stream; charset=utf-8\r\n"
"Cache-Control: no-cache\r\n"
"Connection: keep-alive\r\n"
"X-Accel-Buffering: no\r\n" # 👉 你要的这个头
"\r\n"
)
# 推送
conn.send(sse_response.encode("utf-8"))
# 定义一段你想返回的长字符串
long_text = """
这是一段很长很长的文本内容。
我要把它分成好几段,
通过 SSE 协议一点点推送给客户端。
每一段都会单独发送,
客户端会按顺序接收并拼接起来。
这就是 SSE 流式传输的效果。
"""
# 把长文本按行拆分(你也可以按字数拆分)
lines = long_text.strip().split("\n")
# 循环一段一段发送
for line in lines:
# SSE 格式必须:data: 内容\n\n
msg = f"data: {line}\n\n"
# 发送一段(分段发送!)
conn.send(msg.encode("utf-8"))
# 可以加延迟,看得更清楚
time.sleep(1)
except Exception as e:
print("客户端断开")
finally:
conn.close()
def run_server():
# 创建socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 允许端口快速复用,防止服务重启时,报错端口被占用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定端口
server.bind(("127.0.0.1", 9000))
print("SSE 服务端已启动:127.0.0.1:9000")
# 同时能处理的排队数量,再多的请求同时来就会直接拒绝
server.listen(5)
while True:
# conn是客户端连接对象,addr是客户端地址
conn, addr = server.accept()
# 异步处理客户端请求
t = threading.Thread(target=handle_client, args=(conn,))
t.start()
if __name__ == "__main__":
run_server()
swift
# sse_client.py
import socket
def run_client():
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(("127.0.0.1", 9000))
# 标准 SSE 请求头
req = (
"GET /sse HTTP/1.1\r\n"
"Host: 127.0.0.1:9000\r\n"
"Accept: text/event-stream\r\n"
"Cache-Control: no-cache\r\n"
"Connection: keep-alive\r\n"
"\r\n"
)
sk.send(req.encode())
buffer = b""
while True:
chunk = sk.recv(1024)
if not chunk:
break
buffer += chunk
while b"\n\n" in buffer:
msg, buffer = buffer.split(b"\n\n", 1)
msg_str = msg.decode().strip()
if msg_str.startswith("data:"):
print("✅ 收到:", msg_str.replace("data:", "").strip())
sk.close()
if __name__ == "__main__":
run_client()