【ChinaBeta.Cn 操作系统中心】
一.前言 Serv-U很多朋友都用过吧?功能强大,使用方便,不过这类商业软件大多复杂庞大,看着吓人。其实,FTP协议是比较简单的。如果了解了协议,写一个简单的单个EXE的FTP服务器不是很难,用在小场合也是非常方便的(脚本小子:两眼放光ing!嘿嘿,蝴蝶mm的机器又会多一样东西咯!)。
二.基础 先一起来把理查德爷爷的东西简单地温习一遍:FTP(File Transfer Protocol,文件传输协议)是Internet网络上使用最频繁的协议之一(早期非常使用频繁,后来被http抢去了头座)。FTP的命令数量非常之多,功能也极为强大,协议底层也很复杂,再加上FTP使用的是稳定的TCP协议(TFTP使用的是不稳定的UDP),有很高的传输质量,所以FTP也注定了是大文件传输、大吞吐量的数据交换的首选途径。 FTP一般是以客户机/服务器模式存在的,在数据连接和命令连接的基础上传输文件数据。我们今天要写的就是一个基于FTP协议的FTP服务器。 本文中的FTP服务器基本实现了文件的上传/下载,支持断点续传,支持PORT和PASV模式,并且支持相应的文件操作和目录操作。 太多的原理性的东西我不多说了,毕竟是程序性的文章,想详细知道的,可以去看《TCP/IP详解》的FTP相关章节。 注:限于篇幅关系,本文中的代码我就不加详细注释了,代码部分均不完整,变量或函数可能非法或没有申明。杂志光盘上附有完整源代码,建议对照阅读,编程基础还是要的,代码完全用C和SOCKETAPI写。
三.建立环境 我们要构建的是一个FTP服务器,众所周知,FTP服务器工作的时候占用的知名端口21(当然,你也可以不要这个端口): sockAddr.sin_family=AF_INET;//internet模式 sockAddr.sin_port=htons(21);//端口21 sockAddr.sin_addr.S_un.S_addr=INADDR_ANY;//接受所有的地址 为了不占用21端口,为后续的用户做准备,我们用建立线程的方式,对每一个申请连接的FTP客户做服务。为了实现多用户,用了固定的几个(64)全局结构变量。也可以用数据结构上的链表的方式动态地加入用户结构: beginSock=accept(FTPserver, (struct sockaddr *)&client, &clicnttemp); //等待用户连接 if (beginSock==INVALID_SOCKET) return 1; DWORD funid; if(CreateThread(NULL, 0, ThreadFunctheFtp,(LPVOID)beginSock, 0, &funid)==0) //开始建立ftp用户线程 建立了这个环境了后,等待客户端的到来,接下来的就是FTP的协议了。
四.协议-用户验证 首先,服务器会发送一些欢迎信息,这些信息可以是服务器版本的提示,也可以是其他的信息。但必须要以“220”起头(这些代码的含义可以参考相关文档),信息可以多行,要注意的一点是,数据以CRLF(回车换行)为结束――“\r\n”。 SOCKET inFtpSock=(SOCKET)lpParam; renftpmsg(inFtpSock,"220 Zvrop FTP server ready...\r\n"); renftpmsg函数就看光盘上的源代码了,是发送指定信息给指定的FTP客户端的函数。下面再来就是判断用户名和密码了,这些地方不是规定的。一个strncmp就可以解决了,我想说的是协议:一般情况是,服务器发完220之后,客户端就会发送一个用户名的报文--以“USER”打头,如(“USER zvrop\r\n”): recvnum=recv(inFtpSock,buf,1023,0); if(recvnum<=0) return 1; if((strncmp(buf,"USER",4))==0) { if((renc=chkuser(buf+5))<0)//chkuser是判断用户名函数 如果用户名正确(chkuser返回正常),那么服务器会给客户端响应一个报文(“331 User name okay, need password.\r\n”)信息,告诉客户端,你可以输入密码了: renftpmsg(inFtpSock,"331 User name okay, need password.\r\n"); 这时候,客户端必须返回一个报文(这个报文是以PASS打头,如“PASS 12345\r\n”),其用来提供用户的一个密码进行验证。 这个密码的验证和用户名的差不多,两段验证代码见光盘。 如果用户提供的用户名或者密码不正确,服务器将返回530错误代码,并关闭对应的连接: renftpmsg(inFtpSock,"530 LOGIN ERROR!\r\n"); //或者为:renftpmsg(inFtpSock,"530 PASSWORD ERROR!\r\n"); closesocket(inFtpSock); 如果用户名和密码正确,则返回230报文: sprintf(msg,"230 User logged in %s.\r\n",LoginName); renftpmsg(inFtpSock,msg); 到了这里,用户已经通过了验证,可以开始服务了。 值得注意的是,FTP的用户名和密码在传输的过程中始终保持明文(实际上任何数据都没有加密),如果用一个Sniffer记录带有“user”,“pass”关键字的数据包,那么很容易就可以得到别人的FTP登陆密码了。 编者按:在2003年第9期“嗅探技术对菜鸟的贡献”一文曾对这些问题作了具体分析,大家也可以参考那篇文章。
五.协议――命令和传输方式 现在介绍一下我们的FTP服务器要支持的内容,注意,本文要写的FTP服务器,支持标准的FTP报文格式,对于用WININET函数写的FTP客户端,那还要做一些调整。以下是一些标准命令列表: 命令 简介 LIST 列目录 PORT PORT传输模式的传输方式,这个是比较早期的传输方式,兼容微软命令行下的FTP客户端就是用这种方式 PASV PASV传输模式,现在大多数FTP客户端使用的传输方式 CWD 进入某个目录,切换某个目录 CDUP 命令来返回上层目录,有的FTP客户端用“CWD ..” PWD 返回当前目录,大多数ftp客户端用来确认服务器还能响应(预防超时) TYPE 设置传输类型 STOR 上传文件 REST 一般用于断点续传,其后跟着设置文件从开头的偏移量. RETR 下载文件命令 RNFR/RETO 用户更改文件夹或者文件名 DELE 删除文件 RMD 删除文件夹 MKD 建立文件夹 QUIT 退出 PORT和PASV这两种方式又叫主动和被动方式,PORT方式下,客户端主动打开一个数据端口,告知服务器这个端口。服务器连接上客户端的那个端口,开始数据传输(可以看出,这个方法的缺点是,如果客户端在内网的,服务器就不能连接客户端了,这个时候就需要用PASV方式)。客户端发送PASV命令说明他需要用PASV方式传输,这个时候服务器就响应一个信息,告知服务器自己已经打开的端口,等待客户端连接,传输数据。心细的朋友就会觉得这两种方式都不怎么好,都存在数据劫持的可能性。 [1] [2] 下一页
(责任编辑:hahack)
|