目录
软件开发架构
- C/S架构
eg:QQ
c:client:客户端(用户)
s:server:服务端(饭店中的工作人员)
# 客户端有一个问题是:用户想使用软件就必须下载客户端使用,当用户下载的客户端足够多的时候,电脑就有可能会受到影响,在有就是客户要不断的下载,很不方便
优势: 针对客户端可以高度定制
劣势: 需要用户去下载才能使用
- B/S架构
eg:百度
b:browser:浏览器 ------> 客户端
s:server:服务端(饭店中的工作人员)
# 浏览器的诞生:由于客户端存在的问题,随着互联网的兴起,就有人站出来开发出一款浏览器,其实,浏览器就是一个万能客户端, 或者叫超级客户端
优势: 不需要下载,就可以体验
劣势: 无法做到高度定制, 所以体验感就没那么好
'''
本质和c/s架构一样,只是我们在浏览器上当做客户端,没有专属的APP,通过浏览器页面去享受服务
'''
除服务端之外的都可以称之为客户端,客户端可能是一个网站、一个应用程序、一个python程序、一个java程序等等
服务端所具备的特征:
1. 24小时不间断对外提供服务
2. 固定的地址(服务端要有个公网IP(服务器,理解为另外一台计算机通过阿里云、腾讯云等购买))
3. 支持多用户
网络编程
什么是网络编程
基于网络编写代码,能够实现数据的远程交互
# 我们开发的软件都是基于网络传输的
1. 什么是网络?
网络又称之为信息高速公路
上网的本质:上传和下载数据的过程!!!
最开始的时候,网络是一个个的局域网,世界上的其他电脑不能通信的,后来,把世界上其他电脑互通起来,这就是现在的互联网
2. 网络的组成部分:
物理链接设备(网线,交换机,路由器等)+互联网通信协议(计算机界的英语)
3. 网络的好处
方便数据传输
4. 互联网通信协议
OSI七层协议:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
OSI七层协议
互联网的本质就是一系列的网络协议,这个协议就叫OSI协议(一系列协议),按照功能不同,分工不同,人为的分层七层,实际上还有人把它划成五层、四层。
七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
五层划分为:应用层、传输层、网络层、数据链路层、物理层。
四层划分为:应用层、传输层、网络层、网络接口层。
- 物理层
主要是基于电特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
单纯的发送高低电频是没有意义的,需要进行分组来表示不同的意思,而物理层做不了分组,由数据链路层做分层
- 数据链路层
数据链路层由来:单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思
数据链路层的功能:定义了电信号的分组方式
1.以太网协议
早期的时候,数据链路层就是来对电信号来做分组的。以前每个公司都有自己的分组方式,后来形成了统一的标准,即以太网协议ethernet,数据链路层统一使用的是以太网协议,一个以太网数据称之为数据帧
数据帧的组成部分:head(报头)+data(真实数据)
head部分:#固定18个字节
发送者/源地址 6个字节
接收者/目标地址 6个字节
数据类型 6个字节
data部分:#46字节-1500字节
2.Mac地址
head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即Mac地址
Mac地址:每块网卡出厂时都被烧制上一个世界唯一的Mac地址,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
- 网络层 #网络层中的数据我们称之为数据包
网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
使用的是IP协议,规定每一个接入互联网的计算机都必须要有一个IP地址
ip地址的版本:
1.IPV4:
最小: 0.0.0.0
最大: 255.255.255.255
我们现在使用的都是IPV4地址,但是IP地址已经快不够用了,所以IPV6出来了
2.IPV6:
""" IPV6 可以表示出地球上每一粒沙子 """
他的ip地址数量能够给地球上每一粒沙子分一个ip地址。
IP地址可以跨局域网传输前提是 必须申请公网ip地址。
"""IP地址分为公网IP和私网IP
1. 公网IP:以172.12开头的,是需要购买的(阿里云、腾讯云等平台)且需要实名认证并且备案,买过之后的IP是固定的
2. 内网IP:以192.168开头的IP地址,也叫局域网的IP
"""
网络的划分:广域网,局域网
查看IP地址方式:
1.cmd---->ipconfig
2.Windows系统、Mac系统或者Linux系统:ifconfig
其实一个IP地址可以定位一个局域网,一个mac地址定位局域网中的一台计算机
''' ip+mac地址可以定位一个局域网中得唯一一台计算机,其实根本不需要mac地址,只需要一个IP地址就能确定,是因为有一个arp协议,专门负责把IP地址解析成mac地址,也就说有了IP地址,就有了mac地址,一个IP可以定位到世界范围内独一无二的一台计算机'''
需要记忆的一个IP地址:
本地回环地址:127.0.0.1,就是还没有出去网络,找到自己的电脑
- 传输层 #传输层中的数据我们称之为数据段
传输层的由来:网络层的IP帮我们区分子网,以太网层的Mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序。
传输层功能:建立端口到端口的通信
该层使用的是TCP协议和UDP协议,也称为是端口协议,我们一般用端口来表示一个个的应用程序
TCP协议:可靠传输
UDP协议:不可靠传输
端口范围:0-65535之间的一个数字
1. 0-1024之间的端口默认是系统使用的,我们不要使用,如果使用,就有可能端口冲突
'''在一台计算机中,同一时刻,端口不能重复,因为一个端口就表示这台计算机上的一个应用程序'''
2. 1024-8000之间的端口,一般是常用软件的端口
mysql默认端口:3306
django默认端口:8000
flask默认的端口:5000
http协议:80
https协议:443
以上软件端口都是默认的,是可以修改的
3. 以后我们自己开发的软件,端口最好使用8000之后的
ip+port:定位世界范围内独一无二的一台计算机正在运行的一个应用程序
# 书写格式:127.0.0.1:80
- 应用层
应用层由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式
应用层功能:规定应用程序的数据格式
# 我们程序员其实就在应用层,使用的协议是什么?
1. 如果是客户端程序,协议想用什么就用什么,因为客户端是我们自己写的
2. 如果是浏览器,协议就不能随意用了,就要按照浏览器规定的协议(常见http协议、https协议、FTP协议)
HTTP协议以明文方式发送内容,不提供任何方式的数据加密
https则是具有安全性的ssl加密传输协议
https://www.baidu.com/v2/api/?login
协议://域名:端口/路径?参数
# http://passport.baidu.com:80/v2/api/?login
https协议: http协议 + ssl证书
'''
http和https的区别
1. https更加安全,密文传输数据
2. http不安全,明文传输数据
3. https协议监听的默认端口是:443,http协议:80
'''
TCP协议
三次握手见上图
四次挥手见上图
TCP协议称为流式协议或者是可靠协议, TCP协议数据传输的时候,是需要建立链接的,并且是双向链接
如何建立双向链接?
TCP协议的三次握手,四次挥手
1.三次握手建
彼此之间互相开通道传输数据。
1.客户端向服务端发出开通道需求并附上标识(作为此通道唯一标识)
2. 服务端允许建立通道(+此通道标识),但此时通道是单向的,服务端也想向客户端开通通信通道,发出请求给客户端建立通信通道并附上标识
3. 客户端允许建立通道附上标识
# 上述步骤完成后 就建立了双向通道
2.四次挥手断链接
1.客户端请求断开单向通道。(客户端通向服务端的通道)
2.服务端确认断开单向通道。
3.服务端请求断开单向通道(服务端通向客户端的通道)
4.客户端确认断开单向通道
"listen" 监听
"syn_sent" 请求联系
"SYN_RCVD" 同步收到
"ESTABLISHED"建立
"TIME_WAIT" 时间等待
"FIN_WAIT" 终止等待
题:TCP协议的三次握手(建立的双向链接)和四次挥手(断开链接的),四次挥手能不能缩短为三次?
四次挥手不能与3次握手建链接那样将中间2步合并,因为中间需要确认消息是否发完,考虑到数据安全。
建立链接的时候,服务器在LISTEN状态下,收到建立链接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭链接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭链接
,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次
UDP协议
# 不可靠协议,不建立双向链接
eg :远程控制软件
'''不需要建立双向通道,数据的传输的速度快,但是传输的数据可能会发生丢失'''
两者的区别:
1. UDP的速度更快
2. TCP数据更安全
案例:远程控制软件,UDP协议
# TCP协议之所以比UDP协议可靠 ,就是因为有双向通道对不对?
不对
TCP协议之所以比UDP协议可靠 原因在于TCP协议发送消息有反馈机制
基于TCP发送的消息会在本地先保存该消息 如果地方确认收到才会删除
否则在一定的时间内会频繁的多次发送直到确认或者超时为止
UDP协议发送数据之后会立刻删除内存数据 不会保留
socket编程
什么是Socket呢?我们经常把Socket翻译为套接字,Socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。
AF_UNIX:用在局域网中,基于文件类型的套接字家族(单机)
AF_INET:用在互联网,基于网络类型的套接字家族(联网)
基于TCP套接字
- 简易版本的套接字编程
########################服务端########################
import socket # python提供的socket模块
"""
# SOCK_STREAM:使用的是TCP协议
# SOCK_DGRAM:使用的是UDP协议
"""
# 1. 实例化对象
# SOCK_STREAM ====> 代表的是TCP协议
# socket.socket(socket.AF_INET, socket.SOCK_STREAM)等同于 socket.socket()
# socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
server = socket.socket() # 默认是TCP协议
# 2. 服务端绑定信息
# '0.0.0.0' =====> 代表允许任何的ip链接
# server.bind(('0.0.0.0', 8000)) # 服务端绑定一个地址
server.bind(('127.0.0.1', 8000)) # 服务端绑定一个地址
# 3. 监听消息
server.listen(5) # 监听,半链接池#数字可以自定义指可排队用户数量
print('123')
# 4. 接收消息
conn, client_addr = server.accept() # 接收, 程序启动之后,会在accept这里夯住,阻塞
'''
conn:代表的是当次链接对象
client_addr:代表的是客户端的详细信息(ip:port)
'''
# 5.设置接收消息最大字节数
data = conn.recv(1024) # 括号里面写的是接收的字节数,最多接收1024个字节
print(data) # 还是bytes类型
# 6.服务端向客户端发送消息
conn.send(data.upper())
# 7.断开客户端链接
conn.close()
# 8.关闭服务端
server.close()
########################客户端########################
import socket
# 1.实例化对象
client = socket.socket()
# 2.链接服务端
client.connect(('127.0.0.1', 8000))
# 3.向服务端发送数据
client.send('hello'.encode('utf-8')) # 发送的数据必须是二进制,bytes类型
# 4.接收服务端发送的数据
server_data = client.recv(1024) # 接收的最大字节数
print(server_data)
# 关闭客户端
client.close()
- 实现通信循环
########################服务端########################
import socket
server =socket.socket()
server.bind(('127.0.0.1',8000))
server.listen()
while True:
conn,addr=server.accept()
data=conn.recv(1024)
print(data)
conn.send(data.upper())
conn.close()
server.close()
########################客户端########################
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8000))
client.send('hello'.encode('utf-8')) # 发送的数据必须是二进制,bytes类型
server_data = client.recv(1024)
print(server_data)
# 关闭客户端
client.close()
- 实现链接循环
########################服务端########################
import socket
server = socket.socket() # 默认是TCP协议
server.bind(('127.0.0.1', 8001)) # 服务端捆绑一个地址,必须是元组形式
server.listen(3) # 监听,半链接池数字可自定义代表可排队的用户数量
while True:
conn, client_addr = server.accept() # 接收
#conn代表当次链接的对象
#client_addr代表的是客户端详细信息(ip:port)
while True:
try:"""
如果在windows客户端异常处理退出之后服务端就会直接报错,直接加上异常处理 break结束退出内循环 跳到外循环 接待另一个客人
处理方式: 异常处理 """
# 异常了一个bug,粘包现象
data = conn.recv(1024)
if len(data) == 0:#发送信息不能为空统计长度然后进行判断(len),在linux系统中,一旦data收到为空意味着一种异常行为:客户断开了链接
continue
print('客户端发来数据消息:%s'%data.decode('utf8')) # 还是bytes类型
inp=input('输入要回复的内容:').strip()
# 服务端开始给客户端也发送一个数据
conn.send(inp.encode('utf8'))
except Exception as e:#针对windows系统异常报错
print(e)
break
conn.close()#关闭当前链接对象
server.close()#关闭服务端
########################客户端########################
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001)) # 链接服务端
while True:
# 让用户输入要发送的数据
input_data = input('请输入你要发送的数据:')
# 向服务端主动发送数据
client.send(input_data.encode('utf-8')) # 发送的数据必须是二进制,bytes类型
# 接收服务端发送过来的数据
server_data = client.recv(1024) # 接收的最大字节数
print('服务端发来数据信息:%s'%recv_data.decode('utf8'))
client.close()
粘包现象
data = conn.recv(1024) # 1024代表的是一次性接收的最大字节数
# 如果客户端发送的数据超过了1024字节,那么,服务端一次性不能接收完整,导致客户端发送的数据丢失
'''
解决思路:在客户端发送数据时,一起把数据的大小也一块发给服务端,这里需要使用到内置模块struct利用pack函数把数据的大小转给固定字节个数的数据,服务端接收时候解包unpack函数解4个字节,得到这个数据的总大小,并且是字节数,就可以计算出服务端一共接受的次数。
'''
#黏包终极解决思路
# 发送方
1.先构造一个字典,里面放真实数据的信息
eg: 数据大小、名称、简介、、、
2、对字典做打包处理pack
3、发送固定长度的报头 (字典的长度)
4、发送真实的字典数据
5、发送真实的真正数据
# 接收方
1、先接收打包好固定长度的字典
2、解析出字典的真实长度unpack
3、接收字典数据
4、从字典数据中解析出各种信息
5、接收真实的数据
#############################客户端###################################
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',14334))
# 获取真实数据大小
file_size = os.path.getsize(r'D:\pythonProject\11.16\1.txt')
# 制作真实数据的字典数据
data_dict = {
'file_name': '我爱你.txt',
'file_size': file_size,
'file_desc': '很带劲',
'file_info': '我的心里话'
}
# 制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
# 获取字典报头长度 打包
data_dict_len = struct.pack('i',len(data_dict_bytes))
# 发送字典报头 (i模式报头固定长度是4),更多模式可见上图
client.send(data_dict_len)
# 发送字典
client.send(data_dict_bytes)
# 发送真实数据
with open(r'D:\pythonProject\11.16\1.txt','rb') as f:
for line in f:
client.send(line)
#############################服务端###################################
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 14334))
server.listen(5)
sock, addr = server.accept()
# 接收固定长度的报头
date_dict_head = sock.recv(4)
# 根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i',date_dict_head)[0]
# 接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
# 利用jason 模块 解码并反序列化
data_dict = json.loads(data_dict_bytes)
# 获取真实字典数据
print(data_dict)
total_size = data_dict.get('file_size')
recv_size = 0
with open(data_dict.get('file_name'),'wb') as f:
while recv_size < total_size:
data = sock.recv(1024)
f.write(data)
recv_size += len(data)
print(recv_size)
第二种获取真实数据的方法:
total_size = data_dict.get('file_size')
with open(data_dict.get('file_name'), 'wb') as f:
f.write(sock.recv(total_size))
基于UDP协议的套接字编程
#############################服务端###################################
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024) # recv接收客户端发来的消息
# data接收回来的数据
#client_addr:客户端的信息
print('===>', data, client_addr)
server.sendto(data.upper(), client_addr) #发送给客户端的数据
# server.sendto(data.upper(), ('127.0.0.1',8080))
server.close()
##############################客户端####################################
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
while True:
msg = input('>>: ').strip() # msg=''
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
进程概念
# 进程是一个比较抽象的概念,进程和线程的使用都是有操作系统来调度的
场景比喻:
'''
厨师做饭,厨师按照菜谱来做菜,那么,菜谱就是程序,而做饭的过程就是进程,在这个过程中,厨师就是线程,线程才是真正干事情的,做饭的过程中可以有多个干事的人,其实背后就是在一个进程中,可以有多个线程
'''
进程和程序的区别:
程序:就是一堆代码,没有生命周期;进程是一个过程,而不是过程中做事的人
进程:是动态,有生命周期
进程与线程的关系:先开进程,在进程里面开线程,一个进程中可以有多个线程,一个进程中至少要有一个线程
# 协程:单线程下的并发,比线程还要小,如果开协程,那么,消耗的资源更小
进程 >>> 线程 >>> 协程(程序员调度的)
补充:
CPU的工作机制:
1. 当遇到i/o阻塞的时候,会自动剥夺CPU的执行权限
2. 当CPU遇到占用时间过长的任务时候,也会剥夺执行权限
'''CPU的工作其实是来回切换的!!!!'''
i/o密集型
遇到阻塞,不会一直占用CPU资源,需要等待。比如:sleep状态
计算密集型
没有遇到阻塞,会大量的占用CPU资源,也不需要等待
- 操作系统调度算法
1、先来先服务
2、短作业优先(短时间优先)
3、时间片轮转
4、多级反馈队列
"""操作系统会自行选择哪一种算法"""
并行和并发的概念
1、并行:
在同一时间,执行多个任务,多个进程同时执行,就是多个cpu一起干活,单个cpu无法实现
2、并发:
在一段时间内,执行多个任务,多个进程看起来像同时执行,事实上不是同时执行
这个过程就是让CPU在对个程序之间利用多道技术来回切换+保存状态
'''单核CPU不能实现并行操作,一个单核CPU同一时刻只能执行一个任务,多核的CPU,4核,同一时刻,可以执行4个任务,CPU的最大利用率'''
#高并发和高并行
1、高并发: 就是程序能够支持几个亿的并发量
就是这几个亿的用户来到可以感受到自己被服务着的意思 eg: 12306
2、高并行: 写的软件可以支持几个亿的并行(当然我们还是无法实现)
支持几个亿的并行,就代表这个计算机要有几个亿的CPU
同步异步阻塞非阻塞
#同步异步同步:依赖于上一步的结果,持续等待上一次返回的结果,提交完任务之后会在原地等待任务的返回结果,在等待的过程不会做任何事。异步:不依赖于上一步的结果,提交完任务之后 不原地等待,去干别的事情,有结果自动通知#阻塞非阻塞阻塞:进程的三状态中的阻塞态。任务有IO操作时就会进入非阻塞:就绪态和运行态"""同步异步:用来描述任务的提交方式阻塞非阻塞: 用来描述任务的执行状态"""同步+阻塞: 银行排队办理业务,期间不做任何事,干等着同步+非阻塞: 银行排队办理业务,期间可以去做一些其他的事,但是人还在办理业务的队列中 异步阻塞: 在椅子上坐着,不做任何事异步非阻塞: 在椅子上面坐着,在期间喝水、吃东西、工作、玩手机、、(这个过程就是把程序运行到了极致) #效率 1. 同步阻塞 ---------------> 效率最低2. 同步非阻塞3. 异步阻塞4. 异步非阻塞----------------> 效率最高"""如果想要提高程序被执行效率就要程序一直处于就绪态和运行态"""
开启进程
在python中使用内置模块Process类,实例化这个类得到一个进程对象,开进程的目的是为了执行任务
from multiprocessing import Process
def write_file():#子进程
with open('a.txt', 'w', encoding='utf-8') as f:
f.write('helloworld')
# 在Windows系统中,开启进程必须写在__main__判断里面
if __name__ == '__main__':
p = Process(target=write_file) # 实例化得出一个对象,target=目标任务
#p = Process(target=write_file())#这里目标任务不能这么写,加括号等于执行函数的返回结果
p.start() # 调用Process类start方法通知操作系统去开进程
'''
一个py文件不一定是一个进程,python运行在解释器之上,一个解释器就是一个进程,
在python中,调用Process类,其实又拉起了一个解释器,然后去执行代码
'''
- Process类的参数
"""
参数:target:指定执行的任务名
name:修改进程名称
args:位置传参 args=(1,)元组类型,括号不能省略
kwargs:关键字传参
属性:name:查看进程名,默认进程名Process-1
pid:查看进程号
daemon:守护进程
"""
from multiprocessing import Process
def task(*a,age,gender):
print(a)
print(age)
print(gender)
if __name__ == '__main__':
p=Process(target=task,args=(1,23,4),name='nn',kwargs={'age':18,'gender':'female'})
p.daemon=True#把p进程设置为守护进程,主进程代码执行完毕 子进程立马结束,且必须写在start方法之前
p.start()
print('进程名为:%s'%p.name)
print('进程号为:%s' %p.pid)
"""output
进程名为:nn
进程号为:21100
(1, 23, 4)
18
female
"""
- Process类中的方法
"""
terminate()杀死进程
is_alive()判断进程是否存活
join()等待子进程执行完毕再执行主进程
"""
from multiprocessing import Process
def write_file(a, b, c, name, age):
print('a:', a)
import time
time.sleep(3)
if __name__ == '__main__':
p = Process(target=write_file, name='ly', args=(1, 2, 3), kwargs={'name': 'ly', 'age': 20}) # 实例化得出一个对象
p.start()
print(p.is_alive()) # 查看进程否存活
'''terminate()通知操作系统去杀死进程,操作系统去杀进程需要有一段时间,不会立马杀掉'''
p.terminate() #杀死进程,相当于在任务管理器结束进程
# import time
# time.sleep(0.1)
print(p.is_alive()
p.join()# 子进程先执行完毕,主进程再执行
print('end')
标签:socket,编程,网络,server,client,进程,data,服务端,客户端
From: https://www.cnblogs.com/Super-niu/p/17526852.html