粘包现象
粘包现象的成因:
tcp协议的特点,面向流的,为了传输可靠传输,所以有很多优化机制。
无边界 所有在连接建立的基础上传递的数据之间没有界限
收发消息很有可能不完全相等
缓存机制,导致没发过去的消息会在发送端缓存
没有接收完的消息会在接收端缓存
解决:
给应用层定制协议
先发送一个定长的可以表示待发送数据长度的bytes 先接收一个固定长度
在发送要发送的数据 在按照长度接收数据
方案二:
先发送一个定长表示待发送字典长度的bytes (先接收一个固定长度)
再发送要发送字典(再按照长度接收字典)
在发送文件 (再根据字典中的信息接收相应的内容)
####大文件传输实例
实例一:
import os
import json
import struct
import socket
#
sk = socket.socket()
sk.bind(('192.168.11.36',9000))
sk.listen()
conn,addr = sk.accept()
print(addr)
dic = {'filename':' D:\战狼.rmvb',
'filesize':os.path.getsize(r' D:\战狼.rmvb)}
str_dic = json.dumps(dic).encode('utf-8')
dic_len = struct.pack('i',len(str_dic))
conn.send(dic_len)
conn.send(str_dic)
with open(r' D:\战狼.rmvb','rb') as f:
# content = f.read()
while True:
content = f.read(1024)
if not content:break
conn.send(content)
# if not conn.send(content):break
conn.close()
sk.close()
import json
import struct
import socket
sk = socket.socket()
sk.connect(('192.168.11.36',9000))
dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]
str_dic = sk.recv(dic_len).decode('utf-8')
dic = json.loads(str_dic)
with open(dic['filename'],'wb') as f:
while True:
content = sk.recv(dic['filesize'])
if not content:break
f.write(content)
sk.close()
示例二:
###加入MD5验证
#########server 端
import os
import json
import struct
import socket
import hashlib
md5 = hashlib.md5()
with open(r'D:\战狼.rmvb', 'rb') as f:
while True:
b = f.read(1024)
if not b:
break
md5.update(b)
md5 = md5.hexdigest()
print(md5)
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
print(addr)
dic = {'filename':'黏包.mp4', 'filename_md5':str(md5),
'filesize':os.path.getsize(r'D:\战狼.rmvb')}
str_dic = json.dumps(dic).encode('gbk')
dic_len = struct.pack('i', len(str_dic))
conn.send(dic_len)
conn.send(str_dic)
with open(r'D:\战狼.rmvb', 'rb') as f:
content = f.read()
conn.send(content)
conn.close()
sk.close()
#####客户端
import json
import struct
import socket
import hashlib
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]
str_dic = sk.recv(dic_len).decode('gbk')
dic = json.loads(str_dic)
md5 = hashlib.md5()
with open(dic['filename'],'wb') as f:
while True:
content = sk.recv(1024)
f.write(content)
if not content:break
md5.update(content)
md5 = md5.hexdigest()
print(md5)
if dic['filename_md5'] == str(md5):
print('md5校验正确--下载成功')
else:
print('文件验证失败')
sk.close()
socket 验证客户端的合法性
#server端
#秘钥
# hashlib
# md5
# hashlib.md5('秘钥')
import os
import socket
import hmac
secret_key = '老衲洗头用飘柔'.encode('utf-8') # 秘钥
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
try:
conn,addr = sk.accept()
random_bytes = os.urandom(32) # 加密方法
conn.send(random_bytes)
obj = hmac.new(key =secret_key,msg =random_bytes)
ret = obj.hexdigest()
msg = conn.recv(1024).decode('utf-8')
if msg == ret:print('是合法的客户端')
else:conn.close()
finally:
sk.close()
break
##客户端
import socket
import hmac
secret_key = '老衲洗头用飘柔'.encode('utf-8')
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
urandom = sk.recv(1024)
hmac_obj = hmac.new(key = secret_key,msg =urandom)
sk.send(hmac_obj.hexdigest().encode('utf-8'))
sk.close()
并发编程
import time
import socketserver
#并发编程
class MyServer(socketserver.BaseRequestHandler):
clients = {}
def handle(self):
qq_id = self.request.recv(1024).decode('utf-8')
MyServer.clients[qq_id] = self.request
recv_msg = self.request.recv(1024).decode('utf-8')
print(recv_msg)
if recv_msg.startswith('say'):
recv_msg = recv_msg.replace('say','')
print(recv_msg)
friend,msg = recv_msg.split('|')
time.sleep(5)
print(MyServer.clients)
print(friend)
print('-----',MyServer.clients.get(friend))
if MyServer.clients.get(friend):
MyServer.clients[friend].send(('%s|%s'%(qq_id,msg)).encode('utf-8'))
if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
sk.send('heiheihei'.encode('utf-8'))
import time
time.sleep(0.1)
sk.send('sayhahaha|我是heiheihei'.encode('utf-8'))
sk.close()
执行:
说明:python并发中server不做消息回复,只做接收与转发
ThreadingTCPServer/TCPServer/ForkingTCPServer的区别,原理可同样引申到UDP,
这三个类其实就是对接收到request请求后的不同处理方法。
TCPServer是接收到请求后执行handle方法,如果前一个的handle没有结束,那么其他的请求将不会受理,新的客户端也无法加入。
而ThreadingTCPServer和ForkingTCPServer则允许前一连接的handle未结束也可受理新的请求和连接新的客户端,区别在于前者用建立新线程的方法运行handle,后者用新进程的方法运行handle。
二、socket的更多方法介绍
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回错误码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定的套接字的参数
s.setsockopt() 设置指定的套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
使用hmac加密
hmac是专门来做客户端合法性的
假如黑客渗透到内网,得知到服务器IP地址。就可以做端口扫描,一台计算机的端口范围是0~65535
扫描6万多次,就能知道了。
import hmac
obj = hmac.new(key=b'secret_key',msg=b'23f232r2rq23')
print(obj.hexdigest())
执行:
三、socketserver
SocketServer 内部使用IO多路复用以及"多线程"和"多进程",从而实现并处理多个客户端请求的Socket服务器。
即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个"线程"或者"进程"专门负责处理当前客户端的所有请求
它能实现多个客户端,同时连接,它继承了Socket
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
使用ThreadingTCPServer:
创建一个继承自SocketServer.BaseRequestHandler 的类,必须继承
类中必须定义一个名称为handle的方法,必须重写
看BaseRequestHandler 的源码,它的hendle方法,是空的
1
2
|
def
handle(
self
):
pass
|
需要自己去实现
server.py
1
2
3
4
5
6
7
|
import
socketserver
class
MyServer(socketserver.BaseRequestHandler):
def
handle(
self
):
print
(
self
.request)
server
=
socketserver.ThreadingTCPServer((
'127.0.0.1'
,
9000
),MyServer)
server.serve_forever()
|
client.py
1
2
3
4
|
import
socket
sk
=
socket.socket()
sk.connect((
'127.0.0.1'
,
9000
))
sk.close()
|
先执行server.py,再执行client.py
1
2
3
4
5
6
7
|
import
socket
sk
=
socket.socket()
sk.connect((
'127.0.0.1'
,
9000
))
print
(sk.recv(
1024
))
inp
=
input
(
'>>>'
).encode(
'utf-8'
)
sk.send(inp)
sk.close()
|
连续发送
client连续发送:
1
2
3
4
5
6
7
8
|
import
socket
sk
=
socket.socket()
sk.connect((
'127.0.0.1'
,
9000
))
while
True
:
print
(sk.recv(
1024
))
#inp = input('>>>').encode('utf-8')
sk.send(b
'hahaha'
)
sk.close()
|
server连续接收:
1
2
3
4
5
6
7
8
9
10
|
import
socketserver
class
MyServer(socketserver.BaseRequestHandler):
def
handle(
self
):
while
True
:
print
(
self
.request)
self
.request.send(b
'hello'
)
# 跟所有的client打招呼
print
(
self
.request.recv(
1024
))
# 接收客户端的信息
server
=
socketserver.ThreadingTCPServer((
'127.0.0.1'
,
9000
),MyServer)
server.serve_forever()
|
执行效果如下:
如果server端口重复,使用以下代码: