day8(老男孩-Python3.5-S14期全栈开发)

作者:赵俊            发布日期:2020/09/15

三、Socket实现简单的ssh客户端

1、os.system和os.popen的区别

  os.system     返回值是脚本的退出状态码,只有0,1,2三种状态,直接输出命令结果在控制台

  os.popen      返回值是一个内存地址,需要用read()方法取出返回值

  print(os.system("dir"))
  print(os.popen("ipconfig").read())

2、注意

   服务器端绑定IP和端口,客户端连接IP和端口,传递的参数为元祖(“localhost”,9999),容易忽略少个括号

3、代码

  服务器端

 1 # Author:ZHJ
 2 import socket
 3 import os
 4 
 5 server = socket.socket()
 6 server.bind(("localhost",9999))
 7 server.listen(10)
 8 
 9 # print(server.accept())
10 while True:
11 
12     conn, add = server.accept()
13     # print(add)
14     print("%s:%s连接服务器" % (add[0],add[1]))
15     while True:
16 
17         data = conn.recv(1024)
18         if not data:
19             print("%s:%s断开服务器连接" % (add[0],add[1]))
20             break
21         cmd_data = os.popen(data.decode("utf-8")).read()
22         print(len(cmd_data))
23         if not cmd_data:
24             conn.send("指令错误".encode("utf-8"))
25         else:
26             conn.send(cmd_data.encode("utf-8"))

  客户端

 1 # Author:ZHJ
 2 import socket
 3 
 4 client = socket.socket()
 5 client.connect(("localhost",9999))
 6 while True:
 7 
 8     data = input(">>")
 9     if len(data) == 0:
10         continue
11     client.send(data.encode("utf-8"))
12 
13     print(client.recv(512).decode("utf-8"))

这样有一个问题需要解决

  在客户端缓冲区比较小时,服务器返回的数据大于客户端接收缓冲区,这就造成返回的数据不能一次性接收完毕,导致下一次命令执行时,返回的是上一次命令剩余的数据

四、Socket实现简单的ssh服务端(解决数据缓冲区不足以接收数据)

 解决缓冲区小不足以接收全部数据的方法:

  服务器发送数据时,把要发送数据的大小也发给客户端,然后客户端比较实际接收大小和总大小的差额来决定是否退出接收循环

期间有一个坑,需要注意↓

  在比较长度大小时,必须都是编码过得bytes型,或者解码的str型(因为同样的数据,bytes型和str型长度是不一样的)

 len()——返回对象的长度(元素个数)

 例如下:

>>> len("")
1
>>> len("".encode("utf-8"))
3
>>>

 解决方法的具体代码

服务器端

 1 # Author:ZHJ
 2 import socket
 3 import os
 4 
 5 server = socket.socket()
 6 server.bind(("localhost",9999))
 7 server.listen(10)
 8 
 9 # print(server.accept())
10 while True:
11 
12     conn, add = server.accept()
13     # print(add)
14     print("%s:%s连接服务器" % (add[0],add[1]))
15     while True:
16 
17         data = conn.recv(1024)
18         if not data:
19             print("%s:%s断开服务器连接" % (add[0],add[1]))
20             break
21         cmd_data = os.popen(data.decode("utf-8")).read()
22         # print(len(cmd_data))
23         #服务器端增加发送返回结果的大小(建议计算“字节”的大小)
24         #步骤-先把返回数据变成字节-取字节长度-把整型长度变成字符串-把长度字符串变成字节发送
25         conn.send(str(len(cmd_data.encode("utf-8"))).encode("utf-8"))
26         if not cmd_data:
27             conn.send("指令错误".encode("utf-8"))
28         else:
29             conn.send(cmd_data.encode("utf-8"))

客户端

 1 # Author:ZHJ
 2 import socket
 3 
 4 client = socket.socket()
 5 client.connect(("localhost",9999))
 6 while True:
 7 
 8     data = input(">>")
 9     if len(data) == 0:
10         continue
11     client.send(data.encode("utf-8"))
12     # 客户端先接受总大小,然后转成整型数字,以便比较大小
13     cmd_total_size = int(client.recv(512).decode("utf-8"))
14     print(cmd_total_size)
15     cmd_res_size = 0 # 实际接收数据大小
16     rev_data = b"" #接收数据存储
17     while cmd_res_size < cmd_total_size:
18         rev_data += client.recv(512)
19         cmd_res_size = len(rev_data)#接收数据的 长度赋值给cmd_res_size,以便每次循环比较大小
20     print(rev_data.decode("utf-8"))

五、鸡汤

 略

六、Socket粘包1

 粘包的问题解决思路,服务器给客户端发送完大小,客户端发送一个确认指令,服务器接收这个指令(可以不做任何处理)再发送有效数据,这样可以把上下两条发送分隔开

七、Socket 编程FTP 文件传输

 服务器

 1 # Author:ZHJ
 2 import socket
 3 import os
 4 
 5 server = socket.socket()
 6 server.bind(("localhost",9999))
 7 server.listen(10)
 8 
 9 # print(server.accept())
10 while True:
11 
12     conn, add = server.accept()
13     # print(add)
14     print("%s:%s连接服务器" % (add[0],add[1]))
15     while True:
16 
17         fileName = conn.recv(1024)
18         if not fileName:
19             print("%s:%s断开服务器连接" % (add[0],add[1]))
20             break
21         if os.path.isfile(fileName.decode("utf-8")):
22             f = open(fileName,"rb")
23             fileSize = os.stat(fileName).st_size
24             print(fileSize)
25             conn.send(str(fileSize).encode("utf-8"))
26             print("来自客户端:%s" % conn.recv(1024).decode("utf-8"))
27             conn.send(f.read())
28             f.close()

客户端

 1 # Author:ZHJ
 2 import socket
 3 
 4 client = socket.socket()
 5 client.connect(("localhost",9999))
 6 while True:
 7 
 8     fileName = input(">>")
 9     if len(fileName) == 0:
10         continue
11     client.send(fileName.encode("utf-8"))
12     # 客户端先接受总大小,然后转成整型数字,以便比较大小
13     file_total_size = int(client.recv(512).decode("utf-8"))
14     client.send("我已经收到文件大小,请发文件".encode("utf-8"))
15     print(file_total_size)
16     file_rev_size = 0 # 实际接收数据大小
17     rev_data = b"" #接收数据存储
18     f = open(fileName.split(".")[0] + "副本."+ fileName.split(".")[1], "wb")
19     while file_rev_size < file_total_size:
20         rev_data = client.recv(512)
21         f.write(rev_data)
22         file_rev_size += len(rev_data)#接收数据的 长度赋值给cmd_res_size,以便每次循环比较大小
23     else:
24         print("done")
25     f.close()

八、Socket粘包深入编码(内涵进度条编写)

1、进度条的编写 

  利用这个语句   sys.stdout.write("我和我的祖国"),这个语句输出没有换行,如果在写的字符串前加\r(回车符),那么光标会退到行首写,会覆盖原来的输出

2、又一个坑,前面把文件迭代了,后面再读文件,读不出来,因为迭代后光标到最末尾了

3、发送和接收交替进行可以避免粘包(往往循环时,不知道什么时候结束,还得用发送总大小来判断是否发送完毕)

带有MD5的代码

服务器

 1 # Author:ZHJ
 2 import socket
 3 import os
 4 import hashlib
 5 server = socket.socket()
 6 server.bind(("localhost",9999))
 7 server.listen(10)
 8 
 9 # print(server.accept())
10 while True:
11 
12     conn, add = server.accept()
13     # print(add)
14     print("%s:%s连接服务器" % (add[0],add[1]))
15     while True:
16 
17         fileName = conn.recv(1024)
18         if not fileName:
19             print("%s:%s断开服务器连接" % (add[0],add[1]))
20             break
21         if os.path.isfile(fileName.decode("utf-8")):
22             f = open(fileName,"rb")
23             fileSize = os.stat(fileName).st_size
24             print(fileSize)
25             conn.send(str(fileSize).encode("utf-8"))
26             print("来自客户端:%s" % conn.recv(1024).decode("utf-8"))
27             m = hashlib.md5()
28             for line in f:
29                 m.update(line)
30                 # print(len(line))
31                 con = conn.send(line)
32                 # print(con)
33             conn.recv(1024)# 可以避免粘包
34             f.close()
35             print(m.hexdigest())
36             conn.send(m.hexdigest().encode())

客户端

 1 # Author:ZHJ
 2 import socket
 3 import hashlib
 4 import sys
 5 client = socket.socket()
 6 client.connect(("localhost",9999))
 7 while True:
 8 
 9     fileName = input(">>")
10     if len(fileName) == 0:
11         continue
12     client.send(fileName.encode("utf-8"))
13     # 客户端先接受总大小,然后转成整型数字,以便比较大小
14     file_total_size = int(client.recv(512).decode("utf-8"))
15     client.send("我已经收到文件大小,请发文件".encode("utf-8"))
16     print(file_total_size)
17     file_rev_size = 0 # 实际接收数据大小
18     rev_data = b"" #接收数据存储
19     f = open(fileName.split(".")[0] + "副本."+ fileName.split(".")[1], "wb")
20     m = hashlib.md5()
21     while file_rev_size < file_total_size:
22         rev_data = client.recv(1024)
23         # print("每次接收的大小:%s"%len(rev_data))
24         f.write(rev_data)
25         m.update(rev_data)
26         file_rev_size += len(rev_data)#接收数据的 长度赋值给cmd_res_size,以便每次循环比较大小
27         # print("接收的总大小:%s" % file_rev_size)
28         bar = int((file_rev_size/file_total_size)*100)#进度百分比
29         sys.stdout.write("\r"+int(bar/5)*""+"%s%%"%bar)#进度条显示
30 
31     else:
32         print("")
33         print("done")
34         client.send(b"ok")# 可以避免粘包
35         rev_md5 = client.recv(512).decode("utf-8")
36         print("源文件的md5:%s" % rev_md5)
37         print("接收文件的md5:%s" % m.hexdigest())
38     f.close()

 

九、SocketServer

1、socketserver 提供多用户也就是并发功能,其实就是对socket的一个封装,使其更简单

2、分为几种类型

  class socketserver.TCPServer(server_addressRequestHandlerClassbind_and_activate=True)

  class socketserver.UDPServer(server_addressRequestHandlerClassbind_and_activate=True)

  底下两个不常用

  class socketserver.UnixStreamServer(server_addressRequestHandlerClassbind_and_activate=True)

  class socketserver.UnixDatagramServer(server_addressRequestHandlerClass,bind_and_activate=True)

3、创建一个socketServer需要以下几步

  1、你必须 自己创建一个请求处理类,并继承BaseRequestHandler类,还要重写父类的handle()方法,和客户端所有的交互都在handle里完成

  2、必须实例化一个TCPServer或UDPServer,并传递ip和上面创建的请求处理类给这个TCPServer或UDPServer

  3、实例.handle_request()(只处理一个请求)或者 实例.server_forever(poll_interval=0.5)(处理多个请求,参数意思每0.5秒检测一次关闭请求)

  4、实例.server_close()关闭套接字

简单的socketserver代码实例

服务器

 1 # Author:ZHJ
 2 # import socketserver
 3 from socketserver import *
 4 
 5 
 6 class MyRequest(BaseRequestHandler):
 7     def handle(self):
 8         while True:
 9             self.data = self.request.recv(1024).strip()
10             if not self.data:
11                 print("客户端断开")
12                 break
13             print(self.data)
14             self.request.send(self.data.upper())
15 
16 server = TCPServer(("localhost",9999), MyRequest)
17 server.serve_forever()
18 server.close_request()

客户端

 1 client = socket.socket()# 创建一个socket对象
 2 client.connect(("localhost",9999))# 连接指定ip和端口
 3 while True:
 4     da = input("send>>>")
 5     if len(da) == 0:#不能send空字符
 6         continue
 7     client.send(da.encode("utf-8"))
 8     # print("\033[1;31msend:%s\033[0m" % da)
 9     data = client.recv(1024)
10     print("\033[1;32mrecv:%s\033[0m" % str(data,encoding="utf-8"))
11 client.close()

十、SocketServer多并发

 实例化时使用ThreadingTCPServer就可以多线程实现多并发

 实例化时使用ForkingingTCPServer就可以多进程实现多并发(windows上用不了,在windows上无法使用fork创建新进程,linux上可以)

例:server = ThreadingTCPServer(("localhost",9999), MyRequest)

十一、作业,多用户在线FTP程序

开发一个支持多用户在线的FPT程序

要求:

  1、用户加密认证

  2、允许同时多用户登录

  3、每个用户有自己的家目录,且只能访问自己的家目录

  4、对用户进行磁盘配额,每个用户的可用空间不同

  5、允许用户在FTPserver上随意切换目录

  6、允许用户查看当前目录下文件

  7、允许用户上传和下载文件,保证文件一致性

  8、文件传输过程中显示进度条

  9、附加功能,支持文件的断点续传

posted @ 2024-04-12 10:49  daban2009  阅读(44)  评论(0编辑  收藏  举报