基于TCP协议的简易FTP云盘
创建基于TCP协议的简易FTP云盘涉及多个方面的知识,包括网络编程、文件传输、用户认证等。以下是一种简单的实现思路,你可以根据需要进行调整和扩展:
-
选择编程语言: 选择你熟悉的编程语言,例如Python、Java等。
-
网络编程: 使用套接字(Socket)进行网络通信。服务端和客户端之间通过TCP协议建立连接,服务端监听特定端口,等待客户端连接。
-
用户认证: 在客户端连接到服务器后,需要进行用户认证。可以实现简单的用户名和密码认证,或者使用其他认证方式,如密钥认证。
-
命令交互: 定义FTP命令,例如上传文件、下载文件、列出文件列表等。客户端通过发送这些命令到服务器,服务器执行相应的操作。
-
文件传输: 实现文件的上传和下载功能。可以使用FTP的二进制传输模式,按块传输文件。
-
目录管理: 允许用户创建、删除、列出目录。服务器需要维护用户的文件和目录结构。
-
错误处理: 实现错误处理机制,对于不合法的命令或操作进行适当的处理,并向客户端发送错误信息。
-
安全性考虑: 考虑安全性问题,包括数据加密、用户权限管理等。
以下是一个简单的Python示例,演示了一个基于TCP协议的简易FTP云盘的服务端和客户端的通信:
# 服务端代码
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 12345))
server_socket.listen(5)
print("Server listening on port 12345")
while True:
client_socket, addr = server_socket.accept()
print("Connection from", addr)
data = client_socket.recv(1024).decode()
print("Received:", data)
response = "Hello from server"
client_socket.send(response.encode())
client_socket.close()
# 客户端代码
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 12345))
message = "Hello from client"
client_socket.send(message.encode())
data = client_socket.recv(1024).decode()
print("Received from server:", data)
client_socket.close()
这只是一个简单的例子,实际的FTP云盘需要更多的功能和安全性考虑。你可以在此基础上逐步扩展和完善。
使用套接字(Socket)库来进行网络编程
在C语言中,你同样可以使用套接字(Socket)库来进行网络编程。下面是一个简单的基于C语言的TCP服务器和客户端示例,用于建立简易FTP云盘通信:
服务器端 (ftp_server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUFFER_SIZE 1024
void handle_client(int client_socket) {
char buffer[BUFFER_SIZE];
ssize_t bytes_received;
// Receive data from the client
bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received < 0) {
perror("Error receiving data");
close(client_socket);
exit(EXIT_FAILURE);
}
// Process the received data (you can implement FTP commands here)
// Send a response back to the client
const char *response = "Hello from server";
send(client_socket, response, strlen(response), 0);
// Close the client socket
close(client_socket);
}
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
// Create socket
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// Bind socket to port
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Error binding socket");
close(server_socket);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_socket, 5) < 0) {
perror("Error listening for connections");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
// Accept connection from client
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket < 0) {
perror("Error accepting connection");
close(server_socket);
exit(EXIT_FAILURE);
}
// Handle the client in a separate function
handle_client(client_socket);
}
// Close the server socket
close(server_socket);
return 0;
}
客户端 (ftp_client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUFFER_SIZE 1024
int main() {
int client_socket;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// Create socket
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// Connect to server
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(PORT);
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Error connecting to server");
close(client_socket);
exit(EXIT_FAILURE);
}
// Send data to the server
const char *message = "Hello from client";
send(client_socket, message, strlen(message), 0);
// Receive response from the server
ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received < 0) {
perror("Error receiving data");
close(client_socket);
exit(EXIT_FAILURE);
}
// Process the received data (you can print or use as needed)
printf("Received from server: %.*s\n", (int)bytes_received, buffer);
// Close the client socket
close(client_socket);
return 0;
}
请注意,这只是一个基础的示例,你可能需要根据具体需求实现更多的FTP功能和安全性考虑。
项目实现
这个项目分成ftp客户端及服务端,实现的功能和Linux开源的ftp服务器类似,客戶端通过网络,远程获取服务端磁盘上的文件夹内容,下载文件,上传文件等功能。(基本功能描述)
ftp服务器用到的是Socket通信,当收到客户端接入的时候,创建子进程对接连接,子进程启动后分析来自客户端的指令。比如收到get file1的指令,是客户端想要获取file1文件的,先用strstr函数进行字符串分割,获取到文件名,在判断文件是否存在,如果文件存在,就读取文件內容,再将內容通过套接字发给客户端,客户端收到数据后,创建文件,并将收到的数据写入文件,完成文件的远程下载。
上传文件和下载文件类似,主要还是涉及文件的操作,字符串的操作,以及网络编程。
还支持了Is、pwd、cd等Linux系统常用的指令。普通指令的实现用popen来调用系统质量,并读取执行的结构。如果不需要获取执行结果,用system函数调用就可以了。
实现功能点
利用socket,建立起服务端与客户端的对接;(服务端能支持多台客户端的同时连接)
客户端
ls:查看服务端当前路径下的所有文件;
lls:查看客户端当前路径下的所有文件;
cd xx:服务端进入xx路径;
lcd xx:客户端进入xx路径;
pwd:查看服务端当前路径;
lpwd:查看客户端当前路径;
get xx:从服务端当前路径获取xx文件到客户端当前路径上;
put xx:将客户端当前路径xx文件发送到服务端当前路径;
quit:断开客户端连接;
服务端
1、不断监听客户端的指令(等待指令)。
2、在接收上面客户端的指令后,去执行指令。
代码实现
服务端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include "msg.h"
int get_cmd_type(char *cmd) // 将从客户端发送过来的指令转换成整数返回
{
if (!strcmp("ls", cmd)) return LS;
if (!strcmp("quit", cmd)) return QUIT;
if (!strcmp("pwd", cmd)) return PWD;
if (strstr(cmd, "cd") != NULL) return CD;
if (strstr(cmd, "get") != NULL) return GET;
if (strstr(cmd, "put") != NULL) return PUT;
return 11;
}
char *getdir(char *cmsg) // 将需要带参数的指令进行指令参数分离,返回分离出的参数
{
char *p;
p = strtok(cmsg, " "); // 以空格分离
p = strtok(NULL, " ");
return p;
}
void msg_handle(struct Msg msg, int fd) // 对客户端发送的消息进行相对应处理
{
char dataBuf[1024] = {0}; // 用来存放GET的文件数据
char *file = NULL; // 用来放GET的文件名
int fdfile; // GET时open的fd
char *dir; // 存放cd函数进入地址,即文件夹名字
printf("cmd:%s\n", msg.cmd); // 打印客户端发来消息里的指令名
int ret = get_cmd_type(msg.cmd); // 获取指令所对应的整数
switch (ret)
{
case LS:
case PWD:
FILE *r = popen(msg.cmd, "r"); // 使用popen调用msg.cmd里的ls或pwd指令,并将运行结果读入到文件流r
fread(msg.cmd, sizeof(msg.cmd), 1, r); // 将文件流r里的数据读入到msg.cmd
write(fd, &msg, sizeof(msg)); // 将msg里的数据通过fd发送给客户端
break;
case CD:
dir = getdir(msg.cmd); // 将提取cd后面的参数,即文件夹名字,赋给dir
printf("dir:%s\n", dir); // 服务端打印要进入的文件夹名字
chdir(dir); // 使用chdir改变服务器当前路径,进入到dir文件夹,不使用system
break;
case GET: // 从服务端获取文件
file = getdir(msg.cmd); // 分离get后面的参数,即文件名,赋给file
if (access(file, F_OK) == -1) { // 使用access函数判断该文件是否存在,不存在返回值为-1
strcpy(msg.cmd, "no the file!"); // 复制该字符串到msg.cmd
write(fd, &msg, sizeof(msg)); // 将该字符串发送给客户端
}
else {
fdfile = open(file, O_RDWR); // 将file文件以可读可写方式打开
read(fdfile, dataBuf, sizeof(dataBuf)); // 将fdfile中的数据读入到dataBuf中
close(fdfile); // 关闭文件
strcpy(msg.cmd, dataBuf); // 将dataBuf中的数据复制到msg.cmd中
write(fd, &msg, sizeof(msg)); // 将msg里的数据通过fd发送给客户端
}
break;
case PUT: // 将服务端文件发送给客户端
fdfile = open(getdir(msg.cmd), O_RDWR|O_CREAT, 0666); // 将客户端发送的文件在服务端打开,如果没有就创建该文件
write(fdfile, msg.buf, strlen(msg.buf)); // 将msg.buf里的数据写入到fdfile所指的文件里
close(fdfile); // 关闭文件
break;
case QUIT:
printf("client quit!\n");
exit(-1);
}
}
int main(int argc, char **argv)
{
int c_fd;
int s_fd;
char readBuf[128];
int n_read;
struct sockaddr_in s_addr; // 该结构体用来处理网络通信的地址族、端口号、IP地址等。s_addr服务端
struct sockaddr_in c_addr; // 客户端
struct Msg msg; // 一个包含种类type,指令cmd,缓冲secondBuf的结构体
memset(&s_addr, 0, sizeof(struct sockaddr_in)); // 初始化s_addr
memset(&c_addr, 0, sizeof(struct sockaddr_in));
if (argc != 3) // 判断参数个数是否正确,不正确则退出程序
{
printf("Parameter error: ip prot \n");
exit(-1);
}
// 1.socket
s_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket,设置TCP协议
if (s_fd == -1) {
perror("socket:");
exit(-1);
}
// 2.bind
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2])); // argv[2]为端口号,需要通过htons函数将主机字节序转网络字节序
inet_aton(argv[1], &s_addr.sin_addr); // argv[1]为IP地址,需要通过inet_aton将argv[1]字符串转网络字节序
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in)); // 绑定设置数据
// 3.listen
listen(s_fd,10); //监听,最多10个
// 4.accept
int len = sizeof(struct sockaddr_in);
while (1) {
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &len); // 接受客户端连接
if (c_fd == -1) { // 判断是否连接失败,方便查错
perror("accept:");
}
printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr)); // 连接成功后服务端打印接入的客户端IP地址
if (fork() == 0) { //创建子进程执行客户端所发信息,父进程继续监听,每有一个客户端接入,则创建一个子进程为其服务
// 5.read
while (1) {
memset(msg.cmd, 0, sizeof(msg.cmd)); // 初始化msg.cmd
n_read = read(c_fd, &msg, sizeof(msg)); // 读取客户端通过c_fd发送过来的消息,没有就阻塞在这,知道消息发送过来
if (n_read == -1) { // 如果n_read==-1,则read失败,方便排错
perror("read:");
}
else if (n_read == 0) {
printf("client out\n");
break;
}
else {
msg_handle(msg, c_fd); // 对客户端发送的消息进行处理
}
}
}
}
close(c_fd);
close(s_fd);
return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "msg.h"
int get_cmd_type(char *cmd) // 将命令字符串转换成整数
{
if (!strcmp("ls", cmd)) return LS;
if (!strcmp("quit", cmd)) return QUIT;
if (!strcmp("lpwd", cmd)) return LPWD;
if (!strcmp("pwd", cmd)) return PWD;
if (!strcmp("lls", cmd)) return LLS;
if (strstr(cmd, "lcd")) return LCD; // LCD必须写在CD前,否则当输入lcd时将会返回CD
if (strstr(cmd, "cd")) return CD;
if (strstr(cmd, "get")) return GET;
if (strstr(cmd, "put")) return PUT;
return -1;
}
char *getdir(char *cmd) // 分离参数
{
char *p;
p = strtok(cmd, " "); // strtok函数会破坏被分解字符串的完整,调用前和调用后的cmd已经不一样了。
p = strtok(NULL, " ");
return p;
}
int cmd_handle(struct Msg msg, int fd) // 命令处理
{
char buf[32]; // put时用来存放msg.cmd
int ret;
int filefd;
char *dir = NULL;
ret = get_cmd_type(msg.cmd); // 判断输入的是哪个命令
switch (ret)
{
case LS:
case CD:
case PWD: // 当输入命令是ls、cd、pwd等只对服务器操作的指令时,将msg里的所有数据发送给服务器
write(fd, &msg, sizeof(msg));
break;
case GET: // 当为get时,将msg里数据发给服务器
write(fd, &msg, sizeof(msg));
break;
case PUT:
strcpy(buf, msg.cmd); // 当为put时,将msg.cmd复制给buf
dir = getdir(buf); // 分离参数,即所需发送的文件名,buf破坏
if (access(dir, F_OK) == -1) { // 判断客户端是否存在该文件
printf("%s not exsit\n", dir);
}
else {
filefd = open(dir, O_RDWR); // 如果有就打开该文件
read(filefd, msg.buf, sizeof(msg.buf)); // 将文件里的数据读入到msg.buf
close(filefd); // 关闭文件
write(fd, &msg, sizeof(msg)); // 将msg里的数据发送给服务器
}
break;
case LLS: // 当为lls指令时,在客户端调用ls即可
system("ls"); // system函数调用完ls后,将自动打印输出结果
break;
case LPWD: // 当为lpwd指令时,在客户端调用pwd即可
system("pwd"); // system函数调用完pwd后,将自动打印输出结果
break;
case LCD: // 当为lcd指令时,分离得出路径名,调用chdir函数改变客户端当前路径
dir = getdir(msg.cmd);
chdir(dir);
break;
case QUIT: // 当为quit指令时,将消息发送给服务器,然后关闭fd,结束客户端
strcpy(msg.cmd, "quit");
write(fd, &msg, sizeof(msg));
close(fd);
exit(-1);
}
return ret;
}
void handle_server_msg(int c_fd, struct Msg msg) // 当接受到ls,get、pwd等指令时,客户端处理服务器发送来的消息
{
int n_read;
struct Msg msgget;
int newfilefd;
n_read = read(c_fd, &msgget, sizeof(msgget)); // 将服务器发送来的信息读入msgget
if (n_read == 0) {
printf("server is out,quit\n");
exit(-1);
}
else if (strstr(msg.cmd, "get")) { // 当指令为get时执行
char *p = getdir(msg.cmd); // 提取想要从服务器端获取的文件名
newfilefd = open(p, O_RDWR|O_CREAT, 0600); // 在客户端打开该文件,没有就创建
write(newfilefd, msgget.cmd, strlen(msgget.cmd)); // 将msgget里的数据写入该文件
close(newfilefd);
putchar('>');
fflush(stdout);
}
else {
printf("-------------------------------\n"); // 当指令为ls或pwd时,打印msgget.cmd里的数据
printf("\n%s\n", msgget.cmd);
printf("-------------------------------\n");
putchar('>');
fflush(stdout);
}
}
int main(int argc, char **argv)
{
int c_fd;
struct sockaddr_in c_addr;
struct Msg msg;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
if (argc!=3) {
printf("Parameter error: ip prot\n");
exit(-1);
}
//1.socket
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if (c_fd == -1) {
perror("socket:");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &c_addr.sin_addr);
//2.connect
if (connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) == -1) { // connect函数连接服务器,连接失败则返回-1
perror("connect:");
exit(-1);
}
printf("connect...\n"); // 连接成功打印connect...
int mark = 0;
while (1) {
memset(msg.cmd, 0, sizeof(msg.cmd)); // 初始化msg.cmd
if (mark == 0) {
printf(">"); // 使界面好看
gets(msg.cmd); // 从按键获取msg.cmd,即输入指令
}
if (strlen(msg.cmd) == 0) { // 如果输入的指令长为0,判断是否打印>,continue后下面语句不执行,从while循环第一句重新开始
if (mark == 1) {
printf(">");
continue;
}
}
mark = 1;
int ret = cmd_handle(msg, c_fd); // 命令处理函数
if (ret > IFGO) { // IFGO的值为3,即指令为lcd、lls、cd、put、quit、lpwd时,打印>,然后重新循环,下面语句不执行
putchar('>');
fflush(stdout);
continue;
}
if (ret == -1) { // 如果命令处理函数返回的值为-1,则说明该命令不存在
printf("cmd no have!\n");
printf(">");
fflush(stdout);
continue;
}
handle_server_msg(c_fd, msg); // 当接受到ls,get、pwd等指令时,客户端处理服务器发送来的消息
}
return 0;
}
msg.h
#define LS 0
#define GET 1
#define PWD 2
#define IFGO 3
#define LCD 4
#define LLS 5
#define CD 6
#define PUT 7
#define QUIT 8
#define LPWD 9
struct Msg
{
int type;
char cmd[1024];
char buf[128];
};