网络编程及并发编程总结
知识内容:
1.网络基础总结
2.socket编程
3.黏包问题
4.进程线程相关
一、网络基础总结
1 MAC地址 -> 全球唯一的,是一台主机的物理标识 2 3 IP地址 -> 一台机器在网络上的位置 4 公网IP 局域网IP 5 6 ARP协议 -> 将IP地址转化成MAC地址 7 过程:A机器找B机器,A机器将请求发出去,交换机广播这个 8 请求,告诉所有的机器A在找B,接受到消息的机器检查IP地址 9 是否是本机的,如果不是就丢弃数据包,如果是就把自己的MAC 10 地址发给交换机,交换机再把这个MAC地址发给开始请求的机器 11 12 端口号 -> 网络相关的程序才需要开端口,为的是找到 13 某台机器上唯一的一个程序 14 15 端口号: 0 - 65535 16 我们自己编程时一般使用8000以后的端口,另外 17 在同一台机器上的同一时间内只能有一个程序占用同一个端口 18 19 TCP协议/UDP协议 20 TCP:面向流的、可靠的、面向连接的、三次握手四次挥手、耗时长、字节流传输 21 22 UDP:面向数据包的、不可靠、无连接、效率高 23 24 IP协议属于网络OSI七层模型中的网络层 25 TCP/UDP协议属于传输层 26 ARP协议属于数据链路层
1 OSI模型对应的协议及设备: 2 应用层 -> DNS(域名解析) FTP(文件传输协议) HTTP(超文本传输协议,用于网页中) 3 SMTP(简单邮件传输协议) TELNET(远程登录协议) 4 传输层 -> TCP UDP 5 网络层 -> IP协议(ipv4\ipv6) 路由器 6 数据链路层 -> ARP协议(利用IP找MAC) 交换机
1 交换机发消息的方式: 2 广播 单播 组播 3 4 IP协议:规定IP地址的格式 5 IP地址:一台机器在网络内的唯一标识 6 子网掩码:IP地址与子网掩码做按位与运算。结果得到的是网段 7 网关IP 局域网内的机器访问公网就要通过网关访问
二、socket编程
1.写一个简单的python socket编程
python 编写server的步骤:
第一步是创建socket对象。调用socket构造函数。如:socket = socket.socket(family, type )
family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信
type参数代表套接字类型,可为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)
第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:socket.bind( address )
由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常
第三步是使用socket套接字的listen方法接收连接请求:socket.listen( backlog )
backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求
第四步是服务器套接字通过socket的accept方法等待客户请求一个连接:connection, address = socket.accept()
调用accept方法时,socket会进入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址
第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符 串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区 删除(以及自上次调用recv以来,客户可能发送的其它任何数据)
第六步是传输结束,服务器调用socket的close方法关闭连接
python编写client的步骤:
1. 创建一个socket以连接服务器:socket = socket.socket( family, type )
2.使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:socket.connect( (host,port) )
host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。连接成功客户就可通过套接字与服务器通信,如果连接失败会引发socket.error异常
3. 处理阶段,客户和服务器将通过send方法和recv方法通信。
4. 传输结束,客户通过调用socket的close方法关闭连接。
下面给个简单的例子:
server.py
1 import socket 2 3 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 sock.bind(('localhost', 8001)) 5 sock.listen(5) 6 7 while True: 8 connection, address = sock.accept() 9 try: 10 connection.settimeout(5) 11 buf = connection.recv(1024) 12 if buf == '1': 13 connection.send('welcome to server!') 14 else: 15 connection.send('please go out!') 16 except socket.timeout: 17 print('time out') 18 connection.close()
client.py
1 import socket 2 import time 3 4 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 sock.connect(('localhost', 8001)) 6 7 time.sleep(2) 8 sock.send('1') 9 print(sock.recv(1024)) 10 11 sock.close()
在终端运行server.py,然后运行clien.py,会在终端打印“welcome to server!"。
2.TCP和UDP
对于空消息:
- tcp协议不能发空消息
- udp协议可以
TCP和UDP:
- TCP:可靠的、面向连接的、三次握手四次挥手、耗时长、字节流传输
- UDP:不可靠、无连接、效率高
3.socketserver模块 -> 处理并发问题
三、黏包问题
1.黏包问题的成因
只有在TCP协议中才会出现黏包现象,是因为TCP协议是面向流的协议,在发送的数据传输的过程中还有缓存机制来避免数据丢失,因此在连续发送小数据时以及接受大小不符的数据时都容易出现黏包现象,本质还是因为我们在接受数据的时候不知道发送的数据的长短,黏包可能发送在接受端也可能发生在发送端
面向流的传输是指数据与数据之间没有边界
UDP不会黏包的原因:面向数据包的传输方式并且是不可靠的数据传输方式
2.解决黏包问题
在传输大量数据之前先告诉接受端要发送的数据的大小
如果想要更漂亮的解决问题,可以使用struct模块来定制协议,将发送的数据的大小固定化为4个字节
3.struct模块
pack方法和unpack方法
常用模式: "i"
pack之后的字节长度为4,unpack拿到的数据是一个元组,元组的第一个值才是pack的值
四、进程线程相关
1.多进程相关
1 # 多进程相关 2 # from multiprocessing import Process 3 # 方法: 4 # 进程对象.start() 开启一个子进程 5 # 进程对象.join() 感知一个子进程的结束 6 # 进程对象.terminate() 结束一个子进程 7 # 进程对象.is_alive() 查看某个子进程是否还在运行 8 # 属性 9 # 进程对象.name 进程名 10 # 进程对象.pid 进程号 11 # 进程对象.daemon 值为True的时候,表示新的子进程是一个守护进程 12 # 守护进程 随着主进程代码的执行结束而结束 13 # 一定在start之前设置
进程锁:
1 # from multiprocessing import Lock 2 # l = Lock() 3 # l.acquire() # 拿钥匙 4 # 会造成数据不安全的操作 5 # l.release() # 还钥匙
信号量:
1 # 信号量 Semaphore 2 from multiprocessing import Semaphore 3 # 用锁的原理实现的,内置了一个计数器 4 # 在同一时间 只能有指定数量的进程执行某一段被控制住的代码
事件与队列:
1 # 事件 2 # wait阻塞收到事件状态控制的同步组件 3 # 状态 True False is_set 4 # true -> false clear() 5 # false -> true set() 6 # wait 状态为True不阻塞 状态为False的时候阻塞 7 8 # 队列 9 # Queue 10 # put 当队列满的时候阻塞等待队列有空位置 11 # get 当队列空的时候阻塞等待队列有数据 12 # full empty 不完全准确 13 14 # JoinableQueue 15 # get task_done 16 # put join
数据共享与进程池:
1 # 数据的共享 Manager dict list 2 # 进程池 3 # cpu个数+1 4 # ret = map(func,iterable) 5 # 异步 自带close和join 6 # 所有结果的[] 7 # apply 8 # 同步的:只有当func执行完之后,才会继续向下执行其他代码 9 # ret = apply(func,args=()) 10 # 返回值就是func的return 11 # apply_async 12 # 异步的:当func被注册进入一个进程之后,程序就继续向下执行 13 # apply_async(func,args=()) 14 # 返回值 : apply_async返回的对象obj 15 # 为了用户能从中获取func的返回值obj.get() 16 # get会阻塞直到对应的func执行完毕拿到结果 17 # 使用apply_async给进程池分配任务, 18 # 需要先close后join来保持多进程和主进程代码的同步性
2.线程相关
(1)线程基本概念
1 # 进程 是最小的内存分配单位 是操作系统调度的最小单位 2 # 线程是进程中的执行单位 是cpu执行的最小单位 3 # 线城之间资源共享 4 # 线程的开启和关闭以及切换的时间开销远远小于进程 5 # 线程本身可以在同一时间使用多个cpu 6 # 线程直接被CPU执行,进程内至少含有一个线程,也可以开启多个线程 7 # 开启一个线程所需要的时间要远远小于开启一个进程 8 # 多个线程内部有自己的数据栈,数据不共享 9 # 全局变量在多个线程之间是共享的 10 11 # python 与 线程 12 # Cpython解释器在解释代码过程中容易产生数据不安全的问题 13 # GIL 全局解释器锁 锁的是线程 14 # GIL锁(即全局解释器锁) -> 在Cpython解释器下的python程序 在同一时刻 多个线程中只能有一个线程被CPU执行 15 16 # 高CPU : 计算类 --- 高CPU利用率 17 # 高IO : 爬取网页 200个网页 18 # qq聊天 send recv 19 # 处理日志文件 读文件 20 # 处理web请求 21 # 读数据库 写数据库