网络编程,并发编程
一.网络基础
地址
通信传输中,发送端和接收端可以被视为通信主体,它们都能够被地址加以标识,不同的协议使用不同的地址.
唯一性:每个通信地址必须明确主体对象,否则地址也就失去存在意义,目标主体可以指完成本次指令的计算机群组,也可以是单独的一个计算机,也就是说广播,多播和地址的唯一性并不矛盾.
层次性:地址的层次性其实就是为了高效快速的找出通信的主体对象,可以通过国家的省市区的层次划分来理解;MAC地址不存在层次性,因为MAC地址是由厂商在生产时对每块网卡进行指定的方式,相当于身份证,但不同于身份证,因为没法从mac地址中识别出其使用地址,只能作为网卡的 唯一身份标识.
离线交互:mac地址
传输速率和吞吐量
传输速率:是指两个设备之间数据流动的物理速率,单位为bps(每秒比特数),在不同介质之中传输速率不同,但是传输速率是恒定的,不会出现波动.
吞吐量:指的是处理数据的能力,网络拥堵程度等信息,我们通常使用吞吐量来比较不同web框架的速度.
单播,广播,多播和任播
单播:就是一对一通信,比如说固定电话之间的通信
广播:就是指将消息从单一的源发送到共享以太网的所有主机,但是它指的是在广播域(广播范围)内的才能进行.广播方式会产生大量流量,导致带宽利用率降低,进而影响整个网络 的性能.当需要网络中的所有主机都能接收到相同的信息并进行处理的情况下,通常会使用广播方式.
多播:和广播类似,但是需要限定一组主机作为接收端.
任播:是指在多台主机中选出一台作为接收端的一种通讯方式,所选中的主机会返回一个单播信号,多存在于DNS服务器
网络设备
网卡:是计算机实现联网的设备,也就是在计算机系统中常见的网络适配器,比如想要连接无线网络必须有无线网卡,连接有线网络必须要有有线网卡.
中继器:位于物理层,是一个信号放大器,负责信号之间的转换.
网桥:位于数据链路层连接两个网络的设备,也被称为二层交换机.网桥能够识别数据链路层中的数据帧(和分组数据相似),并将数据帧存储与内存在重新生成全新的数据帧发送给相连的另一个网段;自学式网桥拥有自学机制,能够记住自己曾经转发的所有数据帧的mac地址,并保存在内存中,由此判断是否将数据报文转发给相邻网段.交换集线器(交换机)的每一个端口其实都是提供网桥的功能.
路由器:(3层交换机)工作在网络层,连接两个网络并对分组报文进行转发,网桥基于mac地址进行处理,路由器基于IP地址进行处理.
负载均衡器:(4-7层交换机)工作在OSI参考模型的4-7层 ,以TCP等协议的传输层和网络层为基础,分析数据并进行处理,例如服务器集群的分发
插网线:路由器,交换机,DHCP,IP,子网掩码,网关
访问:域名/域名解析
--内网:arp协议+广播+单播(广播风暴)
--外网:网关
端口:是为了将同一个电脑上的不同程序进行隔离.----寻找电脑上的程序
IP-----寻找电脑
OSI 7层模型:
应用层:为应用程序提供服务并规定应用程序中通信的相关细节,包括文件传输,电子邮件等协议.
表示层:将应用处理的信息转换为合适网络传输的格式,主要负责数据格式的转换.
会话层:主要负责数据传输相关的管理,如建立和断开通信连接和数据的分割等.
socket模块:
传输层:TCP/UDP,起着确保数据可靠传输的作用,只在通信双方节点上进行处理,而无需在路由器上进行处理,会话层决定建立和断开连接的时机,传输层进行实际的处理,传输层值得是逻辑意义上的连接.
网络层:IP,将数据传输到目标地址,主要负责寻址和路由选择.
数据链路层:MAC,负责物理层面上的节点之间的传输,数据帧和比特流之间的转换,数据链路层指的是物理的通信线路上的连接.
物理层:将数据转换成电信号发送,负责高电平.低电平和01比特流之间的互换,也就是电子信号和比特流之间的转换.
5层: 应用层
传输层
网络层
数据链接层
物理层
4层: 应用层
传输层
网络层
物理层
TCP三次握手/四次挥手:
socket客户端向服务端发起链接请求:三次握手
客户端和服务端断开连接:四次挥手
当断开连接时,反应到代码上:抛出异常/发送空内容
二..socket的使用
分为TCP和UDP
服务器(server)端
import socket sk = socket.socket() sk.bind(('192.168.13.23',3333)) #把ip地址绑定到socket sk.listen(5) #监听链接,5代表只允许5个人排队链接 conn,addr = sk.accept() #接收客户端连接,conn是服务器和客户端连接的对象,服务器通过该对象收发数据 ret = conn.recv(1024) #接收客户端信息,一次性最多拿1024字节 print(ret) #打印客户端信息 conn.send(b'heihei') #向客户端发送信息 conn.close() #关闭客户端socket sk.close() #关闭服务器socket
客户(client)端
import socket sk = socket.socket() #创建客户socket sk.connect(('192.168.13.23',3333)) #尝试连接服务器 sk.send(b'sb') #发送对话 ret = sk.recv(1024) #接收 print(ret) sk.close() #关闭
socketserver +多线程
三.多线程,多进程
1.python的GIL锁
- python内置的一个全局解释器锁,锁的作用就是保证同一个时刻一个进程中只有一个线程可以被cpu调度
-为什么有这把GIL锁?
答:python语言的创始人在开发这门语言时,目的快速把语言开发出来,如果加上GIL锁(c语言加锁),切换时按照100条字节指令进行线程间的切换.
2.进程和线程的区别?
-线程,cpu工作最小的单元.
-进程,为线程提供一个资源共享的空间.
- 一个进程中可以有多个线程,默认是有一个主线程
- 对于python来说他的进程和线程和其他语言有差异,有GIL锁
3.应用程序
4.编写多线程
5.IO操作不占用cpu
6.进程和线程的使用准则:
-计算密集型:多进程
-IO密集型:多线程
7.线程创建的越多越好吗?不好
-线程之间进行切换时,要做上下文管理.线程数和性能成抛物线关系
四.线程锁Lock
1.锁:Lock
线程安全,多线程操作时,内部会让所有线程排队处理.
线程不安全 + 人(锁) ==> 排队处理
import threading import time v = [] lock = threading.Lock() def func(arg): lock.acquire() v.append(arg) time.sleep(0.01) m = v[-1] print(arg,m) #打印0 0;1 1;2 2;3 3;4 4;5 5;6 6;7 7;8 8;9 9 lock.release() for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start()
2.锁:RLock
import threading import time v =[] lock = threading.RLock() def func(arg): lock.acquire() #可以锁两次 lock.acquire() v.append(arg) time.sleep(0.01) m = v[-1] print(arg,m) lock.release() #可以解锁两次 lock.release() for i in range(10): t= threading.Thread(target=func,args=(i,)) t.start()
3.BoundedSemaphore(1次放n个) 信号量
import threading import time lock = threading.BoundedSemaphore(3) def func(arg): lock.acquire() print(arg) time.sleep(1) lock.release() for i in range(20): t = threading.Thread(target=func,args=(i,)) t.start() #隔1秒打印三个数,直至打印完20
4.锁:Condition(1次方法x个)
import threading import time lock = threading.Condition() def func(arg): print('线程coming') lock.acquire() lock.wait() #加锁 print(arg) time.sleep(1) lock.release() for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start() while 1: inp = int(input('>>>')) #输入几,就打印几个,直至全部打印完 lock.acquire() lock.notify(inp) lock.release()
import threading import time lock = threading.Condition() def xx(): print('执行 函数ing') input('>>>') return True def func(arg): print('线程进来了') lock.wait_for(xx) print(arg) time.sleep(1) for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start() #输入一次,打印一次 5.锁:Event(1次放所有) import time import threading lock = threading.Event() def func(arg): print('线程来了') lock.wait() #加锁,红灯 print(arg) for i in range(10): t=threading.Thread(target=func,args=(i,)) t.start() input('>>>') lock.set() #绿灯 lock.clear()# 再次红灯 for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start() input('>>>') lock.set()
总结:
线程安全,列表和字典线程安全;
为什么要加锁?
-非线程安全
-控制一段代码
6.threading.local
作用:内部自动为每个线程维护一个空间(字典),用于给当前线程存取属于自己的值.保证线程之间的数据隔离.
{
线程ID:{...}
线程ID:{...}
}
示例:
import time
import threading v = threading.local() def func(arg): v.phone = arg#内部会为当前线程创建一个空间用于存储:phone=自己的值 time.sleep(2) print(v.phone,arg) #去当前线程自己空间取值 for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start()
import threading import time data_dict = {} def func(arg): ident = threading.get_ident() data_dict[ident] = arg time.sleep(1) print(data_dict[ident],arg) for i in range(10): t = threading.Thread(target=func,args=(i,)) t.start()
7.线程池
from concurrent.futures import ThreadPoolExecutor import time def task(a1,a2): time.sleep(2) print(a1,a2) pool = ThreadPoolExecutor(5) #创建一个线程池(最多5个线程) for i in range(40): #去线程池中申请一个线程,让线程执行task函数. pool.submit(task,i,8)
8.生产者消费者模型
三部件:
生产者
队列,先进先出
栈,后进先出
消费者
*生产者消费者模型解决了一直等待的问题.
五.进程
1.进程之间数据不共享
import multiprocessing data_list =[] def task(arg): data_list.append(arg) print(data_list) def run(): for i in range(10): p = multiprocessing.Process(target=task,args=(i,)) p.start() if __name__ =='__main__': run() #打印 [0][1][2][3][4][5][7][6][8][9]
也可以用类继承方式创建进程
import multiprocessing class MyProcess(multiprocessing.Process): def run(self): print('当前进程',multiprocessing.current_process()) def run(): p1 = MyProcess() p1.start() p2 = MyProcess() p2.start() if __name__ == '__main__': run()
2.如果想要进程之间数据共享怎么办呢?以下有两种办法:
通过queue模块:
import multiprocessing import queue def task(arg,q): q.put(arg) if __name__ == '__main__': q = multiprocessing.Queue() for i in range(10): p = multiprocessing.Process(target=task,args=(i,q)) p.start() while True: v = q.get() print(v)
通过Manage方法:
import time import multiprocessing def task(arg,dic): time.sleep(2) dic[arg] = 100 if __name__ == '__main__': m = multiprocessing.Manager() dic = m.dict() process_list = [] for i in range(10): p = multiprocessing.Process(target=task,args=(i,dic,)) p.start() process_list.append(p) while 1: count = 0 for p in process_list: if not p.is_alive(): count += 1 if count == len(process_list): break print(dic) #打印{2: 100, 1: 100, 3: 100, 4: 100, 0: 100, 5: 100, 6: 100, 7: 100, 8: 100, 9: 100}
3.进程也可以设置线程池
import multiprocessing import time from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def task(arg): time.sleep(2) print(arg) if __name__ == '__main__': pool = ProcessPoolExecutor(5) for i in range(10): pool.submit(task,i)
六.IO多路复用
1.作用:检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)
2.基于IO多路复用+socket实现并发请求(一个线程n个请求)
基于事件循环实现的异步非阻塞框架,Twisted
非阻塞:不等待
异步:执行完某个人物后自动调用我给他的函数
IO多路复用的三种模式:--select:不限制1024个socket;循环去检测
--poll:不限制监听socket个数;循环去检测(水平触发)
--epoll:不限制监听socket;回调方式(边缘触发)
3.提高并发方案:
--多进程
--多线程
--异步非阻塞模块(Twisted) scrapy框架(单线程完成并发)
异步非阻塞:
--非阻塞,不等待.
比如创建socket对某个地址进行conncet,获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作.如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可.
--异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知).
比如做爬虫中想想某个地址baidu.com发送请求,当请求执行完成之后自动执行回调函数.
同步阻塞:
--阻塞:等
--同步:按照顺序逐步执行
协程:
协程也可以称为 '微线程' ,就是开发者控制线程执行流程,控制先执行某段代码然后再切到另外执行代码来回切换.
那么协程可以帮助提高并发效率吗?
---协程自己本身无法实现并发(甚至性能会低);但是协程+IO切换性能提高.