一、Socket
当服务端传的东西大于客户端的最大值时怎么办?①改大buffer不行(有上限)②多传几次(用for循环必须要知道循环几次,所以不用for循环,用while)
服务端:
1 import os 2 import socket 3 server=socket.socket() 4 server.bind(("localhost",9999)) 5 6 server.listen() 7 8 while True: 9 conn,addr=server.accept() 10 print("new conn:",addr) 11 while True: 12 print("等待新指令") 13 data=conn.recv(700) 14 if not data : 15 print("客户端已断开") 16 break 17 print("执行指令:",data) 18 cmd_res=os.popen(data.decode()).read() #接受字符串,执行结果也是字符串 19 print("before send",len(cmd_res.encode())) 20 if len(cmd_res)==0: 21 cmd_res="cmd has no output..." 22 conn.send(str(len(cmd_res.encode())).encode("utf-8")) #先发大小给客户端 23 #字符串才能encode,cmd_res要encode 否则因为中文的原因长度会不相等 24 conn.send(cmd_res.encode("utf-8")) 25 print("send done") 26 server.close()
客户端:
1 import socket 2 client=socket.socket() 3 client.connect(("localhost",9999)) 4 5 while True: 6 cmd=input(">>:").strip() 7 if len(cmd) == 0 : 8 continue 9 client.send(cmd.encode("utf-8")) 10 cmd_res_size=client.recv(700) #接收命定结果的长度 11 print("命令结果:",cmd_res_size) 12 received_size=0 13 received_data=b"" 14 while received_size != int(cmd_res_size.decode()): 15 data=client.recv(700) 16 received_size+=len(data) #每次收到的有可能小于700,所以必须用len判断 17 # print(data.decode()) 18 received_data+=data 19 else: 20 print("cmd res receive done",received_size) 21 # cmd_res=client.recv(700) 22 print(received_data.decode()) 23 client.close()
socket粘包:两次send紧挨着,缓冲区将其打包成一次send,导致出错(用time.sleep()解决太low了,不要用),可以在两次send间插入一次交互,如在服务器端client_ack=conn.recv(700) #wait client to confirm,在客户端client.send("准备好接收了,loser可以发了".encode("utf-8")) PS:windows上粘包现象可能不会显示出来,但Linux一定会。
ftp server:1.读取文件名;2.检测文件是否存在;3.打开文件;4.检测文件大小;5.发送文件大小给客户端;6.等客户端确认;7.开始边读边发数据;8.发送md5;
服务端:
1 import hashlib 2 import os 3 import socket 4 server=socket.socket() 5 server.bind(("localhost",9999)) 6 7 server.listen() 8 9 while True: 10 conn,addr=server.accept() 11 print("new conn:",addr) 12 while True: 13 print("等待新指令") 14 data=conn.recv(700) 15 if not data : 16 print("客户端已断开") 17 break 18 cmd,filename=data.decode().split() 19 print(filename) 20 if os.path.isfile(filename): #判断文件是否存在 21 f=open(filename,"rb") 22 m=hashlib.md5() 23 file_size=os.stat(filename).st_size #文件大小 24 conn.send(str(file_size).encode()) #send file size 25 conn.recv(700) #wait for ack 26 for line in f: 27 m.update(line) 28 conn.send(line) 29 print("file md5",m.hexdigest()) #加上md5速度会慢下来 30 f.close() 31 conn.send(m.hexdigest().encode()) #send md5 32 print("send done") 33 server.close()
客户端:
1 import socket 2 import hashlib 3 client=socket.socket() 4 client.connect(("localhost",9999)) 5 6 while True: 7 cmd=input(">>:").strip() 8 if len(cmd) == 0 : 9 continue 10 if cmd.startswith("get"): 11 client.send(cmd.encode()) 12 server_response=client.recv(700) 13 print("server response",server_response) 14 client.send(b"ready to recv file") 15 file_total_size=int(server_response.decode()) 16 received_size=0 17 filename=cmd.split()[1] 18 f=open(filename+".new","wb") 19 m=hashlib.md5() 20 while received_size < file_total_size: 21 if file_total_size - received_size > 700:#要收不止一次 22 size=700 23 else: #最后一次了,剩多少就只收多少 24 size=file_total_size-received_size 25 #上面的判断是为了防止粘包。粘包只可能发生在最后一次 26 print("last receive:",size) 27 data=client.recv(size) 28 received_size+=len(data) 29 m.update(data) 30 f.write(data) 31 #print(file_total_size,received_size) 32 else: 33 new_file_md5=m.hexdigest() 34 print("file recv done",file_total_size,received_size) 35 f.close() 36 server_file_md5=client.recv(700) 37 print("server file md5:",server_file_md5) 38 print("client file md5:",new_file_md5) 39 client.close()
二、Socketsever
最主要的作用:并发处理。定义:简化网络任务服务器端的编写(对socket的再封装)
1.socketserver.
TCPServer;2.
socketserver.
UDPServer;3.
socketserver.
UnixStreamServer;4.
socketserver.
UnixDatagramServer
创建SocketServer的步骤:
1.你必须自己创建一个请求处理类,并且这个类要继承BaseRequestHandler,并且还要重写父类里的handle()。
2.你必须实例化TCPserver(其他也行),并且传递server ip 和你上面创建的请求处理类给这个TCPserver。
3.server.handle_request() #只处理一个请求(不常用)
server.handle_forever()#处理多个请求,永远执行
调用server_close()来关闭
跟客户端所有的交互都是在handle里完成的
1 import socketserver 2 3 class MyTCPHandler(socketserver.BaseRequestHandler): 4 """ 5 The request handler class for our server. 6 7 It is instantiated once per connection to the server, and must 8 override the handle() method to implement communication to the 9 client. 10 """ 11 12 def handle(self): 13 while True: 14 try: 15 # self.request is the TCP socket connected to the client 16 self.data = self.request.recv(1024).strip() #每一个客户端的请求过来都会实例化MyTCPHandler 17 print("{} wrote:".format(self.client_address[0])) 18 print(self.data) 19 # if not self.data:#客户端断了 20 # print(self.client_address,"断开了") 21 # break 22 # just send back the same data, but upper-cased 23 self.request.send(self.data.upper()) 24 except ConnectionResetError as e: 25 print("err:",e) 26 break 27 28 if __name__ == "__main__": 29 HOST, PORT = "localhost", 9999 30 31 # Create the server, binding to localhost on port 9999 32 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) #把ip地址和类当做参数传给TCPServer,TCPServer就开始监听 33 # 和实例化MyTCPHandler,拿handle()与客户端交互 34 35 # Activate the server; this will keep running until you 36 # interrupt the program with Ctrl-C 37 server.serve_forever()
多并发:每来一个请求,开启一个新线程。
让你的socketserver并发起来, 必须选择使用以下一个多并发的类
class socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
ForkingTCPServer 多进程(效果与多线程一样)。(在windows上不能执行,没有fork)
socketserver.
BaseServer
(server_address, RequestHandlerClass) 主要有以下方法:1.fileno() 返回文件描述符(一般用不到)2.handle_request() 处理单个请求
3.serve_forever(poll_interval=0.5) 每0.5秒检测一下是否有shutdown信号 4.service_actions() 5.shutdown() 6.server_close() 7.address_family 8.RequestHandlerClass 9.server_address 10.socket 11.allow_reuse_address 12.request_queue_size 13.socket_type 14.timeout 15.finish_request()(self.setup(),self.handle(),self.finish())16.get_request() 17.handle_error(request, client_address) 18.handle_timeout() 19.process_request(request, client_address) 20.server_activate() 21.server_bind() 22.verify_request(request, client_address)
作业:开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
服务器端:
1 import socketserver 2 import json,os 3 class MyTCPHandler(socketserver.BaseRequestHandler): 4 def put(self,*args): 5 '''接受客户端文件''' 6 cmd_dic=args[0] 7 filename=cmd_dic["filename"] 8 file_size=cmd_dic["size"] 9 if os.path.isfile(filename): 10 f=open(filename+".new","wb") 11 else: 12 f=open(filename,"wb") 13 self.request.send(b"200 ok") #返回客户端请求 14 receive_size=0 15 while receive_size<file_size: 16 data=self.request.recv(1024) 17 f.write(data) 18 receive_size+=len(data) 19 else: 20 print("file [%s] has uploaded..."%filename) 21 def handle(self): 22 while True: 23 try: 24 self.data = self.request.recv(1024).strip() #每一个客户端的请求过来都会实例化MyTCPHandler 25 print("{} wrote:".format(self.client_address[0])) 26 print(self.data) 27 cmd_dic=json.loads(self.data.decode()) 28 action=cmd_dic["action"] 29 if hasattr(self,action): 30 func=getattr(self,action) 31 func(cmd_dic) 32 self.request.send(self.data.upper()) 33 except ConnectionResetError as e: 34 print("err:",e) 35 break 36 if __name__ == "__main__": 37 HOST, PORT = "localhost", 9999 38 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) #把ip地址和类当做参数传给TCPServer,TCPServer就开始监听 39 server.serve_forever()
客户端:
1 import socket 2 import json 3 import os 4 class Ftpclient(object): 5 def __init__(self): 6 self.client = socket.socket() 7 def help(self): 8 msg=''' 9 ls 10 pwd 11 cd../.. 12 get filename 13 put filename''' 14 print(msg) 15 def connect(self,ip,port): 16 self.client.connect((ip, port)) 17 def interactive(self): 18 #self.authenticate() #用户登录 19 while True: 20 cmd=input(">>:") 21 if len(cmd)==0:continue 22 cmd_str=cmd.split()[0]#第一个值是指令 23 if hasattr(self,"cmd_%s"%cmd_str): 24 func=getattr(self,"cmd_%s"%cmd_str) 25 func(cmd) 26 else: 27 self.help() 28 def cmd_put(self,*args): 29 cmd_split=args[0].split() 30 if len(cmd_split) > 1: 31 filename=cmd_split[1] 32 if os.path.isfile(filename): 33 file_size=os.stat(filename).st_size 34 # msg_str="%s|%s"%(filename,file_size) #写死了,要考虑长远 35 msg_dic={ 36 "action":"put", 37 "filename":filename, 38 "size":file_size, 39 "overriden":True 40 } 41 self.client.send(json.dumps(msg_dic).encode("utf-8")) 42 print("send",json.dumps(msg_dic).encode("utf-8")) 43 #防止粘包,等服务器确认 44 server_response=self.client.recv(1024) 45 f=open(filename,"rb") 46 for line in f: 47 self.client.send(line) 48 else: 49 print("file upload success...") 50 f.close() 51 else: 52 print(filename,"is not exist") 53 def cmd_get(self): 54 pass 55 ftp=Ftpclient() 56 ftp.connect("localhost",9999) 57 ftp.interactive()