文件传输协议(FTP)的设计和实现
March 19, 2014
80年代初,美国政府的高级研究工程机构( ARPA)给加利福尼亚大学Berkeley分校提供了资金,让他们在UNIX操作系统下实现TCP/IP协议。在这个项目中,研究人员为TCP/IP网络通信开发了一个API(应用程序接口)。这个API称为Socket接口(套接字),也叫BSD Socket 。 BSD Socket接口是TCP/IP网络中最为通用的API,也是在互联网上进行应用开发时最为通用的API。
Socket是应用层与TCP/IP协议栈通信的中间软件抽象层,它是一组接口,把复杂的TCP/IP协议栈隐藏在Socket接口后面。对普通用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 Socket套接字有四种类型: SOCKSTREAM(字节流套接字)、 SOCKDGRAM(数据报套接字)、SOCKSEQPACKET(有序分组套接字)和SOCKRAW(原始套接字)。 字节流套接字使用了TCP协议,提供面向连接的,具有顺序控制、拥塞控制和可靠传输功能的双向通信服务。 数据报套接字使用了UDP协议,为数据流提供无连接的双向通信,不支持顺序控制、拥塞控制和可靠传输。如果基于数据报套接字的应用程序要求可靠传输的话,需要自己实现确认重传机制。
要求
用Socket编程接口编写两个程序,分别为客户端程序( client ) 和服务器端程序( server), 服务器端程序在后台进行时,运行客户端程序,应能够实现以下功能:
- 获取远方的一个文件;
- 传送给远方一个文件;
- 显示远方的当前目录;
- 列出远方当前目录下的内容;
- 改变远方的当前目录;
- 显示可以提供的命令,即Help;
- 退出返回。
server.cpp
#include <errno.h>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <ctime>
#include <fcntl.h>
using namespace std;
int open_listenfd(int port);
void ftp_server(int connectfd);
void wait_child_process(int sig);
string rootDir = "./ftp";
string nowDir = "/";
string connect_ip;
sockaddr_in clientaddr;
socklen_t clientlen = sizeof(clientaddr);
int main(int argc, char* argv[])
{
int port = 21;
if(argc == 2)
port = atoi(argv[1]);
signal(SIGCHLD, wait_child_process);
srand((unsigned) time(0));
int listenfd;
if((listenfd = open_listenfd(port)) < 0) {
cout << "port: " << port << " failed" <<endl;
return 0;
}
while(true) {
int connectfd = accept(listenfd, (sockaddr*) &clientaddr, &clientlen);
connect_ip = inet_ntoa(clientaddr.sin_addr);
cout << "connect ip : " << connect_ip << endl;
if(fork() == 0) {
ftp_server(connectfd);
return 0;
}
close(connectfd);
}
close(listenfd);
return 0;
}
void wait_child_process(int sig)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int open_listenfd(int port)
{
int listenfd;
sockaddr_in addr;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons((unsigned short) port);
if(bind(listenfd, (sockaddr*) &addr, sizeof(addr)) < 0)
return -1;
if(listen(listenfd, 1) < 0 )
return -1;
return listenfd;
}
int command_PORT(string content);
int command_PASV(int connectfd);
void command_pwd(int connectfd);
void command_dir(int connectfd, int datafd);
void command_cd(int connectfd, string content);
void command_get(int connectfd, int datafd, string filename);
void command_put(int connectfd, int datafd, string filename);
void ftp_server(int connectfd)
{
const int BUFSIZE = 300;
char buf[BUFSIZE];
read(connectfd, &buf, BUFSIZE);
string request = buf;
string command;
int pos = request.find(' ');
pos = pos < 0 ? request.length() : pos;
command = request.substr(0, pos);
if(command == "open") {
char response[] = "logged on";
write(connectfd, response, sizeof(response));
} else {
char response[] = "failed";
write(connectfd, response, sizeof(response));
return;
}
int datafd = -1;
string content;
while(true) {
if(read(connectfd, &buf, BUFSIZE) <= 0)
break;
request = buf;
cout << connect_ip << " : " << request << endl;
pos = request.find(' ');
pos = pos < 0 ? request.length() : pos;
command = request.substr(0, pos);
pos = pos == request.length() ? pos : pos + 1;
content = request.substr(pos);
if(command == "PORT")
datafd = command_PORT(content);
else if(command == "PASV")
datafd = command_PASV(connectfd);
else if(command == "pwd")
command_pwd(connectfd);
else if(command == "dir")
command_dir(connectfd, datafd);
else if(command == "cd")
command_cd(connectfd, content);
else if(command == "get")
command_get(connectfd, datafd, content);
else if(command == "put")
command_put(connectfd, datafd, content);
else if(command == "quit")
break;
}
close(connectfd);
}
void close_datafd(int datafd);
const int BUFSIZE = 1400;
void command_put(int connectfd, int datafd, string filename)
{
int filefd = open((rootDir + nowDir + filename).c_str(), O_WRONLY | O_CREAT);
int count = 0;
char buf[BUFSIZE];
while((count = read(datafd, buf, sizeof(buf))) > 0)
write(filefd, buf, count);
close(filefd);
close_datafd(datafd);
}
void command_get(int connectfd, int datafd, string filename)
{
int filefd = open((rootDir + nowDir + filename).c_str(), O_RDONLY);
int count = 0;
char buf[BUFSIZE];
while((count = read(filefd, buf, sizeof(buf))) > 0)
write(datafd, buf, count);
close(filefd);
close_datafd(datafd);
}
void command_cd(int connectfd, string content)
{
if(content.substr(0,1) == "/")
nowDir = content;
else
nowDir = nowDir + content + "/";
cout << "cd " << content << endl;
}
void command_dir(int connectfd, int datafd)
{
char buf[BUFSIZE];
FILE* ls = popen(("ls -l " + rootDir + nowDir).c_str(), "r");
int read_count = fread(buf, sizeof(char), sizeof(buf), ls);
buf[read_count] = '\0';
write(datafd, buf, read_count + 1);
pclose(ls);
close_datafd(datafd);
}
void command_pwd(int connectfd)
{
write(connectfd, nowDir.c_str(), nowDir.length() + 1);
}
bool is_PORT_PASV = true;
int PASV_listenfd = -1;
void close_datafd(int datafd)
{
close(datafd);
if(!is_PORT_PASV)
close(PASV_listenfd);
}
int command_PORT(string content)
{
is_PORT_PASV = true;
int datafd;
if((datafd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
int optval = 1;
if(setsockopt(datafd, SOL_SOCKET, SO_REUSEADDR, (const void*) &optval, sizeof(optval)) < 0)
return -1;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(20);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(datafd, (sockaddr*) &addr, sizeof(addr)) < 0)
return -1;
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(content.c_str()));
sin.sin_addr = clientaddr.sin_addr;
while(connect(datafd, (sockaddr*) &sin, sizeof(sin)) < 0);
return datafd;
}
int command_PASV(int connectfd)
{
is_PORT_PASV = false;
int datafd;
int port = 1024 + rand() % 3900;
while((PASV_listenfd = open_listenfd(port)) < 0) {
cout << "PASV bind port failed : " << port << endl;
port = 1024 + rand() % 3900;
}
char buf[20];
sprintf(buf, "%d", port);
string port_str = buf;
cout << "PASV port : " << port_str << endl;
write(connectfd, port_str.c_str(), port_str.length() + 1);
sockaddr_in addr;
addr.sin_family = AF_INET;
socklen_t addrlen = sizeof(addr);
datafd = accept(PASV_listenfd, (sockaddr*) &addr, &addrlen);
return datafd;
}
client.cpp
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <ctime>
#include <fcntl.h>
using namespace std;
int open_connectfd(char* ip_addr, int port);
int command_PORT(int connectfd);
int command_PASV(int connectfd);
void command_pwd(int connectfd);
void command_quit(int connectfd);
void command_dir(int connectfd);
void command_cd(int connectfd);
void command_get(int connectfd);
void command_put(int connectfd);
void command_help();
bool is_PORT_PASV = true; // true : PORT, false : PASV
sockaddr_in sin;
int main(int argc, char* argv[])
{
char* ip_addr = (char*) "127.0.0.1";
int port = 21;
if(argc == 3) {
ip_addr = argv[1];
port = atoi(argv[2]);
} else if (argc == 2)
ip_addr = argv[1];
int connectfd = open_connectfd(ip_addr, port);
if(connectfd < 0) {
cout << "connect failed" << endl;
return 0;
}
cout << "connect sucessed" << endl;
const int BUFSIZE = 300;
char buf[BUFSIZE];
char open_str[] = "open";
write(connectfd, open_str, sizeof(open_str));
read(connectfd, buf, sizeof(buf));
cout << buf << endl;
string response;
if((response = buf) != "logged on") {
cout << "connect ftp server failed" << endl;
return 0;
}
srand((unsigned)time(0));
string command;
while(true)
{
cout << ">> ";
cin >> command;
if(command == "PORT")
is_PORT_PASV = true;
else if(command == "PASV")
is_PORT_PASV = false;
else if(command == "pwd")
command_pwd(connectfd);
else if(command == "dir")
command_dir(connectfd);
else if(command == "cd")
command_cd(connectfd);
else if(command == "?")
command_help();
else if(command == "get")
command_get(connectfd);
else if(command == "put")
command_put(connectfd);
else if(command == "quit") {
command_quit(connectfd);
break;
}
}
close(connectfd);
return 0;
}
int PORT_listenfd = -1;
inline int open_datafd(int connectfd)
{
return is_PORT_PASV ? command_PORT(connectfd) : command_PASV(connectfd);
}
void close_datafd(int datafd)
{
close(datafd);
if(is_PORT_PASV)
close(PORT_listenfd);
}
int open_connectfd(char* ip_addr, int port)
{
int connectfd;
if((connectfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
inet_pton(AF_INET, ip_addr, &sin.sin_addr);
if(connect(connectfd, (sockaddr*) &sin, sizeof(sin)) < 0)
return -1;
return connectfd;
}
int open_listenfd(int port);
const int BUFSIZE = 1400;
void command_put(int connectfd)
{
int datafd = open_datafd(connectfd);
string filename;
cin >> filename;
string put_str = "put " + filename;
write(connectfd, put_str.c_str(), put_str.length() + 1);
int filefd = open(filename.c_str(), O_RDONLY);
int count = 0;
char buf[BUFSIZE];
while((count = read(filefd, buf, sizeof(buf))) > 0)
write(datafd, buf, count);
close(filefd);
close_datafd(datafd);
}
void command_get(int connectfd)
{
int datafd = open_datafd(connectfd);
string filename;
cin >> filename;
string get_str = "get " + filename;
write(connectfd, get_str.c_str(), get_str.length() + 1);
int filefd = open(filename.c_str(), O_WRONLY | O_CREAT);
int count = 0;
char buf[BUFSIZE];
while((count = read(datafd, buf, sizeof(buf))) > 0)
write(filefd, buf, count);
close(filefd);
close_datafd(datafd);
}
void command_cd(int connectfd)
{
string path;
cin >> path;
path = "cd " + path;
write(connectfd, path.c_str(), path.length() + 1);
}
void command_dir(int connectfd)
{
int datafd = open_datafd(connectfd);
char dir_str[] = "dir";
write(connectfd, dir_str, sizeof(dir_str));
char buf[1400];
read(datafd, buf, sizeof(buf));
cout << buf << endl;
close_datafd(datafd);
}
void command_quit(int connectfd)
{
char quit_str[] = "quit";
write(connectfd, quit_str, sizeof(quit_str));
}
void command_pwd(int connectfd)
{
char pwd_str[] = "pwd";
write(connectfd, pwd_str, sizeof(pwd_str));
char buf[40];
read(connectfd, buf, sizeof(buf));
cout << buf << endl;
}
void command_help()
{
cout << "command : \n"
<< "get [filename]\n"
<< "put [filename]\n"
<< "pwd\n"
<< "dir\n"
<< "cd\n"
<< "quit\n";
}
int command_PORT(int connectfd)
{
int datafd;
int port = 1024 + rand() % 10000;
while((PORT_listenfd = open_listenfd(port)) < 0) {
cout << "PORT bind port failed : " << port << endl;
port = 1024 + rand() % 10000;
}
char temp_str[20];
sprintf(temp_str, "PORT %d", port);
string port_str = temp_str;
cout << port_str << endl;
write(connectfd, port_str.c_str(), port_str.length() + 1);
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
datafd = accept(PORT_listenfd, (sockaddr*) &addr, &addrlen);
return datafd;
}
int command_PASV(int connectfd)
{
char PASV[] = "PASV";
write(connectfd, PASV, sizeof(PASV));
char buf[10];
read(connectfd, buf, sizeof(buf));
int port = atoi(buf);
int datafd;
if((datafd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
sockaddr_in pasv_sin;
pasv_sin.sin_family = AF_INET;
pasv_sin.sin_port = htons(port);
pasv_sin.sin_addr = sin.sin_addr;
cout << "PASV " << port << endl;
while(connect(datafd, (sockaddr*) &pasv_sin, sizeof(pasv_sin)) < 0);
return datafd;
}
int open_listenfd(int port)
{
int listenfd;
sockaddr_in addr;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if(bind(listenfd, (sockaddr*) &addr, sizeof(addr)) < 0)
return -1;
if(listen(listenfd, 1) < 0 )
return -1;
return listenfd;
}