3、验证客户端合法性
场景:如果别人知道了你的服务器的IP,那么他就可以使用扫端口的方式去连接上你的服务器,因为我们都知道,端口的范围是0-65535,
那么别人知道了你的服务器IP后,就可以循环扫这些端口,就可以连接上你的服务,你服务器所进行的一些操作,比如一些数据的传输,
就会被别人所获取,所以这个时候就需要验证客户端的合法性。
代码
服务端:importosimporthmacimportsocketdefauth(conn):
msg= os.urandom(32) #生成一个32位的随机的字节码(urandom生成的字节码就是bytes类型的)
conn.send(msg) #把这个随机的字节码发送到client端
#hmac接收两个参数,第一个参数相当于hashlib的盐,第二个参数是我们随机生成的字节码,两个参数都是bytes类型
result = hmac.new(secret_key,msg) #处理这个随机字节码,socket_key是盐
res = result.hexdigest() #得到结果(字符串)
client_digest = conn.recv(1024) #接收client端处理的结果
if res == client_digest.decode('utf-8'):print('合法的连接') #对比成功可以继续通信
returnTrueelse:print('不合法连接') #不成功
returnFalse
secret_key= b'xiaoming' #hmac的盐
sk =socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()ifauth(conn):
msg= conn.recv(1024) #True正常的和client端进行沟通
print(msg.decode('utf-8'))
conn.close()else:
conn.close()#False 直接关闭和这个客户端的连接
sk.close()
客户端:importhmacimportsocketdefauth(sk):
msg= sk.recv(32) #接收服务端传来的随机字节码
result = hmac.new(secret_key,msg) #处理接收到的随机字节码
res = result.hexdigest() #得到结果
sk.send(res.encode('utf-8')) #把结果发回给服务端,让服务端进行验证
secret_key= b'xiaoming' #因为盐是程序员自己设置的,那么程序员写的客户端肯定知道自己的盐是什么
sk =socket.socket()
sk.connect(('127.0.0.1',8080))
auth(sk)
sk.send(b'connect success') #进行其他正常的和server端的沟通
sk.close()
二、socketserver
正常服务端的socket,每一次只能连接一个客户端,只有跟当前客户端断开连接后才能和下一个客户端连接,
而用socketserver可以跟多个客户端同时连接(并发)。
服务端:importsocketserver#tcp协议的server端就不需要导入socket
class Myserver(socketserver.BaseRequestHandler): #继承socketserver.BaseRequestHandler这个类
def handle(self): #必须继承handle方法并重写
conn = self.request #self.request就是客户端的对象
while True: #和客户端进行交互
conn.send(b'helloworld')print(conn.recv(1024).decode('utf-8'))#设置allow_reuse_address允许服务器重用地址
socketserver.TCPServer.allow_reuse_address =True#创建一个对象server,绑定ip和端口,相当于sk = socket.socket() sk.bind(('127.0.0.1',8888))这两步的结合
server = socketserver.ThreadingTCPServer(('127.0.0.1',8888),Myserver)#让server一直运行下去,除非强制停止程序
server.serve_forever()
客户端:importsocket
sk=socket.socket()
sk.connect(('127.0.0.1',8888))whileTrue:
ret= sk.recv(1024)print(ret.decode('utf-8'))
sk.send(b'hiworld')
sk.close()
解释为什么一定要重写handler方法:
Myserver这个类没有__init__方法,那么它就会去继承使用父类BaseRequestHandler的__init__方法
看看BaseRequestHandler源码:classBaseRequestHandler:def __init__(self, request, client_address, server):
self.request= request #获取客户端的连接(对象),设置为自己的属性
self.client_address = client_address #客户端的地址
self.server =server
self.setup()try:
self.handle()#初识化对象的时候执行handler方法
finally:
self.finish()defsetup(self):pass
defhandle(self):pass
deffinish(self):pass总结:
也就是说,子类继承了父类的__init__方法,这个方法里面已经取到了客户端的对象conn,和地址addr,
并且初始化的时候调用了handler方法,但是父类的handler方法并没有实现任何功能,所以子类应该重写handler方法便于与客户端交互。
实例:上传文件
server.pyimportjsonimportstructimportsocketserverimportoperate_handlerclassMyFTP(socketserver.BaseRequestHandler):defhandle(self):
conn=self.request
length= conn.recv(4)
length= struct.unpack('i',length)[0]
operate= (conn.recv(length)).decode('utf-8')
operate_dic=json.loads(operate)
opt= operate_dic['operate']
usr= operate_dic['user']print(opt,usr)
getattr(operate_handler,opt)(conn,usr)
socketserver.TCPServer.allow_reuse_address=True
server= socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyFTP)
server.serve_forever()
operate_handler.pyimportosimportjsonimportstruct
base_path= r'E:\PythonProject\ftp\server\root'
defupload(conn,usr):
fileinfo_len= conn.recv(4)
fileinfo_len= struct.unpack('i',fileinfo_len)[0]
fileinfo= (conn.recv(fileinfo_len)).decode('utf-8')
fileinfo=json.loads(fileinfo)
file_path= os.path.join(base_path,usr,fileinfo['filename'])
file_path=os.path.abspath(file_path)
with open(file_path,'wb') as f:while fileinfo['filesize']:
content= conn.recv(20480)
fileinfo['filesize'] -=len(content)
f.write(content)print('接收完毕')
client.pyimportosimportjsonimportstructimportsocket#发送信息
defmy_send(sk,operate_info):
b_optinfo= (json.dumps(operate_info)).encode('utf-8')
num= struct.pack('i',len(b_optinfo))
sk.send(num)
sk.send(b_optinfo)
sk=socket.socket()
sk.connect(('127.0.0.1',9000))#[登录,注册,退出]
#要进行的操作
operate_info = {'operate':'upload','user':'xiaoming'}
my_send(sk,operate_info)#选择一个文发送到server端
file_path = r'F:\电影\电影\荒野生存.mp4'
#发送文件信息
file_name =os.path.basename(file_path)
file_size=os.path.getsize(file_path)
file_info= {'filename':file_name,'filesize':file_size}
my_send(sk,file_info)#server端接收写入
with open(file_path,'rb') as f:whilefile_size:
content= f.read(20480)
file_size-=len(content)
sk.send(content)print('上传完毕')
sk.close()