网络编程02--并发理论


昨日复习

  • socket基本使用
    内置的模块
import socket
s = socket.socket()  # 创建网络传输,默认TCP协议
s.bind((IP+port))  # 绑定IP+端口
s.listen(5)  # 半连接池
sock, addr = s.accept  # 监听,三次握手的lisent态
sock.recv(1024)  # 接收内容
sock.send(发送的内容)  # 发送内容

c = socket.socket()
c.connect((IP+port))
  • 通信循环
    将recv和send用while循环
  • 连接循环
    监听代码使用while循环起来
  • 代码健壮性校验
    1. 异常捕获
    2. 端口冲突
    3. 系统问题
      在客户端判断用户是否输入为空
      在服务端判断接受是否为空
  • TCP粘包特性
    1. 双向同道中数据量较大接收有残余
    2. TCP会将数据量较小并且时间较短的数据一次性打包发送(流式协议)
  • 如何解决黏包问题
    • 报头:固定长度,内部包含诸多信息
      能够固定打包的模块struct
      客户端

      1. 打包固定长度的字典的报头并发送
      2. 发送字典数据
      3. 发送真实数据

      服务端

      1. 先接收固定长度的字典的报头
      2. 解析出字典的长度
      3. 接收字典数据,并解析出真实数据相关的内容
      4. 接收真实的数据
  • 大文件如何传输
    1. 如何校验文件数据的一致性
      使用hashlib模块对文件内容做加密处理,之后发送成功之后取出随机字符串做对比
      切片解密对比
      读取文件部分内容加密(划分区域)
    2. 数据储存
      for循环一行行发送,一行行储存

昨日代码务必掌握

昨日需求剖析

  • 将客户端与服务端全部使用软件开发目录规范编写
  • 将各个功能写成函数

1、UDP代码编写

# 服务端
import socket


amg = socket.socket(type = socket.SOCK_DGRAM)  # UDP协议
amg.bind(('127.0.0.1', 9000))  # 绑定IP地址
msg, addr = amg.rescvfrom(1024)
amg.sendto(发送的内容,addr发送的地址)
amg.close()

# 客户端
import socket
ip_port = ('127.0.0.1', 9000)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(内容,ip_port)
back, addr = udp_sk.recvfrom(1024)
print(back.decode('utf-8'),addr)


# 建议QQ程序练习

2、操作系统的发展史

学习并发编程其实就是现在学习操作系统的发展史(底层逻辑)

  1. 穿孔卡片时代:
    • CPU的利用率极低(所有的输入只能通过穿孔卡片操作,每个计算每次只能一个人操作专属的代码,没办法执行其他代码)
  2. 联机批处理系统
    • 将多个程序员的程序一次性录入磁带中,之后交由输入机输入并交由CPU执行
  3. 脱机批处理系统
    • 现代计算机的雏形(远程输入,高速磁带,主机)

3、多道程序系统(多道技术)

前提:针对单核CPU(同一时间计算机只能高一件事)
单道技术(串行)
多道技术(单核)

  • 切换+保存状态

    CPU的工作机制(单核)
  1. 当某个程序进入IO状态,才做系统会自动剥夺改程序的CPU执行权限
  2. 当某个程序长时间占用CPU时,那么操作系统也会剥夺改程序的CPU的执行权限

并行与并发

  • 并行:多个程序同时执行(需多个CPU)
  • 并发:多个程序看起来像同时运行即可

简单小问题:

  • 单核CPU能都实现并行
    不能,但可以实现并发
  • 12306可以同时支持上亿的用户买票,问是并发还是并行
    并发(高并发)

星轨:微博可以同时支持多个星轨

4、进程理论(重要重点关注)

  1. 进程与程序的区别

    •   程序:及代码,在类似py文件内的代码(死的)
      
    •   进程:运行起来的程序(代码)(活的)
      
      •   看成内存中独立的空间(运行的程序)
        
  2. 单核情况下的进程调度

    • 进程调度算法演变
      1. FCFS:先来先服务
        缺点:对短作业不友好
      2. 短作业优先调度算法
        缺点:对长作业不友好
      3. 时间片轮转发+多级反馈列队(现在计算机正在使用的算法)
        先分配给担心的多个进程相同的时间片
        之后根据进程消耗的时间片分类别
  3. 进程三状态图

  • 就绪态
  • 运行态
  • 阻塞态
  • 程序要想进入运行态,必须先经过就绪态
  1. 同步&异步(用于面熟任务的提交方式)
  • 同步:
    • 提交任务之后原地等待任务的返回结构,期间不做任何事
  • 异步:
    • 体检任务有之后不原地等待任务的返回结果,直接去做其他事情,结构由反馈机制自动提醒
  1. 阻塞&非阻塞(描述任务的执行状态)
  • 阻塞:阻塞态
  • 非阻塞:就绪态,运行态

5、创建进程

# 代码层面创建进程
# 高端模块
from mulirocessing import Process
import time


def test(name):
	print('%s正在运行'%name)
	time.sleep(3)
	print('%s已经结束'%name)
	
if __name__ = '__main__':
	p = Process(target=test, args=('kk',))  # s生成一个进程对象
	p.start()  # 告诉操作系统开设一个新进程,并运行。异步提交

'''
在win系统中开设进程类似于导入模块
	从上往下再次执行代码
	一定要在__main__判断语句内执行开设进程的代码块
		if __name__ = '__main__':
			print(123)
在linux系统中直接将代码完整的复制一份执行
	不需要__main__判断语句内执行
'''
# 类创建进程
class MyProcess(Process):
	def __init__(self, name):
		super().__init__()
		self.name = name
	def run(self):
		print('%s正在运行'%name)
		time.sleep(3)
		print('%s已经结束'%name)
if __name__ == '__main__':
	p = MyProcess('kk')
	p.start()


6、进程的join方法

from multiprocessing import Process
import time


def test(name, n):
	print('%s is running' % name)
	time.sleep(n)
	print('%s is over' % name)


if __name__ == '__main__':
	p_list = []
	start_time = time.time()
	for i in range(1, 4):
		p = Process(target=test, args=(i, i))
		p.start()
		p_list.append(p)
		# p.join()  # 串行  9s+
	for p in p_list:
		p.join()
	print(time.time() - start_time)
"""
	p = Process(target=test, args=('jason',))
	p1 = Process(target=test, args=('kevin',))
	p2 = Process(target=test, args=('oscar',))
	p.start()
	p1.start()
	p2.start()
"""
	print('主进程')

7、进程间默认无法交互

# 进程间数据是相互隔离的
from multiprocessing import Process

money = 100


def test():
    global money
    money = 999


if __name__ == '__main__':
    p = Process(target=test)
    p.start()
    # 先确保子进程运行完毕了 再打印
    p.join()
    print(money)

8、对象方法

from mulirocessing import Process
import time
import os


def test(name):
	print('%s正在运行'%name)
	time.sleep(3)
	print('%s已经结束'%name)
	
if __name__ = '__main__':
	p = Process(target=test, args=('kk',))  # s生成一个进程对象
	p.start()  # 告诉操作系统开设一个新进程,并运行。异步提交
	p.join()  # 等待子进程运行结束在运行主进程
	p.terminate()  # 立刻结束子进程
	print(p.is_alive())  # 查看进程是否存在,返回布尔值
	print(current_process().pid)  # 查看当前进程号
	print(os.getpid())  # 查看当前进程号
	print(os.getppid())  # 查看父进程号
	
"""
1.current_process查看进程号
2.os.getpid() 查看进程号  os.getppid() 查看父进程进程号
3.进程的名字,p.name直接默认就有,也可以在实例化进程对象的时候通过关键字形式传入name=''
3.p.terminate()  杀死子进程 
4.p.is_alive()  判断进程是否存活	3,4结合看不出结果,因为操作系统需要反应时间。主进程睡0.1即可看出效果
"""

扩展

时间服务器的实现原理

  • 1、内部电容小电池
    • 专门为时间模块供电
  • 2、远程时间同步
    • 联网之后会自动更新时间

linux多台服务器定时任务

  • 随机抽取一台服务器的时间为标准,到一定时间所有的机器执行定时脚本

相关