上节复习、验证客户端连接的合法性、hmac模块、socketserver模块的引入 第二十八天 2018.11.17
上节复习:
解决黏包问题:(面试题)
为什么会出现黏包现象?
首先只有在TCP协议中才会出现黏包现象
是因为TCP协议是面向流的协议
在发送的数据传输的过程中还有缓存机制来避免数据丢失
因此,在连续发送小数据的时候,以及接收大小不符的时候的都容易出现黏包现象
本质还是因为我们在接收数据的时候不知道发送的数据的长短
解决黏包问题
在传输大量数据之前先告诉接收端要发送的数据大小
如果想更漂亮的解决问题,可以通过struct模块来定制协议
面试题:
一台服务器如何在网络中找到另一台服务器
osi五层模型
应用层
传输层 tcp协议和udp协议
网络层 ip协议(ipv4 ipv6) 路由器
数据链路层 arp协议(利用ip找mac) 交换机
物理层
tcp协议 可靠地 面向连接的 字节流传输
udp协议 不可靠的 无连接的 高效的传输
TCP协议中 三次握手和四次挥手
粘包 针对 tcp协议
拆包机制 nagel算法(合包) 缓存机制
面向流的传输 - 数据与数据之间没有边界
粘包机制可能发生在发送端和接收端
udp协议不会粘包
面向数据包的传输方式
不可靠
对于空消息:
tcp协议不能发空消息
udp协议可以
struct模块
pack、unpack
模式:'i'
pack之后的长度:4个字节
unpack后拿到的数据是一个元组:元组的第一个元素才是pack的值
验证客户端连接的合法性:
访问网站-------->client---->server ip_port server
检测一下客户端是否合法 不依靠登录认证
hmac模块:
import hmac 模块hashlib h = hmac.new() #secret_key 密钥,你想进行加密的bytes 密文的内容 = h.digest() # hmac.compare_digest() #对比密文与另外一个密文 密钥---->执行时不可改变、可配置的
验证客户端的合法性:
server端(服务端):
#服务端:利用os生成一个随机32位字节发给客户端
#服务端中OS生成的字节和密钥组合利用hmac进行摘要与客户端发来的摘要相比较
#即:利用服务端和客户端使用相同的摘要,在密钥相同的情况下---->判定为合法
import os #使用os的随机方法,生成一次每次都不一样的字节和原密钥组合生成新密钥
import socket
import hmac #用来加密---->密钥和OS生成的组合
def check_conn(conn): #面向函数---->用函数
msg = os.urandom(32) #每次执行都随机生成一个32位的字节
conn.send(msg)
h= hmac.new(secret_key,msg) #用hamc加密密钥和msg随机生成的一个32位字节
digest = h.digest() #bytes类型---->取摘要后的密文内容
if conn.recv(1024) == digest: #如果server生成的和client生成的一致即为合法
print( '合法的客户端')
return True
else:
print('非法的客户端')
return False
secret_key = b'egg' # 密钥
sk = socket.socket()
sk.bind(('127.0.0.1',8090)) #ip_port
sk.listen() #建立监听
conn,addr = sk.accept() #建立conn连接
ret = check_conn(conn)
while ret: #检验合法性---->合法后进入无限循环聊天
info = input('>>>')
conn.send(info.encode('utf-8')) #注意是conn.send---->conn是连接
talk = conn.recv(1024)
print(talk.decode('utf-8'))
conn.close() #结束聊天后才关闭掉,加个判断break即可
sk.close()
client端(客户端):
#客户端
import socket
import hmac
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
msg = sk.recv(1024) #接收服务端中os模块随机生成的一个32位字节
# 用和server端相同的方法对这个字符串进行摘要
secret_key = b'egg' # 密钥
h = hmac.new(secret_key,msg) #用hmac进行摘要
ret = h.digest() #取摘要后的密文内容
sk.send(ret)
talk = sk.recv(1024)
if talk: #server端已经验证过客户端的合法性了
print(talk.decode('utf-8'))
while True:
info = input('>>>')
sk.send(info.encode('utf-8'))
talk = sk.recv(1024)
print(talk.decode('utf-8'))
sk.close()
socketserv模块:
socket tcp---->只能和一个client通信
socketserver tcp---->可以和多个通信
import socketserver(类似并发)
server端整个过程相当于面向对象中定义一个类,然后将该类实例化为一个对象,并给这个实例化的对象一些操作
定义的类必须继承socketserver模块中的BaseRequestHandler类,定义的类无init方法(出现的init创建self对象向该类父类中找),必
须有handle方法
实例化为:对象名 = socketserver.ThreadingTCPServer((ip,端口号),类名)
并给该对象一个永远开启的服务(被多个用户所使用的,因此需要永久开启)

server端:
# TCP服务端一对多服务---->socketserver模块
# 每次运行后请关闭显示器窗口---->因为永久运行,否则就会报错端口和套接字被占用
import socketserver
class MyServer(socketserver.BaseRequestHandler): #必须继承socketserver模块中的BaseRequestHandler类
#Base---->父类,handler---->处理 必须继承此类
def handle(self): #必须有这个方法
while True:
ret = self.request.recv(1024).decode('utf-8') #self.request相当于一个conn
print(ret)
if ret == 'bye':
self.request.send('bye'.encode('utf-8'))
self.request.close()
break #因为客户端永久运行,即使bye掉本次,还可以输入
info = input('>>>').encode('utf-8')
self.request.send(info)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9090),MyServer) #类的实例化
#Thread---->线程 一个程序里运行起来只有一个线程---->每一个线程都可以请求cup
server.serve_forever() #永远开启一个服务
#因为对多个人提供连接服务,因此不会关闭---->类似于加个永久运行
client端:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9090))
while True:
msg = input('>>>')
if msg == 'bye':
sk.send('bye'.encode('utf-8'))
break
sk.send(('花花:'+msg).encode('utf-8'))
talk = sk.recv(1024)
print(talk.decode('utf-8'))
sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9090))
while True:
msg = input('>>>')
if msg == 'bye':
sk.send('bye'.encode('utf-8'))
break
sk.send(('明明:'+msg).encode('utf-8'))
talk = sk.recv(1024)
print(talk.decode('utf-8'))
sk.close()

浙公网安备 33010602011771号