python学习笔记 第十章 网络编程

Python学习笔记 第十章 网络编程

补充一点导入包的知识

导入一个包(文件夹)相当于执行了这个包下的__init__文件
并不相当于把这个包下的所有文件都导入进来
#想直接导入某个包下的文件
方式一:
import glance.api

方式二:
from glance import api
即from ... import xxx 后面的xxx不能带.

文件目录如下
-app
 -cmd
  	__init__.py
    versions.py
 -db
 -api
 -__init__.py

在cmd下的__init__.py需要导入cmd下的versions.py
可以通过sys.path.append(r'D:\app')
或者在app目录下的__init__.py中添加from app import cmd 使用:cmd.

想要在

# . 表示当前目录 ..表示上级目录
当你需要写一个功能,这个功能不是直接运行的,而是被别人导入之后使用的,这种情况如果你的独立功能形成文件夹
文件夹内的所有文件都需要使用相对导入

如果我们自己开发一个项目,这个项目有一些文件需要直接运行的,这情况下不适合用相对导入,适合绝对导入

面向对象的补充:

class Person(object):
  def __init__(self, name, age):
    self.name = name
    slef.age = age
  def __eq__(self, other):  #两个对象比较的时候会自动调用这个方法
    if self.name == other.name and self.age == other.age:
      return True
    else:
      return False
apple = Person('apple', 12)
banana = Person('banana', 18)
print(apple == banana) # == 符号刚好回调用apple对象对应的__eq__方法
#banana会当作参数传到__eq__方法当中
#apple == banana 得到的值就是__eq__方法的返回值

1.基础知识

不变的:mac地址 能够唯一的标识你这台机器

变化的:ip地址 能够更好的更方便的找到你的机器

局域网:连接在同一个交换机的 交换机:识别mac地址

知道一台机器的ip地址,要给这台机器发送信息,这时候交换机不认识ip地址,就先获取这台机器的mac地址,然后双方都知道对方的mac地址,用交换机就可以进行数据的传输

这个过程用到了交换机, 广播(发给所有的)、单播(单独给一台机器)、组播(给其中一部分发,另一部分不发)

arp协议:地址解析协议,通过一台机器的ip地址获取到它的mac地址,用到了交换机的广播和单播

局域网:网段 交换机 不能理解ip地址,只能理解mac地址

局域网和局域网之间的通信: 网关 路由器 可以理解ip地址

机器1 - 》 交换机1 - 〉 网关 -》路由器 -〉

ip地址:

ipv4:四位点分十进制 0.0.0.0 - 255.255.255.255

公网地址:需要我们自己申请购买的地址

内网地址:保留字段,192.168 是常见的内网地址,永远不会和公网地址冲突 还有

  • 10.0.0.0 - 10.255.255.255 公司
  • 172.16.0.0 - 172.31.255.255 学校
  • 192.168.0.0 - 192.168.255.255 学校

特殊的ip地址:127.0.0.1 本地回环地址 测试的时候使用

查看自己的ip地址 ipconfig (win) ifconfig(mac/linux)

子网掩码:也是一个ip地址,用来判断两台机器在不在一个局域网内

192.168.12.1

11000000.10101000.00000110.00000001

255.255.255.0

11111111.11111111.11111111.00000000 #192.168.12.0

ipv6: 0:0:0:0:0:0 - FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF

ip和mac是确认机器的

端口:0-65535 确认机器上的具体应用程序

ip:8080

1.1概念的整理:

  • 局域网的概念
    • 交换机
      • 在同一个局域网内的机器由交换机负责通信
      • 交换机只认识mac地址
      • 可以完成广播、组播、单播(mac地址,在网卡上)
    • 局域网之间的通信
      • 路由器
        • 提供网关ip,同一个局域网的所有机器共享一个网关
        • 我们不能访问除了本局域网之外的其他内网的ip地址
      • 子网掩码
        • 用来判断两台机器是不是在一个网段内
    • ip地址:ipv4 ipv6
    • mac地址:arp协议(通过ip找到mac)
    • 端口port:0-65535 用来确认一台机器上的应用程序

client端 客户端:多个 用户自由的打开关闭 我们自己安装

server端 服务端:一个 7*24小时工作 不需要安装

1.2网络开发架构:

  • C/S 架构 client客户端 server服务器 需要安装才能使用的
  • B/S 架构 browser浏览器 server服务器 如百度、博客园、谷歌
  • B/S和C/S有什么关系:B/S架构也是C/S架构的一种

C/S架构的好处

  • 可以离线使用
  • 功能更完善
  • 安全性更高

B/S架构的好处

  • 不安装可以使用
  • 统一PC端用户入口

1.3 分层协议

应用层 表示层 会话层

传输层 网络层 数据链路层

物理层

osi五层协议:

应用层

传输层 port 与端口打交道

网络层 ipv4 ipv6 路由器 (三层交换机--具有路由器的功能)

数据链路层 mac arp协议 交换机 网卡

物理层

(层数以数楼层来,从下往上数)

1.4tcp和udp

tcp:语音聊天、视频聊天、线下缓存高清电影、远程控制、发邮件

  • 需要先建立连接,然后才能通信

  • 占用连接、可靠(消息不回丢失)、实时性高、慢

  • 建立连接-三次握手、四次挥手

    • 三次握手

      客户端向服务器发送syn请求建立连接,服务端向客户端回复ack并发送syn请求,客户端接收到请求之后再回复ack表示建立连接

      由客户端的connect+服务端的accept

    • 四次握手

      客户端向服务端发送FIN请求,服务端向客户端回复ack,等数据完成传输之后再想客户端发送FIN请求断开连接,客户端回复ack表示断开连接

      客户端的close和服务端的close

    server.  <--------------------SYN 请求连接服务------------------client
    				 -------------------ACK----------------->    | -> SYN+ACK
      			-------------------SYN------------------->   |
        		<----------------------ACK----------------------------
    
    全双工通信;双方能相互通信
    
    send     -------------------------------------------------->  recv
    				<--------------------------------------------------
      
    挥手为什么要四次:因为还有遗留的数据没传送完,挥手的这两次必须要分开
    				---------------------FIN--------------------------->
        		<--------------------ACK----------------------------
       			<--------------------FIN----------------------------
            ---------------------ACK---------------------------->
    
    

udp:发消息- 在线播放视频、qq微信

  • 不需要建立连接、就可以通信的
  • 不占用连接、不可靠、消息可能因为网络不稳定丢失、快

2.socket

socket套接字:

#先启动server.py
import socket

#server 服务端
sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()  #监听

conn, addr = sk.accept()   #conn是连接
conn.send(b'hello')
msg = conn.recv(1024)  #只接收1024
print(msg)

conn.close()  #断开连接

sk.close()  #关闭连接
#返回b'byebye'
#再启动client.py
import socket

#client 客户端
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

msg = sk.recv(1024)
print(msg)
sk.send(b'byebye')
sk.close()

#返回b'hello'

3.tcp协议和udp协议

3.1tcp协议

server.py

import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))  #申请操作系统的资源
sk.listen()

while True: #能够和多个客户端进行握手
  conn, addr = sk.accept()  #	conn里存储的是一个客户端和一个server端的连接信息,连接需要进行三次握手
  while True:  #为了能和一个客户端讲多句话
    send_msg = input('>>>')
    conn.send(send_msg.encode('utf-8'))   #按照utf-8的格式转换为buyes类型
    #str -> encode -> bytes  b'12278' -> decoded('utf-8') -> 你 
    if send_msg.upper() == 'Q':break
    msg = conn.recv(1024)
    msg2 = msg.decode('utf-8')
    if msg.upper() == 'Q': break
    print(msg, msg2)
  conn.close()  #挥手断开连接

sk.close()  #归还操作系统的资源

client.py

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))
whike True:
  msg = sk.recv(1024) #接收到的bytes
  msg2 = msg.decode('utf-8') #将bytes按utf-8的方式进行解码,得到字符
  print(msg, msg2)
  if msg2.upper() == 'Q':break
  send_msg = input('>>>')
  sk.send(send_msg.encode('utf-8'))
  if send_msg.upper() == 'Q':break

sk.close()

操作系统:统一分配计算机的所有资源

端口号:0-65535

3.2udp协议

server.py

import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 9001))
while True:
  msg, addr = sk.recvfrom(1024)  #不需要连接
  print(msg.decode('utf-8'))
  msg = input('>>>')
  sk.sendto(msg.encode('utf-8'), addr) #收到谁的消息就给谁发
  

client.py

import socket
sk = socket.socket(type = socket.SOCK_DGRAM)

server = ('127.0.0.1', 9001)
while True:
	msg = input('>>>')
  if msg.upper() == 'Q': break
  sk.sendto(msg.encode('utf-8'), server)
	msg = sk.recv(1024).decode('utf-8')
  if msg.upper() == 'Q': break
  print(msg)

3.3tcp协议完成文件上传

server.py

import json
import struct
import socket
#接收

sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()

conn, addr = sk.accept()


#解决粘包现象
mlen = conn.recv(4) #收到mlen
dic_len = struct.unpack('i', mlen)[0] #解包之后是一个元组

#接收文件名和文件大小
msg = conn.recv(dic_len).decode('utf-8')
msg = json.loads(msg)

#接收文件
with open(msg['file_name'], 'wb') as f:
	file_data = conn.recv(msg['file_size'])
  f.write(file_data)
  #对于大文件传输
  while msg['file_size'] > 0:
    content = conn.recv(1024)
    msg['file_size'] -= len(content) #tcp每次收到的不一定就是1024,通过网络tcp会自动拆包
    f.write(content)
  
conn.close()
sk.close()

client.py

import os
import json
import struct
import socket
#发送
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))

#文件名、文件大小、文件
abs_path = r'D:\app\tmp'
file_name = os.path.basename(abs_path)
file_size = os.path.getsize(abs_path)

#传输习惯上习惯传输json
dic = {'file_name':file_name, 'file_size':file_size}
str_dic = json.dumps(dic)

sk.send(str_dic.encode('utf-8'))  #此处可能会发生粘包
#解决粘包问题
b_dic = str_dic.encode('utf-8')
mlen = struct.pack('i', len(b_dic))
sk.send(mlen) #四个字节表示字典的长度
sk.send(b_dic) #具体的字典


#传输文件
with open('a.txt', mode = 'rb') as f:
  file_data = f.read()
  sk.send(file_data)
  #对于大文件
  while file_size > 0:
    content = f.read(1024)
    file_size -= 1024
    sk.send(content)
  
sk.close()

4.粘包

粘包现象:只出现在tcp协议当中,多条消息之间没有边界,并且还有一堆的优化算法

  • 发送端:两条消息很短并且发送的间隔很短
  • 接收端:接收消息不及时,多条消息在接收方的缓存短堆在一起导致的粘包

粘包的本质:tcp协议的传输是流式传输,数据和数据之间没有边界

怎么解决粘包:自定义协议

  • 先发送四个字节的数据长度 #先接收四个字节 知道数据的长度
  • 再按照长度发送数据 #再按照长度接收数据

网络的最大带宽限制为MTU=15000字节

udp协议中如果没有做优化,会有发送的字节的限制(如qq消息发送会限制一定的长度)

自定义协议:

server.py

import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()

conn, addr = sk.accept()
msg1 = input('>>>').encode()
msg2 = input('>>>').encode()

num = str(len(msg1)) #
ret = num.zfill(4)  #补充到四个字节为止
print(ret)
conn.send(ret.encode('utf-8'))

#blen = struct.pack('i', len(msg1))
#conn.send(blen)
conn.send(msg1)
conn.send(msg2)

client.py

import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))

length = int(sk.recv(4).decode('utf-8'))
#length = sk.recv(4)
#length = struct.unpack('i', length)[0]
msg1 = sk.recv(length)
msg2 = sk.recv(1024)

print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))

当数据超过0-9999四个字节的时候

import struct

nums1 = 12345152134
num2 = 123
num3 = 8

ret1 = struct.pack('i', num1)
#只要这个数字不超过2**32,那么就能使用struct表示成4个字节
#可以传两个G的数据

struct.unpack('i', ret1)

通过计算即将要发送的数据的长度,通过struct模块把长度转换成固定的四个字节,发送四个字节的长度

收四个字节,再使用struct.unpack把四个字节转换成数字,这个数字就是即将要接收的数据的长度,再根据长度进行接收数据

python 代码: 用户态

操作系统:内核态

硬件:

5.验证客户端的合法性

什么场景?一定是公司内部,无用户的情况下

外界想要对开放的服务器获取端口号和服务,可以通过扫端口来进行

服务器为了安全,可以设置一些不能正常访问到的端口(蜜罐),只要使用了扫端口,那么可以立马封禁这个ip

日志一般会定期发送到一个专门存储的系统,但是会被外界发现后,同时也发送一个病毒到这个系统,会导致信息泄漏或者服务器的损坏

会制定一个认证机制,约定好的一个字符串作为密钥,密钥一般不会经过网络

客户端:2.使用随机发送的内容 + 密钥经过一个算法 = 结果 3.将结果发送会服务端

服务端:1.随机发送一个内容给客户端 4.服务端接收结果,用发送的内容+密钥 经过同一个算法,和接收的结果进行比对 5.相同则认证成功,这个是一个合法的客户端

当然经过网络的过程中,会被拦截,这个涉及到更安全的

#生成一个随机字符串
import os
ret = os.urandom(32) #随机生成一个32位的字符串
print(ret)

import hashlib
sha = hashlib.sha1('密钥')
sha.update(ret) #传入随机的字符串
result = sha.hexdigest()

#hmac 可以替代hashlib模块
import hmac
h = hmac.new(b'apple', os.urandom(32))
ret = h.digest()
print(ret) # b'uu\...'

server.py

import os
import socket
import hashlib

sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()

conn, addr = sk.accept()

#密钥
secret_key = b'apple'
#创建一个随机的字符串
ret = os.urandom(32)
sha = hashlib.sha1(secret_key)
sha.update(ret) 
result = sha.hexdigest()
#发送字符串
conn.send(ret)
#接收字符串
msg = conn.recv(len(result))
if msg.decode('utf-8') == result: 
  print('验证成功')
else:
  #不合法的客户端
  conn.close()
  
sk.close()

client.py

import hashlib
import socket
sk = socket.socket()
sk = sk.connect(('127.0.0.1', 8001))

#密钥
secret_key = b'apple'

#接收32位字符串
msg = sk.recv(32)
#将接收的内容+密钥经过算法得到余个字符串
sha = hsahlib.sha1(secret_key)
sha.update(msg)
result = sha.hexdigest()

sk.send(result.encode('utf-8'))

sk.close()

使用hmac

server.py

import socket
import os
import hmac

sk = socket.socket()
sk.bind(('127.0.0.1', 9001))
sk.listen()

msg = os.urandom(32)
secret_res = hmac.new(b'apple', msg, digestmod='MD5')
res = secret_res.digest()

conn, addr = sk.accept()
conn.send(msg)

result = conn.recv(len(res))
if res == result:
    print('登陆成功')
else:
    conn.close()

sk.close()

client.py

import socket
import hmac

sk = socket.socket()
sk.connect(('127.0.0.1', 9001))

msg = sk.recv(32)
secret_res = hmac.new(b'apple', msg, digestmod='MD5')
res = secret_res.digest()

sk.send(res)

sk.close()

7.socketserver

socket是更底层的模块,封装度低,效率不固定

socketserver模块是基于socket完成的,封装度更高,效率比较固定

处理tcp协议的server端处理并发的客户端的请求

server.py

import socket
import time
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()

while True:
  conn, addr = sk.accept()
  while True:
    try:
    	msg = conn.recv(1024)
    	conn.send(msg.upper().encode('utf-8'))
    	time.sleep(0.5)
    except ConnectionResetError:
      break
  conn.close()
  
sk.close()  

client.py

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 8001))

sk.send(b'hello')
msg = sk.recv(1024)
print(msg.decode('utf-8'))

sk.close()

这样只能在一个客户端关闭以后才能连接下一个客户端

改进:server.py

import time
import socketserver

class Myserver(socketserver.BaseRequestHandler): #自己没有__init__,到父类中去找,父类中会有self.handle()执行函数
  #此时的self是Myserver,当然父类中也有handle方法,Myserver不实现handle则
  
  #可以理解为每一个客户端都会有自己的一个客户端
  def handle(self):
    #所有的客户端都会从第一句开始执行,只需要考虑一个客户端怎么做
    conn = self.request
    while True:
      try:
        msg = conn.recv(1024)
        conn.send(msg.upper().encode('utf-8'))
        time.sleep(0.5)
      except ConnectionResetError:
        break
  	conn.close()
    
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver)
server.serve_forever()

"""
class BaseRequestHandler:
	def __init__(self):
		#初始化
		self.handle()
		
	def handle(self):
		pass

class Myserver(BaseRequestHandler):
	def handle(self):
		pass
my = Myserver() #先找自己类的handle方法,再找父类中的handle
#由于自己没有__init__,会调用父类的__init__方法
"""

6.小结

tcp协议的多人通信

  • 和一个人通信说多几句话
  • 和一个人聊完再和其他人聊
  • socket() tcp协议的
  • bind绑定一个IP和端口(元组)
  • listen监听,代表socket服务的开启
  • accept等到有客户端来访问和客户端建立连接
  • send直接通过连接发送信息,不需要写地址
  • sendto需要写一个对方的地址
  • recv只接收消息
  • recvfrom接收消息和地址
  • connect客户端/tcp协议的方法,和server端建立连接
  • close关闭连接、服务

udp:

  • socket(type = SOCK_DGRAM)
  • senddto 需要写一个对方的地址
  • recvfrom接收消息和地址
  • close关闭服务、连接

每一句话什么意思?执行到哪里程序会阻塞,为什么阻塞,什么时候结束阻塞?

  • input #等待,用户输入enter
  • accept #阻塞,有客户端来和我建立完连接之后
  • recv #阻塞,直到对方发过来消息之后
  • recvfrom #阻塞,直到对方发送消息之后
  • connect #阻塞,直到server端结束了一个对client的服务,开始和当前client建立连接的时候

注意:

  • tcp协议的自定义协议解决粘包问题

    • recv(1024)不代表一定收到1024个字节,而是最多只能收到这么多
    • 两条连续发送的数据一定要避免粘包问题
    • 先发送数据的长度,再发送数据
      • 发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发的数据的长度,再发送数据
  • 验证客户端合法性

    • 什么场景用
    • 什么逻辑 什么是密钥 为什么要发送随机的字符串 使用什么算法 算法有什么要求(客户端和服务端的算法相同)
    • 代码只能实现
  • 并发的tcp协议server端

    • 会背

      import socketserver
      class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
          conn = self.request
          """
          
          """
          conn.close()
      server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver)
      server.serve_forever()
      
    • 知道代码从哪里开始:有客户端来的时候,从handle方法开始

作业:

1.登陆+文件下载+文件上传(密文登陆,至少要在server端进行一次摘要)

2.通过socketserver来实现

posted @ 2021-04-14 00:03  wrrr  阅读(76)  评论(0编辑  收藏  举报