我们买的打印机是惠普的HP Laser 103a,并不支持无线连接打印,而我们又需要多人同时使用这台打印机,觉得很不方便,便经过一番尝试,找到了一种对这类打印机适用的方法,我的方法不是最好的解决方案,但是我尝试的众多方案中唯一可行的。当网上的其他网络打印机教程都行不通时,可以考虑一下本文的方案,希望对你有所帮助。
目录
概述
解决方案
本人使用树莓派4B搭建共多用户访问打印机的服务器,树莓派4B安装的win10系统,通过socket实现客户端与服务端的数据传输,打印的方法是使用ShellExecute函数
通过此方案能够实现多人同时使用一台打印机,打印类型支持pdf,word和txt(满足日常使用)
硬件方面
树莓派4B(4G)、TF卡(>32GB)、暂时的显示屏和键鼠、打印机一台
软件方面
使用Python编写客户端和服务端的代码
准备
为树莓派安装win10
我参考 树莓派4b安装Win10 ARM (教程向) 安装
win10的安装过程是非常的慢的,而且在刚安装好后,运行着是较为卡顿,尽量操作不要太快。一段时间后便流畅许多。安装完成后,建议首先打开任务管理器的详细信息(进程),这可以查看应用是否正常运行(看对应进程是否有cpu和磁盘占用),及时结束无响应进程。
我安装的是win10专业版(32位),不用激活。
安装相应应用
安装大部分软件基本上没有问题,如果安装不成功,可以尝试将Windows的实时保护关掉(Windows安全中心 > 病毒和防护威胁 > 管理设置)
安装打印机驱动
根据自己的打印机,安装相应驱动(这一步一定要成功,不然后面没法进行)
安装Python
在Python官网下载,我选择的是Python3.8(32位),勾选添加环境变量(Add Python 3.8 to PATH),免得自己再设置环境变量。
之前为了省事,直接给树莓派安装了Anaconda,安装完成后才发现存储空间不够,所以不建议安装Anaconda,不仅占空间,而且费时间。
安装其他应用
由于打印时需要依赖应用,我又安装了office2010(不需要激活),福昕pdf阅读器,为了方便传文件和调试,我有安装了TeamViewer。
程序
服务端和客户端代码均由python编写
为了方便调试,我使用虚拟机配合主机编写代码,在虚拟机编写客户端代码,在主机上编写服务端代码。
服务端代码
服务端的代码的思路
文件接收函数:
def rec_file(new_client_socket):
try:
# 接受数据,此处接收的是文件的信息(文件的大小和文件名)
rec = new_client_socket.recv(1024).decode("utf-8")
except:
print("rec_file stop")
return
if rec:
file_len = int(rec.split()[0]) # 文件长度
file_name = rec.split()[1] # 文件名字
print("接收到的文件名", file_name, file_len, "byte")
# 向客户端反馈接收信息
new_client_socket.send("OK".encode("utf-8"))
total_data = b''
num = 0
# 接收文件
recv_data = new_client_socket.recv(1024 * 1024) # 接受速度取决于网速
total_data += recv_data
num += len(recv_data)
process_bar.progress_bar(file_len, num) # 进度条显示接收进度
while num < file_len:
recv_data = new_client_socket.recv(1024 * 1024 * 16)
num += len(recv_data)
total_data += recv_data
process_bar.progress_bar(file_len, num)
# 向客户端反馈文件已经接收完毕
new_client_socket.send("success".encode("utf-8"))
print("")
# 保存接收文件
file = open("./receive/" + file_name, "wb")
file.write(total_data)
file.close()
# 判断文件格式,使用正则表达式判断
if re.match(r"(.*)(.)docx", file_name) \
or re.match(r"(.*)(.)pdf", file_name) \
or re.match(r"(.*)(.)dox", file_name) \
or re.match(r"(.*)(.)txt", file_name):
# 打印文件,具体函数见下方
print_loading.printer_loading(print_loading.get_path() + "\\receive\\" + file_name)
new_client_socket.close()
打印文件函数:
def printer_loading(filename):
f = open(filename, "r")
win32api.ShellExecute(
0,
"print",
filename,
'/d:"%s"' % win32print.GetDefaultPrinter(),
".",
0
)
f.close()
不知道为什么,ShellExecute只使用默认打印机打印,而不能选定其他打印机
由于socket.accept()阻塞而无法真正关闭线程,所以在结束线程前,先在主线成关闭服务器的socket,处理在子线程中抛出的异常便可退出线程
客户端代码
为了便于操作,有更好的用户体验,我使用了pyqt5创建ui界面
客户端代码思路:
文件发送函数:
def send_file(self):
self.selectButton.setEnabled(False)
thread_wait1 = Thread(target=wait, args=(self,))
thread_wait1.start()
# 判断服务器是否在线
try:
# 创建套接字
self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 链接服务器
self.tcp_socket.connect((self.server_ip, self.server_port))
print("连接成功")
stop_thread.stop_thread(thread_wait1)
self.status_label.setText("服务器正常工作")
self.selectButton.setEnabled(True)
except:
stop_thread.stop_thread(thread_wait1)
self.status_label.setText("无法连接至服务器")
self.selectButton.setEnabled(True)
return
# 获取发送打印的文件
file_path, file_type = QFileDialog.getOpenFileName(None, "选取文件", "./",
"All Files (*);;TextFiles (*.txt)") # 对应软件的选择框
if file_path == "":
# 判断是否选择
return
self.communate_label.setText("正在发送中")
file_name = file_path.split("/")[-1]
# 读取文件
f = open(file_path, "rb")
file_content = f.read()
f.close()
# 获取文件的大小
file_len = len(file_content)
# 发送大小和文件名
self.tcp_socket.send("{} {}".format(file_len, file_name).encode('utf-8')) # 发送名字不是路径
# 获取服务端的反馈("OK")
status = recv(self.tcp_socket)
print("来自服务端的回复:", status)
if status == "OK":
self.tcp_socket.sendall(file_content)
# 等待接收服务器的反馈("success")
status = self.tcp_socket.recv(1024).decode()
if status == "success":
self.communate_label.setText(file_name + "文件发送成功")
else:
self.communate_label.setText("文件发送时出现问题")
else:
print("服务器无响应")
self.communate_label.setText("服务器无响应")
self.tcp_socket.close()
return
self.tcp_socket.close()
问题
此解决方案也存在着一些问题,比如说受树莓派性能限制,从上传文件到打印所需的时间稍微长一些,不过还在可接受范围内。其次是树莓派在运行win10系统的耗能不会太低。
总结
此方案虽然实现这比较麻烦,性能也不是那么理想,但是经历了一次又一次的失败的成功是令人兴奋的。希望本文能对读者有所帮助。