一个简单的ftp服务器。
ftp服务器使用2个端口,命令端口21,数据端口20, 其中数据端口20仅在主动模式下启用。 数据连接有主动模式与被动模式2种,主动/被动都是相对服务器来描述的。
主动模式下,服务器需主动与客户端建立数据连接。 客户端通过命令端口发送其监听数据连接的地址与端口(数据端口号>=1024且为命令端口号+1),服务器在收到需要进行数据连接的命令时,绑定端口20向客户端发起数据连接。
对于客户端的防火墙来说,主动模式下服务器主动向客户端发起连接,是从外部到内部的连接,可能会被阻塞。 而在被动模式下,服务器收到PASV命令后,开启一个>=1024的端口监听数据连接,并将此端口号及主机地址通过命令连接发送给客户端,由客户端以端口(命令端口+1)主动连接服务器。 这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
- client向server主进程发起命令连接,主进程fork出子进程单独为这一客户服务。
- 子进程开启unix域套接字便于nobody进程与服务进程之间传递套接字。
- 子进程fork出服务进程后,切换为nobody用户并赋予绑定<1024端口号的能力。
- nobody进程循环读取服务进程的命令,按要求返回PORT/PASV下的数据连接socket描述符.
- 服务进程循环读出client发来的命令,经解析后调用对应函数处理,需要建立数据连接时先通知nobody进程建立数据连接并接收nobody进程发来的socket描述符,再对数据端口进行读写。
关于限速的实现: 采用nanosleep实现,通过 标准速度*(睡眠时间+传输已用时间)=已传输字节数 ,这一等式计算出睡眠时间,在每次上传/下载若干字节后进行限速。
- 为什么要使用root用户启动服务器?
需要绑定1024以下的端口号。
- 为何不仅仅只是用capset给进程赋权?
非root用户无法使用capset。
- 为什么使用服务进程而不是服务线程?
最主要的原因,ftp的登陆用户就是系统中的用户,系统中的用户访问文件是有权限限制的,只有一个进程才有用户id,才能控制访问权限,而线程没有,所以用进程。 其次才是目录的问题,进程可以保存工作目录。(当然如果没有第一个限制确实可以在线程中记录用户当前目录 上传下载都用绝对路径)。
- 为什么要采用nobody进程?
主要是基于安全考虑的,防止程序本身有安全问题的时候,不会被黑客获得root权限。此外这个用户的权限也很低。因此有比较高的安全性
- 为什么服务进程不采用nobody身份?
ftp协议规定,客户使用其在ftp服务器端的系统用户身份登陆,故服务进程使用的是客户对应的普通用户身份。
- 单ip最大连接数如何限制?
用hash table 记录每个ip对应的连接数,在命令连接建立后检查是否超出限制。为了维护服务进程结束后,ip地址对应连接数的正确性,这里为主进程注册了SIGCHLD的信号处理函数,函数中给ip对应的连接数-1,并移除连接数为0的节点。
- 如何保证hash table不因多个服务进程同时退出而造成对hash table节点的重复删除?
信号处理函数执行时处于原子上下文,如果没有异常和更高级别的信号,不会再被中断,即ip地址对应连接数以及删除该ip节点的操作是一同执行的。
- 为何套接字描述符可在进程间传递?
unix域下,sendmsg可传送额外数据,socket就是作为额外数据在进程间传递的。