提示:本文主要介绍了如何在Python中利用WebSocket技术实现多人聊天通信。
文章目录
项目成品展示
加入第三个人后(理论上可以加入N个人)
前言
第一次发CSDN😂,简单分享一下
去年自己在学习的时候学习到了WebSocket这个技术,当时觉得挺新鲜和好玩的,可以实现远程收发消息,所以后来我就利用Android开发了一个客户端并把它部署在了一个免费的服务器上(送的一个月😂),我当时迫不及待的找了几个人做测试,发现还真的可以用!但是后面服务器过期之后再加上我又把我的电脑系统给重装了,重装了系统之后之前写的代码就都不见了( ̄﹃ ̄),这次我学聪明了,我准备把他分享在网上😂(虽然也不是什么重要的东西😂),如果能帮助到一些人学到一点东西,也挺不错的。好了,不要再啰嗦了,正片开始!
一、主要用到哪些技术?
客户端(这里是用python编写的控制台应用程序)
- python(客户端逻辑开发)
- websocket(本项目中实现收发消息最主要的技术)
- pyinstaller(用它把我们开发好的程序打包成.exe文件)
- colorama(用它来改变控制台输出的颜色)
- pygame(用它来播放背景音乐)
- requests(用来请求网络)
- datetime(格式化日期)
- threading(多线程相关)
- json(解析json)
服务端
- spring boot(开发后端程序,用它来实现消息转发)
- websocket(同样也会用到它)
二、原理解析
原理其实很简单,主要是利用WebSocket技术将用户想要发送的消息发送到服务端,服务端解析我们发送的消息,然后将解析后的消息进行一个封装,封装好指定的格式后转发给其他当前连接到服务器的其他用户就可以了,在这个案例中,服务器主要的作用就是实现消息转发功能,而客户端主要的作用就是发送消息到服务端和接收服务端的消息并展示。
三、什么是WebSocket?
上述说到WebSocket是:本文中实现收发消息最主要的技术!所以我们要先来简单了解一下WebSocket这个技术
以下内容摘自某AI:
WebSocket是一种在客户端和服务器之间进行双向通信的通信协议。它允许服务器主动将数据推送给客户端,而不需要客户端发送请求。相比传统的HTTP请求,WebSocket具有更低的延迟和更高的效率,因为它使用了持久连接,避免了频繁的建立和关闭连接的开销。
WebSocket协议基于TCP协议,通过使用特定的握手机制进行连接的建立,之后双方可以在随后的连接中自由地发送和接收数据。WebSocket可以在Web浏览器中直接使用,也可以在其他客户端应用程序中使用。它已经被广泛应用于实时聊天、在线游戏、股票行情等领域。
四、在Python中使用WebSocket
WebSocket可以在很多平台上使用,比如:Web浏览器、移动设备、桌面应用程序等,这次我是利用Python开发的一款比较简陋的控制台程序,但是对于学习来说还相对简单一点,不用搞那些图形化界面的东西。
1.引入WebSocket库
代码如下(示例):
# websocket
import websocket
2.创建一个WebSocket对象
代码如下(示例):
# websocket的url的格式大概长这样
# ws://host:port/path
# 这里的path要跟后端的设置要一致
ws = websocket.WebSocketApp(url,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
除了url
这个参数,其他的参数实际上是在给这个ws
对象绑定回调函数,比如on_open
参数,这个参数的作用是:指定在将来打开连接时应该回调哪个函数,这里指定的是on_open
函数(名称一样而已)。
3.调用WebSocket对象的run_forever()
方法
代码如下(示例):
ws.run_forever()
run_forever()
是 WebSocket 的方法之一,用于在服务器端持续运行 WebSocket 连接。 它的作用是让 WebSocket 服务器一直运行,接收和处理来自 WebSocket 客户端的消息,并在必要时向客户端发送消息。
需要注意的是:run_forever()
这个方法会一直阻塞当前线程,直到 WebSocket 连接关闭或发生错误。这个方法通常在启动 WebSocket 服务器时使用,以便持续监听来自客户端的消息。
4.多线程
上面讲到了run_forever()
方法会一直阻塞当前线程,而在Python中实现用户输入的方法input()
也是一个阻塞的方法,它们两者如果运行在同一个线程中会造成“互斥”,即:同时只能有一个方法被执行(执行一个时会把当前线程阻塞住,另一个方法没办法执行),但是我们又必须使用input()
方法来实现在python程序运行过程中让用户进行输入(毕竟我们的程序主要的功能是聊天),所以这个时候我们就得使用一下多线程了,因为它们两个方法都是阻塞方法(都会阻塞当前线程),那理论上我们只要给它们两个阻塞方法都放到一个单独的线程中去运行,把它们两个“隔离”起来,不就解决了吗?
代码如下(示例):
# 创建线程对象
receive_thread = threading.Thread(target=ws_receive, args=(ws,))
send_thread = threading.Thread(target=input_and_send, args=(ws,))
# 开启两个线程一个是接收消息用的,一个是发送消息用的
receive_thread.start()
send_thread.start()
# 等待线程执行完毕
receive_thread.join()
send_thread.join()
target
参数是指定具体的线程执行的方法
args
参数是指定调用具体执行的方法时所需要的参数(注意一定要加后面的逗号)
这样做就可以做到接收消息和发送消息互不干扰了!关于这部分的代码就不展开说了,具体的可以查看源代码。
五、完整的代码(Python)
# 解析json
import json
# 操作系统
import os
import sys
# 多线程
import threading
import time
# 网络请求
import requests
# websocket
import websocket
# 控制台输出样式
from colorama import Fore, Back, init
# 日期
from datetime import datetime
# 用于播放背景音乐
import pygame
def on_message(ws, message):
debug_print(f"Received from server: {
message}")
data = json.loads(message)
if data['code'] == 2:
# 系统消息
msg = f'系统:{data['msg']}'
show_system_message(False, msg)
elif data['code'] == 1:
now = datetime.now()
# 用户消息
msg = f'[{now.strftime('%H:%M:%S')}][{'🩵' if data['data']['gender'] == 0 else '🩷'}/{data['data']['nickname']}]\u0020==>\u0020{data['data']['message']}'
show_message(False, msg)
else:
# 操作失败
print(f'{
Fore.RED}\r获取数据失败:{data['msg']}')
restore_input_state()
def on_error(ws, error):
show_system_message(True, f'WebSocket错误:{
error}')
def on_close(ws, close_status_code, close_msg):
show_system_message(True, '服务器已断开')
global connection_state
connection_state = False
debug_print(f'close_status_code: {
close_status_code}')
debug_print(f'close_msg: {
close_msg}')
def on_open(ws):
# print("### connected ###")
show_system_message(False, '服务器已连接')
global connection_state
connection_state = True
print('现在你可以开始发送消息了')
def print_message(msg):
print(f'{
Back.GREEN}{
Fore.BLACK}{'\u0020' * 2}{app_name}(版本:{app_version}){'\u0020' * 2}')
print(f'{
Back.LIGHTBLACK_EX}{'#' * title_bar_length}')
print(msg)
print(f'{
Back.LIGHTBLACK_EX}{'#' * title_bar_length}')
print(f'{
Back.YELLOW}{
Fore.BLACK}{'-' * title_bar_length}')
def show_system_message(is_error, msg):
if not is_error:
if len(msg) > 40:
print(f'\r{
Fore.GREEN}###\u0020🤖:{
msg}\u0020###', flush=True)
else:
print(f'\r{
Fore.GREEN}###\u0020🤖:{
msg}\u0020###{'\u0020' * 40}', flush=True)
else:
if len(msg) > 40:
print(f'\r{
Fore.RED}###\u0020🤖:{
msg}\u0020###', flush=True)
else:
print