内容会有少许删除,但是面试重点有提到,字体加粗的请注意,尤其hash设计那块。
后序代码和文章整理好会压缩上传。
文件传输协议FTP(File Transfer Protocol ,由 RFC959描述)。
FTP工作在TCP/IP协议的应用层,其传输层使用的是TCP协议,基于C/S模式工作
ASCII码文件, 这是FTP默认的文本格式
EBCDIC码文件,它也是一种文本类型文件,用8位代码表示一个字符,该文本文件在传输时要求两端都使用EBCDIC码(文本文件)
图像文件,也称二进制文件类型,发送的数据为连续的比特流,通常用于传输二进制文件(二进制文件就是常见的乱码文件)
【二进制文件和ASCII码文件区别?】
【二进制文件和文本文件在物理上没有差别:由一系列的比特位组成的。差别在于逻辑上不同的解析方式。
以ASCII码进行传输会将 Windows中的\r \n Linux中为\n
以二进制方式传输文件,那么不做任何解析。】
本地文件,字节的大小由本地主机定义,也就是说每一个字节的比特数据由发送方规定
文件结构,这是FTP默认的方式,文件被认为是一个连续的字节流,文件内部没有表示结构的信息
记录结构,该结构只适用于文本文件(ASCII码或EBCDIC码文件),记录结构文件是由连续的记录构成的
页结构,在FTP中,文件的一个部分被称为页,当文件是由非连续的多个部分组成时,使用页结构,这种文件成为随机访问文件,每页都带有页号发送,以便收方能随机地存储各页
流方式,这是支持文件传输的默认方式,文件以字节流形式传输
块方式,文件以一系列块来传输,每块前面都带有自己的头部,头部包含描述子代码域(8位)和计数域(16位),描述子代码域定义数据块的结束标志等内容,计数域说明了
数据块的字节数
压缩方式,用来对连续出现的相同字节进行压缩,现在已经很少使用
在客户端,通过交互的用户界面,客户从终端输入启动FTP的用户交互式命令
客户端TCP协议层根据用户命令给出的服务器IP地址,向服务器提供FTP服务的21端口(该端口是TCP协议层用来传输FTP命令的端口)发出主动建立连接的请求,服务器收到请求后,通过3次握手,就在进行FTP命令处理的用户协议解释器进程和服务器协议解释器进程之间建立一条TCP连接
当客户通过交互式的用户界面,向FTP服务器发出要下载服务器上某一文件的命令时,该命令被送到用户协议解释器
当客户发出退出FTP的交互式命令时,控制连接被关闭,FTP服务结束
命令 | 功能说明 |
---|---|
访问控制命令 | |
USER | 服务器上的用户名 |
PASS | 用户口令 |
CWD或XCWD | 改变工作目录 |
CDUP或XCUP | 回到上一层目录 |
QUIT | 退出 |
ACCT | |
SMNT | |
REIN | |
传输参数命令 | |
PORT | 数据端口,主要向服务器发送客户数据连接端口,格式为PORT h1,h2,h3,h4,p1,p2,其中32位的ip地址用h1,h2,h3,h4表示,16位的TCP端口号用p1,p2表示 |
PASV | 此命令要求服务器数据传输进程在随机端口上监听,进入被动接收请求的状态 |
TYPE | 文件类型,可指定ASCII码,二进制等 |
STRU | 文件结构 |
MODE | 传输模式 |
服务命令 | |
STOR | 保存文件,向服务器传输文件,如果文件已存在,原文件将被覆盖,如果文件不存在,则新建文件 |
APPE | 与STOR功能类似,但如果文件在指定路径已存在,则把数据附加到原文件尾部,如果不存在,则新建文件 |
LIST | 列出目录详细清单 |
NLIST | 列出名字列表 |
REST | 重新开始,参数代表服务器要重新开始的那一点,它并不传送文件,而是略过指定点前的数据,此命令后应该跟其他要求文件传输的FTP命令 |
ABOR | 异常终止,此命令通知服务器终止以前的FTP命令和与之相关的数据传输,如果先前的操作已完成,则没有动作,返回226,如果没有没有完成,返回225 |
PWD或XPWD | 打印当前目录 |
MKD或XMKD | 新建目录 |
RMD或XRMD | 删除目录 |
DELE | 删除文件 |
RNFR,RNTO | 重命名 |
SITE CHMOD | 修改权限 |
SYST | 获取系统信息 |
FEAT | 服务器特性 |
SIZE | 获得文件大小 |
STAT | 返回服务器状态 |
NOOP | 该命令不指定任何动作,只是要求服务器返回OK响应 |
HELP | 帮助 |
STOU | 暂不实现 |
ALLO | 暂不实现 |
服务器通过控制连接发送给客户端的FTP应答,由ASCII码形式的3位数字和一行文本提示信息组成,它们之间用一个空格分隔
应答信息的每行文本以回车和换行对结尾,如果需要产生一条多行的应答,第一行在3位数字应答代码之后包含一个连字符“-”,而不是空格符,最后一行包含相同的3位数字应答码,后跟一个空格符
确保在文件传输过程中的请求和正在执行的动作保持一致
保证用户程序总是可以得到服务器的状态信息,用户可以根据收到的状态信息对服务器是否正常执行了有关操作进行判定
第一位数字标识了响应是好,是坏或者未完成
应答 | 说明 |
---|---|
1yz | 预备状态 |
2yz | 完成状态 |
3yz | 中间状态 |
4yz | 暂时拒绝状态 |
5yz | 永久拒绝状态 |
第二位数相应大概是发生了什么错误(比如,文件系统错误,语法错误等)
应答 | 说明 |
---|---|
x0z | 语法 –这种响应指出了有语法错误 |
x1z | 信息 –对于请求信息的响应,比如对状态或帮助的请求 |
x2z | 连接 –关于控制连接和数据连接的响应 |
x3z | 身份验证和账户 –对登录过程和账户处理的响应 |
x4z | 为使用 |
x5z | 文件系统 –请求传输时服务器文件系统的状态或其他文件系统动作状态 |
第三位为第二位数字更详细的说明
如:
|–|--|
500 | Syntax error, command unrecognized(语法错误,命令不能被识别) |
---|---|
501 | (参数语法错误) |
502 | (命令没有实现) |
503 | (命令顺序错误) |
504 | (没有实现这个命令参数) |
1.74 ftp应答示例
#define FTP_DATACONN | 150 |
---|---|
#define FTP_NOOPOK | 200 |
#define FTP_TYPEOK | 200 |
#define FTP_PORTOK | 200 |
#define FTP_EPRTOK | 200 |
#define FTP_UMASKOK | 200 |
#define FTP_CHMODOK | 200 |
#define FTP_EPSVALLOK | 200 |
#define FTP_STRUOK | 200 |
#define FTP_MODEOK | 200 |
#define FTP_PBSZOK | 200 |
#define FTP_PROTOK | 200 |
#define FTP_OPTSOK | 200 |
#define FTP_ALLOOK | 202 |
#define FTP_FEAT | 211 |
#define FTP_STATOK | 211 |
#define FTP_SIZEOK | 213 |
#define FTP_MDTMOK | 213 |
#define FTP_STATFILE_OK | 213 |
#define FTP_SITEHELP | 214 |
#define FTP_HELP | 214 |
#define FTP_SYSTOK | 215 |
#define FTP_GREET | 220 |
#define FTP_GOODBYE | 221 |
#define FTP_ABOR_NOCONN | 225 |
#define FTP_TRANSFEROK | 226 |
#define FTP_ABOROK | 226 |
#define FTP_PASVOK | 227 |
#define FTP_EPSVOK | 229 |
#define FTP_LOGINOK | 230 |
#define FTP_AUTHOK | 234 |
#define FTP_CWDOK | 250 |
#define FTP_RMDIROK | 250 |
#define FTP_DELEOK | 250 |
#define FTP_RENAMEOK | 250 |
#define FTP_PWDOK | 257 |
#define FTP_MKDIROK | 257 |
#define FTP_GIVEPWORD | 331 |
#define FTP_RESTOK | 35 |
#define FTP_RNFROK | 350 |
#define FTP_IDLE_TIMEOUT | 421 |
#define FTP_DATA_TIMEOUT | 421 |
#define FTP_TOO_MANY_USERS | 421 |
#define FTP_IP_LIMIT | 421 |
#define FTP_IP_DENY | 421 |
#define FTP_TLS_FAIL | 421 |
#define FTP_BADSENDCONN | 425 |
#define FTP_BADSENDNET | 426 |
#define FTP_BADSENDFILE | 451 |
#define FTP_BADCMD | 500 |
#define FTP_BADOPTS | 501 |
#define FTP_COMMANDNOTIMPL | 502 |
#define FTP_NEEDUSER | 503 |
#define FTP_NEEDRNFR | 503 |
#define FTP_BADPBSZ | 503 |
#define FTP_BADPROT | 503 |
#define FTP_BADSTRU | 504 |
#define FTP_BADMODE | 504 |
#define FTP_BADAUTH | 504 |
#define FTP_NOSUCHPROT | 504 |
#define FTP_NEEDENCRYPT | 522 |
#define FTP_EPSVBAD | 522 |
#define FTP_DATATLSBAD | 522 |
#define FTP_LOGINERR | 530 |
#define FTP_NOHANDLEPROT | 536 |
#define FTP_FILEFAIL | 550 |
#define FTP_NOPERM | 550 |
#define FTP_UPLOADFAIL | 553 |
1、 客户端向服务器端发送PORT命令
客户端创建数据套接字
客户端绑定一个临时端口
客户端在套接字上监听
将IP与端口格式化为h1,h2,h3,h4,p1,p2
2、 服务器端以200响应
服务器端解析客户端发过来的IP与端口暂存起来,以便后续建立数据连接
3、 客户端向服务器端发送LIST
服务器端检测在收到LIST命令之前是否接收过PORT或PASV命令
如果没有接受过,则响应425Use PORT or PASV first
如果有接收过,并且是PORT,则服务器端创建数据套接字(bind 20端口),调用connect主动连接客户端IP与端口,从而建立数据连接
4、 服务器发送150应答给客户端,表示准备就绪,可以开始传输了
5、 开始传输列表
6、 服务器发送226应答给客户端,表示数据传输结束
传输结束,服务器端主动关闭数据套接字
1、 客户端向服务器端发送PASV命令
2、 服务器端以227响应
服务器端创建监听套接字
服务器端绑定一个临时端口
服务器在套接字上监听
将IP与端口格式化为h1,h2,h3,h4,p1,p2响应给客户端,以便客户端发起数据连接
3、 客户端向服务器端发送LIST
服务器端检测在收到LIST命令之前是否接受过PORT或PASV命令
如果没有接收过,则响应424 Use PORT or PASV first
如果有接收过,并且是PASV,则调用accept被动接受客户端的连接,返回已连接套接字,从而建立数据连接
4、 服务器发送150应答给客户端,表示准备就绪,可以开始传输
5、 开始传输列表
6、 服务器发送226应答给客户端,表示数据传输结束
传输结束,客户端主动关闭数据套接字
NAT的全称是(Network Address Transiation),通过NAT可以将内网私有IP地址转换为公网IP地址,一定程度上解决了公网地址不足的问题。
建立控制连接通道:
因为NAT会主动记录由内部发送外部的连接信息,而控制连接通道的建立是由客户端向服务器端连接的,因此这一条连接可以顺利建立起来。
客户端与服务器端数据连接建立时的通知
客户端先启用PORT BB端口,并通过命令通道告知FTP服务器,且等待服务器的主动连接
服务器主动连接客户端
由于通过NAT转换之后,服务器只能得知NAT的地址并不知道客户端的IP地址,因此FTP服务器会以20端口主动向NAT的PORT BB端口发送主动连接请求,但NAT并没有启用PORT BB端口,因而连接被拒绝。
【为什么PORT需要用20端口?(主动必须给NAT服务器专门配置映射规则)】
因为服务器并不是直接传输给客户端的,中间会先传送给NET服务器,然后经由NET服务器发送给客户端。假若服务器未绑定20端口,端口号是动态的,那么NET服务器就需要配置很多映射,不然连接会被拒。因此给客户端与NET服务器与FTP服务器的主动模式专门配置了一个20端口号。此外客户端可能还有防火墙会挡住,因此专门配置20端口号为连接通道。
总结FTP客户端处于NAT或防火墙之后,若主动(port)模式必须配置映射规则,否则不一定能连接成功。若被动(PASV)模式,客户端是可以经由NAT连接成功FTP服务器的。
(从内部向NAT服务器发送连接请求,NAT是可以自动维护的,但是从外部不一定可以,因此PORT BB 连接不到FTP服务器,除非加映射规则)
vi /etc/vsftpd/vsftpd.conf
1-最大连接数限制
2-总连接数限制
3-同一个IP连接数限制
【为什么使用多进程?】(父进程nobody进程,子进程FTP服务进程)
绝对不能使用多线程(多个线程共享同一工作目录,一个线程改变会影响其他线程)
IO复用也不可以,IO复用是用一个线程处理多客户端。
【为什么一个客户端连接服务器需要用两个进程?】
当一个客户端连接过来,普通用户是没有权限直接连接20端口,无法正常建立数据连接通道。nobody进程是协助服务进程创建数据连接通道,以及一些特殊权限的管理。
服务进程与nobody进程之间采用sockpair函数进行通信
(具体后面会有上传文档和代码)
【如何实现断点续载?】
在本项目中断点续载的实现仅仅实在是在客户端要求服务端断点续载的时候会给服务端发送一个定位。在代码中具体实现是用lseek() 函数来定位。然后就不从起始位置传输,,从定位的位置进行传输,这就实现了断点续载了。(三四行有效代码即可实现)
断点续传对应 REST+STOR命令,断点续载对应REST+RETR 命令。覆盖对应ATOR命令
https://blog.csdn.net/weixin_38055381/article/details/82753480?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
断点续传也可以采用APPE的方式进行续传。
不管是断点续传还是断点续载都是需要客户端来维护断点。
【数据下载阶段优化?】
在找的项目视频来做的,(用read函数和writen函数)它本身是用了多次的从用户态到内核态不断切换的拷贝,效率比较慢。之后进行优化是用了sendfile 函数来发送文件。该函数进行数据的读取和发送均在内核中完成,并且不用处理信号中断的情况。不涉及从用户空间到内核空间的切换,也不会将数据拷贝到用户空间的缓冲区中,因此效率比较高。
注:在未优化之前一个几十兆的文件得七八秒才能完成下载,优化之后一两秒就能完成下载。
限速的关键是睡眠,如果发现当前传输速度超过最大传输速度就让进程睡眠。
传输速度 = 传输字节数 / 传输时间;
如果 当前传输速度>最大传输速度 则
睡眠时间 = (当前传输速度/最大传输速度-1)×当前传输时间
速度1/速度2 -1 = 时间2 / 时间1 -1 = (时间2-时间1) /时间1
(时间2 – 时间1) = (速度1 / 速度2 -1) * 时间1
首先是安装信号SIGALRM,并启动定时闹钟
如果在闹钟到来之前没有收到任何命令,则在SIGALRM信号处理程序中关闭控制连接,并给客户421Timeout的响应,并且退出会话。
如果当前处于数据传输的状态,那么即使控制连接通道空闲(在空闲时间内没有收到任何客户端的命令),也不应该退出会话。实现方法,只需要将先前设定的闹钟关闭即可
数据连接通道建立了,但是在一定的时间没有传输数据,那么应该将整个会话断开
在传输数据之前安装信号SIGALRM,并启动闹钟
在传输数据过程中,如果收到SIGALRM信号
如果sess->data_process = 0,则给客户端超时的响应421Data timeout. Reconnect Sorry, 并且退出会话。
如果sess->data_proces=1,将sess->data_process=0,重新安装信号SIGALRM,并启动闹钟。
如果在进行数据传输,那么客户端向服务器发送的ABOR命令是通过紧急模式来传输的,否则是按照正常模式传输的。所以要处理ABOR命令,需要开启紧急模式接收数据。
服务器接收这个命令是可能处在两种状态:1、FTP服务命令已经完成,或者2、FTP服务命令还在执行中。
第一种情况:服务器关闭数据连接(如果数据连接是打开的)回应226代码,表示放弃命令已经成功处理
第二种情况,服务器放弃正在进行的FTP服务,关闭数据连接,放回426响应代码,表示请求服务请求异常终止,然后服务器发送226响应代码,表示放弃命令成功处理。
散列表是在建立的时候,将数据项的关键码和数据项在表中的存储位置进行映射,从而实现在查找的时候,可不必进行关键码的比较即可直接访问相应的数据项的一种数据结构。
数据项所存储的表要用数组来实现。
给定关键码,经过散列函数计算得到的是关键码对应的数据项在数组中存储下标。
Hash table:缺点:表空间不易扩展, 优点:查找比较快
Hash map: 缺点:查找比较慢, 优点:表空间易扩展。
哈希表的概念:根据关键码而直接进行访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做哈希表。
哈希函数构造方法:
散列函数是一个压缩映像函数。关键码集合比散列表地址集合大得多。因此有可能经过散列函数的计算,把不同的关键码映射到同一个散列地址上,这就产生了冲突。即key1≠ key2,而hash(key1)=hash(key2),这种现象称冲突。
哈希函数选择的标准: 简单和均匀
简单指:计算简单快速,用时短。均匀指:哈希函数计算出来的地址能均匀分布在整个地址空间。
构造方法有:
直接定址法 数字分析法 平方取中法 折叠法 随机数法 除留余数法 乘余取整法
做本次项目之前对比了这几种方法性能,实验结果表明 链地址法与除留余数法配合性能最好,搜索关键码时所需对桶的平均访问次数最少。
冲突处理方法:
(哈希桶)链地址法(重点) 开地址法 建立公共溢出区
(1)链地址法:(本项目所用,并且其中的链表为双向链表,插入链表的头部)
基本思想:将所有哈希地址为i的元素构成一个称为同义词链的链表,并将链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。
通常,每个桶中的同义词子表都很短,设有N个关键码通过某有个散列函数,存放到散列表中的m个桶中。那么每一个桶中的同义词子表的平均长度为N/m。这样,以搜索平均长度为N/m的同义词子表代替了搜索长度为n的顺序表,搜索速度快得多。
应用链地址法处理移除,需要增设链接指针,似乎增加了存储开销。事实上,由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装填因子α≤0.5,而表项所占空间又比指针大得多,所以使用连地址法反而比开地址法节省存储空间。
(2)开地址法:
思想:当关键码key的哈希地址Ho=hash(key)出现冲突时,以Ho为基础,擦汗恒另一个哈希地址H1,如果H1仍然冲突,再以Ho为基础产生另外一个哈希地址H2,……,直到找到一个不冲突的哈希地址Hi,将相应元素存入其中,这种方法有一个通用的再散列函数形式:
Hi=(Ho+di)%m, i=1,2,……,m-1;
其中Ho为hash(key),m为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下四种:
线性探测再散列 二次探测再散列 伪随机探测再散列 双散列法
散列表性能分析:
由于冲突的存在,散列法在进行搜索的时候,实际上也要进行关键码的比较。
用平均搜索长度ASL衡量散列方法的搜索的性能。
根据搜素成功与否,它又有搜索成功的平均搜索长度ASLsucc和搜索不成功的平均搜索长度ASLunsucc之分。
搜索成功的平均搜索长度ASLsucc是指能搜索到待查表项的平均探查次数。它是找到表中各个已有表项的探查次数的平均值。
搜索不成功的平均搜索长度ASLunsucc是指在表中搜索不到待查的表项,但找到插入位置的平均探查次数。它是表中所有可能散列到的位置上要插入新元素时找到空桶的探查次数的平均值。。
哈希查找过程仍是一个给定值与关键字进行比较的过程。
评价哈希查找效率仍要用ASL
哈希发中影响关键字比较次数的因素有三个:
哈希函数、处理冲突的方法以及哈希表的装填因子。哈希表的装填因子α的定义如下:
α=哈希表中元素个数/哈希表的长度
散列表的装填因子α表明了表中的装满程度。越大,说明表越满,再插入新元素时发生冲突的可能性就越大。
当装填因子α较高时,选择的散列函数不同,散列表的搜索性能差别很大。在一般情况下多选用除留数法,其中的除数在使用上应选择不含有20以下的质因数的质数。
对散列表技术进行的实验评估表明,它具有很好的平均性能,优于一些传统的技术,如平均树。
但散列表在最坏情况下性能很不好。如果对一个有n个关键码的散列表执行一次搜索或插入操作,最坏情况下需要O(n)的时间。
最大连接数限制:设置一个变量计数连接数,然后与.conf文件设置的连接数对比
每IP连接数限制
将当前连接数保存于变量num_clients,然后与配置项tunable_max_clients进行比较,如果超过了就不让登录,当一个客户登录的时候,num_clients加1,当一个客户退出的时候,num_clients减1。
维护两个哈希表
Static hash_t *s_ip_count_hash; :连接时候需要对ip数进行计数(维护ip和计数的对应关系)
Static hash_t *s_pid_ip_hash; :存储客户端的ip号。用来查找退出客户端对应的IP号。(维护进程和ip的对应关系)
将当前ip的连接数保存在变量nun_this_ip中,然后与配置项tunable_max_per_ip进行比较,如果超过了就不让登录,当一个客户登录的时候,要在s_ip_count_hash更新这个表中的对应表项,即该ip对应的连接数要加1,如果这个表象不存在,要在表中添加一条记录,并且将ip对应的连接数置1,当一个客户端退出的时候,那么该客户端对应ip的连接数要减1,处理过程是这样的,首先是客户端退出的时候,父进程需要知道这个客户端的ip,这可以通过在s_pid_ip_hash查找得到,得到了ip进而我们就可以在s_ip_count_hash表中找到对应的连接数,进而进行减1操作。