这次利用python完成一个电子词典的查询,首先我们需要明确查询电子词典需要用到数据的操作,将单词及解释存数据库中,考虑用户的注册登录,查询单词,查看历史记录等功能,需要用到搭建TCP网络,方便客户端查询,在这里我们防止管理员查看用户的信息对用户的密码进行加密,这样管理员在数据库中就无法查看到用户的密码。 首先我们考虑创建数据库,准备三个数据库,一个用来储存用户的数据信息,一个用来保存词典,一个用来保存用户的历史纪录。 下面是我们创建的数据库,创建了user,words,hist三个表分别用来储存用户,单词,及历史记录。其中用户的密码我们设置为128字节,是为了等会做加密处理。 有了数据库我们就可以用python对数据库进行操作了,电子词典的词汇量非常大我们不可能一条一条的插入,此时就需要借助pymysql来对其操作。先看代码!
import pymysql import re f = open('dict.txt') db = pymysql.connect('localhost', 'root', '123456', 'dict') cur = db.cursor() sql = 'insert into words (word,mean) \ VALUES (%s,%s)' for line in f: # 获取匹配内容元组 (word,mean) tup = re.findall(r'(\w+)\s+(.*)', line)[0] try: cur.execute(sql, tup) except Exception: db.rollback() db.commit() f.close() cur.close() db.close()导入pymysql,re模块 打开词典文件,连接数据库,创建游标对象,插入语句,这是一连串的套路,没有什么好说的,那我们怎么拿到单词及解释呢,我们对已知数量的操作用for来完成,我么的词典单词和解释都在一行,这就大大降低我们拿取数据的难度,此时我们需要用到正则表达式中的捕获组,w+代表单词,s+代表中间的空格,.*代表解释,这样我们就方便的拿到了单词和解释,还有一种操作,在文件操作中我么学习过用readlines读取每一行,然后按空格切在拿到这个元组的第一位和后边的所有,就完成了。但是这种方法大大降低了工作的效率不建议使用。接下来执行sql语句,这里我们尝试捕获错误,如果有写错的就停止,回滚,最后统一提交到数据库,关闭文件,关闭游标对象,断开数据的连接。这里就完成了数据的写入操作此时我们只需要在数据库中查看一下是否插入成功即可。 搭建服务器,搭建客户端,搭建数据库提供服务端查询。这三个文件是同时进行的!为了方便看,直接展示完整的代码 首先是服务器:
""" dict 服务端部分 处理请求逻辑 """ from socket import * from multiprocessing import Process import signal import sys from time import sleep from operation_db import * # 全局变量 HOST = '0.0.0.0' PORT = 8000 ADDR = (HOST,PORT) # 处理注册 def do_register(c,db,data): tmp = data.split(' ') name = tmp[1] passwd = tmp[2] if db.register(name,passwd): c.send(b'OK') else: c.send(b"FAIL") # 处理登录L name passwd def do_login(c,db,data): tmp = data.split(' ') name = tmp[1] passwd = tmp[2] if db.login(name,passwd): c.send(b'OK') else: c.send(b"FAIL") # 处理查询Q name word def do_query(c,db,data): tmp = data.split(' ') name = tmp[1] word = tmp[2] # 插入历史记录 db.insert_history(name,word) # 查单词 没查到返回None mean = db.query(word) if not mean: c.send("没有找到该单词".encode()) else: msg = "%s : %s"%(word,mean) c.send(msg.encode()) def do_hist(c,db,data): name = data.split(' ')[1] r = db.history(name) if not r: c.send(b"FAIL") return c.send(b'OK') for i in r: # i ==> (name,word,time) msg = "%s %s %s"%i sleep(0.1) # 防止沾包 c.send(msg.encode()) sleep(0.1) c.send(b'##') # 处理客户端请求 def do_request(c,db): db.create_cursor() # 生成游标 db.cur while True: data = c.recv(1024).decode() print(c.getpeername(),':',data) if not data or data[0] == 'E': c.close() sys.exit("客户端退出") elif data[0] == 'R': do_register(c,db,data) elif data[0] == 'L': do_login(c,db,data) elif data[0] == 'Q': do_query(c,db,data) elif data[0] == 'H': do_hist(c,db,data) # 网络链接 def main(): # 创建数据库链接对象 db = Database() # 创建tcp套接字 s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(ADDR) s.listen(5) # 处理僵尸进程 signal.signal(signal.SIGCHLD,signal.SIG_IGN) # 等待客户端链接 print("Listen the port 8000") while True: try: c,addr = s.accept() print("Connect from",addr) except KeyboardInterrupt: s.close() db.close() sys.exit("服务器退出") except Exception as e: print(e) continue # 创建子进程 p = Process(target=do_request,args=(c,db)) p.daemon = True p.start() if __name__ == "__main__": main()服务器要完成的就是对客户端请求的处理,发送给数据库端,处理一定的信息,在交给服务器,返回给客户端。为了满足多用户同时的操作,创建多进程来完成,在main方法中,搭建流式套接字,连接数据库,保证端口号一直可以正常使用,处理僵尸进程,循环等待客户端连接,处理客户端的操作,创建子进程,子进程为do_request,父进程为循环等待客户端连接,一旦子进程退出,对应的父进程交给系统处理。在客户端请求时,我们编写了各种操作协议,传入套接字和游标对象,循环接受客户端的操作!接下来看客户端的代码!在查询历史数据的时候我们为了防止沾包,主动延迟0.1秒,我们知道在TCP中发生沾包是非常棘手的事情,很可能造成数据的错误,防止沾包的方法很多,我们这里采用最简单的延迟。
""" dict 客户端 发起请求,展示结果 """ from socket import * from getpass import getpass ADDR = ('127.0.0.1', 8000) # 所有函数都用s s = socket() s.connect(ADDR) # 查单词 def do_query(name): while True: word = input("单词:") if word == '##': # 结束单词查询 break msg = "Q %s %s" % (name, word) s.send(msg.encode()) # 等待回复 data = s.recv(2048).decode() print(data) def do_hist(name): msg = "H %s" % name s.send(msg.encode()) data = s.recv(128).decode() if data == 'OK': while True: data = s.recv(1024).decode() if data == '##': break print(data) else: print("还没有历史记录") # 二级界面 def login(name): while True: print(""" ================Query================= 1. 查单词 2. 历史记录 3. 注销 ====================================== """) cmd = input("输入选项:") if cmd == '1': do_query(name) elif cmd == '2': do_hist(name) elif cmd == '3': return else: print("请输入正确命令!") # 注册 def do_register(): while True: name = input("User:") passwd = getpass() passwd1 = getpass("Again:") if (' ' in name) or (' ' in passwd): print("用户名或密码不能有空格") continue if passwd != passwd1: print("两次密码不一致") continue msg = "R %s %s" % (name, passwd) # 发送请求 s.send(msg.encode()) # 接收反馈 data = s.recv(128).decode() if data == 'OK': print("注册成功") login(name) else: print("注册失败") return # 处理登录 def do_login(): name = input("User:") passwd = getpass() msg = "L %s %s" % (name, passwd) s.send(msg.encode()) # 等待反馈 data = s.recv(128).decode() if data == 'OK': print("登录成功") login(name) else: print("登录失败") # 创建网络链接 def main(): while True: print(""" ================Welcome=============== 1. 注册 2. 登录 3. 退出 ====================================== """) cmd = input("输入选项:") if cmd == '1': do_register() elif cmd == '2': do_login() elif cmd == '3': s.send(b'E') print("谢谢使用") return else: print("请输入正确命令!") if __name__ == "__main__": main()在客户端中同样采用流式套接字来完成,主方法中同样采用循环接受的方法,在客户端中我们把请求发送给服务器,服务器帮我们完成需要的操作,咋数据库文件中把服务器需要的操作做相应的操作写入数据库。接下来看一下数据的代码!在客户端中我们在注册时候,经常会让我们输入两遍密码,用户名和密码中不能含有空格。这边我们就模仿实际的注册方法,我们同样限制用户名和密码的操作。
""" dict项目用于处理数据 """ import pymysql import hashlib import time # 编写功能类 提供给服务端使用 class Database: def __init__(self, host='localhost', port=3306, user='root', passwd='123456', database='dict', charset='utf8'): self.host = host self.port = port self.user = user self.passwd = passwd self.database = database self.charset = charset self.connect_db() # 链接数据库 def connect_db(self): self.db = pymysql.connect(host=self.host, port=self.port, user=self.user, passwd=self.passwd, database=self.database, charset=self.charset) # 创建游标 def create_cursor(self): self.cur = self.db.cursor() # 关闭数据库 def close(self): self.cur.close() self.db.close() # 处理注册 def register(self, name, passwd): sql = "select * from user where name='%s'" % name self.cur.execute(sql) r = self.cur.fetchone() # 如果查询到结果 if r: return False # 加密处理 hash = hashlib.md5((name + "the-salt").encode()) hash.update(passwd.encode()) sql = "insert into user (name,passwd) values (%s,%s)" try: self.cur.execute(sql, [name, hash.hexdigest()]) self.db.commit() return True except Exception: self.db.rollback() return False # 处理登录 def login(self, name, passwd): sql = "select * from user where \ name = %s and passwd = %s" # name + the-salt -->盐 hash = hashlib.md5((name + "the-salt").encode()) hash.update(passwd.encode()) self.cur.execute(sql, [name, hash.hexdigest()]) r = self.cur.fetchone() if r: return True else: return False # 插入历史记录 def insert_history(self, name, word): tm = time.ctime() sql = "insert into hist (name,word,time) \ values (%s,%s,%s)" try: self.cur.execute(sql, [name, word, tm]) self.db.commit() except Exception: self.db.rollback() # 单词查询 def query(self, word): sql = "select mean from words \ where word = '%s'" % word self.cur.execute(sql) r = self.cur.fetchone() if r: return r[0] # 历史记录 def history(self,name): sql = "select name,word,time from hist \ where name='%s' order by id desc limit 10"%name self.cur.execute(sql) return self.cur.fetchall()在数据库中重点说一下对用户数据的加密处理,这样即使管理员也不能查看我们的密码, 在这里我们首先导入hashlib模块,用模块调用md5加盐,这个盐就是“asd”这里可以自定义接下来加入我们之前定义的密码,在对其进行加密,此时我们看到的是遗传16进制的数,这就大大增加了破解难度。然后在把这条数据插入数据库中,即使是管理员也只能看到加密后的数据无法知道用户的正真的密码,我们在登录的时候只需要做相同的事情就可以查到数据库是否有相同的用户了,如果有就提示登录成功,进入二级界面,如果没有就提示用户输入输入的有误。 这就完成了我们所有的操作,我们必须明白我们需要在服务端客户端数据库端都需要完成什么样的操作,这个没有逻辑的处理,只是实现功能更多的是客户端的请求和服务端对数据请求的处理,我们编写各种协议也是为了区分各个操作,防止发生混乱,计算机不会知道你要干什么,但你得告诉计算你你要干什么才可以,针对不同的操作,编写不同的协议,这是最好不过的方法了。