用Python做一款带用户界面本地通讯桌面软件 (二:简单网络通讯模型)
前言
上一期,用pyqt5 写了用户登录及注册界面:
用Python做一款带用户界面本地通讯桌面软件 (一:用户登录与注册)
并且用本地文件操作,模拟了用户登录和注册的业务逻辑,本期主要介绍将用户登录和注册加上网络通信模块。
1、模块分析
这里我们主要分以下2个模块:
1、客户端 Client 模块:主要存放用户操作界面代码,负责用户操作,接收服务端返回数据,呈现给客户。
2、服务端 Service 模块:主要存放服务端代码,负责接收处理客户请求,并返回数据给客户端。
其他的文件分析:
Client 模块下的temp文件夹:保存用户基本的资料数据,如聊天记录等。
data 文件夹:因没用数据库,所以data文件夹可以看做是服务端数据保存路径。
image文件夹:保存客户端界面用到的图片。
2、客户端功能及代码:
本次客户端相比于上期内容基本上没有多大变化,只是在上期基础上引入了负责与服务端通讯的client.py文件,修改了do_login用户登录方法及注册界面的register_func用户注册方法。
2.1 client.py
与服务端通讯:
'''
客户端通讯
'''
from socket import *
# 获取服务器地址:
with open('setting.txt','r') as f:
data = f.readlines()
address = (data[0].strip(),int(data[1]))
class Client(object):
def __init__(self):
# 创建套接字对象
self.sock_fd = socket(AF_INET,SOCK_STREAM)
# 连接服务器
self.sock_fd.connect(address)
# 发送请求,并接收服务端返回数据
def send_receive(self,message):
self.sock_fd.send(message.encode())
data = self.sock_fd.recv(2048)
return data.decode()
这里使用TCP套接字来进行通讯。
# 获取服务器地址:
with open('setting.txt','r') as f:
data = f.readlines()
address = (data[0].strip(),int(data[1]))
setting 文件作为客户端通讯地址文件,方便后期客户端在其他地址登录时修改。
以下为setting.txt文件内容:
127.0.0.1
8888
客户端通讯模块的引入:
from client import Client
# 用户登录页面
class LoginPage(QWidget):
'''用户注册,登录窗口'''
# 创建客户端以进行连接
try:
client = Client()
except:
pass
2.2 LoginPage类中do_login方法改写
# 登录方法
# 在登录方法中,检查用户设置的登录状态
def do_login(self):
# 获取用户输入的用户名及密码
name,password = self.name_line.text(),self.password_line.text()
# 向服务端发送登录请求
method = 'LG'
# 构建消息内容
message = '%s@%s?%s' % (method,name,password)
# 发送消息,并获得返回数据
try:
response = self.client.send_receive(message)
except:
QMessageBox.warning(self,'警告','服务器启动失败!')
return
# 根据服务器返回信号,处理登录逻辑
if response == 'OK':
self.check_login_state()
QMessageBox.information(self, '提示', '登录成功!')
elif response == 'None':
QMessageBox.warning(self, '警告', '暂无用户数据!请先注册!')
elif response == 'NME':
QMessageBox.warning(self, '警告', '用户不存在!')
elif response == 'PDE':
QMessageBox.warning(self, '警告', '密码错误!')
解析:
自定义登录请求头为’LG‘并与登录请求的数据(用户名,密码)用’@‘连接发送给服务端,服务端接收到信息,根据请求头的不同处理相应请求。
用户名及密码匹配成功,返回 ’OK‘,客户端接收到’OK‘则提示登录成功,并执行相应的其他业务逻辑。
返回 ’None‘ 说明暂无用户数据,客户端发出警告提示框。提示:‘暂无用户数据!请先注册!’。
其他逻辑类似。
注意:
客户端与服务器可能会连接失败,如:服务端没有启动,所以发送请求的时候加了个try,except语句。
try:
response = self.client.send_receive(message)
except:
QMessageBox.warning(self,'警告','服务器启动失败!')
return
2.3 LoginPage类中do_register方法的改写
将创建的通讯对象,传递给注册页面:
# 用户注册方法
def do_register(self):
# 调用注册界面
register_page = RegisterPage(self.client)
# 注册页面的注册成功信号绑定,在登录页面输入注册成功后的用户ID及密码
register_page.successful_signal.connect(self.successful_func)
register_page.exec()
使注册页面可以和服务器进行通讯。
2.3 RegisterPage类中register_func的改写
# 用户注册方法
def register_func(self):
# 注册界面数据
# 用户性别数据
gender = self.gender_data()
# 注意将性别的整数类型,转化为字符串类型
rt_data = [self.name_line.text(), self.password1_line.text(),
self.nick_line.text(),str(gender)]
# 构造发送注册请求数据
method = 'RT'
data = '?'.join(rt_data)
message = '%s@%s' % (method,data)
response = self.client.send_receive(message)
# 根据服务器返回信号,处理注册逻辑
if response == 'NME':
QMessageBox.information(self, '提示', '该用户ID已被注册!')
elif response == 'OK':
choice = QMessageBox.information(self, '提示',
'注册成功,是否登录?',QMessageBox.Yes | QMessageBox.No)
# 如选择是,关闭注册页面,并在登录页面用户ID显示注册ID,密码
if choice == QMessageBox.Yes:
self.successful_signal.emit([self.name_line.text(),
self.password1_line.text()])
self.close()
# 如选择否,直接关闭注册页面。
else:
self.close()
解析:
注册请求的请求头为 ‘RT’,将请求头与注册的数据发送给服务器。让服务器完成注册的业务,并返回相应的信息。
3、服务端功能及代码
3.1 服务端主要代码:
'''
本地通讯服务端
'''
from socket import *
from multiprocessing import Process
import sys
# 导入处理客户端简单请求自定义模块
from function import SimpleFun
server_ip = '0.0.0.0'
server_port = 8888
server_ar = (server_ip,server_port)
# 创建服务器类
class Service(object):
simple = SimpleFun()
def __init__(self):
# 创建套接字,流式套接字
self.sock_fd = socket(AF_INET, SOCK_STREAM)
self.sock_fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 绑定服务器地址
self.sock_fd.bind(server_ar)
# 启动服务器方法,创建多进程来处理多个客户端的连接
def service_forever(self):
print('服务器已启动!')
# 设置监听数
self.sock_fd.listen(10)
while True:
try:
# 连接对象,连接对象的IP地址
con, ar = self.sock_fd.accept()
print('连接对象IP:', ar)
except:
continue
# 创建进程
handle_client = Process(target=self.handle_client, args=(con,))
# 启动进程
handle_client.start()
# 与客户端交互方法
def handle_client(self,con):
while True:
# 接收客户端消息
message = con.recv(2048)
# 处理客户端异常关闭
if not message:
break
# 解析消息
request = message.decode().split('@')
# 拆分消息请求方法,数据
method, data = request[0],request[1]
# 处理请求,并返回数据
response = self.hand_request(method,data)
# 向客户端返回数据
con.send(response.encode())
# 处理异常关闭
con.close()
sys.exit()
# 处理客户端请求方法
def hand_request(self,method,data):
# 处理登录请求
if method == 'LG':
# 调用登录方法
response = self.simple.login(data)
return response
# 处理注册请求
elif method == 'RT':
# 调用注册方法
response = self.simple.register(data)
return response
# 启动客户端
if __name__ == '__main__':
S = Service()
S.service_forever()
解析:
1、先创建套接字。
2、创建服务器启动方法: service_forever
这里用了多进程来处理客户端信息,用了Python的Process模块。不了解的同学可以自己网上搜索下资料简单的了解一下,这里用的也很简单。
3、服务端处理登录请求,注册请求方法:
# 处理客户端请求方法
def hand_request(self,method,data):
# 处理登录请求
if method == 'LG':
# 调用登录方法
response = self.simple.login(data)
return response
# 处理注册请求
elif method == 'RT':
# 调用注册方法
response = self.simple.register(data)
return response
根据请求头的不同,服务端执行相应的代码,这里我们处理登录和注册的函数放在function.py文件中:
# 导入处理客户端简单请求自定义模块
from function import SimpleFun
3.2 服务端的登录方法及注册方法
登录方法:
'''
处理客户端简单请求方法
'''
import pickle
class SimpleFun(object):
def __init__(self):
pass
# 处理登录请求
def login(self,data):
# 拆分接收到的数据
name,password = data.split('?')
try:
# 用Python自带模块pickle来进行文件读取操作,以rb方式读取文件
with open('./data/users.pkl', 'rb') as f:
users = pickle.load(f)
except:
# 无注册数据返回值
return 'None'
if name in users.keys():
if password == users[name][0]:
# 登录成功返回值
return 'OK'
else:
# 密码错误返回值 password error
return 'PDE'
else:
# 用户名错误返回值 name error
return 'NME'
解析:
根据客户端发送过来的数据,来执行相应的数据判断。
主要注意信息的处理方式,客户端在构造登录请求的数据时,请求头与请求数据用‘@’连接,用户名与密码之间用‘?’连接,所以这里也用相应的方式分割数据。
# 拆分接收到的数据
name,password = data.split('?')
注册方法:
# 处理注册请求
def register(self,data):
ID = data.split('?')[0]
try:
with open('./data/users.pkl', 'rb') as f1:
users = pickle.load(f1)
except:
users = {}
# 如果用户ID已存在,提示用户ID已被注册
if ID in users.keys():
# 返回用户ID已存在信号
return 'NME'
# 否则收集用户注册信息
else:
user_data = data.split('?')[1:]
users[ID] = user_data
with open('./data/users.pkl', 'wb') as f2:
pickle.dump(users, f2)
# 返回注册成功信号
return 'OK'
数据处理方面的逻辑也和登录方法类似,代码也比较简单,应该不需要解释啥。
4 下篇预告
本次主要建立起了简单的通讯框架,下一篇应该主要会是登录后聊天界面的创建。
5 最后
这次模块多了一点,全部源码贴上来意义也不大。还是那句话,要源码的可以私信。
以上内容为原创,未经允许,禁止转载!!