用户能够登陆和注册python
用户名 密码mysql
用户名不能重复linux
用户信息能够长期保存sql
程序分为服务端,客户端数据库
服务端负责数据处理django
启动服务端后应知足多个客户端同时操做编程
经过基本的图形界面 print 提示客户端输入后端
启动后进入一级界面bash
一级界面功能服务器
登陆 注册 退出
登录: 成功进入二级界面
注册: 成功返回一级界面,或者直接使用注册用户进入二级界面
退出: 退出软件
二级界面功能
查单词 历史记录 注销
查单词: 循环输入单词.获得单词解释,输入特殊符号退出查询状态
直接使用单词本查询.(文本操做)
现将单词本存入数据库查询.经过数据库查询
历史记录: 查询当前用户的查词记录 要求记录包含 name word time能够查看全部记录,或者只显示前10条
注销: 返回上级页面
特色
文本形式
每一个单词占一行
单词按照从小到大顺序排列
单词和解释之间必定有空格
形式以下的一个 e_dict.txt 文件
a indef art one abacus n.frame with beads that slide along parallel rods, used for teaching numbers to children, and (in some countries) for counting abandon v. go away from (a person or thing or place) not intending to return; forsake; desert abandonment n. abandoning abase v. ~ oneself/sb lower oneself/sb in dignity; degrade oneself/sb ; abash to destroy the self-possession or self-confidence of:disconcert abashed adj. ~ embarrassed; ashamed abate v. make or become less abattoir n. = slaughterhouse (slaughter) abbess n. woman who is head of a convent or nunnery abbey n. buildingin which monks or nuns live as a community under an abbot or abbess abbot n. man who is head of a monastery or abbey abbreviate v. ~ sth shorten (a word, phrase, etc), esp by omitting letters
练如下的技能
socket, 协程, MySQL, pymysql,
服务端执行启动后等待客户端链接
客户端链接后显示一级界面
用户名输入,密码,重复密码输入,都没法展现.注册成功后返回一级界面
登陆输入验证成功后进入二级界面
查询单词后返回查询结果,输入## 退出查看 返回二级目录
返回当前用户最近10条历史查询单词,而后返回二级菜单
注销当前用户返回一级菜单
大白话思路
首先想好要用哪些技术.
数据库这边选择 mysql. 而后使用 pymysql 链接数据库这定死了.
而后就是数据库表结构设计
而后就是网络部分选择 tcp_socket 分为服务端客户端两套来作.各自处理各自的逻辑也没得说
而后关于高并发问题用 多进/线/协程来处理,这里选择最简单的协程最方便
而后就是具体的逻辑代码问题了.
逻辑方面:
登陆验证这块就和数据库的用户表打交道了.
查词这块是和字典表来查询.查询这块的选择方式为了知识点覆盖咱们不经过数据库来查询.基于文件来查询
历史记录这块重点就是sql语句的问题了,要排序要限制取数据条数,这块为了技术覆盖面咱们要基于数据库来
那总体差很少就是这样的分析了.首先设计表结构把
首先这里咱们可使用外键,这样设计是能够的.可是鉴于咱们仅仅是练习仍是特么这么简单的结构设计就懒得这么折腾了
table user ----------------------- id | username | pwd | ----------------------- table user_hist -------------------------- id | user_id | hist_id | -------------------------- table user_history ----------------------------- id | use_id | dict_id | time ----------------------------- table dict_hist --------------------------- id | dict_id | hist_id | --------------------------- table e_dict.txt ----------------- id | word | val -----------------
因此咱们使用更简单的结构设计,直接三张表解决吧
其次还须要作一件事就是要把 e_dict.txt 文件中的 数据添加到数据库中
这里存在着优化点.
文件中有 2w条数据,本觉得不算特别大竟然也用了接近5分钟.这个效率不敢恭维
所以可使用 将数据整合在列表中,而后用 excutemany 来实现批量的添加更加高效.
""" 一次性的执行脚本文件 目的 实现将文件中的 数据插入在数据库中 """ import pymysql f = open("e_dict.txt") db = pymysql.connect("127.0.0.1", "root", "123456", "dict") cursor = db.cursor() for line in f: # 循环读取文件内容 tmp = line.split(" ") word = tmp[0] mean = " ".join(tmp[1:]).strip() sql = 'insert into words (word, mean) values ("%s","%s")' % (word, mean) try: cursor.execute(sql) db.commit() except: db.rollback() f.close()
执行完毕后,数据库的相关操做就所有完成了
服务端和客户端彼此的职能是彻底不一样的,可是须要数据的交互
业务逻辑也所有都是彼此分割
前面使用了 sys.argv, 此方法用与获取用户命令行操做的参数.这样能够实现用户不经过修改源码的方式实现自定义属性的传入
此方法在 django ,scarpy 中都有体现
服务端这须要建立tcp_socket 以及并发的相关操做.以及一些其余的善后处理.固然还有与 DB 的交互也要完成
而且定义了 相关的业务逻辑操做码.以及 基于协程的 并发模式处理
def main(): # 链接数据库 db = pymysql.connect("127.0.0.1", "root", "123456", "dict") # 建立套接字 s = socket() # s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 保证端口复用的 s.bind(ADDR) s.listen(5) # 僵尸进程的处理 # signal.signal(signal.SIGCHLD, signal.SIG_IGN) # 循环等待链接 while True: try: c, addr = s.accept() print("Connect from", addr) except KeyboardInterrupt: s.close() sys.exit("服务器退出") continue # 建立子进程 gevent.spawn(do_child, c, db) # 若是在 linux 下直接这样也能够 # pid = os.fork() # if pid == 0: # s.close() # do_child(c, db) # sys.exit() # else: # c.close() def do_child(c, db): 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_history(c, db, data) return
客户端要实现一个图形界面的展现以及用户的操做指引.顺便须要对用户的操做指定 操做码
好比在框架中能够提现 退出程序 的操做码为 " E "
一样和服务端同样, 客户端也须要在命令行进行定制
def main(): if len(sys.argv) < 3: print(""" argv is error""") return HOST = sys.argv[1] PORT = int(sys.argv[2]) s = socket() try: s.connect((HOST, PORT)) except Exception as e: print(e) return while True: print(""" =====================Welcome====================== -- 1. 注册 2.登陆 3 退出 -- ================================================== """) try: cmd = int(input("输入选项:")) except Exception as e: print("命令错误") continue if cmd not in [1, 2, 3]: print("请输入正确选项") continue elif cmd == 1: do_register(s) elif cmd == 2: do_login(s) elif cmd == 3: s.send(b'E') sys.exit("谢谢使用")
服务端对业务逻辑 为登陆的时候定义 操做码为 " L ", 而后定义经过 do_login函数具体操做
do_login 函数具体取到 客户端的发送数据而后格式化处理后在数据库查询用户验证
判断基于状态返回
def do_login(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] sql = "select * from user where name = '%s' and pwd = '%s'" % (name, passwd) # 查找用户 cursor = db.cursor() cursor.execute(sql) r = cursor.fetchone() if r == None: c.send(b'FAIL') else: c.send(b"OK") return
客户端的操做主要是获取用户数据而后发送 与服务端约定的格式和操做码.而后等待服务端的回传消息
在基于回传消息进行对用户的信息展现.
若是 登陆成功会进一步的 经过 login 函数进入二级界面.
def do_login(s): name = input("User:") passwd = getpass.getpass() msg = "L %s %s" % (name, passwd) s.send(msg.encode()) data = s.recv(128).decode() print(data) if data == "OK": print("登录成功") login(s, name) else: print("登陆失败") return
依旧和登陆相似,数据结构处理后对数据库的操做
中间有一步对用户名存在的判断
def do_register(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] cursor = db.cursor() sql = "select * from user where name = '%s' " % name cursor.execute(sql) r = cursor.fetchone() if r != None: c.send(b"EXISTS") return # 插入用户 sql = "insert into user (name, pwd) value ('%s','%s')" % (name, passwd) try: cursor.execute(sql) db.commit() c.send(b"OK") except: db.rollback() c.send(b"FAIL") return
客户端依旧是获取用户数据以及状态消息回传. 密码部分的显示用了 getpass 来隐藏输入显示
def do_register(s): while True: name = input("User:") passwd = getpass.getpass() passwd1 = getpass.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(s,name) # 注册成功进入二级界面 return elif data == "EXISYS": print("用户已存在") else: print("注册失败") return
服务端的查询须要拿到单词的解释以及分区是否能够查询到,
未查询到的时候要返回具体的错误消息(由于客户端很差判断,这里直接返回具体消息更方便)
由于这里使用过的是基于文件的查询,在判断对比 查询字段的时候 基于 字符串的排序,由于文件内的字段都是排序过的.直接这样对比能够更加快速
固然也须要考虑 "zzzz" 这样的单词到死也查询不到的可能性,所以须要注意判断的嵌套,最外层要又一次表示
def do_query(c, db, data): l = data.split(" ") name = l[1] word = l[2] # 插入历史记录 cursor = db.cursor() tm = time.ctime() sql = "insert into hist (name, word, time) value ('%s','%s','%s')" % (name, word, tm) try: cursor.execute(sql) db.commit() except: db.rollback() f = open(DICT_TEXT) for line in f: tmp = line.split(" ")[0] # 获取单词 if tmp > word: break elif tmp == word: c.send(line.encode()) f.close() return c.send("没有该单词".encode()) f.close()
客户端的查询逻辑,获取用户输入.基于结束码结束
这里须要注意的是不肯定是否拿到的是单词解释仍是由于找不到单词而返回的结果.
可是不管是什么这里很差区分咱们由后端来区分,这里直接所有展现便可
def do_query(s, 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)
主要仍是 mysql 的使用 sql 语句,要用到排序和限制.
须要注意的是数据库建立的时候图方便没使用 time 相关的字段而是用了字符串
所以这里排序不能按照时间,要按照自增的 id 倒叙来保证最近时间的数据可靠性
def do_history(c, db, data): name = data.split(" ")[1] cursor = db.cursor() sql = "select * from hist where name='%s' order by id desc limit 10" % name cursor.execute(sql) r = cursor.fetchall() if not r: c.send(b"FAIL") else: c.send(b"OK") time.sleep(0.1) for i in r: msg = "%s %s %s" % (i[1], i[2], i[3]) c.send(msg.encode()) time.sleep(0.1) c.send(b"##")
客户端这块就没什么好说的了,惟一须要注意的是由于你不知道要查询的字段到底有多少数据,
所以直接选择用 死循环不停接收便可,也由于死循环,须要定义一个结束标识, 即 "##"
def do_history(s,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("没有历史记录")
整合了网络编程,并发编程.mysql,以及前期阶段的集成项目.很是适合练手.
项目虽然完成可是中间有大量的优化空间,
尤为是大量的冗余代码.好比 DB 操做能够整合为一个 工具类这样 服务端中的数据库操做会更加温馨
其次全程使用 函数式编程,整合成 面向对象方式更加清晰.
还有为了不沾包问题,这里用了很蠢的 sleep 来处理也是优化空间
窝草.通篇忘了使用 pymysql 的防注入了.全程铁头本身拼字符串还行...醉了....回过头来看问题真的多
以上差很少就是这样吧,,,算了算了...心放宽...宽...
from socket import * import sys import getpass # 建立网络链接 def main(): if len(sys.argv) < 3: print(""" argv is error""") return HOST = sys.argv[1] PORT = int(sys.argv[2]) s = socket() try: s.connect((HOST, PORT)) except Exception as e: print(e) return while True: print(""" =====================Welcome====================== -- 1. 注册 2.登陆 3 退出 -- ================================================== """) try: cmd = int(input("输入选项:")) except Exception as e: print("命令错误") continue if cmd not in [1, 2, 3]: print("请输入正确选项") continue elif cmd == 1: do_register(s) elif cmd == 2: do_login(s) elif cmd == 3: s.send(b'E') sys.exit("谢谢使用") def do_register(s): while True: name = input("User:") passwd = getpass.getpass() passwd1 = getpass.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(s,name) # 注册成功进入二级界面 return elif data == "EXISYS": print("用户已存在") else: print("注册失败") return def do_login(s): name = input("User:") passwd = getpass.getpass() msg = "L %s %s" % (name, passwd) s.send(msg.encode()) data = s.recv(128).decode() print(data) if data == "OK": print("登录成功") login(s, name) else: print("登陆失败") return def login(s, name): while True: print(""" =====================Welcome====================== -- 1. 查词 2.历史记录 3 注销 -- ================================================== """) try: cmd = int(input("输入选项:")) except Exception as e: print("命令错误") continue if cmd not in [1, 2, 3]: print("请输入正确选项") continue elif cmd == 1: do_query(s, name) elif cmd == 2: do_history(s,name) elif cmd == 3: return def do_query(s, 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_history(s,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("没有历史记录") if __name__ == '__main__': main()
from gevent import monkey monkey.patch_all() from socket import * import pymysql import sys import time import gevent import os # 制定输入格式,提供用户输入指引 if len(sys.argv) < 3: print(""" Start as: python3 dict_server.py 0.0.0.0 8000 """) sys.exit() # 定义全局变量 HOST = sys.argv[1] # sys.argv 获取命令行参数 PORT = int(sys.argv[2]) ADDR = (HOST, PORT) DICT_TEXT = "e_dict.txt" # 搭建网络链接 def main(): # l链接数据库 db = pymysql.connect("127.0.0.1", "root", "123456", "dict") # 建立套接字 s = socket() # s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 保证端口复用的 s.bind(ADDR) s.listen(5) # 僵尸进程的处理 # signal.signal(signal.SIGCHLD, signal.SIG_IGN) # 循环等待链接 while True: try: c, addr = s.accept() print("Connect from", addr) except KeyboardInterrupt: s.close() sys.exit("服务器退出") continue # 建立子进程 gevent.spawn(do_child, c, db) # 若是在 linux 下直接这样也能够 # pid = os.fork() # if pid == 0: # s.close() # do_child(c, db) # sys.exit() # else: # c.close() # 处理客户端请求 def do_child(c, db): 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_history(c, db, data) return def do_register(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] cursor = db.cursor() sql = "select * from user where name = '%s' " % name cursor.execute(sql) r = cursor.fetchone() if r != None: c.send(b"EXISTS") return # 插入用户 sql = "insert into user (name, pwd) value ('%s','%s')" % (name, passwd) try: cursor.execute(sql) db.commit() c.send(b"OK") except: db.rollback() c.send(b"FAIL") return def do_login(c, db, data): l = data.split(" ") name = l[1] passwd = l[2] sql = "select * from user where name = '%s' and pwd = '%s'" % (name, passwd) # 查找用户 cursor = db.cursor() cursor.execute(sql) r = cursor.fetchone() if r == None: c.send(b'FAIL') else: c.send(b"OK") return def do_query(c, db, data): l = data.split(" ") name = l[1] word = l[2] # 插入历史记录 cursor = db.cursor() tm = time.ctime() sql = "insert into hist (name, word, time) value ('%s','%s','%s')" % (name, word, tm) try: cursor.execute(sql) db.commit() except: db.rollback() f = open(DICT_TEXT) for line in f: tmp = line.split(" ")[0] # 获取单词 if tmp > word: break elif tmp == word: c.send(line.encode()) f.close() return c.send("没有该单词".encode()) f.close() def do_history(c, db, data): name = data.split(" ")[1] cursor = db.cursor() sql = "select * from hist where name='%s' order by id desc limit 10" % name cursor.execute(sql) r = cursor.fetchall() if not r: c.send(b"FAIL") else: c.send(b"OK") time.sleep(0.1) for i in r: msg = "%s %s %s" % (i[1], i[2], i[3]) c.send(msg.encode()) time.sleep(0.1) c.send(b"##") if __name__ == '__main__': main()