题目 :简单的小说阅读器的设计,服务器端保存小说文本(txt 格式的即可),客户可以打开对应的文本,翻页,翻章,跳页,书签,下载,关闭等,有图形界面,因为是 txt 格式,所谓的“页”可以通过规定每次内容包含的字节来规定。
#sever
import socket
import re
import chardet
import math
import os
import json
class NovelServer:
def __init__(self, novel_path, page_size=1024):
# 创建一个新的 socket 对象
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 将 socket 绑定到本地的 8080 端口
self.server.bind(("localhost", 8080))
# 开始监听连接请求,最大连接数为 1
self.server.listen(1)
# 设置每页的大小,即每次发送给客户端的小说内容的长度
self.page_size = page_size
# 加载小说,获取小说的内容、章节数、每个章节的开始和结束位置
(
self.novel_content,
self.total_chapters,
self.chapter_starts,
self.chapter_ends,
) = self.load_novel(novel_path)
# 计算总页数
self.total_pages = math.ceil(len(self.novel_content) / self.page_size)
# 设置当前页码为 0
self.current_page = 0
# 设置当前章节为 1
self.current_chapter = 1
# 设置书签文件的名称
self.bookmarks_file = "bookmarks.json"
# 加载书签
self.load_bookmarks()
# 设置请求处理器,每个处理器都是一个正则表达式和一个方法的对应关系
# 当收到客户端的请求时,会根据请求的内容匹配相应的正则表达式,然后调用对应的方法处理请求
self.request_handlers = {
re.compile(r"^NEXT_PAGE$"): self.handle_next_page,
re.compile(r"^PREVIOUS_PAGE$"): self.handle_previous_page,
re.compile(r"^SHOW_CHAPTERS$"): self.handle_show_chapters,
re.compile(r"^NEXT_CHAPTER (\d+)$"): self.handle_next_chapter,
re.compile(r"^JUMP_TO_PAGE (\d+)$"): self.handle_jump_to_page,
re.compile(r"^ADD_BOOKMARK$"): self.handle_add_bookmark,
re.compile(r"^SHOW_BOOKMARKS$"): self.handle_show_bookmarks,
re.compile(r"^DOWNLOAD$"): self.handle_download,
re.compile(r"^REMOVE_BOOKMARK (\d+)$"): self.handle_remove_bookmark,
re.compile(r"^CLOSE$"): self.handle_close,
}
def start(self):
# 打印服务器启动的消息
print("Server is starting...")
while True:
# 接受客户端的连接请求
client, address = self.server.accept()
# 打印客户端的地址
print(f"Connected by {address}")
# 处理客户端的请求
self.handle_client(client)
def detect_encoding(self, novel_path):
# 检测小说文件的编码
with open(novel_path, "rb") as f:
result = chardet.detect(f.read())
return result["encoding"]
def load_novel(self, novel_path):
# 打开并读取小说文件,使用检测到的编码
with open(novel_path, "r", encoding=self.detect_encoding(novel_path)) as f:
content = f.read()
# 查找所有的章节
chapters = self.find_chapters(content)
# 查找每个章节的开始和结束位置
chapter_starts, chapter_ends = self.find_chapter_starts_and_ends(content)
# 替换章节标记
content = self.replace_chapter_markers(content)
# 返回小说的内容、章节数、每个章节的开始和结束位置
return content, len(chapters), chapter_starts, chapter_ends
def find_chapters(self, content):
# 使用正则表达式查找所有的章节
return re.findall(r"(第\d+章|第\d+节|第\d+回)", content)
def find_chapter_starts_and_ends(self, content):
# 使用正则表达式查找每个章节的开始和结束位置
matches = list(re.finditer(r"(第\d+章|第\d+节|第\d+回)", content))
starts = [match.start() for match in matches]
ends = [match.end() for match in matches[:-1]] + [len(content)]
return starts, ends
def replace_chapter_markers(self, content):
# 替换章节标记,每个章节标记前面添加一个换行符
return re.sub(r"(第\d+章)", lambda m: "\n" + m.group(1), content)
def load_bookmarks(self):
# 如果书签文件存在,就加载书签
if os.path.exists(self.bookmarks_file):
with open(self.bookmarks_file, "r") as f:
self.bookmarks = json.load(f)
else:
# 否则,初始化一个空的书签列表
self.bookmarks = []
def save_bookmarks(self):
# 保存书签到文件
with open(self.bookmarks_file, "w") as f:
json.dump(self.bookmarks, f)
def handle_client(self, client):
# 循环处理客户端的请求
while True:
# 接收客户端的请求
request = client.recv(512).decode("utf-32")
# 遍历所有的请求处理器
for pattern, handler in self.request_handlers.items():
# 如果找到了匹配的处理器,就调用它处理请求
match = pattern.match(request)
if match:
handler(client, match)
break
else:
# 如果没有找到匹配的处理器,就发送一个错误消息
client.send("Invalid request".encode("utf-8"))
def handle_next_page(self, client, match):
# 如果当前页码小于总页数,就将当前页码加 1
if self.current_page < self.total_pages:
self.current_page += 1
# 发送当前页的内容给客户端
self.send_page(client)
def handle_previous_page(self, client, match):
# 如果当前页码大于 1,就将当前页码减 1
if self.current_page > 1:
self.current_page -= 1
# 发送当前页的内容给客户端
self.send_page(client)
def handle_show_chapters(self, client, match):
# 生成章节列表的字符串
chapters = "\n".join(f"Chapter {i+1}" for i in range(self.total_chapters))
# 将章节列表发送给客户端
client.send(chapters.encode("utf-32"))
def handle_next_chapter(self, client, match):
# 获取客户端请求的章节号
next_chapter = int(match.group(1))
# 如果章节号在有效范围内,就跳转到该章节
if 1 <= next_chapter <= self.total_chapters:
self.current_chapter = next_chapter
self.current_page = (
self.chapter_starts[next_chapter - 1] // self.page_size + 1
)
# 发送当前页的内容给客户端
self.send_page(client)
def handle_jump_to_page(self, client, match):
# 获取客户端请求的页码
page = int(match.group(1))
# 如果页码在有效范围内,就跳转到该页
if 1 <= page <= self.total_pages:
self.current_page = page
# 发送当前页的内容给客户端
self.send_page(client)
def handle_add_bookmark(self, client, match):
# 将当前页码添加到书签列表
self.bookmarks.append(self.current_page)
# 保存书签列表到文件
self.save_bookmarks()
# 向客户端发送添加书签成功的消息
client.send("Bookmark added".encode("utf-32"))
def handle_show_bookmarks(self, client, match):
# 如果书签列表为空,就发送一个消息告诉客户端没有书签
if not self.bookmarks:
client.send("没有书签".encode("utf-32"))
else:
# 否则,生成书签列表的字符串,然后发送给客户端
bookmarks = "\n".join(
f"书签{index+1}: 第{page}页" for index, page in enumerate(self.bookmarks)
)
client.send(bookmarks.encode("utf-32"))
def handle_download(self, client, match):
# 将小说的内容写入到文件
with open("Download_novel.txt", "w", encoding="utf-32") as f:
f.write(self.novel_content)
# 向客户端发送下载成功的消息
client.send("小说已成功下载并保存到 'Download_novel.txt'。".encode("utf-32"))
# 打印一条消息,表示已经发送了下载成功的消息
print("已发送下载成功的消息。")
def handle_remove_bookmark(self, client, match):
# 获取客户端请求删除的书签的索引
index = int(match.group(1))
# 如果索引在有效范围内,就删除该书签
if 1 <= index <= len(self.bookmarks):
del self.bookmarks[index - 1]
# 保存书签列表到文件
self.save_bookmarks()
# 向客户端发送删除书签成功的消息
client.send("Bookmark removed".encode("utf-32"))
def handle_close(self, client, match):
# 关闭客户端的连接
client.close()
def get_chapter_start_page(self, chapter_number):
# 生成章节标题的字符串
chapter_title = f"第{chapter_number}章"
# 在小说的内容中查找章节标题
match = re.search(chapter_title, self.novel_content)
# 如果找到了章节标题,就返回该章节的开始页码
if match:
return match.start() // self.page_size + 1
else:
# 如果没有找到章节标题,就打印一条消息,并返回当前页码
print(f"Chapter {chapter_number} not found.")
return self.current_page
def send_page(self, client):
# 计算当前页的开始和结束位置
start = self.page_size * (self.current_page - 1)
end = start + self.page_size
# 获取当前页的内容
page_content = self.novel_content[start:end]
# 查找最后一个句号、问号或感叹号的位置
end_mark = max(page_content.rfind(mark) for mark in "。?!")
# 如果找到了句号、问号或感叹号,并且它的位置在当前页的后半部分,就将结束位置设置为它的位置
if end_mark != -1 and end_mark > start + self.page_size / 2:
end = start + end_mark + 1
# 重新获取当前页的内容
page_content = self.novel_content[start:end]
# 查找当前页所在的章节
current_chapter = next(
(
i
for i, (chapter_start, chapter_end) in enumerate(
zip(self.chapter_starts, self.chapter_ends)
)
if chapter_start > start
),
self.total_chapters,
)#比较每个章节的开始位置和当前页的开始位置,如果章节的开始位置大于当前页的开始位置,就说明当前页在该章节中,然后返回该章节的索引
# 生成当前页的信息
page_info = f"Page {self.current_page}/{self.total_pages}, Chapter {current_chapter}/{self.total_chapters}\n"
# 将当前页的信息和内容发送给客户端
client.send((page_info + page_content).encode("utf-32"))
# 创建一个 NovelServer 对象,指定小说文件的路径和每页的大小
server = NovelServer(
novel_path="YourPath", page_size=512
)
# 启动服务器
server.start()
将yourpath更换为你的文件路径
#client
import socket
import tkinter as tk
from tkinter import scrolledtext
from tkinter import simpledialog
import threading
import tkinter.messagebox as messagebox
class NovelClient:
def __init__(self, root):
# 创建一个socket对象
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
self.client.connect(("localhost", 8080))
# 保存Tkinter的root对象
self.root = root
# 创建界面上的组件
self.create_widgets()
def _send_request(self, request):
# 向服务器发送请求,并接收响应
try:
self.client.send(request.encode("utf-32"))
response = self.client.recv(4096).decode("utf-32")
self.text.delete("1.0", tk.END) # 清空文本框
self.text.insert(tk.END, response) # 将响应显示在文本框中
except Exception as e:
print(f"Error sending request: {e}")
def handle_download(self):
# 处理下载小说的请求
try:
print("开始下载小说...")
self.client.send("DOWNLOAD".encode("utf-32"))
response = self.client.recv(4096).decode("utf-32")
messagebox.showinfo("下载小说", response)
except Exception as e:
print(f"下载小说时出错: {e}")
def create_button(self, text, command, row, column):
# 创建一个按钮
button = tk.Button(self.root, text=text, command=command)
button.grid(row=row, column=column, padx=10, pady=10) # 添加间距
def create_widgets(self):
# 创建界面上的组件
self.text = scrolledtext.ScrolledText(self.root, width=80, height=25)
self.text.grid(row=0, column=0, columnspan=4) # 将文本框放在顶部
# 创建一系列的按钮,每个按钮都有一个对应的命令
buttons = {
"Next Page": lambda: self.send_request("NEXT_PAGE"),
"Previous Page": lambda: self.send_request("PREVIOUS_PAGE"),
"Jump to Page": self.jump_to_page,
"Next Chapter": self.next_chapter,
"Add Bookmark": lambda: self.send_request("ADD_BOOKMARK"),
"Show Bookmarks": lambda: self.send_request("SHOW_BOOKMARKS"),
"Download Novel": self.handle_download,
"Show Chapters": lambda: self.send_request("SHOW_CHAPTERS"),
"Remove Bookmark": self.remove_bookmark,
}
# 遍历按钮字典,获取每个按钮的文本和命令
for i, (text, command) in enumerate(buttons.items()):
# 按钮的行号是1加上索引除以3的商,列号是索引除以3的余数这样可以将按钮平均分布在网格上
self.create_button(text, command, 1 + i // 3, i % 3)
def send_request(self, request):
# 创建并启动一个新的线程来发送请求避免阻塞主线程
threading.Thread(target=self._send_request, args=(request,)).start()
def close(self):
# 关闭客户端连接
self.client.close()
self.root.quit()
def jump_to_page(self):
# 请求用户输入要跳转的页码
page_number = self.ask_integer("Jump to Page", "Enter page number:")
# 如果用户输入了页码,就发送请求跳转到该页
if page_number is not None:
self.send_request(f"JUMP_TO_PAGE {page_number}")
def next_chapter(self):
# 请求用户输入要跳转的章节号
chapter_number = self.ask_integer("Next Chapter", "Enter chapter number:")
# 如果用户输入了章节号,就发送请求跳转到该章节
if chapter_number is not None:
self.send_request(f"NEXT_CHAPTER {chapter_number}")
def remove_bookmark(self):
# 请求用户输入要删除的书签索引
bookmark_index = self.ask_integer("Remove Bookmark", "Enter bookmark index:")
# 如果用户输入了书签索引,就发送请求删除该书签
if bookmark_index is not None:
self.send_request(f"REMOVE_BOOKMARK {bookmark_index}")
def ask_integer(self, title, prompt):
# 弹出一个对话框,请求用户输入一个整数
# title是对话框的标题,prompt是提示信息
return simpledialog.askinteger(title, prompt)
# 创建一个Tkinter应用程序的根窗口
root = tk.Tk()
# 设置根窗口的大小为600x600像素
root.geometry("600x600")
# 创建一个NovelClient对象用于处理小说客户端的逻辑
client = NovelClient(root)
# 启动Tkinter应用程序的主事件循环
root.mainloop()
你的小说内容应为以下形式(例)
第1章
内容
第2章
内容
..........