1.实现ftp上传、下载功能
1.1 循环接收数据直到接收完毕
server端接收client发送的命令(比如说ifconfig),然后server端将命令执行结果反馈给客户端,这时候有个问题,server端是一次性的把数据发给client了,但是client怎么接收全部呢?比如client.recv(1024)调大接收的值行吗?增加接收的次数行吗?
调大接收的值,多大合适,每次server端发送的数据大小都不同;
增加接收的次数,那增加到接收几次合适呢,也是不定值。
循环接收buffer缓冲里的数据,一直到buffer里没有数据,这样也不行,因为buffer里可能存储着好多条命令的执行结果,一次性接收过来,显示在client端可能就是杂乱的数据了。除非server和client是一对一的,而且是只能发一条命令接收一条结果再发送一条命令,这样单线程的走。
所以解决办法是,server在发送数据前先将要发的数据总大小发送给client,然后client接收这个总大小的字节即可接收完全部数据。
#!/usr/bin/env python #coding:utf-8 import socket,os server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('localhost',9543)) server.listen() while True: conn,addr = server.accept() while True: data = conn.recv(10240) if not data:break result = os.popen(data.decode()).read() if result == '': #命令不存在的话result会为空 result = 'command not found.' conn.send(str(len(result.encode('UTF-8'))).encode('UTF-8')) #这里把result.encode(),是因为如果result里包含中文的话,会有点问题。 #a = '期望' #len(a) 结果是2,计算的是字符数量 #len(a.encode()) 结果是6,计算的是字节数量。 #发过去的大小,一个中文算一个数量,client接收到后,是按字节计算的,所以一个中文算3个数量,为了统一,就在发送长度的时候直接encode一下。 conn.recv(1024) conn.send(result.encode('UTF-8')) server.close() import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('localhost',9543)) while True: u_input = input('>') if len(u_input) == 0:continue client.send(u_input.encode('UTF-8')) data_size = client.recv(1024).decode() client.send('OK'.encode()) receive_size = 0 print(data_size) print(type(data_size)) while receive_size != int(data_size): #只要已接收的字符小于server发送过来的字符数量,就一直接收。#如果server端发送字节长度时没有encode,同时发送的内容包含中文,当内容接收完毕后,就会出现receive_size 大于 data_size(包含中文越多相差值越大),因为一个中文在data_size那算一个数量,但是在receive_size那算三个数量。 data = client.recv(1024) receive_size += len(data) print(data.decode()) else: print('total length is :',receive_size) client.close()
1.2 粘包的概念与解决
server: conn.send('123'.encode()) conn.send('456'.encode()) server端连续发送两个数据 client: client.recv(1024) client.recv(1024) client端接收server发送的两个数据 因为server端的两次发送时紧挨着,所以在windows上可能出现粘包,在linux上肯定会出现粘包。 粘包就是把两次send的内容一起放到了buffer里,client第一次recv时就能取到所有数据,就会client收到的结果造成混乱。 #解决粘包: #方法一,不推荐:可以在两次send中间插一个time.sleep(0.5),这样两个包就会分开发了,不会黏在一起;但是每次都会停顿0.5秒,能明显感觉到延迟;比如股票这种强调速度的应用,用sleep根本不行。 #方法二:在两次send中间插入一个recv server: conn.send(str(len(result.encode('UTF-8'))).encode('UTF-8')) conn.recv(1024) #接收client一个消息后,再继续发送第二个send。 conn.send(result.encode('UTF-8')) client: data_size = client.recv(1024).decode() client.send('OK'.encode()) #接收到第一条内容后,给server发送一个消息,让server代码继续执行 data = client.recv(1024) #方法三: client端只接收该接收的部分,比如server给client发了一个文件(大小为2048字节),紧接着又发了一个md5值(32字节),那客户端可以先client.recv(2048),然后client.recv(32),这样肯定不会收错了,需要注意的是如果内容包含中文,必须先对内容编码(encode)再len计算内容长度。
1.3 下例示范一个简单的ftp应用
import socket import time import hashlib import os server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('localhost',956)) server.listen() while True: conn,addr = server.accept() while True: cmd,filename = conn.recv(1024).decode().split() if len(cmd) == 0 or len(filename) == 0: #命令或文件为空就退出 break if os.path.isfile(filename): #判断是否存在文件 filesize = os.stat(filename).st_size #获取文件长度 print(filesize) conn.send(str(filesize).encode()) conn.recv(1024) m = hashlib.md5() with open(filename) as f: for line in f: m.update(line.encode()) #计算一行md5 conn.send(line.encode()) server_md5 = m.hexdigest() #获取最终的md5值 conn.send(server_md5.encode()) server.close() import socket import hashlib client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('localhost',956)) while True: u_input = input('>') if len(u_input) == 0: continue cmd,filename = u_input.split() if cmd.startswith('get'): client.send(u_input.encode()) file_size = int(client.recv(1024).decode()) #接收文本大小 print(file_size) client.send('ok'.encode()) received_size = 0 m = hashlib.md5() with open(filename + '.new','w') as f: while received_size < file_size: if file_size - received_size > 1024: size = 1024 else: size = file_size - received_size #如果剩余需要接收的字节小于1024,就只接收剩余大小的字节,防止接收到粘包数据。 data = client.recv(size) received_size += len(data) #将已接收的大小求和 m.update(data) f.write(data.decode()) else: print('receitotal size is :%s' % received_size) client_md5 = m.hexdigest() server_md5 = client.recv(1024).decode() print('client_md5:%s' % client_md5) print('server_md5:%s' % server_md5) else: print('get please.') continue client.close()
2.SocketServer
socket很好用,但是无法实现并发,所以就有了SocketServer,SocketServer是对socket的再封装,可实现网络并发处理。
创建一个socketserver 至少分以下几步:
- First, you must create a request handler class by subclassing the
BaseRequestHandler
class and overriding itshandle()
method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()
orserve_forever()
method of the server object to process one or many requests. - Finally, call
server_close()
to close the socket.
示例代码: