一.原理概述
1.1 FTP原理概述
文件传送协议FTP(File Transfer Protocol)是TCP/IP体系的一个重要协议,它采用Internet标准文件传输协议FTP的用户界面,向用户提供了一组用来管理计算机之间文件传输的应用程序。FTP是基于客户—服务器(C/S)模型而设计的,在客户端和FTP建立两个TCP连接。
FTP 的独特的优势同时也是与其它客户服务器程序最大的不同点就在于它在两台通信的主机之间使用了两条 TCP 连接,一条是数据连接,用于数据传送;另一条是控制连接,用于传送控制信息(命令和响应),这种将命令和数据分开传送的思想大大提高了 FTP 的效率,而其它客户服务器应用程序一般只有一条 TCP 连接。
图 1 给出了 FTP 的基本模型。客户有三个构件:用户接口、客户控制进程和客户数据传送进程。服务器有两个构件:服务器控制进程和服务器数据传送进程。在整个交互的 FTP 会话中,控制连接始终是处于连接状态的,数据连接则在每一次文件传送时先打开后关闭。
用户通过一个支持FTP协议的客户机程序,连接在远程主机FTP服务器程序。通过在客户端向服务器端发送FTP命令,服务器执行该命令,并将执行结果返回给客户端。由于控制连接的因素,客户端发送FTP命令,服务器都会有相应的应答。
1.2 FTP工作流程
FTP进行文件传输的基本工作流程主要分为四个阶段,即建立连接阶段,身份认证阶段、命令交互阶段和断开连接阶段。
- 建立连接阶段:该阶段是由FTP客户端通过TCP三次握手与FTP服务端进行建立连接。客户端向FTP服务器发出建立连接的请求,FTP服务器对请求进行应答。如果FTP服务器上的21端口是启用的,可以接受来自其他主机的请求,服务器给出应答220,即告诉客户端需要的FTP服务器已经准备好了。返回应答后,FTP服务端需要客户端进行身份认证,向客户端发送身份认证请求。
- 身份认证阶段:客户端需要向FTP服务提供登录所需的用户名和密码。FTP服务器对客户端输入的用户名和密码都会给出相应的应答。如果验证正确,将成功登录FTP服务器,此时会进入FTP会话。
- 命令交互阶段:在与FTP服务器会话中,用户可以执行FTP命令进行文件传输,如查看目录,切换目录,文件上传和下载等。客户端输入想要执行的FTP命令,服务器会给出应答。输入的命令正确,服务器会将命令的执行结果返回给客户端。执行结果返回完成后,服务器会继续等待客户端发出FTP命令。
- 断开连接阶段:当客户端不再与FTP服务器进行文件传输时,需要断开连接。客户端向FTP服务器发送断开连接请求,FTP服务器收到相应的请求会给出相应的应答。
FTP工作流程如图所示
1.3 TCP三次握手四次挥手
在实现简单FTP客户端的实现过程当中,基于SOCKET通信进行文件传输的底层协议是TCP传输协议,故有必要对TCP进行通信的基本流程进行详细说明。
TCP提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作。准备工作即TCP服务器西安创建传输控制快TCB,时刻准备接收客户进程的连接请求,此时服务器处于监听状态,服务器处于关闭状态。
三次握手
- 第一次握手:客户端给服务器发送一个同步报文段SYN,并指定客户端的初始序列号ISN,此时客户端处于SYN_SENT状态。
- 第二次握手:服务器收到来自客户端的同步报文段SYN后,会以自己的SYN报文作为应答,并且也指定了自己的初始序列号ISN。同时会把客户端的SEP+1确认序列号ack的值,表示自己收到了客户端的同步报文段SYN,此时服务器处于SYN_RECD的状态。
- 第三次握手: 客户端收到来自服务器的同步报文段SYN之后,会发送一个确定报文段ACK,以服务器的SEP+1作为ack的值,表明已经收到来自服务器的同步报文段ACK。客户端会进入建立状态,服务器确认报文段之后,也会进入建立状态,此时双方已经建立起连接,可以正常的发送数据。
当数据传输完成后,需要断开连接,断开连接的过程就是四次挥手。
四次挥手
- 第一次挥手;当客户端发起中断连接请求,即发送FIN报文。服务器接收到FIN报文后,向服务器表明,客户端已经没有数据要发送给服务端,但如果服务端数据还没有发送完成,不必着急关闭socket,可以继续发送数据。
- 第二次挥手,服务器先发送ACK,告诉客户端,关闭连接的请求已经收到,此时客户端处于FIN_WAIT状态,继续等待服务器发送FIN报文。
- 第三次挥手:当服务端的数据确认发送完毕后,向客户端发送FIN报文,告诉客户端,服务器的数据已经发送完毕,准备好关闭连接了
- 第四次握手:当客户端收到来自服务器的FIN报文后,会向服务器发送一个ACK然后进入TIME-WAIT状态,服务器收到ACK后,就可以断开连接,客户都安等待了2MSL后依然没有收到回复,证明服务端已经正常关闭,此时客户端也关闭连接了。
在进行FTP简单客户端实现的过程当中,以python语言为基础,调用socket包实现模拟FTP文件传输,所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信的句柄。应用进程通过“套接字”向网络发出请求或者应答网络请求。套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。可以将套接字视作不同主机进程之间进行双向通信的断电,它构成了单个主机内及整个网络间的编程界面。
1.4 socket通信过程
Socket通信的连接过程可以分为三个步骤:服务器监听、客户端请求,连接确认。
- 服务器监听,是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
- 客户端请求,是指由客户端提出请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端的套接字的地址和端口号,然后向服务器发送请求。
- 连接确认,是指当服务器端套接字监听或者说接收到客户端套接字的连接请求,他就响应客户端套接字的请求,建议一个新的线程,把服务器套接字的描述发给客户端,一旦客户端确认了此描述,连接就成功建立。而服务器端套接字继续处于监听状态,继续接受其他客户端套接字的连接请求。
在进行具体实现时,首先利用socket包中的socket函数,将AF_INET以及SOCK_STREAM作为参数初始化信道,然后利用bind函数,将一个本地协议地址赋予一个套接字,bind函数绑定的IP地址必须属于其所在主机的网络接口之一,服务器在绑定端口后,利用listen()函数,将一个未连接的套接字装换位一个被动套接字,指示内核应接受指向该套接字的连接请求,调用listen函数将导致套接字从CLOSE状态转换为LISTEN状态,第二个参数规定了内核应为相应套接字排队的最大连接个数,即当客户端与服务器连接进行数据交互时,新的未连接的套接字处于等待连接状态的最大个数。
在执行完listen后,服务器端TCP会将到达的数据进行排队,最大数量为相应已连接套接字的接收缓冲区大小,执行完listen,而没有执行accept,客户是可以成功建立连接的,只不过该连接被加入到已连接队列中,当调用accept时会被提取出来。Accept函数有TCP服务器调用,用于从已完成队列中列头返回下一个已完成连接,如果队列为空,则进程被投入睡眠,accept成功返回值是由内核自动生成一个全新的套接字,代表与返回客户的TCP连接,函数返回的第一个参数为监听套接字,第二个为客户端的套接字号。
二.网络拓扑结构图
FTP文件传输协议网络拓扑结构图为星型拓扑结构
三.功能设计
3.1 文件上传功能
文件传输协议的目的旨在消除或减少由不同操作系统下处理文件的不兼容性。文件上传功能是指客户在远程能够得到服务器响应,从服务器上下载相应的资源。在实际实现过程中,首先用套接字建立起连接,选择TCP协议,即流模式传输协议作为我们通信基础协议,客户端将数据进行编码转为相应的二进制文件,然后利用send()函数在客户端将数据发送出去,然后服务端可以使用recv()函数将客户端发送的二进制数据进行接收,再用客户端对应编码的方式进行解码,得到原始数据。编码方式默认在windows操作系统上编码的方式为gbk,在linux操作系统上编码的方式为utf-8,在此说明,windows操作系统与linux操作系统在充当服务器的角色上,更加倾向于选择linux,因为windows操作系统更加注重客户体验而linux操作系统更加倾向于稳定。
3.2文件下载功能
文件下载功能,是客户端向服务器请求获取服务器相应的数据,原理同文件上传一样,都是利用send()函数和recv()函数进行发送数据和接收数据。
3.3目录功能
对客户端来说,登入客户端即进入了自己的根目录,用户可以查看自己所有目录文件夹下的所有文件,同时也可以查看服务端的目录文件,切换当前的目录。
3.4文件校验功能
在文件传输的过程中,文件在传输过程中是否会发生错误,以及传输的文件内容是否准确,对文件传输至关重要。本次实验,模拟实际传输过程,在服务器发送数据之前,制作报头,并且将整个文件作为输入,利用python里面的hashlib库对文件取相应的哈希值,将此哈希值作为报头内的信息发送给客户端,当文件达到客户端时,对文件同样取哈希,得到哈希值,将此哈希值和报头内发送的哈希值对比,若相同,则在文件传输的过程中,没有数据出现差错;若不同,则证明在传输数据的过程当中,出现了差错。
3.5多线程并发通信
服务器在响应客户端时,并不是只允许一台客户机对服务端进行通信,实际的情况是多台客户机需要同时对服务器上的资源进行数据传输,故对于每一个访问服务器资源的客户端来说,服务器都应该接收其建立连接的请求并且对发送的请求做出应答。此时,采用多线程,就可以处理好这一问题。对于服务器来说,客户端进行通信的过程可以简化为建立连接、进行数据交换这两个过程。客户端需要不断地监控想要与服务器建立连接的请求,一旦建立好连接,后续的数据传输过程就可以由一个线程函数进行控制。一个形象的比喻这个过程可以描述为;服务器相当于一家营业的饭店,而服务器不断地监听和客户端建立连接的过程相当于饭店外面的招待员,会将想要吃饭的客人引入饭店,而进行数据交换的过程,可以理解为客人在饭店吃饭的过程,不论客户提什么需求,都应该由服务员去满足客户的需求,最好的情况是一桌客人对应一个服务员来服务。招待员就是源源不断地将想要吃饭的客人带入饭店,在数量上,招待员的数量可以只有一个,而服务员必须得有若干个,这就是多线程在文件传输当中的重要作用。
四.关键程序代码
4.1 建立连接函数
4.2线程处理函数
def action_thread(conn): base_path = r'D:\computercode\文件传输\sever' while True: # 1、接受命令 res = conn.recv(1024) if not res: break # 解析命令 cmds = res.decode('utf-8').split() # print(cmds) if cmds[0] == 'get': filename = cmds[1] # 文件名称,用于制作报头以及确定文件大小 sendmsg(filename, conn,base_path) elif cmds[0] == 'post': download_to_file(conn,base_path) elif cmds[0] == 'exit': print('客户端已经断开连接!') break elif cmds[0]=='dirs': send_sever_list(conn) elif cmds[0]=='paths': send_sever_path(conn) elif cmds[0]=='chdir': os.chdir(cmds[1]) base_path=os.getcwd() send_sever_list(conn)
4.3文件下载函数
4.4文件上传函数
4.5哈希函数
4.6文件目录函数
#获取当前文件所在文件路径def get_cur_dir(): return os.getcwd()#获取当前文件夹下所有文件def get_cur_all(cur_dir): return os.listdir(cur_dir)#发送当前文件夹下的目录列表def send_sever_list(conn): rec_msg_list = get_cur_all(get_cur_dir()) rec_msg_str = ' '.join(rec_msg_list) rec_msg_bytes = rec_msg_str.encode('utf-8') conn.send(rec_msg_bytes)#发送当前文件所在路径def send_sever_path(conn): cur_dir = get_cur_dir() cur_dir_bytes = cur_dir.encode('utf-8') conn.send(cur_dir_bytes)
如果需要全部代码,关注博主并私信回复需要代码,博主看到后会第一时间回复的。
来源地址:https://blog.csdn.net/weixin_56935264/article/details/129622803