python_way day10
1、python的作用域和其他语言的作用域
2、python2.7多继承和3.5多继承的区别
3、socket和socketserver源码(并发处理socket)
一、python的作用域
在python中没有块级作用域,所以name已经被定义了
if 1 ==1: name ="hx" print(name)
hx
for i in rang(10)
name = i
print(name)
9
python中的作用域是函数:
def func():
name = "alex"
print(name)
NameError: name "name" is not defined
例子1: name = "out" def f1(): print(name) f1() out
例子2:
name = "o"
def f1():
name = "a"
def f2():
name = "b"
print(name)
f2()
f1()
b #python中变量会在当前的作用域中寻找值
例子3:
name = "out"
def f1():
print(name)
def f2():
name = "in"
return f1
print(name) #作用域为 name = “in“
ret = f2()
ret()
"out"
原因:在函数没有执行前,已经确定了作用域了,变量在已经确定的作用域中寻找变量的值
语法介绍
li = [ x+100 for x in range(10) if x > 6 ]
将x+100这个作为一个元素添加到li中, 循生成x , x>6的值才会在第一段中做添加
print(li)
[107, 108, 109]
li = [lambda :x for x in range(10)]
li中的元素是什么?
我们把这个表达式拆开来看
li = []
for x in range(10):
def f1():
return x
li.append(f1)
1、def f1这个函数的作用域是在for x in range下面
2、所以函数f1中的x会在先在for x in range中找
3、当每次循环时将f1函数添加到li列表中时这个f1函数没有被执行,所以return x 中的x 只是个变量名,没有一个实际的数值
下面我们就来操作了
ret = li[0]()
9
#说明:为什么是9,因为在执行的时候x已经循环完了,此时x已经循环完了它的值为 9 ,所以我们最后在执行li中列表中元素时,值就为9.
二、多继承
python2.7中分了2种情况:红线是没有继承任何父类(经典类),绿线是继承了object类(新式类):
python3中的多继承是自下向上找,直到还差一个就是顶端了
原因是python3默认几成object类
三、socketserver
并发
a、sockserver并发连接本质:
1、每个链接都会被前面的循环所发现,并创建线程或进程对其进行处理。
2、前面的循环就是IO多路复用
一个简单的socket链接是不支持多并发的:
import socket host = ('127.0.0.1',9999) sk =socket.socket() sk.bind(host) sk.listen(5) while True: conn, address = sk.accept() conn.sendall(bytes("欢迎", "utf8")) while True: ret = conn.recv(1024) if ret.decode() == "q": break conn.sendall(ret)
import socket server_host = ('127.0.0.1',9999) sk = socket.socket() sk.connect(server_host) recv = sk.recv(1024) a = recv.decode("utf8") print(a) while True: inp = input(">>>") sk.sendall(bytes(inp, "utf8")) if inp == "q": break recv = sk.recv(1024) print(recv.decode("utf8"))
上面例子中的简单的socket是不支持多并发的。当一个client链接server时,新的client会被卡主,知道第一个client断开连接后才会连通server
我们怎么使用IO多路复用呢?
b、IO多路复用实际是调用底层的 select,poll,epoll这些底层的代码
那么IO多路复用是干什么的? 其实它是用来监听socket内部是否变化了。
socket在什么时候变化:链接或收发消息的时候socket对象内部会变化
哪看看上面的代码,什么时候能够第一时间的发现变化?
就是服务器端accept的时候,是服务器端第一时间发现变化本次新的对话发生了变化。
host = ('127.0.0.1',9999) #sk是服务器对象 sk.bind(host) sk.listen(5) while True: conn, address = sk.accept() #收发数据的时候没有用到服务器的对象, #sk.accept是第一时间也是最后一次服务器对象接收触发的时候,就是只要此时的sk有变化了就是有新连接来了 #conn是给客户端收发消息创建的对象,这个客户端对象是和创建连接没有任何关系的。
#所以:sk只针对客户端来的新连接
#conn只针对客户端的收发消息
#此时我们已经知道了数据变化的点,只要一直坚守这个点,我们就可以发现每次的变化点,并处理他了
conn.sendall(bytes("欢迎", "utf8")) while True: ret = conn.recv(1024) if ret.decode() == "q": break conn.sendall(ret)
那我们用什么来一直坚守这这个点?循环!!对了,本质上就是循环,但是只是循环还不能实现所以我们就用到了被人封装好的一个方法!
就是IO多路复用 ------ 用来箭筒socket对象内部是否发生变化!
我们下面就用select来举例子
host = ('127.0.0.1',9999) sk =socket.socket() sk.bind(host) sk.listen(5)
while True: rlist, w, e = select.select([sk, ], [], [], 1) select()中的第一个值是我监听这个对象,只要有变化我就写到rlist的【】中,rlist中就是 socket对象列表
print(rlist)
[]
[]
[]
...
...
此时rlist是空值,就代表没有新连接进来
#上面总结,循环使用select监听socket对象的变化,如果sk对象发生变化,表示客户端来连接了,此时rlist值为【sk】
我们再往里面加点东西host = ('127.0.0.1',9999)
import socket server_host = ('127.0.0.1',9999) sk = socket.socket() sk.connect(server_host) ret = sk.recv(1024) print(ret.decode()) while True: inp = input(">>>: ") sk.sendall(bytes(inp,"utf8")) ret = sk.recv(1024) for i in ret: #一旦写成这样,服务器发来的消息就能看到不是一条,有好多条!不知道为什么,所以被迫就把for训话能去掉,这样就只接收一条服务器端发来的消息了 print(ret.decode())
实例1:读写放到一起
sk =socket.socket() sk.bind(host) sk.listen(5) inputs = [sk,] while True: rlist, w, e = select.select(inputs, [], [], 1) print(len(inputs),len(rlist)) for r in rlist: #r 就是rlist中的服务器端socket的对象 if r == sk: #r = sk的时候表示新客户端新链接时产生的服务器对象 conn, address = r.accept() print(conn) inputs.append(conn) conn.sendall(bytes("hello", "utf8")) else: #否则就是上面append进去的客户端的对象 try: #如果下面没有报错,则执行 ret = r.recv(1024) #因为使用了for循环,此时如果从一个客户端来了个大文件,socketserver就要把这个大文件先处理完,再处理下一个客户端发来的请求 if not ret: #不同的系统在断开连接时接收的是不一样的,有的是发个空字符串,有的直接断线导致报异常
raise Exception
print(ret.decode())
r.sendall(ret) except Exception: #ret没有值就会报错,代表客户端断开连接了 inputs.remove(r) #那就要把这个客户端从inputs监听列表中移除
实例2:读和写分离开,让代码更整洁,明了。 sk =socket.socket() sk.bind(host) sk.listen(5) inputs = [sk,] outputs = [] while True: #rlist, wlist, e = select.select(inputs, [sk], [], 1) #此时就用到了第二个参数 #这个第二个参数只要有值,select一直在变化,并且wlist就等于第二个参数里面的值 #我们利用这个相等的机制,就可以往第二个参数里面放置,如果有客户端发消息我们就把这个socket对象放到第二个参数中 rlist, wlist, e = select.select(inputs, outputs, [], 1 print(len(inputs),len(rlist)) for r in rlist: if r == sk: conn, address = r.accept() print(conn) inputs.append(conn) conn.sendall(bytes("hello", "utf8")) else: try: ret = r.recv(1024) if not ret: raise Exception else: output.append(r) #这样 wlist中我们就构造出了是所有给我们发消息的人! r.sendall(ret) except Exception: #ret没有值就会报错,代表客户端断开连接了 inputs.remove(r) #那就要把这个客户端从inputs监听列表中移除,由于wlist中的值适合outputs相等,此时wlist中也没有值了 for w in wlist: #循环wlist这个列表 w.sendall(bytes("resprones", "utf8")) #这里面都是给我发消息的人,我就可以在这里处理消息 outputs.remove(w) #回完消息证明本次通话就结束了,所以要停止本次消息了,就要把这个对象从outputs中移除 问题出来了,我们回复之前,有可能需要对客户端发来的消息做处理,然后再回复,那我们怎么能在回复的这个代码块中得到上一个代码块中客户端发来的消息哪?
下面我们就自己构造一个存储信息的位置
host = ('127.0.0.1',9999) sk =socket.socket() sk.bind(host) sk.listen(5) inputs = [sk,] outputs = [] messages = {} #这里定义一个消息字典 while True: rlist, wlist, e = select.select(inputs, outputs, [], 1) print(len(inputs), len(rlist), len(wlist),len(outputs)) for r in rlist: #r 就是rlist中的服务器端socket的对象 if r == sk: #r = sk的时候表示新客户端新链接时产生的服务器对象 conn, address = r.accept() print(conn) inputs.append(conn) messages[conn] = [] #谁链接上我了,这个对象就把客户端这个socketet对象存到消息的字典中当成key,因为只是链接,还没有发消息,所以就是为空列表 conn.sendall(bytes("hello", "utf8")) else: try: ret = r.recv(1024) if not ret: raise Exception else: #不是断开的情况 outputs.append(r) print(ret) messages[r].append(ret) #如果不是断开的情况,此时把就把发信息变动的对象 r 发来的ret信息保存到message的对象中的列表中。
except Exception: #如果是断开的情况 inputs.remove(r) del messages[r] #断开连接的时候就要把这个人的信息也删除了 for w in wlist: #循环wlist这个列表 msg = messages[w].pop() #读取客户端上一次发来的消息,这里的w 就是上面添加的r,pop是取出最后一条信息 resp = msg + bytes("resprones", "utf8") print(resp,"======") w.sendall(resp) #这里面都是给我发消息的人,我们可以在这里处理消息 outputs.remove(w) #回完消息证明本次通话就结束了,所以要停止本次消息了,就要把这个对象从outputs中移除
最后第三个参数作用是产生错误的socket链接
host = ('127.0.0.1',9999) sk =socket.socket() sk.bind(host) sk.listen(5) inputs = [sk,] outputs = []
messages = {} while True: rlist, wlist, elist = select.select(inputs, outputs, [sk], 1)
第三个参数我们也监听这socket对象,如果这个对象发生错误,elist中就有这个错误的对象
问题:是什么错误才能放到这里?
四、多线程
上面的io多路复用只是单线程 ,然后用for循环监听链接过来的socket对象,这样来伪造的一个多线程
单线程实例:
f1和f2会依次执行
import time def f1(i): time.sleep(1) print(i) f1(1) f1(2)
多线程实例: import threading import time def f1(i): time.sleep(1) print(i) #下面我们来创建2个人来一起执行f1函数 t1 = threading.Thread(target=f1,args=(1,)) t1.start() t2 = threading.Thread(target=f1,args=(2,)) t2.start()
target 就是让这个t1线程干什么
1,2就会一起执行出来
效果不明显,来颗仙豆,看看效果!
for g in range(10):
t1 = threading.Thread(target=f1,args=(g,))
t1.start()
t2 = threading.Thread(target=f1,args=(g,))
t2.start()
一下子就打出来了10个数字
socket 多线程处理源码流程图