一、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()