1.长连接和短连接的由来
1)TCP在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立通过三次握手,释放则需要四次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的。
2)短连接就是我们平时登陆注册,建立的连接
3)长连接主要适用于通信,比如说qq聊天
2.示例代码
server.py
import socket
import threading
BUF_SIZE = 1024
host = 'localhost'
port = 8083
def handle_client(client_socket, address):
print("Connected by", address)
while True:
try:
data = client_socket.recv(BUF_SIZE)
if not data:
print("Connection closed by client", address)
break
print(data.decode())
except ConnectionResetError:
print("Connection reset by client", address)
break
client_socket.close()
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5) # 增加可接收连接数
print("Server is listening on port", port)
while True:
client_socket, address = server.accept()
client_handler = threading.Thread(target=handle_client, args=(client_socket, address))
client_handler.start()
client.py
import socket
import time
host = 'localhost'
port = 8083
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # 开启心跳维护
try:
client.connect((host, port))
print("Connected to server")
while True:
client.send('hello world\r\n'.encode())
print('send data')
time.sleep(1) # 可以设置更长的时间来验证长时间不发送数据的情况
except ConnectionRefusedError:
print("Connection failed")
except Exception as e:
print("An error occurred:", e)
finally:
client.close()
3.flask提供长连接
flask-socket.py
from flask import Flask, render_template
from flask_socketio import SocketIO, send
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('./index.html')
@socketio.on('message')
def handle_message(message):
print('Received message: ' + message)
send('Message received: ' + message)
if __name__ == '__main__':
socketio.run(app, debug=True)
html代码
<!DOCTYPE html>
<html>
<head>
<title>SocketIO Chat</title>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
document.addEventListener('DOMContentLoaded', () => {
const socket = io();
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('message', (msg) => {
console.log('Message from server: ' + msg);
});
document.querySelector('#send').addEventListener('click', () => {
const message = document.querySelector('#message').value;
socket.send(message);
});
});
</script>
</head>
<body>
<input id="message" type="text" placeholder="Enter message">
<button id="send">Send</button>
</body>
</html>
4.flask工厂模式修改为socket
1)目录结构
LongChain/
│
├── app/
│ ├── __init__.py
│ ├── main/
│ │ ├── __init__.py
│ │ ├── routes.py
│ ├── socketio.py
├── templates/
│ └── index.html
├── create_app.py
├── requirements.txt
2)/app/__init__.py
from flask import Flask
from .socketio import socketio
def create_app():
app = Flask(__name__, template_folder='../templates')
app.config['SECRET_KEY'] = 'secret!'
# Initialize SocketIO with the app
socketio.init_app(app)
# Import and register blueprints
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
print(app.template_folder) # 打印模板文件夹路径
return app
3)app/socketio.py
from flask_socketio import SocketIO
socketio = SocketIO()
@socketio.on('message')
def handle_message(message):
print('Received message: ' + message)
socketio.send('Message received: ' + message)
4)app/main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__)
from . import routes
5)app/main/routes.py
from flask import render_template
from . import main
@main.route('/')
def index():
return render_template('./index.html')
6)templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>SocketIO Chat</title>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
document.addEventListener('DOMContentLoaded', () => {
const socket = io();
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('message', (msg) => {
console.log('Message from server: ' + msg);
});
document.querySelector('#send').addEventListener('click', () => {
const message = document.querySelector('#message').value;
socket.send(message);
});
});
</script>
</head>
<body>
<input id="message" type="text" placeholder="Enter message">
<button id="send">Send</button>
</body>
</html>
7)create_app.py
from app import create_app
from app.socketio import socketio
app = create_app()
if __name__ == "__main__":
socketio.run(app, debug=True, host='0.0.0.0')
5.项目代码更改
首先要在app文件夹下建一个extensions.py文件,是为了解决在run.py和init.py里面循环引入socket的问题
1)__init__.py修改
# app/__init__.py
from flask import Flask, render_template, request, g, jsonify, redirect, url_for, session
from app.user.views import user_bp
from app.blog.views import blog_bp
from app.comment.views import comment_bp
from app.drunk1.views import drunk1_bp
from app.onedrunk1.views import onedrunk1_bp
from app.extensions import socketio
import sqlite3
import time
import traceback
def create_app():
app = Flask(__name__, static_url_path='/static')
# 加载配置
app.config.from_object('config.Config')
socketio.init_app(app)
# 注册蓝图
app.register_blueprint(user_bp, url_prefix='/conversation')
app.register_blueprint(blog_bp, url_prefix='/userprofile')
app.register_blueprint(comment_bp, url_prefix='/drunk5')
app.register_blueprint(drunk1_bp, url_prefix='/drunk1')
app.register_blueprint(onedrunk1_bp, url_prefix='/onedrunk1')
DATABASE = r'C:\Users\lzt\Desktop\work\flask\app\static\users2.db'
# session密钥
app.secret_key = 'Cxh12300'
current_user = {}
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def login():
return render_template('./login/login.html')
@app.route('/regist')
def regist():
return render_template('./regist/regist.html')
@app.route('/registuser', methods=['POST'])
def getRigistRequest():
try:
if request.method == 'POST':
username = request.form.get('user')
password = request.form.get('password')
conn = get_db()
cursor = conn.cursor()
sql = "INSERT INTO user(user, password) VALUES (?, ?)"
cursor.execute(sql, (username, password))
conn.commit()
return redirect(url_for('login'))
else:
return render_template('login.html')
except Exception as e:
traceback.print_exc()
return '注册失败'
@app.route('/login', methods=['POST'])
def getLoginRequest():
try:
username = request.form.get('user')
password = request.form.get('password')
conn = get_db()
cursor = conn.cursor()
sql = "SELECT * FROM user WHERE user=? AND password=?"
cursor.execute(sql, (username, password))
user = cursor.fetchone()
if user:
name = user[0]
user_id = user[2]
current_user['name'] = user[0]
current_user['user_id'] = user[2]
session['name'] = user[0]
session['user_id'] = user[2]
current_time = time.time()
expiration_time = current_time + 10
payload = {'name': name, 'user_id': user_id, 'exp': expiration_time}
return render_template('./user/index2qian.html', name=name, user_id=user_id)
else:
return '用户名或密码不正确'
except Exception as e:
traceback.print_exc()
return '登录失败'
return app
2)run.py
from app import create_app, socketio
app = create_app()
if __name__ == "__main__":
socketio.run(app, debug=True, host='0.0.0.0')
3)app/user/views.py的修改
from flask import Blueprint, render_template,request,jsonify,session
import sqlite3
import traceback
import time
import requests
import json
from flask_socketio import emit
from app.extensions import socketio
user_bp = Blueprint('user', __name__, template_folder='templates')
@user_bp.route('/')
def index():
return render_template('./user/index2qian.html')
def generate_user_profile(session):
# 基本 URL
base_url = "http://192.168.1.140:5000/userprofile/generate_user_profile"
# 从 session 中获取 user_id 和 name
print("session111",session)
user_id = session['user_id']
name = session['name']
print("user_id",user_id)
# 构建完整的 URL
url = f"{base_url}?user_id={user_id}&name={name}"
try:
# 发送 GET 请求
response = requests.get(url)
# 检查响应状态码
if response.status_code == 200:
# 成功
print("Profile generated successfully")
return response.json()
else:
# 请求失败
print(f"Failed to generate profile, status code: {response.status_code}")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
def get_latest_content_by_user_id(session):
# 数据库文件名
db_file = r'C:\Users\lzt\Desktop\work\flask\app\static\conversions2.db'
# 连接到 SQLite 数据库
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
try:
# SQL 查询语句,按 id 降序排列并只获取最新的一条记录
query = '''
SELECT content FROM profile
WHERE user_id = ?
ORDER BY id DESC
LIMIT 1
'''
# 执行查询
cursor.execute(query, (session['user_id'],))
# 获取结果
result = cursor.fetchone()
if result:
content = result[0] # 获取 content 字段的值
return content
else:
return None
except sqlite3.Error as e:
print(f"An error occurred: {e}")
return None
finally:
# 关闭数据库连接
conn.close()
@socketio.on('message')
def handle_message(data):
user_input = data['message']
conversation_history = data['history']
response = ChatBot.call_api(user_input, conversation_history)
# print("user_input",user_input)
# print("conversation_history",conversation_history)
# print("触发emit")
# print("response",response)
emit('response', {'response': response})
class ChatBot:
context = None
conversation_counter = 0
@classmethod
def call_api(cls, user_input, conversation_history):
# 每十轮对话更新一次 profile
print("cls.conversation_counter:", cls.conversation_counter)
if cls.conversation_counter % 10 == 0:
print("更新 profile,因为对话计数是 10 的倍数")
generate_user_profile(session)
profile = get_latest_content_by_user_id(session)
if profile:
profile_prompt = f"根据你的行为分析,你的兴趣和喜好如下:{profile}\n"
print("Profile 更新成功:", profile)
else:
profile_prompt = ""
print("Profile 更新失败或没有新的 profile 数据")
else:
profile_prompt = ""
# 更新对话计数器
cls.conversation_counter += 1
# API URL
url = 'http://192.168.1.108:11434/api/chat'
# 定义基本 prompt
prompt = (
"你只会中文。你只能说中文。"
"你的性格积极乐观,善良可爱,喜欢和朋友聊天,善解人意。但你不会直说自己的性格。"
"你喜欢提出问题,并继续聊下去一两句。当你感觉到用户的回答很敷衍的时候,你会找新话题。"
"你的每句回应要尽可能像真正的朋友。你的回答不可以敷衍。"
"<人物画像>的意思是对人物行为分析,从而能够区别其喜好等特点."
"你对有关用户的<人物画像>的问题都很好奇。如果说了一些性格特征,你会追问一两次,然后开启新的话题。"
"但你是个情商很高的人,你会说很多种句式的话。你会用中文表达的很完美。"
"禁止直接提'人物画像'的字眼。你可以问有关性格爱好的问题,但不能明说<人物画像>一词。"
"每次说话不多余30个单词。\n"
)
# 将 profile 添加到 prompt 中
prompt = profile_prompt + prompt
# print("最终 prompt:", prompt)
data = {
"model": "llama3",
"messages": conversation_history + [
{
"role": "user",
"content": prompt + user_input
}
],
"context": cls.context,
"stream": False
}
headers = {'Content-Type': 'application/json'}
data=data_clean(data)
# print(type(data))
# print('data',data)
try:
response = requests.post(url, data=json.dumps(data), headers=headers)
print("API 请求已发送")
# print("json.dumps(data)",json.dumps(data))
if response.status_code == 200:
response_json = response.json()
print("API 响应成功:", response_json)
messages = response_json.get('message', {})
content = messages.get('content')
cls.context = response_json.get('context')
return content
else:
print(f'请求失败,状态码: {response.status_code}')
print('响应内容:', response.content)
return None
except Exception as e:
print(f'发生错误: {e}')
traceback.print_exc()
return None
def data_clean(data):
try:
messages = data['messages']
conversations = []
for item in messages:
if 'role' in item and 'content' in item:
role = "user" if item['role'] == 'user' else "system"
content = item['content']
conversations.append({'role': role, 'content': content})
else:
role = item['sender']
content = item['message']
if role != 'AI':
role = 'user'
else:
role = 'system'
conversations.append({'role': role, 'content': content})
data['messages']=conversations
return data
except Exception as e:
print(f"数据处理错误: {e}")
return None
@user_bp.route('/chat', methods=['POST'])
def chat():
data=request.get_data()
print("data",data)
# user_input=data['message']
data_str = data.decode('utf-8')
# 将字符串解析为字典
data_dict = json.loads(data_str)
print("data_dict",data_dict)
# 获取 "message" 键对应的值
messages = data_dict["message"]
userInput=data_dict['userInput']
conversations = []
for item in messages:
role = item["sender"]
content = item["message"]
if role != "AI":
role = "user"
else:
role = "system"
conversations.append({'role': role, 'content': content})
response=ChatBot.call_api(userInput,conversations)
return jsonify({"response": response})
@user_bp.route('/save-message', methods=['POST'])
def save_message():
data = request.get_json()
print("save_message data",data)
conn = sqlite3.connect('C:\\Users\\lzt\\Desktop\\work\\flask\\app\\static\\conversions2.db')
cursor = conn.cursor()
for msg in data:
message = msg.get('message')
timestamp = msg.get('timestamp')
sender = msg.get('sender')
user_id = msg.get('user_id') # 获取用户的 user_id
# 插入数据到数据库,包括 user_id
cursor.execute('''
INSERT INTO conversation (timestamp, sender, message, user_id) VALUES (?, ?, ?, ?)
''', (timestamp, sender, message, user_id))
# 提交更改
conn.commit()
conn.close()
return jsonify({'message': 'Messages saved to database successfully.'})
4)前端的修改
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat with AI</title>
<link rel="stylesheet" href="../static/styles.css">
</head>
<body>
<h1 style="text-align: center;">Chat with AI</h1>
<div id="chat-container">
<div class="message ai-message">AI: Hello there! How can I assist you today?</div>
</div>
<div id="userProfile"></div>
<div id="input-container">
<input type="text" id="user-input" placeholder="Your message..." onkeypress="handleKeyPress(event)">
<button id="send-button" class="button" onclick="sendMessage()">Send</button>
<button id="save-button" class="button" onclick="fetchUserProfile()">生成我的用户画像</button>
<button id="drunk1" class="button" onclick="redirectToDrunk1()">one drunk</button>
<button id="onedrunk1" class="button" onclick="redirectToDrunk2()">two drunk</button>
</div>
</body>
</html>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script>
function redirectToDrunk1() {
var url = "http://192.168.1.140:5000/onedrunk1?user_id=" + encodeURIComponent(userid) + "&name=" + encodeURIComponent(name);
window.location.href = url;
}
function redirectToDrunk2() {
window.location.href = "http://192.168.1.140:5000/drunk1";
}
// 全局变量,记录已经保存到数据库的消息数量
var savedMessageCount = 0;
var userid = "{{ user_id }}"; // 从服务器端获取的用户ID
var name="{{name}}"
console.log("name",name,"userid",userid)
function parseMessages() {
var chatContainer = document.getElementById('chat-container');
var messages = [];
chatContainer.querySelectorAll('.message').forEach(message => {
messages.push(message.textContent);
});
console.log("messages",messages)
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth() + 1;
var day = now.getDate();
var hours = now.getHours();
var minutes = now.getMinutes();
var formattedTime = year + "-" + month + "-" + day + " " + hours + ":" + minutes;
var result = [];
if (messages.length >= 20) {
messages = messages.slice(-15);
}
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
var sender = "";
const regex = /([^:]+)/;
const messageType = regex.exec(message);
if (messageType[0] === "AI") {
sender = "AI";
} else{
sender = userid;
}
if (sender !== "") {
var messageContent = message.substring(message.indexOf(":") + 1);
var messageObject = {
"timestamp": formattedTime,
"sender": sender,
"message": messageContent
};
result.push(messageObject);
}
}
console.log("functionresult",result)
return result;
}
function isTokenExpired(token) {
if (!token) {
return true; // 如果没有token,认为已过期
}
// 从JWT中解析出exp字段,判断是否小于当前时间戳
const exp = jwt_decode(token).exp;
return exp < Date.now() / 1000;
}
// 检查JWT是否过期,如果过期则重新登录
function checkAndRefreshToken() {
const jwt = localStorage.getItem('jwt');
if (isTokenExpired(jwt)) {
// JWT已过期,重新登录
window.location.href = '/login'; // 重定向到登录页面
}
}
function saveToDatabase(message) {
// 调用函数检查并刷新JWT
// checkAndRefreshToken();
// 遍历数据,为每个对象添加"user_id"
message.forEach(function(msg) {
msg["user_id"] = userid;
});
// console.log("data", message);
var jwt = localStorage.getItem('jwt');
// 发送 POST 请求到后端接口
fetch('/conversation/save-message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + jwt
},
body: JSON.stringify(message) // 将消息数据转换为 JSON 字符串并作为请求体发送
})
.then(response => response.json())
.then(data => {
// 在控制台打印后端返回的响应数据
// console.log(data);
// fetchUserProfile();
})
.catch(error => {
// 发生错误时打印错误信息
console.error('Error:', error);
});
}
var socket = io.connect('http://' + document.domain + ':' + location.port);
socket.on('connect', function() {
console.log('Connected to server');
});
socket.on('response', function(data) {
var aiMessageDiv = document.createElement('div');
aiMessageDiv.classList.add('message', 'ai-message');
aiMessageDiv.textContent = 'AI: ' + data.response;
console.log("data.response",data.response)
document.getElementById('chat-container').appendChild(aiMessageDiv);
var chatContainer = document.getElementById('chat-container');
var messages = [];
chatContainer.querySelectorAll('.message').forEach(message => {
messages.push(message.textContent);
});
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth() + 1;
var day = now.getDate();
var hours = now.getHours();
var minutes = now.getMinutes();
var formattedTime = year + "-" + month + "-" + day + " " + hours + ":" + minutes;
var result = [];
for (var i = savedMessageCount; i < messages.length; i++) {
var message = messages[i];
var sender = "";
const regex = /([^:]+)/; // 匹配 ":" 之前的任意字符(不包括 ":"),并且只提取匹配到的内容
const messageType = regex.exec(message);
console.log("messageType[0] ", messageType[0] )
if (messageType[0] === "AI") {
sender = "AI";
} else {
sender = userid;
}
// console.log("sender",sender)
if (sender !== "") {
var messageContent = message.substring(message.indexOf(":") + 1);
// console.log("messageContent",messageContent)
var messageObject = {
"timestamp": formattedTime,
"sender": sender,
"message": messageContent
};
// console.log("messageObject",messageObject)
result.push(messageObject);
}
}
savedMessageCount = messages.length;
console.log("result", result);
saveToDatabase(result);
});
async function sendMessage() {
var userInput = document.getElementById('user-input').value.trim();
if (userInput === '') {
return;
}
var chatContainer = document.getElementById('chat-container');
var userMessageDiv = document.createElement('div');
userMessageDiv.classList.add('message', 'user-message');
userMessageDiv.textContent = name +':'+ userInput;
chatContainer.appendChild(userMessageDiv);
newValue=parseMessages();
socket.emit('message', {
message: userInput,
history: newValue
});
document.getElementById('user-input').value = '';
}
function handleKeyPress(event) {
if (event.key === 'Enter' && !event.shiftKey) { // 检查是否按下了 Enter 键且未按下 Shift 键
event.preventDefault(); // 阻止默认的 Enter 键行为(即提交表单)
sendMessage(); // 调用 sendMessage 函数发送消息
} else if (event.key === 'Enter' && event.shiftKey) { // 如果同时按下了 Shift 键和 Enter 键
var input = document.getElementById('user-input');
var start = input.selectionStart;
var end = input.selectionEnd;
var value = input.value;
input.value = value.substring(0, start) + '\n' + value.substring(end);
input.selectionStart = input.selectionEnd = start + 1; // 将光标移动到换行符后
}
}
function saveConversation() {
var chatContainer = document.getElementById('chat-container');
var messages = [];
// 遍历聊天容器中的所有消息,并将其保存到数组中
chatContainer.querySelectorAll('.message').forEach(message => {
messages.push(message.textContent);
});
var now = new Date();
var year = now.getFullYear(); // 获取年份
var month = now.getMonth() + 1; // 获取月份(注意要加1,因为月份是从0开始的)
var day = now.getDate(); // 获取日期
var hours = now.getHours(); // 获取小时
var minutes = now.getMinutes(); // 获取分钟
var formattedTime = year + "-" + month + "-" + day + " " + hours + ":" + minutes;
var result = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
var sender = "";
var messageType = message.substring(0, 4);
// 根据消息类型确定发送者
if (messageType === "AI: ") {
sender = "AI";
} else if (messageType === "User") {
sender = userid;
}
// 构造消息对象并添加到结果数组中
if (sender !== "") {
var messageContent = message.substring(message.indexOf(":") + 2); // 获取冒号后面的消息内容
// console.log(messageContent)
var messageObject = {
"timestamp": formattedTime,
"sender": sender,
"message": messageContent
};
result.push(messageObject);
}
}
}
function fetchUserProfile() {
fetch( 'http://192.168.1.140:5000/userprofile/generate_user_profile?user_id=' + userid + '&name=' + name, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text(); // 直接获取文本内容
})
.then(data => {
console.log("response.data", data);
// 这里可以对返回的文本内容进行处理,例如将其显示在页面上
const userProfileDiv = document.getElementById('userProfile');
userProfileDiv.innerText = data;
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
主要就是前端点击发送按钮触发
socket.emit('message', {
message: userInput,
history: newValue
});
然后后端接受前端传的消息并且调用对应的方法传参,再将结果传给前端
@socketio.on('message')
def handle_message(data):
user_input = data['message']
conversation_history = data['history']
response = ChatBot.call_api(user_input, conversation_history)
print("user_input",user_input)
print("conversation_history",conversation_history)
print("触发emit")
print("response",response)
emit('response', {'response': response})
前端收到返回的信息socket.on('response', function(data) {)
进行下一步的处理
-
后端:
- 监听客户端连接、断开连接和消息事件。
- 接收到消息后,将其广播给所有连接的客户端。
-
前端:
- 监听服务器广播的消息事件,并在页面上显示消息。
- 监听表单提交事件,发送用户输入的消息到服务器。