一.什么是websocket
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。
二.应用场景分析
假设我们需要实现一个实时更新(更新频率约为0.001s)的折线图,数据来自后端从文件中读取。
正确演示效果如下
2.1不使用websocket的实现方法
前端通过轮询来获取数据,即每一次更新图表就像后端发送请求
前端代码如下
function fetchNewData() {
fetch('/data')
.then(response => response.json())
.then(newData => {
data.push(newData);
if (data.length > 1000) {
data.shift();
}
updateChart();
});
}
// 设置定时器 每0.2s发送请求
setInterval(fetchNewData, 200);
轮询频率为0.2s的效果
轮询频率为1s的效果
2.2原因分析
当折线图所要求时间频率不是很快,前端读取(轮询)的速度是小于等于后端更新的速度所以此刻折线图能够精准的显示正确读取的数据但是图表更新速度很慢
而如果当前端更新(轮询)速度大于后端更新的速度此时就会出现获取到多次相同数据。在折线图上显示的是直线,这段时间过后(更新(轮询)时间与后端更新的时间的时间差)直线才会变化数据
三.问题分析
3.1Fetch API 的不足
function fetchNewData() {
fetch('/data')
.then(response => response.json())
.then(newData => {
data.push(newData);
if (data.length > 1000) {
data.shift();
}
updateChart();
});
}
前端通过使用 Fetch API (Fetch API: 是浏览器提供的一种接口,使得 JavaScript 能够发起 HTTP 请求)来获取数据,实际上是在使用 HTTP 协议进行通信。HTTP协议是基于请求-响应模式的,客户端需要定期向服务器发起请求来获取最新的数据。
3.2WebSocket的优势
WebSocket允许服务器向客户端推送数据。WebSocket是一种全双工通信协议,属于持久化连接一旦建立连接,服务器可以随时向客户端推送数据,而不需要客户端主动发起请求。因此在数据更新时,后端可以主动将新数据推送给前端,而不是通过轮询的方式。
四.多线程处理提升并发性能线程
4.1新的问题
当数据量是比较庞大时,(前端代码是完全正确且能够接收实时更新),后端所有数据发送完时前端才开始显示所有数据,这明显和我们实时高频率展示数据的需求不符。
后端代码:
def handle_connect():
global now, value_index
print('Client connected')
# Load initial values
initial_values = load_initial_values(global_filepath)
global now, value_index
if initial_values is not None:
count = 0
a=0
current_time = datetime.datetime.now()
for value in initial_values:
now = datetime.datetime.now()
data={
"name": count,
"value": value
}
time.sleep(0.01)//模拟其他线程所需时间
emit('new_data', data)
count += 0.001
a+=1
if a >= 500:
print("完",time.time())
break
4.2基本概念
先理清几个概念
进程(process) : 是操作系统对一个正在运行的程序的一种抽象。
线程(thread):一个进程实际上可以有由多个叫做线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。进程可进一步细化为线程, 是一个程序内部的一条执行路径。 若一个进程同一时间并行执行多个线程,就是支持多线程的。
4.3问题分析
在处理客户端连接时,在主线程中执行耗时的操作(如数据发送),会导致主线程被阻塞。这样做会使得主线程无法及时响应新的连接请求或其他并发操作,从而降低了应用程序的响应速度和并发处理能力。
问题1
在我们的例子中循环每次发送数据后使用 time.sleep(0.01)
强制线程暂停了10毫秒。虽然这段时间很短,但它会阻塞当前线程,直到暂停时间结束再继续执行。这种方式称为阻塞式IO操作,因为它会使得当前线程在等待期间不能执行其他任务,直到暂停结束。
问题2
后端通过 emit('new_data', data)
向前端发送数据时,这个过程本质上是一个网络IO操作。网络IO操作是比较耗时的,特别是在发送大量数据或者频繁发送小量数据时,前端的网络连接或者处理能力有限,不能及时接收后端发送的数据,后端发送数据的线程可能会被阻塞,直到前端确认接收完毕或者发送缓冲区有空间来继续发送数据。
4.4多线程处理提升并发性能
直接上代码
@socketio.on('connect')
def handle_connect():
print('Client connected')
initial_values = load_initial_values(global_filepath)
if initial_values is not None:
thread = threading.Thread(target=send_data_to_client, args=(initial_values,))
thread.start()
def send_data_to_client(initial_values):
count = 0
a = 0
current_time = datetime.datetime.now()
for value in initial_values:
now = datetime.datetime.now()
data = {
"name": count,
"value": value
}
socketio.emit('new_data', data)
count += 0.001
a += 1
if a >= 5000:
print("Complete", time.time())
break
time.sleep(0.01)
优势分析
-
异步处理:使用了
threading.Thread
创建了一个新的线程thread
来执行send_data_to_client
函数。当有新的客户端连接时,handle_connect
函数可以迅速返回并继续处理下一个连接请求,而不会因为数据发送操作阻塞主线程。 -
避免阻塞:主线程的阻塞会导致其他客户端的请求响应延迟。通过将数据发送操作放入单独的线程中,可以避免主线程被长时间占用,从而保持服务器对其他客户端的响应速度。
-
并发性能:当有多个客户端同时连接并请求数据时,每个连接都会触发一个新的线程来处理数据发送,这样服务器可以更有效地处理并发请求,提升整体的并发性能
五.websocket的使用教程
python的flask-socketio文档,通过这个后端框架的基本介绍,内容很详细,可以了解它的功能
浏览器一般通过导入js文件来实现websocket功能,已经免费上传了点击此处