环境:python3.8?
参考:
https://blog.csdn.net/qq_36119192/article/details/83662680(socket模块使用)
https://blog.csdn.net/jing16337305/article/details/79856116(send()和sendall())
https://HdhCmsTestcnblogs测试数据/linhaifeng/articles/6129246.html(python网络编程)
https://segmentfault测试数据/a/1190000008707682(Thread模块实现线程并发)
socket通信原理简介:
????许多人把socket默认当为ip+端口,之前我也是这么认为的,直到看了:https://HdhCmsTestcnblogs测试数据/linhaifeng/articles/6129246.html的介绍,摘抄如下
????? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
????看起来比较抽象,其实socket就类似于一个市场,所谓的服务sever就类似于一个卖家,卖家不关心市场的建造和维护之类的事,他只需知道在市场可以等来买家,市场会保证他们的正常交易,所谓的客户client就类似于一个买家,一个卖家要卖东西,他要做四件事:
????1.走进市场(申请 socket)
????2.租一个摊位(bind(“IP+端口”))【ip+端口就相当于摊位号】
????3.摆好商品(listen(backlog))【backlog相当于有多少商品】
????4.等待客户(accept)
所以服务端的代码如下:
from?socket?import?* backlog=10 buffsize=1024 server=socket(AF_INET,SOCK_STREAM) server.bind(("127.0.0.1",5666)) server.listen(backlog) clientsocket,?clientaddr?=?server.accept() while?True: ????msg=clientsocket.recv(buffsize) ????print('client?msg:',?msg.decode('utf-8')) ????clientsocket.sendall(input('your?msg:').encode('utf-8'))
客户端就相对简单,它只要做两件事:
????1.走进市场(socket)
????2.找到对应摊位(connect)
如此就可以进行交流,所以客户端代码如下:
from?socket?import?* backlog=10 buffsize=1024 client=socket(AF_INET,SOCK_STREAM) client.connect(("127.0.0.1",5666)) while?True: ????client.sendall(input('your?msg:').encode('utf-8')) ????print('server?msg:',client.recv(buffsize).decode('utf-8'))
debug:
1.要想一次接受多行,必须用socket.sendall()方法,和send()的差别参考:https://blog.csdn.net/jing16337305/article/details/79856116
2.socket建通信必须用encode和decode方法编解码,否则会报错:
????TypeError: a bytes-like object is required, not 'str'
这大概是网络传输要求二进制的缘故(个人猜测,未证实)
实现了通信功能后,模拟ftp思路其实很简单,既然利用socket可以互相通信,那么ftp的功能,无非是在本地执行一些操作,把结果发送给对端
比如ls命令,思路就是利用os模块执行命令,把命令返回结果保存发送给对端
而这里的get,我则模拟了ftp的主动模式,当客户用get,我会让其输入文件名,判断文件是否存在,如果 存在则用“file send ready”作为标志传给客户端,客户端收到这个信息,则就跟上文所说的卖家卖商品,临时监听一个socket,然后发送服务端“file recv ready”作为标志告诉服务端已经准备好接收数据,然后服务端读文件,发送给客户端,客户端写文件,结束关闭socket,进入下一次循环
而put命令则模拟ftp的被动模式,与put命令的实现是一个相反的过程,服务端以"file recv ready"作为标志告诉客户端可以接受信息,客户端发送完毕进入下一次循环
cd命令比较简单,留给有兴趣的看官自己实现,不会的可以给我留言
本程序依然有大量的改进空间,比如上传文件没有判断是否已存在文件等等,以后有空回来完善
ftp服务端程序如下:
from?socket?import?* import?os,threading backlog?=?10 buffsize?=?1024 ftpserver?=?socket(AF_INET,?SOCK_STREAM) localip="192.168.1.1" ftpserver.bind((localip,?21)) ftpserver.listen(backlog) welcome_list?=?'''welcome!what?do?you?want?? ????put?get?ls?cd ''' def?cd(): ????pass def?put(): ????clientsocket.sendall('file?recv?ready'.encode('utf-8')) ????data_r?=?socket(AF_INET,?SOCK_STREAM) ????data_r.bind((localip,?20)) ????data_r.listen(1) ????clientsocket.sendall('please?input?your?filename:?'.encode('utf-8')) ????filename=clientsocket.recv(buffsize).decode('utf-8') ????filesize=clientsocket.recv(buffsize).decode('utf-8') ????recv_s,recv_addr=data_r.accept() ????datalen=0 ????with?open(filename,'w',encoding='utf-8')?as?write_f: ????????data=recv_s.recv(buffsize).decode('utf-8') ????????while?datalen<int(filesize): ????????????write_f.write(data) ????????????datalen=datalen+len(data) ????data_r.close() def?get(): ????result?=?os.popen('ls').read() ????clientsocket.sendall(result.encode('utf-8')+'please?input?filename:??'.encode('utf-8')) ????filename=clientsocket.recv(buffsize) ????if?os.path.isfile(filename.decode('utf-8')): ????????clientsocket.sendall('file?send?ready'.encode('utf-8')) ????????if?clientsocket.recv(buffsize).decode('utf-8')=='recv?ready': ????????????filesize=os.stat(filename.decode('utf-8')).st_size ????????????clientsocket.sendall(filename) ????????????clientsocket.sendall(str(filesize).encode('utf-8')) ????????????data_s=socket(AF_INET,?SOCK_STREAM) ????????????data_s.connect((clientadd[1],20)) ????????????with?open(filename.decode('utf-8'),'r',encoding='utf-8')?as?read_f: ????????????????for?i?in?read_f: ????????????????????data_s.sendall(i.encode('utf-8')) ????else: ????????clientsocket.sendall('file?not?exit'.encode('utf-8')) def?ls(): ????result=os.popen('ls').read() ????clientsocket.sendall(result.encode('utf-8')) welcome_msg?=?welcome_list?+?'current?dir?is?'?+?os.popen('pwd').read() order?=?['put',?'get',?'ls',?'cd'] var={1} class?handler(): ????def?__init__(self,clientsocket,clientaddr): ????????self.clientsocket=clientsocket ????????self.clientaddr?=clientaddr ????def?run(self): ????????self.clientsocket.sendall(welcome_msg.encode('utf-8')) ????????while?True: ????????????msg?=?self.clientsocket.recv(buffsize) ????????????print(msg.decode('utf-8')) ????????????if?msg.decode('utf-8')?not?in?order: ????????????????self.clientsocket.sendall('only?support?:?put?get?ls?cd?'.encode('utf-8')) ????????????else: ????????????????globals()[msg.decode('utf-8')]()?? def?worker(clientsocket,clientaddr): ????a=handler(clientsocket,clientaddr) ????a.run() while?True: ????for?i?in?range(backlog): ????????clientsocket,?clientaddr?=?ftpserver.accept() #print?clientaddr ????????t=threading.Thread(target=worker,args=(clientsocket,clientaddr)) ????????t.start()
客户端程序如下:
from?socket?import?* import?time,os backlog=10 buffsize=1024 localip="192.168.1.2" serverip="192.168.1.1" client=socket(AF_INET,SOCK_STREAM) client.connect((serverip,21)) def?data_recv(): ????data_s=socket(AF_INET,SOCK_STREAM) ????data_s.bind((localip,20)) ????data_s.listen(1) ????client.sendall('recv?ready'.encode('utf-8')) ????filename=client.recv(buffsize).decode('utf-8') ????filesize=client.recv(buffsize).decode('utf-8') ????recv_s,recv_addr=data_s.accept() ????datalen=0 ????with?open(filename,'w',encoding='utf-8')?as?write_f: ????????data=recv_s.recv(buffsize).decode('utf-8') ????????while?datalen<int(filesize): ????????????write_f.write(data) ????????????datalen=datalen+len(data) ????data_s.close() def?data_send(): ????client.recv(buffsize).decode('utf-8') ????filename?=?input('your?filename:?') ????print?(filename) ????if?not?os.path.isfile(filename): ????????print(os.popen('ls').read()) ????????filename=input('file?not?exist,input?again?:') ????client.sendall(filename.encode('utf-8')) ????filesize?=?os.stat(filename).st_size ????client.sendall(str(filesize).encode('utf-8')) ????data_s?=?socket(AF_INET,?SOCK_STREAM) ????data_s.connect((serverip,?20)) ????with?open(filename,?'r',?encoding='utf-8')?as?read_f: ????????for?i?in?read_f: ????????????data_s.sendall(i.encode('utf-8')) ????data_s.close() while?True: ????msg=client.recv(buffsize).decode('utf-8') ????if?msg==?'file?send?ready': ????????data_recv() ????elif?msg==?'file?recv?ready': ????????data_send() ????else: ????????print('server?msg:',msg) ????client.sendall(input('your?msg:').encode('utf-8'))
这里还引用了线程模块实现并发,一般都用serve_forever,python的线程有个特点是gil的存在使得它一个进程一次只能执行一个线程,达不到真正意义的并行,要并行只能用多进程的模式,所以python多线程的意义只存在于io密集型的操作(因为cpu在io等待的时间可以利用其计算性能),对于计算密集型的操作则反而会降低效率(io密集型,cpu一直执行运算,多线程只会导致需要额外的切换开销)
那么为什么不直接使用多进程呢?
这是因为进程的开销远远比线程大,进程好比办公室,线程好比工位,创建多进程就相当于多租了几间办公室,而多线程就相当于多搞了几个工位,线程之间可以共用办公室的大部分东西,而进程与进程之间就要跨办公室,这就涉及到几种办法:1.两个办公室之间凿同一个通道(比如pipe管道)2.在外面放一张公用的桌子,桌子的东西大家通用(比如共享变量)3.找别人帮忙(比如socket)等等,具体的等下一篇说明
查看更多关于python之利用socket模块实现通信,模拟ftp部分功能的详细内容...