1 网络基础相关的知识
- 架构
- C / S架构 : client客户端 和 server服务器端
优势 : 能充分发挥PC机的性能
- B / S架构 : browser浏览器 和 server服务器 隶属于C/S架构
B / S架构 统一了应用的接口.
- 通信的事:
- 同一台电脑上两个py程序通信 : 打开一个文件
- 两个电脑如何通信 : 连一个网线
- 多个电脑通信 :
ex : 电脑1(源)要找电脑2(目标)
电脑1首先发送一个请求帧,期中包含(我的ip是192.168.1.1,我的mac地址是xxxxxxxx,我要找ip地址为192.168.1.2的主机),将此请求发送给交换机.
交换机要广播这条消息给其他所有的主机
目标主机接收到消息后,对比发现自己就是被找的主机,回复给交换机信息(我的ip地址是192.168.1.2,我的mac地址是yyyyyyyyy,请回复给ip地址为192.168.1.1,mac地址为xxxxxxx的主机)
交换机单播形式返回给源主机
知识点 :
1 mac地址 : 是一个物理地址,全球唯一, 类似于身份证
2 ip地址: 是一个四位点分十进制,它标识了计算机在网络中的位置.类似于 学号
3 交换机的通信方式:
广播 : 吼一嗓子
单播 : 一对一
组播 : 一对多
4 arp协议 : 通过目标ip地址获取目标mac地址的一个协议.
5 端口 : 操作系统为本机上每一个运行的程序都随机分配一个端口,其他电脑上的程序可以通过端口获取到这个程序
ip地址 + 端口 能唯一找到某台电脑上的某一个服务程序
6 路由器 : 连接不同网段 , 路由
7 网关 : 类似于一个局域网的出口和入口
8 网段 : 一个局域网内的ip地址范围
9 子网掩码 : 子网掩码 & ip地址 得到网段
10 osi 五层模型:
应用层 : http,https,ftp
传输层 : tcp / udp 四层交换机 四层路由器
网络层 : ip协议 路由器 三层交换机
数据链路层 : arp协议 以太网交换机 网卡 网桥
物理层 : 传输电信号 集线器 网线 光纤
SOCKET层
AF_INET:基于网络类型的套接字。
TCP:安全可靠的连接。
UDP:速度快,不可靠。
服务端的代码
import socket
sk=socket.socket()#不传参数,默认使用基于网络类型的套接字,协议:TCP
sk.bind((‘192.168.12.104’,8080)) #端口的范围是0-65535,但是0-1023不要使用,为系统默认使用
sk.listen()#同时能接收的连接
conn,addr = sk.accept()#等待接收客户端的连接 ,阻塞等待
print('addr:' , conn)
print('addr:' ,addr)
time.sleep(20)
conn.close()
sk.close()
客户端的代码
import socket
import time
sk = socket。socket()
sk。connect(‘192.168.12.104’,8080)
time.sleep(20)
第2次通信
服务端的代码
import socket
sk=socket.socket()#我买一个新手机
sk.bind((‘192.168.12.104’,8080)) #我买一个手机卡
sk.listen()#开机
conn,addr = sk.accept()#等待朋友给我打电话
msg=conn.recv(2048) #接收数据,接收10个字节
print(msg.decode('utf-8'))
conn.close()#挂断电话
sk.close()#关机
客户端的代码
import socket
import time
sk = socket。socket()
sk。connect(‘192.168.12.104’,8080)#连接
sk。send(b“message”)#
sk。send(“中文”.encode("utf-8"))
time.sleep(20)
第二节课程
UDP协议
服务器端
import socket
sk=socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8090))
#进行收发
while 1:
msg_r,addr=sk.recvfrom(1024) #这里为什么用两个参数,因为如果是一个参数的话,这个参数里面包含ip端口号等,是元组 形式的。
print(msg_r.decode('utf-8'))
msg_s = input('>>>>')
sk.sendto(msg_s.encode('utf-8'),addr)
sk.close()
客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
name=input(“请输入你的名字:”)
while 1:
msg_s = input('>>>>')
info = name + ":" + msg_s
sk.sendto(info.encode('utf-8'),('127.0.0.1',8090))
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode('utf-8'))
sk.close()
UDP:允许一个服务器同时和多个客户端通信
自定义类继承socket类 My_UDP.py
import socket
class Mysocket(socket.socket): #继承自socket文件中的socket类,此时socket就是父类
def __init__(self,encoding="utf-8"):
self.encoding = encoding
super(Mysocket,self).__init__(type=socket.SOCK_DGRAM) #执行父类socket中的__init__方法
def my_sendto(self,msg,addr):
return self.sendto(msg.encode(self.encoding),addr) #调用父类中的sendto方法
def my_recvfrom(self,num):
msg_r,addr=self.recvfrom(num) #调用父类中的recvfrom方法
return msg_r.decode(self.encoding),addr
服务端程序
from My_UDP import Mysocket
sk = Mysocket()
sk.bind(("127.0.0.1",8080))
msg,addr = sk.my_recvfrom(1024)
print(msg)
sk.close()
客户端程序
from My_UDP import Mysocket
sk = Mysocket()
sk.my_sendto("abc",('127.0.0.1',8080))
sk.close()
第三节 执行命令
粘包问题就是数据混乱问题。
发送端发送数据,接收端不知道应该如何去接收,造成的一种数据混乱的现象。
在TCP协议中,有一个合包机制(nagle算法),将多次连续发送且间隔较小的数据,进行打包成一块数据传送。
还有一个机制,拆包机制。在发送端,因为受到网卡MTU限制,会将大的超过MTU限制的数据进行拆分,拆分成多个
小的数据进行传输。当传输到目标主机的操作系统层时,会重新将多个小的数据合并成原本的数据。
只有TCP协议才会发生粘包,UDP协议不会。UDP协议本层对一次收发数据大小的限制是:
65535-ip包头(20)-UDP包头(8)=65507
站在数据链路层,因为网卡的mtu一般被限制在1500,所以对于数据链路层,一次收发数据的大小被限制在
1500-ip包头(20)-udp包头(8)=1472
得到结论:如果sendto(num)num>65507 报错
1472<num<65507 ,会在数据链路层拆包,而UDP本身就是不可靠协议,所以一旦拆包之后,造成的多个小数据包
在网络传输中,如果丢任何一个,那么此次数据传输失败。
num<1472,是比较理想的状态。
在python代码中,如何去调用操作系统的命令
import os
r = os.popen('dir') #执行操作系统的命令
print(r.read()) #打印出来的是目录
新模块:subprocess
import subprocess
r = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#subprocess.Popen(cmd,shell=True,subprocess.stdout,subprocess.stderr)
cmd: 代表系统命令
shell=True 代表这个命令是系统命令,告诉操作系统将cmd当成系统命令去执行
stdout 是执行完系统命令之后,用于保存结果的一个管道
stderr 是执行完系统命令之后,用于保存错误结果的一个管道
stdout=r.stdout.read().decode('gbk')
stderr=r.stderr.read().decode(“gbk”)
print('正确的返回结果:',stdout)
print('错误的返回结果:',stderr)
客户端代码
import socket
sk=socket。socket()
sk。connect((‘127.0.0.1’,8080))
while 1:
cmd=input(“请输入一个命令》》》”)
sk。send(cmd。encode(“utf-8”))
result = sk。recv(1028)。decode(‘gbk’)
sk。close()
服务器端代码
import socket
import subprocess
sk = sk。socket()
sk。bind((‘127.0.0.1’,8080))
sk。listen()
conn,addr = sk。accept()
while 1:
cmd = conn。recv(1024).decode(“utf-8”)
r = subprocess。Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout=r.stdout.read().decode('gbk')
stderr=r.stderr.read().decode(“gbk”)
if not stderr: #如果stderr没有内容,此时bool=false
conn.send(stdout)
else:
conn.send(stderr)
conn.close()
sk.close()
演示粘包
service
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8888))
sk.listen()
conn,addr=sk.accept()
conn.send(b'hello')
conn.send(b'world')
conn.close()
sk.close()
client
import socket
sk = socket.socket()
sk.connect_ex(('127.0.0.1,8888'))
msg1=sk.recv(1024)
msg2=sk.recv(1024)
print(msg1)
print(msg2)
sk.close()
文件的上传下载
service
import socket
import json
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()
str_dic = conn.recv(9090).decode('utf-8')
dic = json.loads(str_dic) # dic = {'opt':menu.get(num),'filename':None,'content':None}
if dic['opt'] == 'upload':
#上传
filename = ‘1’ + dic[filename]
with open(filename,'w',encoding='utf-8') as f:
f.write(dic['content'])
elif dic['opt'] == 'download':
#下载
conn.close()
sk.close()
client
import socket
import os
import json
sk = socket.socket()
s = sk.connect_ex('127.0.0.1',8080)#带返回值,如果出错,不会报错,会返回错误的编码。
menu= {‘1’:‘upload’,‘2’:‘download’}
for k,v in menu.items():
print(k,v)
num = input('请输入功能选项:')
if num == '1':
#上传功能
dic = {'opt':menu.get(num),'filename':None,'content':None}
file_path=input('请输入一个文件的绝对路径》》》') #'D:/sylar/python/day21/__init__.py
filename = os.path.basename(file_path)
with open (file_path,'r',encoding='utf-8') as f:
content = f.read()
dic['filename'] = filename
dic['content'] = content
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8'))
elif num =='2':
#下载功能
else:
print(“错误”)
sk.close()
实现小文件的传输
service端
import socket
import json
sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()
conn,addr=sk.accept()
str_dic = conn.recv(9090).decode('utf-8')
dic = json.loads(str_dic)
if dic[''opt''] == ''upload'':
filename = ''1''+dic[''filename'']
with open (filename,''w'',encoding=''utf-8'') as f:
f.write(dic[''content''])
elif dic[''opt''] ==''download'':
pass
conn.close()
sk.close()
client端
import socket
import os
import json
sk = socket.socket()
sk.connect(('127.0.0.1',8001))
menu={''1'':''upload'',''2'':''download''}
for k ,v in menu.items():
print(k,v)
num = input(''请输入功能选项:'')
if num == ''1'':
dic = {''opt'':menu.get(num),''filename'':None,''content'':None}
file_path = input(''请输入一个绝对路径'')
filename=os.path.basename(file_path)
with open(file_path,''r'',encoding=''utf-8'') as f:
content=f.read()
dic[''filename''] = filename
dic[''content''] = content
str_dic = json.dumps(dic) #把字典序列化成字符串形式的字典
sk.send(str_dic.encoding(''utf-8''))
elif num == '2':
pass
大文件传输
client端
import socket
import os
import json
sk = socket.socket()
sk.connect(('127.0.0.1',8001))
menu={''1'':''upload'',''2'':''download''}
for k ,v in menu.items():
print(k,v)
num = input(''请输入功能选项:'')
if num == ''1'':
dic = {''opt'':menu.get(num),''filename'':None,''filesize'':None}
file_path = input(''请输入一个绝对路径'') #文件的绝对路径
filename=os.path.basename(file_path) #文件的名字
filesize=os.path.getsize(file_path) #获取用户输入的路径中文件的大小
dic[''filename''] = filename
dic[''filesize''] = filesize
str_dic = json.dumps(dic) #把字典序列化成字符串形式的字典
sk.send(str_dic.encoding(''utf-8'')) #将被填充完成的字典先发送给服务器
sk.recv(1024) #为什么要有一个recv,因为上边send字典时,如果程序执行过快,可能会马上执行到下边的 send(content),此时有可能发生粘包,所以在中间加一个recv,
with open(file_path,''rb'') as f:
while filesize:
content = f.read(1024)
sk.send(content)
filesize -= len(content)
elif num == '2':
pass
service端
import socket
import json
sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()
conn,addr=sk.accept()
str_dic = conn.recv(100).decode('utf-8') # str_dic = {''opt'':menu.get(num),''filename'':None,''filesize'':None}
conn.send(b'ok')
dic = json.loads(str_dic)
if dic[''opt''] == ''upload'':
filename = ''1''+dic[''filename'']
with open (filename,''ab'') as f:
while dic['filesize']:
content = conn.recv(1024)
f.write(content)
dic['filesize'] -= len(content)
elif dic[''opt''] ==''download'':
pass
conn.close()
sk.close()
优化
client端
import socket
import os
import json
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8001))
menu={''1'':''upload'',''2'':''download''}
for k ,v in menu.items():
print(k,v)
num = input(''请输入功能选项:'')
if num == ''1'':
dic = {''opt'':menu.get(num),''filename'':None,''filesize'':None}
file_path = input(''请输入一个绝对路径'') #文件的绝对路径
filename=os.path.basename(file_path) #文件的名字
filesize=os.path.getsize(file_path) #获取用户输入的路径中文件的大小
dic[''filename''] = filename
dic[''filesize''] = filesize
str_dic = json.dumps(dic) #把字典序列化成字符串形式的字典
len_dic = len(str_dic) #获取到字典的长度,是一个int类型的数据
b_len_dic = struct.pack('i',len_dic) #将len_dic字典打包成bytes类型,bytes类型的数据可以使用+指令,用一个4bytes的数据 表示字典的长度
sk.send(b_len_dic + str_dic.encoding(''utf-8'')) #将bytes类型的长度加上bytes类型的数据内容,一起发给服务器
with open(file_path,''rb'') as f:
while filesize:
content = f.read(1024)
sk.send(content)
filesize -= len(content)
elif num == '2':
pass
service端
import socket
import json
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()
conn,addr=sk.accept()
b_len_dic = conn.recv(4) # 接收到的是bytes类型的 字典的长度
len_dic = struct.unpack('i',b_len_dic)[0] #获取到int类型字典的长度,unpack得到的是一个元组,要取下标为0的位置
str_dic = conn.recv(len_dic).decode('utf-8') #根据字典的长度去接收字典的数据
conn.send(b'ok')
dic = json.loads(str_dic)
if dic[''opt''] == ''upload'':
filename = ''1''+dic[''filename'']
with open (filename,''ab'') as f:
while dic['filesize']:
content = conn.recv(1024)
f.write(content)
dic['filesize'] -= len(content)
elif dic[''opt''] ==''download'':
pass
conn.close()
sk.close()
切换目录
service
import socket
import os
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()
def send_data(conn,path): #把对应的路径转换成字符串发送
list_dir = os.listdir(path)
str_dir = '--'.join(list_dir)
conn.send(str_dir.encode('utf-8')
abs_path = conn.recv(1024).decode('utf-8') #获取用户输入的绝对路径
current_dir = abs_path + '/' #以下在处理,都要根据当前路径去处理,无论是返回上一层,还是进入下一个文件夹
send_data(conn,current_dir) #把用户输入的路径下的所有文件及文件夹返回给客户端
while 1:
cmd =conn.recv(1024).decode('utf-8')
if cmd == ‘..’:
current_dir =current_dir.split('/')[:-1] #[:-1] 的作用是把空字符串去掉
current_dir.pop()
current_dir = '/'.join(current_dir)+‘/’ #在windows操作系统中,/和./是代表当前目录, ../是代表上一级目录
send_data(conn,current_dir)
else:
filename = cmd.split(' ')[1] #获取用户输入的文件名字
current_dir += filename + '/' #将文件名字添加到当前路径下,组成一个完整的新路径
if os.path.isdir(current_dir): #如果客户输入的文件名字是一个文件夹
send_data(conn,current_dir)
else: #如果不是文件夹
conn.send(b'不是文件夹')
conn.close()
sk.close()
client
import socket
import os
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
abs_path = input('请输入您的根目录:')
sk.send(abs_path.encode('utf-8'))
current_dir = sk.recv(1024).decode('utf-8')
print(current_dir.split('--'))
while 1:
cmd = input('请输入》》‘) #指令为cd 文件夹 或者 。。
if cmd == '..':
sk.send(cmd.encode('utf-8'))
current_dir = sk.recv(1024).decode('utf-8')
print(current_dir.split('--'))
if cmd == ‘cd':
filename = input("请输入一个文件夹名”)
sk.send((cmd +' ' + filename).encode('utf-8'))
current_dir = sk.recv(1024).decode('utf-8')
print(current_dir.split('--'))
sk.close()
SOCKETSERVER模块
主要是为了解决TCP协议中,服务器不能同时连接多个客户端的问题。
是处于socket抽象层和应用层之间的一层,比socket更贴近用户。
server端:
import socketserver
class MySocket(socketserver.BaseRequestHandler):
def handle(self): #这个方法的名字是固定的,必须是这个名字
#收发的逻辑代码,其中self.request就等效于conn
msg = self.request.send()
self.request.recv(1024).decode('utf-8')
self.request.send(msg.upper().encode('utf-8'))
pass
server = socketserver.TCPServer((‘127.0.0.1’,8080),MySocket) #固定的
server.serve_forever() #开启一个永久性的服务
client端:
import socket
sk = socket。socket()
sk.connect(('127.0.0.1',8080))
msg_s = input('>>>>')
sk.send(msg_s.encode('utf-8'))
print(sk.recv(1028).decode('utf-r'))
sk.close()
用户三次登录验证
server
import socketserver
import json
import hashlib
class Mysocket(socketserver.BaseRequestHandler):
def handle(self):
sor=b'wusir'
str_dic = self.request.recv(1024).decode('utf-8')
dic = json.loads(str_dic)
if not dic['status']: # '''没有登录成功的情况'''
with open ('info0,'r',encoding='utf-8') as f:
for info in f:
username,password = info.strip().split('|')
if username == dic["username"]:
md5_obj = hashlib.md5(sor)
md5_obj.update(dic['password'].encode('utf-8'))
pawd = md5_obj.hexdigest()
if pawd-txt == pwd:
dic['status'] = True
else:
dic['reason'] = ‘密码错误’
break
else:
'用户不存在'
dic['reason']='用户不存在'
else: # '''已经登录成功'''
server = socketserver.TCPServer(('127.0.0.1',8080),Mysocket)
server.server_forever()
client
import socket
import hashlib
import json
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
dic ={'status':False,'username':None,'password':None}
c = 3
while c:
username = input('请输入用户名')
password = input('请输入密码')
md5_obj = hashlib.md5(password.encode('utf-8'))
mf5_obj.update(username.encode('utf-8'))
pawd_m=md5_obj.hexdigest()
dic['username'] = username
dic['passwoed'] = pawd_m
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8'))
sk.close()