python简单的消息并发及FTP作业分析
socketserver模块实现并发
tcp协议的socket一次只能和一个客户端通信,如果用socketserver可以实现和多个客户端通信;它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket,在py2.7里面叫做SocketServer也就是大写了两个S,在py3里面就小写了;用它来实现并发,也就是同时可以和多个客户端进行通信,多个人可以同时进行上传下载等(一个服务端与每个客户端都建立一个线程连接交互)
1 服务端
2 import socketserver
3
4 # 写一个类名字随便起,然后继承socketserver这个模块里面的BaseRequestHandler这个类
5 class Myserver(socketserver.BaseRequestHandler):
6
7 # 写一个handle方法,必须用这个名字
8 def handle(self):
9 while 1:
10
11 # 收消息,self.request相当于一个conn
12 from_client_msg = self.request.recv(1024)
13 # 同下边功能一样
14 # self.from_client_msg = self.request.recv(1024).strip()
15
16 # 打印接收到的客户端消息
17 print('客户端消息:',from_client_msg.decode('utf-8'))
18 # 同下边功能一样
19 # 打印的是客户端IP和进程号
20 # print('客户端消息:',self.client_address)
21 # print(str(self.from_client_msg,'utf-8'))
22
23 # request.send服务端发送消息
24 self.server_msg = input('服务端消息:')
25 self.request.send(self.server_msg.encode('utf-8'))
26 # self.request.sendall(bytes(self.server_msg,'utf-8'))
27
28 # self.request.close() #关闭链接
29
30 if __name__ == '__main__':
31 ip_port = ('127.0.0.1',8888)
32
33 # 使用socketserver的ThreadingTCPServer这个类(ThreadingTCPServer 多线程),将IP和端口传进去,还需要将上面自己定义的类传进去,得到一个对象.相当于我们通过它进行了bind、listen
34 server = socketserver.ThreadingTCPServer(ip_port,Myserver)
35
36 # 让server永远运行下去,除非强制停止程序;使用上面这个类的对象来执行serve_forever()方法,并且serve_forever()帮我们进行了accept
37 server.serve_forever()
38 # 同下边代码一样
39 # HOST, PORT = "127.0.0.1", 9999
40 # 设置allow_reuse_address允许服务器重用地址
41 # socketserver.TCPServer.allow_reuse_address = True
42 # 创建一个server, 将服务地址绑定到127.0.0.1:9999
43 #server = socketserver.TCPServer((HOST, PORT),Myserver)
44 # server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver)
45
46
47 客户端
48 import socket
49 client = socket.socket()
50 client.connect(('127.0.0.1',8888))
51 while 1:
52
53 # 发送给服务端的消息
54 client_msg = input('客户端消息:')
55 # client.sendall(bytes(client_msg,'utf-8'))
56 # 同下边的代码一样的功能
57 client.send(client_msg.encode('utf-8'))
58
59 # 接收并打印客户端的消息
60 # from_server_msg = str(client.recv(1024),'utf-8')
61 # print('服务端消息:',from_server_msg)
62 # 同下边的代码一样的功能
63 from_server_msg = client.recv(1024)
64 print('服务端消息:',from_server_msg.decode('utf-8'))
send()与sendall()区别:
1 使用send()进行发送的时候,Python将内容传递给系统底层的send接口,也就是说,Python并不知道这次调用是否会全部发送完成,比如MTU是1500,但是此次发送的内容是2000,那么除了包头等等其他信息占用,发送的量可能在1000左右,还有1000未发送完毕,但是send()不会继续发送剩下的包,因为它只会发送一次,发送成功之后会返回此次发送的字节数,会返回数字1000给用户,然后就结束了,如果需要将剩下的1000发送完毕,需要用户自行获取返回结果,然后将内容剩下的部分继续调用send()进行发送
2
3 sendall()是对send()的包装,完成了用户需要手动完成的部分,它会自动判断每次发送的内容量,然后从总内容中删除已发送的部分,将剩下的继续传给send()进行发送,发送完整的TCP数据,成功返回None,失败抛出异常
实现简单的文件上传
1 服务端:
2 import socket,subprocess,struct,json,os
3 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4 server.bind(('127.0.0.1',8888))
5 server.listen(5)
6
7 file_base_path = 'd:\上传下载'
8 conn, addr = server.accept()
9
10 # 1,接受4个字节
11 head_dic_json_bytes_size_strcut = conn.recv(4)
12
13 # 2,利用struct反解出head_dic_json_bytes的具体size
14 head_dic_json_bytes_size = struct.unpack('i',head_dic_json_bytes_size_strcut)[0]
15
16 # 3,接受head_dic_json_bytes具体数据
17 head_dic_json_bytes = conn.recv(head_dic_json_bytes_size)
18
19 # 4,将head_dic_json_bytes转化成json的格式
20 head_dic_json = head_dic_json_bytes.decode('utf-8')
21
22 # 5, head_dic_json 反序列化成字典类型
23 head_dic = json.loads(head_dic_json)
24
25 # 6 循环接收数据
26 file_path = os.path.join(file_base_path,head_dic['file_name'])
27
28 with open(file_path,mode='wb') as f1:
29 total_data_size = 0
30 while total_data_size < head_dic['file_size']:
31 every_data = conn.recv(1024)
32 f1.write(every_data)
33 total_data_size += len(every_data)
34
35 conn.close()
36 server.close()
37
38
39 客户端
40 import socket,struct,json,os
41 client = socket.socket()
42 client.connect(('127.0.0.1',8888))
43
44 # 1, 自定制报头
45 head_dic = {
46 'file_name': '测试.mp4',
47 'file_md5': 13213213343244343423,
48 'file_path': r'C:\Users\oldboy\PycharmProjects\s19\day30\06 文件的上传下载\测试.mp4',
49 'file_size': None,
50 }
51 head_dic['file_size'] = os.path.getsize(head_dic['file_path'])
52
53 # 2,将head_dic利用json转化成json字符串
54 head_dic_json = json.dumps(head_dic)
55
56 # 3,将json字符串转化成bytes
57 head_dic_json_bytes = head_dic_json.encode('utf-8')
58
59 # 4,利用strcut将head_dic_json_bytes转化成固定的4个字节(内容bytes长度固定的4个字节)
60 head_dic_json_bytes_struct = struct.pack('i', len(head_dic_json_bytes))
61
62 # 5,发送固定4个字节
63 client.send(head_dic_json_bytes_struct)
64
65 # 6,发送bytes类型的字典的报头数据head_dic_json_bytes
66 client.send(head_dic_json_bytes)
67
68 # 7, 发送数据
69 with open(head_dic['file_path'],mode='rb') as f1:
70 # 利用for循环每次如果读取一行,对于这种非文字类的文件会出现问题.
71 total_data_size = 0
72 while total_data_size < head_dic['file_size']:
73 every_data = f1.read(1024)
74 client.send(every_data)
75 total_data_size += len(every_data)
76 client.close()
练习
1 服务端
2 import json
3 import socket
4 import struct
5
6 server = socket.socket()
7 server.bind(('127.0.0.1',8888))
8 server.listen()
9 conn,addr = server.accept()
10
11 # 首先接收文件的描述信息的长度
12 struct_data_len = conn.recv(4)
13 data_len = struct.unpack('i',struct_data_len)[0]
14
15 # 通过文件信息的长度将文件的描述信息全部接收
16 print('data_len>>',data_len) #data_len>> 47
17 file_info_bytes = conn.recv(data_len)
18
19 # 将文件描述信息转换为字典类型,以便操作
20 file_info_json = file_info_bytes.decode('utf-8')
21 file_info_dict = json.loads(file_info_json) #{'file_name': 'aaa.mp4', 'file_size': 24409470}
22
23 print(file_info_dict)
24
25 # 统计每次接收的累计长度
26 recv_sum = 0
27
28 # 根据文件描述信息,指定文件路径和文件名称
29 file_path = 'E:\Py3Practise\\test' + '\\' + file_info_dict['file_name']
30
31 # 接收文件的真实数据
32 with open(file_path,'wb')as f:
33
34 #循环接收,循环结束的依据是文件描述信息中文件的大小,也是通过一个初始值为0的变量来统计
35 while recv_sum < file_info_dict['file_size']:
36 every_recv_data = conn.recv(1024)
37 recv_sum += len(every_recv_data)
38 f.write(every_recv_data)
39
40
41 客户端
42 import os
43 import socket
44 import json
45 import struct
46
47 client = socket.socket()
48 client.connect(('127.0.0.1',8888))
49
50 #统计文件大小
51 file_size = os.path.getsize(r'E:\知识总结\python开发\day029 socketserver ftp功能简单讲解\代码\day029\aaa.mp4')
52
53 #统计文件描述信息,给服务端,服务端按照我的文件描述信息来保存文件,命名文件等等,现在放到一个字典里面了
54 file_info = {
55 'file_name':'aaa.mp4',
56 'file_size': file_size,
57 }
58
59 #由于字典无法直接转换成bytes类型的数据,所以需要json来将字典转换为json字符串.在把字符串转换为字节类型的数据进行发送
60 #json.dumps是将字典转换为json字符串的方法
61 file_info_json = json.dumps(file_info)
62
63 #将字符串转换成bytes类型的数据
64 file_info_byte = file_info_json.encode('utf-8')
65
66 #为了防止黏包现象,将文件描述信息的长度打包后和文件的描述信息的数据一起发送过去
67 data_len = len(file_info_byte)
68 data_len_struct = struct.pack('i',data_len)
69
70 #发送文件描述信息
71 client.send(data_len_struct + file_info_byte)
72
73 #定义一个变量,=0,作为每次读取文件的长度的累计值
74 sum = 0
75
76 #打开的aaa.mp4文件,rb的形式,
77 with open(r'E:\知识总结\python开发\day029 socketserver ftp功能简单讲解\代码\day029\aaa.mp4','rb') as f:
78 #循环读取文件内容
79 while sum < file_size:
80 #每次读取的文件内容,每次读取1024B大小的数据
81 every_read_data = f.read(1024)
82 #将sum累加,统计长度
83 sum += len(every_read_data)
84 #将每次读取的文件的真实数据返送给服务端
85 client.send(every_read_data)
验证客户端的链接合法性
1、os.urandom(n)
1 其中os.urandom(n) 是一种bytes类型的随机生成n个字节字符串的方法,而且每次生成的值都不相同.再加上md5等加密的处理,就能够成内容不同长度相同的字符串了
2
3 os.urandom(n)函数在python官方文档中做出了这样的解释
4
5 函数定位: Return a string of n random bytes suitable for cryptographic use.
6 意思就是,返回一个有n个byte那么长的一个string,然后很适合用于加密.
7
8 然后这个函数,在文档中,被归结于os这个库的Miscellaneous Functions,意思是不同种类的函数(也可以说是混种函数)
9 原因是: This function returns random bytes from an OS-specific randomness source. (函数返回的随机字节是根据不同的操作系统特定的随机函数资源.即,这个函数是调用OS内部自带的随机函数的.有特异性)
10
11
12 使用方法:
13 import os
14 from hashlib import md5
15
16 for i in range(10):
17 print md5(os.urandom(24)).hexdigest()
2、hmac: 我们完全可以用hashlib来实现,但是这个操作更方便一些
1 Python自带的hmac模块实现了标准的Hmac算法,我们首先需要准备待计算的原始消息message,随机key,哈希算法,这里采用MD5,使用hmac的代码如下:
2 import hmac
3 message = b'Hello world'
4 key = b'secret'
5 h = hmac.new(key,message,digestmod='MD5')
6 print(h.hexdigest())
7 比较两个密文是否相同,可以用hmac.compare_digest(密文、密文),然会True或者False.
8
9
10 可见使用hmac和普通hash算法非常类似.hmac输出的长度和原始哈希算法的长度一致.需要注意传入的key和message都是bytes类型,str类型需要首先编码为bytes.
11 def hmac_md5(key, s):
12 return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()
13
14 class User(object):
15 def __init__(self, username, password):
16 self.username = username
17 self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)])
18 self.password = hmac_md5(self.key, password)
事例
1 服务端
2 from socket import *
3 import hmac,os
4
5 secret_key=b'Jedan has a big key!'
6 def conn_auth(conn):
7 '''
8 认证客户端链接
9 :param conn:
10 :return:
11 '''
12 print('开始验证新链接的合法性')
13 msg=os.urandom(32)#生成一个32字节的随机字符串
14 conn.sendall(msg)
15 h=hmac.new(secret_key,msg)
16 digest=h.digest()
17 respone=conn.recv(len(digest))
18 return hmac.compare_digest(respone,digest)
19
20 def data_handler(conn,bufsize=1024):
21 if not conn_auth(conn):
22 print('该链接不合法,关闭')
23 conn.close()
24 return
25 print('链接合法,开始通信')
26 while True:
27 data=conn.recv(bufsize)
28 if not data:break
29 conn.sendall(data.upper())
30
31 def server_handler(ip_port,bufsize,backlog=5):
32 '''
33 只处理链接
34 :param ip_port:
35 :return:
36 '''
37 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
38 tcp_socket_server.bind(ip_port)
39 tcp_socket_server.listen(backlog)
40 while True:
41 conn,addr=tcp_socket_server.accept()
42 print('新连接[%s:%s]' %(addr[0],addr[1]))
43 data_handler(conn,bufsize)
44
45 if __name__ == '__main__':
46 ip_port=('127.0.0.1',9999)
47 bufsize=1024
48 server_handler(ip_port,bufsize)
49
50
51 客户端
52 from socket import *
53 import hmac,os
54
55 secret_key=b'Jedan has a big key!'
56 def conn_auth(conn):
57 '''
58 验证客户端到服务器的链接
59 :param conn:
60 :return:
61 '''
62 msg=conn.recv(32)
63 h=hmac.new(secret_key,msg)
64 digest=h.digest()
65 conn.sendall(digest)
66
67 def client_handler(ip_port,bufsize=1024):
68 tcp_socket_client=socket(AF_INET,SOCK_STREAM)
69 tcp_socket_client.connect(ip_port)
70
71 conn_auth(tcp_socket_client)
72
73 while True:
74 data=input('>>: ').strip()
75 if not data:continue
76 if data == 'quit':break
77
78 tcp_socket_client.sendall(data.encode('utf-8'))
79 respone=tcp_socket_client.recv(bufsize)
80 print(respone.decode('utf-8'))
81 tcp_socket_client.close()
82
83 if __name__ == '__main__':
84 ip_port=('127.0.0.1',9999)
85 bufsize=1024
86 client_handler(ip_port,bufsize)

浙公网安备 33010602011771号