前情回顾
1. 多线程并发网络模型
2. 基于Process的多进程并发网络 3. 集成模块socketserver完成网络并发 4. HTTPServer v2.0:模块封装,多线程并发,请求解析 5. 协程基础 : 定义,原理,优缺点 6. 介绍greenlet,学习gevent 【1】 gevent.spawn() 生成协程对象 【2】 gevent.joinall() 阻塞等待协程执行完成 【3】 gevent.sleep() 提供gevent阻塞*********************************************************
一. gevent模块(续)1. 动机:在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。
2. 转换方法:gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。
【1】 导入monkey from gevent import monkey【2】 运行相应的脚本,例如转换socket中所有阻塞
monkey.patch_socket() 【3】 如果将所有可转换的IO阻塞全部转换则运行all monkey.patch_all() 【4】 注意:脚本运行函数需要在对应模块导入前执行1 import gevent 2 from gevent import monkey 3 monkey.patch_all()#执行脚本插件,修改IO阻塞行为 4 from socket import * 5 6 #创建套接字 7 def server(): 8 s = socket() 9 s.bind(('0.0.0.0',8888)) 10 s.listen(10) 11 while True: 12 c,addr = s.accept()#主程序遇到阻塞触发协程 13 print("Conect from",addr)#打印客户端 14 # handle(c)#处理客户端请求 15 gevent.spawn(handle,c)#把函数变成协程,c->不定参数}-----协程方案,多客户端 16 17 def handle(c): 18 while True: 19 data = c.recv(1024)#协程阻塞在recv 20 if not data: 21 break 22 print(data.decode()) 23 c.send(b"OK") 24 25 server() 26
1 from socket import * 2 3 #创套接字 4 sockfd = socket() 5 6 #发起连接 7 server_addr = ('172.40.71.149',8888) 8 sockfd.connect(server_addr) 9 10 #收发消息 11 while True: 12 data = input(">>") 13 if not data: 14 break 15 sockfd.send(data.encode()) 16 data = sockfd.recv(1024) 17 print("From server:",data.decode()) 18 19 #关闭套接子 20 sockfd.close() 21
二. 网络电子词典
1. 功能说明
【1】用户可以登录和注册 * 登录凭借用户名和密码登录 * 注册要求用户必须填写用户名,密码,其他内容自定 * 用户名要求不能重复 * 要求用户信息能够长期保存 【2】可以通过基本的图形界面print以提示客户端输入。 * 程序分为服务端和客户端两部分 * 客户端通过print打印简单界面输入命令发起请求 * 服务端主要负责逻辑数据处理 * 启动服务端后应该能满足多个客户端同时操作 【3】客户端启动后即进入一级界面,包含如下功能: 登录 注册 退出* 退出后即退出该软件
* 登录成功即进入二级界面,失败回到一级界面 * 注册成功可以回到一级界面继续登录,也可以直接用注册用户进入二级界面 【4】用户登录后进入二级界面,功能如下: 查单词 历史记录 注销* 选择注销则回到一级界面
* 查单词:循环输入单词,得到单词解释,输入特殊符号退出单词查询状态 * 历史记录:查询当前用户的查词记录,要求记录包含name word time。可以查看所有记录或者前10条均可。 2. 单词本说明【1】 特点 : 1. 每个单词一定占一行
2. 单词按照从小到大顺序排列 3. 单词和解释之间一定有空格 【2】 查词说明 : 1. 直接使用单词本查询(文本操作) 2. 先将单词存入数据库,然后通过数据库查询。(数据库操作) 3. 操作步骤 【1】 确定并发方案? 确定套接字使用? 确定具体细节? 使用文件查询还是数据库? * fork 多进程 ,tcp套接字 * 注册后回到一级界面,历史记录显示最近10条 * 文本直接查询【2】 建立数据库 : 建立几个表,表关系,表字段及类型 * 想办法将单词导入数据库 用户表 : id name passwd 历史记录:id name word time 单词存储:id word mean
1. 创建数据库:
create database dict default charset=utf8;2. 创建用户表:
create table user (id int primary key auto_increment,name varchar(32) not null,passwd varchar(16) default '000000'); 3. 创建历史记录表: create table hist (id int primary key auto_increment,name varchar(32) not null,word varchar(32) not null,time varchar(64)); 4. 创建单词表: create table words (id int primary key auto_increment,word varchar(32),mean text); 【3】 结构设计:即如何封装,客户端和服务端工作流程。具体项目有几个功能模块。* 函数封装
* 客户端启动--》进入一级界面--》登录--》二级界面--》具体请求--》展示内容 * 服务端循环接收请求--》处理请求--》将数据发送给客户端 * 功能模块:登录,注册,查询单词,历史记录 【4】 完成通信的搭建【5】 分析具体通能,逐个模块实现
1、注册 客户端:*输入注册信息 *将信息发送给服务端 *得到服务器反馈 服务端:*接收请求 *判断是否允许注册 *反馈结果给客户端 *如果可以注册则插入数据库 2. 登录 客户端: * 输入用户名密码 * 将信息发送给服务器 * 得到服务端反馈 * 如果登录成功进入二级界面 服务端: * 接收请求 * 判断是否允许登录 * 反馈结果3. 查词
客户端 : * 输入查询单词 * 发送请求给服务端 * 获取结果 服务端 : * 接收请求 * 查找单词 * 将查询结果发送给客户端 * 插入历史记录4. 历史记录
1 import pymysql 2 3 f = open('dict.txt') 4 db = pymysql.connect('localhost','root','123456','dict') 5 6 cursor = db.cursor() #创建游标 7 8 for line in f: 9 tmp = line.split(' ') 10 world = tmp[0] 11 mean = ' '.join(tmp[1:]).strip() 12 sql='insert into worlds (world,mean) values ("%s","%s")'%(world,mean) 13 14 try: 15 cursor.execute(sql) 16 db.commit() 17 except Exception: 18 db.rollback() 19 f.close()
1 ''' 2 dict project for AID 3 ''' 4 from socket import * 5 import pymysql 6 import os,sys 7 import time#处理沾包 8 import signal#处理僵尸 9 10 #定义全局变量 11 if len(sys.argv)<3: 12 print('''Start as: 13 python3 dict_server.py 0.0.0.0 8000 14 ''') 15 sys.exit(0) 16 17 HOST = sys.argv[1] 18 PORT = int(sys.argv[2]) 19 ADDR = (HOST,PORT) 20 DICT_TEXT = "./dict.txt" 21 22 #搭建网络连接 23 def main(): 24 #连接数据库 25 db = pymysql.connect('localhost','root','123456','dict') 26 #创建套接字 27 s = socket() 28 # s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 29 s.bind(ADDR) 30 s.listen(5) 31 32 33 #僵尸进程处理 34 signal.signal(signal.SIGCHLD,signal.SIG_IGN) 35 36 37 while True: 38 try: 39 c,addr = s.accept() 40 print("Connect from",addr) 41 except KeyboardInterrupt: 42 s.close() 43 sys.exit("服务器退出") 44 except Exception as e: 45 print(e) 46 continue 47 48 #创建子进程 49 pid = os.fork() 50 if pid ==0: 51 s.close() 52 do_child(c,db) 53 sys.exit() 54 else: 55 c.close() 56 57 58 59 60 61 #处理客户端请求 62 def do_child(c,db): 63 while True: 64 #接收客户端请求 65 data = c.recv(1024).decode() 66 print(c.getpeername(),':',data) 67 if not data or data[0]=="E": 68 c.close() 69 sys.exit() 70 elif data[0]=='R': 71 do_register(c,db,data) 72 elif data[0]=='L': 73 do_login(c,db,data) 74 elif data[0]=='Q': 75 do_query(c,db,data) 76 elif data[0]=='H': 77 do_hist(c,db,data) 78 79 80 81 82 83 84 def do_register(c,db,data): 85 l = data.split(' ') 86 name =l[1] 87 passwd = l[2] 88 cursor = db.cursor() 89 90 sql ="select * from user where name='%s'"%name 91 cursor.execute(sql) 92 r = cursor.fetchone() 93 if r != None: 94 c.send(b'EXITS') 95 return 96 97 #插入用户 98 sql = "insert into user(name,passwd) values('%s','%s')"%(name,passwd) 99 try:100 cursor.execute(sql)101 db.commit()102 c.send(b'OK')103 except:104 db.rollback()105 c.send(b'FAIL')106 107 108 109 def do_login(c,db,data):110 l = data.split(' ')111 name = l[1]112 passwd = l[2]113 cursor = db.cursor()114 115 116 sql = "select * from user where name='%s' and passwd='%s'"%(name,passwd)117 #查询用户118 cursor.execute(sql)119 r = cursor.fetchone()120 if r ==None:121 c.send(b'FAIL')122 else:123 c.send(b'OK')124 125 126 127 128 129 def do_query(c,db,data):130 l = data.split(' ')131 name = l[1]132 world = l[2]133 134 135 #插入历史记录136 cursor = db.cursor()137 tm = time.ctime()138 sql = "insert into hist(name,world,time) values ('%s','%s','%s')"%(name,world,tm)139 try:140 cursor.execute(sql)141 db.commit()142 except:143 db.rollback()144 145 #单词本查找146 f = open(DICT_TEXT)147 148 for line in f:149 tmp = line.split(' ')[0]#获取单词150 if tmp > world:151 break152 elif tmp == world:153 c.send(line.encode())154 return155 c.send("没有找到该单词")156 f.close()157 158 159 def do_hist(c,db,data):160 name = data.split(' ')[1]161 cursor = db.cursor()162 sql = "select * from hist where name='%s' order by id desc limit 10"%name163 cursor.execute(sql)164 r = cursor.fetchall()165 if not r:166 c.send(b'FAIL')167 return168 else:169 c.send(b'OK')170 time.sleep(0.1)171 for i in r:172 msg = "%s %s %s"%(i[1],i[2],i[3])173 c.send(msg.encode())174 time.sleep(0.1)175 c.send(b'##')176 177 178 179 180 if __name__=="__main__":181 main()182
1 from socket import * 2 import sys 3 import getpass 4 5 #创建网络连接 6 def main(): 7 if len(sys.argv) <3: 8 print("argv is error") 9 return 10 HOST = sys.argv[1] 11 PORT = int(sys.argv[2]) 12 s = socket() 13 try: 14 s.connect((HOST,PORT)) 15 except Exception as e: 16 print(e) 17 return 18 while True: 19 print(''' 20 ===============================wecome============================== 21 --1.注册 2.登录 3.退出-- 22 ''') 23 try: 24 cmd = int(input("输入选项:")) 25 except Exception as e: 26 print("命令错误") 27 continue 28 if cmd not in [1,2,3]: 29 print("请输入正确选项") 30 continue 31 elif cmd ==1: 32 do_register(s) 33 elif cmd ==2: 34 do_login(s) 35 elif cmd ==3: 36 s.send(b'E') 37 sys.exit("谢谢使用") 38 39 def do_register(s): 40 while True: 41 name = input("User:") 42 passwd = getpass.getpass() 43 passwd1= getpass.getpass("Again:") 44 45 if (' 'in name) or (' ' in passwd): 46 print("用户名密码不能有空格") 47 continue 48 if passwd != passwd1: 49 print("两次密码不一致") 50 continue 51 52 msg = "R %s %s"%(name,passwd) 53 #发送请求 54 s.send(msg.encode()) 55 #等待回复 56 data = s.recv(128).decode() 57 if data =='OK': 58 print("注册成功") 59 # login(s,name)#注册成功进入二级界面 60 elif data =='EXISTS': 61 print("用户已存在") 62 else: 63 print("注册失败") 64 return 65 66 67 68 def do_login(s): 69 name = input("User:") 70 passwd = getpass.getpass() 71 msg = "L %s %s"%(name,passwd) 72 s.send(msg.encode()) 73 data = s.recv(128).decode() 74 if data =="OK": 75 print("登录成功") 76 login(s,name) 77 else: 78 print("登录失败") 79 80 def login(s,name): 81 while True: 82 print(''' 83 ===============================wecome============================== 84 --1.查词 2.历史记录 3.注销-- 85 ''') 86 try: 87 cmd = int(input("输入选项:")) 88 except Exception as e: 89 print("命令错误") 90 continue 91 if cmd not in [1,2,3]: 92 print("请输入正确选项") 93 continue 94 elif cmd ==1: 95 do_query(s,name) 96 elif cmd ==2: 97 do_hist(s,name) 98 elif cmd ==3: 99 return #回到一级界面100 101 def do_query(s,name):102 while True:103 world = input("单词:")104 if world == '##':105 break106 msg ='Q %s %s'%(name,world)107 s.send(msg.encode())108 #可能是单词解释.也可能是找不到109 data = s.recv(2048).decode()110 print(data)111 112 def do_hist(s,name):113 msg = "H %s"%name114 s.send(msg.encode())115 data = s.recv(128).decode()116 if data =="OK":117 while True:118 data = s.recv(1024).decode()119 if data =="##":120 break121 print(data)122 else:123 print("没有历史记录")124 125 126 127 128 129 130 131 132 if __name__=="__main__":133 main()
【1】 import sys
【2】 sys.argv 可以将命令行输入参数收集为一个列表 【3】 默认命令行以空格分隔每一项,如果一个整体中有空格则用引号注明一个整体 【4】 收集的列表中所有项均为字符串 2.使用getpass输入密码 【1】import getpass 【2】passwd=getpass.getpass(),其中getpass()函数用法与input相同,只是可以自动 隐藏输入内容 作业 : 1. 整理进程线程网络内容 2. 复习mysql的理论内容和基本操作