网络编程总结
socket, socket套接字分类, 基于tcp的socket, 常见错误
复制代码
img
##为什么需要socket
复制代码
“”“
在标准的OIS模型中并没有规定说必须有socket层,也就是说不使用socket也能完成通讯,是的,的确如此!
那为什么需要socket呢?一个字 懒,程序员都是懒的!
我们发现还没有开始实现应用程序逻辑,就需要花大把时间来实现各种协议,太特么费事儿了,就有人专门把协议中一堆复杂的事情进行了封装,于是socket就诞生了!
有了socket以后,无需自己编写代码实现三次握手,四次挥手,ARP请求,打包数据等等,socket已经封装好了,只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
”“”
复制代码
##scoket套接字分类
复制代码
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于大部通讯都是网络通讯,所以大部分时候使用AF_INET)
复制代码
##python中的socket
复制代码
需明确:关于网络协议 和socket相关概念,对于所有编程语言都是一致的,区别仅仅是各编程语言的函数名称不同
1.导入socket模块
import socket
2.创建socket对象 函数定义如下
socket.socket(socket_family,socket_type,protocal=0)
#socket_family 可以是 AF_UNIX 或 AF_INET。
#socket_type 可以是 SOCK_STREAM表示TCP协议 或 SOCK_DGRAM表示UDP协议。
#protocol 一般不填,默认值为 0。
2.1获取TCP 套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
或者 后面的参数都有默认值,可以不写,默认创建的是TCP协议socket
tcpSock = socket.socket()
2.2获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
由于 socket 模块中有太多的属性。可以使用'from module import '语句。使用 'from socket import ',把 socket 模块里的所有属性都导入当前命名空间里了,这样能大幅减短代码。
例如:tcpSock = socket(AF_INET, SOCK_STREAM)
要明确一点:无论是客户端服务器端都使用的都是socket对象
“”“
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
”“”
复制代码
##基于TCP的socket
前言:已经明确socket是别人封装好的接口,使用接口就变的简单了
按照通讯流程编写代码即可
##TCP通讯流程
img
##socket运行示例
复制代码
--------------------#socket服务器-------------------------------
import socket
作为服务器必明确自己的ip和端口号 并且不应该变化
参数1指定 socket类型AF_INET 表示网络类型
参数2指定的传输协议为 SOCK_STREAM表示TCP协议 SOCK_DGRAM UDP协议
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 1.买电话机
默认就是网络类型 TCP协议
server = socket.socket()
127.0.0.1这叫回送地址 表示当前电脑本身
ip一定本机ip 本机可能会有多个IP (无线|有限)
注意:需要参数是一个元组 端口就是普通整数
server.bind(("127.0.0.1",1688)) # 2.插入手机卡
无论是服务器端还是客户端 都是socket 类型
开始监听1688端口 盯着这个端口看以后没有数据过来
server.listen() # 3.手机开始待机
接收链接请求
第一个是表示客户端的socket 第二个客户端的地址信息
client,addr = server.accept() # 4.接电话
print(type(client))
print(addr)
5.收发数据
data = client.recv(1024)
print(data)
client.send("copy!".encode("utf-8"))
server.close() # 关机
----------------------#socket客户端-----------------------------------
import socket
买个电话
client = socket.socket()
作为客户端 ip和端口可以变化 所有系统会自动分配随机端给客户端
client.connect(("127.0.0.1",1688))
开始通话
发送数据 注意发送的内容只能是二进制 bytes
client.send("hello".encode("utf-8"))
接收数据 单位为字节
data = client.recv(1024)
print(data)
client.close()----------------------------------------------------------------------#总结注意TCP中必须先启动服务器再启动客户端,否则客户端由于无法链接服务器,直接报错!
复制代码
##改版以后的socket
复制代码
注解:一个最基本的TCP通讯,但是建立是为了传输数据,二传输数据很多时候并不是一次性就传输完成了,需要多次收发过程,所以需要给代码加上循环
————————————socket服务器.py————————————————————
import socket
server = socket.socket()
server.bind(("127.0.0.1",8989))
server.listen()
while True:
client_socket,client_addr = server.accept()
buffer_size = 1024 #缓冲区 就是一个临时的容器
# 缓冲区大小 不能随便写 太大会导致内存溢出 太小效率低 在内存能够承受的情况下 大一些
while True:
try:
data = client_socket.recv(1024)
# 在linux中 对方如果强行下线了 服务器不会抛出异常 只会收到空消息
# 得加上判断 如果为空则意味着 对方下线了 应该关闭链接 跳出循环
# windows上正常关闭 也会收到空消息 if 必须要加
if not data:
client_socket.close()
break
print(data.decode("utf-8")) # 解码时必须保证双方同意编码方式
# 转为大写在发回去
client_socket.send(data.upper())
except ConnectionResetError as e:
print("%s %s" % (client_addr[0],client_addr[1]),e)
# 如果对方下线了 那服务器也应该关闭对应的客户端对象
client_socket.close()
break
通常服务器不会关闭
server.close()
————————————socket客户端.py————————————————————
import socket
client = socket.socket()
指定服务器的端口和ip 客户端的端口系统自动分配的
client.connect(("127.0.0.1",8989)) #
添加循环 用来重复收发数据
while True:
# 收发的顺序 必须很对面相反 否则卡死了
msg = input("输入内容:")
if not msg:continue
client.send(msg.encode("utf-8"))
print("sended!")
data = client.recv(1024)
print(data.decode("utf-8"))
client.close()
复制代码
##socket常见错误
复制代码
1、端口占用
在调试过程中,可能会遇见以下错误:
问题发生原因:
1》.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!
2》.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!
3》.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接
解决的方案:
第1种原因,很简单关闭之前运行的服务器即可
第2,3中原因导致的问题,有两种解决方案:
1》.设置服务器重用端口
加入一条socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,必须在bind前加
phone.bind(('127.0.0.1',8081))
2》.通过调整linux内核参数解决(了解)
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
示例
import socket
server = socket.socket()
server.bind(("127.0.0.1",9891))
server.listen()
server.accept()
server.close()
如果已经开启了服务器 再次运行将会抛出 端口占用异常 把之前开启的服务器关掉即可
"""
有些情况 明明已经关闭了进程 但是 还是端口占用
可能是进程正在后台运行 可以通过端口查出进程 进行关闭
windows下
netstat -ano | findstr 9898
tasklist | findstr 进程id 拿到进程名称
taskkill /f /t /im 进程名称
大招: 重启电脑
"""
复制代码
## 半链接数, 粘包问题,操作系统缓存理解, 粘包解决方案,strutc模块, 自定义报头
复制代码
2.强行关闭链接
当客服端与服务器链接成功后,如果一方没有执行close,而是直接强行终止程序(或是遇到异常被迫终止),都会导致另一方发送问题,所以要分别在服务端和客户端针对发送和接收代码添加try exception进行异常判断
错误如下
img
复制代码
1.socket
套接字,本质上是一个模块,里面封装了一些网络通讯协议
是处于传输层和应用层之间的一个抽象层,实际在OSI中并不存在
也就是没有socket也能能够通讯 ,但是这样一来 我们必须完全按照OSI规定的各种协议来编码
这是一个重复,复杂的过程,为了提高开发效率,就出现了socket模块,专门帮我们封装了传输层以下的一堆协议
有socket模块之后 当我们需要编写网络通讯的程序时,就不需要在关心具体的协议细节,直接使用socket提供的功能接口即可
“”“
门面设计模式,隐藏复杂的内部细节,仅提供最简单的使用接口 本质就是封装
”“”
2.TCP通讯
网络通讯一定分为两端,服务器和客户端
服务器:
1.创建socket对象 server = socket.socket()
2.绑定一个固定的ip和端口 server.bind ip必须是本机ip 端口1024-65535 不要使用常见的端口 web:80 / 8080 mysql 3306 ssh:22 ftp:21
3.开始监听客户端的到来 server.listen
4.接收客户端的链接请求 server.accept # 阻塞直到客户链接到来 没有新连接则不可能执行该函数
5.收发数据 需要循环
recv 收 收多少字节数
send 发 只能发二进制数据
客户端:
1.创建socket对象 client = socket.socket()
2.链接服务器 client.connect((ip,port))
3.收发数据 通常需要循环
send 发 只能发二进制数据
recv 收 收多少字节数
4.断开链接 client.close()
3.常见的异常
1.一方异常下线,另一方还要收发数据 ,ConnectionResetError
在send recv的地方 加上try 客户端也需要异常处理
2.端口占用异常
重复运行服务器
之前的进程没有正确关闭
关闭和打开端口 都是操作系统负责 在一些极端情况下 可能应用程序已经正确接收并且通知了操作系统要关闭端口
但是操作系统 没及时处理
2.1 更换端口
2.2 查后台进程 杀掉它
2.3 重启服务器电脑
4循环通讯
复制代码
##半连接数
复制代码
“”“
三次握手没有完成 称之为半连接
原因1 恶意客户端没有返回第三次握手信息
原因2 服务器没空及时处理你的请求
socket中 listen(半连接最大数量)
”“”
理解
import socket
server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen(5)
backlog # 了解4
最大半连接数 本质就是一个数组 未完成链接的socket 就会被加入到数组中 ,
每一次执行accept 就会挑一个来完成三次握手 ,如果达到最大限制 额外的客户端将直接被拒绝
我们可以调整内核参数来修改 最大等待时长 如果超时 客户还是没有回复第三次握手信息 就直接删除
复制代码
img
##粘包问题(TCP)和操作系统缓存理解
复制代码
TCP流式协议, 指的是数据与数据之间没有明确的分界线,导致不能正确读取数据, 就像水 一杯水和一杯牛奶倒在一起了!
要理解粘包问题,需要先了解TCP协议传输数据时的具体流程
“”“
应用程序无法直接操作硬件,应用程序想要发送数据则必须将数据交给操作系统,而操作系统需要需要同时为所有应用程序提供数据传输服务,也就意味着,操作系统不可能立马就能将应用程序的数据发送出去,就需要为应用程序提供一个缓冲区,用于临时存放数据,,具体流程如下:
发送方:
当应用程序调用send函数时,应用程序会将数据从应用程序拷贝到操作系统缓存,再由操作系统从缓冲区读取数据并发送出去
接收方:
对方计算机收到数据也是操作系统先收到,至于应用程序何时处理这些数据,操作系统并不清楚,所以同样需要将数据先存储到操作系统的缓冲区中,当应用程序调用recv时,实际上是从操作系统缓冲区中将数据拷贝到应用程序的过程
上述过程对于TCP与UDP都是相同的不同之处在于:
UDP:
UDP在收发数据时是基于数据包的,即一个包一个包的发送,包与包之间有着明确的分界,到达对方操作系统缓冲区后也是一个一个独立的数据包,接收方从操作系统缓冲区中将数据包拷贝到应用程序
这种方式存在的问题:
1.发送方发送的数据长度每个操作系统会有不同的限制,数据超过限制则无法发送
2.接收方接收数据时如果应用程序的提供的缓存容量小于数据包的长度将造成数据丢失,而缓冲区大小不可能无限大
TCP:
当我们需要传输较大的数据,或需要保证数据完整性时,最简单的方式就是使用TCP协议了
与UDP不同的是,TCP增加了一套校验规则来保证数据的完整性,会将超过TCP包最大长度的数据拆分为多个TCP包 并在传输数据时为每一个TCP数据包指定一个顺序号,接收方在收到TCP数据包后按照顺序将数据包进行重组,重组后的数据全都是二进制数据,且每次收到的二进制数据之间没有明显的分界
”“”
重点来了
“”“
粘包 仅发生在TCP协议中
- 发送端 发送的数据量小 并且间隔短 会粘
- 接收端 一次性读取了两次数据的内容 会粘
- 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起
无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
解决方案就是 提前告知接收方 数据的长度
”“”
复制代码
##粘包解决方案
复制代码
“”“
先发长度给对方 再发真实数据
发送端
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
发送端和接收端必须都处理粘包 才算真正的解决了
案例: 远程CMD程序 “”“需求: 基于TCP开发一款远程CMD程序 客户端连接服务器后 可以向服务器发送命令 服务器收到命令后执行 无论执行成功还是失败 将执行结果返回给客户端 执行系统指令使用subprocess模块完成”“”
”“”
案例 远程CMD程序
————————————服务器.py————————————————————
import socket
import subprocess
import struct
from 二_CMD程序 import smallTool
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
back
while True:
# socket,addr一个元组 客户端的ip和port
client,addr = server.accept()
print("客户端链接成功!")
# 循环收发数据
while True:
try:
cmd = smallTool.recv_data(client)
if not cmd:
break
print(cmd)
p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 不要先读err错误信息 它会卡主 原因不详 linux不会有问题 tasklist netstat - ano啥的
data = p.stdout.read()
err_data = p.stderr.read()
len_size = len(data) + len(err_data)
print("服务器返回了: %s " % len_size)
len_bytes = struct.pack("q",len_size)
# 在发送真实数据前先发送 长度
client.send(len_bytes)
# 返回的结果刚好就是二进制
# 发送真实数据
client.send(data + err_data)
except ConnectionResetError as e:
print("客户端了挂了!",e)
break
client.close()
server.close()
————————————客户端.py————————————————————
"""
客户端输入指令
服务器接收指令并执行 最后返回执行结果
"""
import socket
from 二_CMD程序 import smallTool
import struct
client = socket.socket()
try:
client.connect(("127.0.0.1",1688))
print("链接成功!")
while True:
msg = input("请输入要执行指令:").strip()
if msg == "q": break
if not msg: continue
# 发送指令
# 先发长度
len_bytes = struct.pack("q",len(msg.encode("utf-8")))
client.send(len_bytes)
# 在发指令
client.send(msg.encode("utf-8"))
data = smallTool.recv_data(client)
print(data.decode("GBK"))
client.close()
except ConnectionRefusedError as e:
print("链接服务器失败了!",e)
except ConnectionResetError as e:
print("服务器挂了!", e)
client.close()
————————————smalltool.py——————————————————
“”“
说明:该模块作用于服务器和客户端读长度,读数据内容的公共代码,抽调出来的模块
返回的是:data 为二进制数据
”“”
import struct
def recv_data(client):
# 接收长度数据 固定位8个字节
len_bytes = client.recv(8)
# 转换为整型
len_size = struct.unpack("q", len_bytes)[0]
print("服务器返回了%s长度的数据" % len_size)
# 再接收真实数据
# 问题 如果数据量太大 则不能 一次收完 必须循环一次收一部分
# 缓冲区大小
buffer_size = 1024
# 已接受大小
recv_size = 0
# 最终的数据
data = b""
while True:
# 如果剩余数据长度 大于缓存区大小 则缓冲区有多大就读多大
if len_size - recv_size >= buffer_size:
temp = client.recv(buffer_size)
else:
# 剩余数据长度 小于缓冲区大小 剩多少就收多少
temp = client.recv(len_size - recv_size)
recv_size += len(temp)
data += temp
# 当已接受大小等于数据总大小则跳出循环
if recv_size == len_size:
break
# print(data.decode("gbk")) # windows执行指令返回的结果默认为GBK
return data
复制代码
## struct模块
复制代码
struct 是一个内置模块 作用是 将python中的类型 转为字节数据 并且长度固定
import struct
将整数 转为固定长度的二进制
data = struct.pack("q",100000000000000000)
print(data)
print(len(data))
将二进制转换回整型 返回一个元组
res = struct.unpack("q",data)[0]
print(res)
复制代码
##引出自定义报头之前文件下载案例
复制代码
-----------------------------服务器.py-------------------------------------------------
"""
理解注释
粘包问题解决了,但是存在一个问题
当文件过大时,不能直接发给send发给操作系统
"""
import socket
import os
import struct
"""
发文件大小-----发文件长度-----发数据
"""
server =socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
while True:
client,addr = server.accept()
print("客户端连接成功")
f=None
try:
#先发送文件大小
path = r"D:\python8期\课堂内容\day32\视频\2.半链接数.mp4"
file_size=os.path.getsize(path)
print(file_size)
len_bytes=struct.pack("q",file_size)
#发送长度
client.send(len_bytes)
#循环发送文件数据,每次发送2048
f=open(path,'rb')
while True:
temp = f.read(2048)
if not temp:
break
client.send(temp)
print("文件发送完毕")
except Exception as e:
print("出问题了",e)
finally:
if f:
f.close()
client.close()
-----------------------------客户端.py-------------------------------------------------
import socket
import struct
client=socket.socket()
try:
client.connect(("127.0.0.1",1688))
print("连接成功")
#第一步 先收长度
file_size=struct.unpack("q",client.recv(8))[0]
#第二步 再收文件内容
#已收大小
recv_size=0
#缓冲区
buffer_size=2048
f=open("new.mp4",'wb')
while True:
if file_size - recv_size >= buffer_size:
temp=client.recv(buffer_size)
else:
temp=client.recv(file_size - recv_size)
f.write(temp)
recv_size += len(temp)
print("已接收大小:%s%%"%(recv_size/file_size *100))
if recv_size == file_size:
break
f.close()
except ConnectionRefusedError as e:
print("服务器连接失败")
client.close()
复制代码
##自定义报头
复制代码
“”“
当需要在传输数据时 传呼一些额外参数时就需要自定义报头
报头本质是一个json 数据 ,例如我们要实现文件上传下载,不光要传输文件数据,还需要传输文件名字,md5值等等,如何能实现呢?
具体过程如下:
发送端
1 发送报头长度 ,定义的报头包含所需的信息
2 发送报头数据 其中包含了文件长度 和其他任意的额外信息
3 发送文件内容
接收端
1.接收报头长度
2.接收报头信息
3.接收文件内容
”“”
案例:优化上一个下载视频文件案例
——————————————服务器.py——————————————————
import socket
import os
import struct
import json
"""
发文件大小-----发文件长度-----发数据
"""
server =socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
while True:
client,addr = server.accept()
print("客户端连接成功")
f=None
try:
path = r"D:\python8期\课堂内容\day32\视频\2.半链接数.mp4"
file_size = os.path.getsize(path)
file_info={"file_name":"广东雨神.mp4","file_size":file_size}
json_str=json.dumps(file_info).encode("utf-8")
#发送报头长度
client.send(struct.pack("q",len(json_str)))
#发送报头
client.send(json_str)
#发文件
#循环发送文件数据,每次发送2048
f=open(path,'rb')
while True:
temp = f.read(2048)
if not temp:
break
client.send(temp)
print("文件发送完毕")
except Exception as e:
print("出问题了",e)
finally:
if f:
f.close()
client.close()
——————————————客户端.py——————————————————
import socket
import struct
import json
client=socket.socket()
try:
client.connect(("127.0.0.1",1688))
print("连接成功")
#第一步 先收报头长度
head_size=struct.unpack("q",client.recv(8))[0]
#第二步 再收报头信息
head_str = client.recv(head_size).decode("utf-8")
file_info=json.loads(head_str)
print("报头数据:",file_info)
file_size=file_info.get("file_size")
file_name=file_info.get("file_name")
#已收大小
recv_size=0
#缓冲区
buffer_size=2048
f=open(file_name,'wb')
while True:
if file_size - recv_size >= buffer_size:
temp=client.recv(buffer_size)
else:
temp=client.recv(file_size - recv_size)
f.write(temp)
recv_size += len(temp)
print("已接收大小:%s%%"%(recv_size/file_size *100))
if recv_size == file_size:
break
f.close()
except ConnectionRefusedError as e:
print("服务器连接失败")
client.close()
复制代码
UDP协议, DNS服务器, 进程, 多道技术
- 半连接数
指的是没有完成的链接
1.1 客户端恶意攻击,只进行两次握手 导致服务器处于等待状态
1.2 有大量的客户端同时访问 (高并发) 服务器处理不过来
listen() 函数中可以指定最大的半连接数量 超出这个数量将会被拒绝
处于半连接状态的链接 会在超时后被丢弃 - 粘包问题 网络编程重点
粘包问题只出现在TCP协议中,因为TCP是流式协议,数据之间没有明显分隔
之所以出现粘包- 把时间间隔短的 数据量小的数据 一次性发送
- 数据已经到达 接收多了或少了 都会粘包
之所以粘包本质原因就是接收方不清楚数据长度
解决方案就是提前告诉接收方数据长度
发送方
1.发送数据长度
长度信息也会粘包 所以 一定要确保每一次发送的数据信息的长度 所占的字节是固定的
例如 8 或 4
struct 模块就可以把一个python的数据类型 转为二进制 并且字节数固定
q 转为8字节 i 转为4字节2.发送真实数据
接收方
1.先接受长度
2.再接收真实数据 自定义报头
不仅要传输数据过去 可能还要额外传输一些信息 例如文件名称 大小 md5等等........
报头就是传输数据前 先传输的一个信息 通常用json
本质就是 多传输了一个json数据
发送
1.先发json数据的长度
2.在发送json(报头)数据
3.在发送真实数 也会粘包 所以要把长度放入报头中json也可以传输二进制 但是json本质是字符串 所以必须把二进制转为字符串 接收方吧字符串转二进制 BASE64 编码
接收:
1.先收报头长度
2.收报头
3.真实数据
复制代码
##UDP协议
复制代码
#什么是UDP协议
用户数据包协议
OSI模型中 属于传输层的协议, 仅用于不要求可靠性,不要求分组顺序且数据量较小的简单传输,力求快
如何使用
通讯流程类似对讲机 只管发送不管对方是否接受到 甚至不关心对方在不在
1.买对讲机
2.固定频道
3.收发数据
1.买个对讲机
2.指定发送的频道
3.收发数据
与TCP的区别 *****
不可靠传输
不需要建立连接
不会粘包
单次数据包不能太大
代码 :
服务器端
服务器不需要监听 listen
不需要接收请求 accept
收数据 recvfrom(缓冲区大小)
发数据 sendto(数据,地址)
客户端:
不需要建立连接
收数据 recvfrom(缓冲区大小)
发数据 sendto(数据,地址)
示例1
—————————————服务器.py——————————————————————
from socket import *
创建基于UDP的scoket 必须手动指定
server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",1688))
while True:
data,addr = server.recvfrom(1024)
print("收到来自%s的消息 : %s" % (addr,data))
server.sendto(data.upper(),addr)
server.close()
—————————————客户端.py——————————————————————
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
UDP 不需要建立链接
发送数据时 要指定接受方地址
client.sendto("hello".encode("utf-8"),("127.0.0.1",1688))
data,addr = client.recvfrom(1024)
print("收到来自%s的消息 : %s" % (addr,data))
client.close()
示例2:在接受的时候 缓冲区大小必须大于数据的长度
—————————————服务器.py——————————————————————
from socket import *
创建基于UDP的scoket 必须手动指定
server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",1688))
在接受的时候 缓冲区大小必须大于数据的长度
data,addr = server.recvfrom(1)
print(data)
—————————————客户端.py——————————————————————
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
UDP 不需要建立链接
发送数据时 要指定接受方地址
client.sendto("hello".encode("utf-8"),("127.0.0.1",1688))
client.sendto("world".encode("utf-8"),("127.0.0.1",1688))
client.close()
示例3:UDP发不了大数据
—————————————服务器.py——————————————————————
from socket import *
server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",1688))
while True:
data,addr = server.recvfrom(1024*1024)
print(data)
—————————————客户端.py——————————————————————
"""UDP单次数据包不能太大"""
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
data = b""
for i in range(1024 * 60):
data += b"1"
client.sendto(data,("127.0.0.1",1688))
示例4:多个客户端同时处理
—————————————服务器.py——————————————————————
from socket import *
import time
server = socket(AF_INET,SOCK_DGRAM)
server.bind(("192.168.13.93",1688))
UDP 可以处理多个客户端 但是并不是真正的同时处理 而是按顺序处理 速度非常快 感觉像是同时处理 叫并发
并行 真正同时处理 想要真正的同时运行 必须由多个执行单位
模拟一下聊天室
客户端发消息来 服务器就把客户端地址存起来
再有消息来 服务器循环给每一个人都发一份
客户端列表
clients = {}
while True:
try:
data,addr = server.recvfrom(1472)
if addr[0] not in clients:
clients[addr[0]] = addr
try:
print("%s说:%s" % (addr[0],data.decode("utf-8")))
except:
print("编码有问题啊 .....")
# 遍历所有客户端 转发消息给他们
for k,v in clients.items():
server.sendto(data,v)
except Exception as e:
# print(e)
pass
# 如果要限制发消息不能太频繁 思路如下:
# 收到数据后 把你的地址 和发送数据的时间记录下来
# 遍历出所有数据 1888888888 188888888.1 10
# 以当前时间为起始 100 取出消息的时间 如果时间范围为98-100
—————————————客户端.py——————————————————————
“”“
多个客户端问题:可以在一个客户端.py模块执行多个窗口来测试
pycharm默认只能开一个问题:界面code下面,有个start标记----选择里面的Edit Configurations---------然后在弹出的窗口右上方打勾---就可以执行多个客户端
”“”
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
while True:
# msg = input("msg:").strip()
client.sendto("client2".encode("utf-8"),("127.0.0.1",1688))
data,addr = client.recvfrom(1472)
print(data)
复制代码
##DNS服务器
复制代码
DNS Domain Name System 全称 :域名解析服务器
DNS 是干什么的 :
将域名转换为IP地址 要连接服务器 一定的知道IP 为什么需要DNS
单独ip不方便记忆 所以我们吧ip和一个域名绑定到一起 域名一串有规律的字符串 www.baidu.com
DNS 是CS结构的server端
DNS 使用的是UDP 协议 因为 传输的数据小 但对速度要求高 一个DNS要服务很多计算机
http:// news.cctv.com /2019/05/29/ARTIXRqlqFBp59eECweiXTUU190529.shtml
协议名称 域名 文件路径
DNS 本质就是一个数据库 里面就存储 域名和ip的对应关系
news.cctv.com
.com 顶级域名
cctv 二级域名
news 三级域名
复制代码
##进程
复制代码
进程是什么?
正在运行的程序
进程来自于操作系统 没有操作系统就没有进程
操作系统是什么?
也是一套软件 ,
主要功能
1.控制硬件,隐藏丑陋复杂的硬件细节
2.将无序的硬件竞争变得有序
早些年 计算机同一时间只能运行一个程序,这时候是不可能并发的
要并发 当然需要不同的应用程序 ,如何使多个应用程序同时被运行
这就需要多道技术来支持
复制代码
##多道技术
复制代码
多道技术:为了提高计算机的利用率
1.空间复用 把内存分割为不同区域 ,每个区域装入不同的程序
2.时间复用 当一个程序执行IO操作时,切换到另一个程序来执行
光切换还不行 必须在切换前保存当前的状态 以便与恢复执行
当内存中有多个程序时,必须保证数据是安全的
每个进程之间的内存区域是相互隔离的,而且是物理层面的隔离
有了多道技术
计算机就可以在同一时间处理多个任务(看着像 由于计算远比人块 所以感觉同时执行了)
注意:并不是多道就一定提高了效率
如果多个任务都是纯计算 那么切换反而降低了效率
遇到IO操作才应该切换 这才能提高效率