以linux为例,使用socket编程中的read()函数和write()函数已可实现文件的发送接收,为啥还要专门建立ftp协议呢?单单使用read()函数和write()函数,数据接口和命令接口未分开,效率低。而ftp将数据接口和命令接口分开,提高了文件传输效率和安全性。
ftp协议的实现仍是使用socket编程,首先是实现tcp连接。
Socket 客户端编程主要步骤如下:
- socket() 创建一个 Socket
- connect() 与服务器连接
- write() 和 read() 进行会话
- close() 关闭 Socket
Socket 服务器端编程主要步骤如下:
- socket() 创建一个 Socket
- bind()
- listen() 监听
- accept() 接收连接的请求
- write() 和 read() 进行会话
- close() 关闭 Socket
建立tcp连接代码简示如下:
SOCKET control_sock;
struct hostent *hp;
struct sockaddr_in server;
memset(&server, 0, sizeof(struct sockaddr_in))
/* 初始化socket */
control_sock = socket(AF_INET, SOCK_STREAM, 0);
hp = gethostbyname(server_name);
memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
server.sin_family = AF_INET;
server.sin_port = htons(port);
/* 连接到服务器端 */
connect(control_sock,(struct sockaddr *)&server, sizeof(server));
/* 客户端接收服务器端的一些欢迎信息 */
read(control_sock, read_buf, read_len);
ftp客户端与服务器建立起tcp连接后,然后向服务器发送命令。(这一步建立的是命令接口的tcp连接,数据接口连接尚未建立。)
通常第一步发送USER和PASS命令验证账号和密码后登陆服务器。若是匿名服务器则另当别论。
登陆服务器代码简示如下:
/* 命令 ”USER username\r\n” */
sprintf(send_buf,"USER %s\r\n",username);
/*客户端发送用户名到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”331 User name okay, need password.” */
read(control_sock, read_buf, read_len);
/* 命令 ”PASS password\r\n” */
sprintf(send_buf,"PASS %s\r\n",password);
/* 客户端发送密码到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”230 User logged in, proceed.” */
read(control_sock, read_buf, read_len);
接下来是选择发送PORT命令选择主动模式还是发送PASV命令选择被动模式。
主动模式还是被动模式是相对服务器来说的。被动模式即命令接口连接和数据接口连接都由客户端主动建立;主动模式是数据接口连接由服务器主动建立。这里仅简述被动模式的建立,主动模式可依理建立。
被动模式建立连接代码:
/* 命令 ”PASV\r\n” */
sprintf(send_buf,"PASV\r\n");
/* 客户端告诉服务器用被动模式 */
write(control_sock, send_buf, strlen(send_buf));
/*客户端接收服务器的响应码和新开的端口号,* 正常为 ”227 Entering passive mode ()” */
read(control_sock, read_buf, read_len);
客户端发送PASV命令让服务器进入被动模式。服务器会打开数据端口并监听。并返回响应码 227 和数据连接的端口号。
接下来通过数据端口下载上传查看文件就不赘述了。
/* 连接服务器新开的数据端口 */
connect(data_sock,(struct sockaddr *)&server, sizeof(server));
/* 命令 ”CWD dirname\r\n” */
sprintf(send_buf,"CWD %s\r\n", dirname);
/* 客户端发送命令改变工作目录 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”250 Command okay.” */
read(control_sock, read_buf, read_len);
/* 命令 ”SIZE filename\r\n” */
sprintf(send_buf,"SIZE %s\r\n",filename);
/* 客户端发送命令从服务器端得到下载文件的大小 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”213 ” */
read(control_sock, read_buf, read_len);
/* 命令 ”RETR filename\r\n” */
sprintf(send_buf,"RETR %s\r\n",filename);
/* 客户端发送命令从服务器端下载文件 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”150 Opening data connection.” */
read(control_sock, read_buf, read_len);
/* 客户端创建文件 */
file_handle = open(disk_name, CRFLAGS, RWXALL);
for( ; ; ) {
... ...
/* 客户端通过数据连接 从服务器接收文件内容 */
read(data_sock, read_buf, read_len);
/* 客户端写文件 */
write(file_handle, read_buf, read_len);
... ...
}
/* 客户端关闭文件 */
rc = close(file_handle);
下载完成后客户端退出服务器,关闭连接。
/* 客户端关闭数据连接 */
close(data_sock);
/* 客户端接收服务器的响应码和信息,正常为 ”226 Transfer complete.” */
read(control_sock, read_buf, read_len);
/* 命令 ”QUIT\r\n” */
sprintf(send_buf,"QUIT\r\n");
/* 客户端将断开与服务器端的连接 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码,正常为 ”200 Closes connection.” */
read(control_sock, read_buf, read_len);
/* 客户端关闭控制连接 */
close(control_sock);
断点续传的实现
由于网络不稳定,在传输文件的过程中,可能会发生连接断开的情况,这时候需要客户端支持断点续传的功能,下次能够从上次终止的地方开始接着传送。需要使用命令 REST。如果在断开连接前,一个文件已经传输了 512 个字节。则断点续传开始的位置为 512,服务器会跳过传输文件的前 512 字节。
ps:响应码啊响应码。
非常简单的简述,以后再加。