基于 Socket 的FTP 云盘系统

一、项目介绍

FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务。 FTP是File Transfer Protocol(文件传输协议)。 程序运行,服务端不断接收客户端指令,服务端可同时处理多个客户端接入并对指令作出解析,并把执行结果返回给客户端,客户端根据服务端对指令的解析并把由服务端传递过来的处理信息通过客户端呈现给客户,实现文件的各种操作。

Linux网络编程实现的FTP服务器,服务器由服务端和客户端组成,具有浏览远程服务端的文件和浏览客户端本地文件,同时支持对远程服务端文件的删除,存储,归档操作处理,以及客户端对远程服务端文件的上传和下载。

二、基本功能

利用socket实现云盘的:

  • ls---------查看服务端文件
  • lls---------查看客户端自己的文件
  • cd---------切换服务端目录
  • lcd---------切换客户端自己的目录
  • put---------上传文件
  • get---------下载文件
  • g------------退出连接

三、基本思路

1、流程图

2、基本思路

服务端: 1:socket 创建服务端的套接字 2:bind 端口号和IP地址 3:listen 监听客户端的连接 4:accept 接受客户端的接 入 5:read 接收客户端发送的message 6:服务端开始处理从客户端接收到的消息 7:send (write)服务端的 msg到客户端

客户端: 1.socket 创建客户端的套接字 ,构建客户端和服务端发送和接收信息的桥梁 2.connect 连接上服务端 3.获取用户键 盘输入,处理输入命令buf 4.send (write)客户端的command到服务端 5.read 服务端返回的message

四、代码基本框架

1、服务器基本框架

复制代码
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<string.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <fcntl.h>   
#include <sys/stat.h>

int change(char cmd[128])
{
	if(!strcmp("ls",cmd))
	{
		return 1;
	}
	else if(!strcmp("ps",cmd))
	{
		return 2;
	}
	else if(!strcmp("g",cmd))
	{
		return 3;
	}
	else if(strstr(cmd,"cd") != NULL)
	{
		return 4;
	}
	else if(strstr(cmd,"get") != NULL)
	{
		return 5;
	}
	else if(strstr(cmd,"put") != NULL)
	{
		return 6;
	}

	return -1;
}

void choosecmd(char cmd[128],int c_fd)
{
	int ret;
	switch(ret)
	{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
	}
	free(p);
}
int main(int argc,char **argv)
{
	int c_fd;
	int s_fd;
	int clen;
	int nread;

	char Readbuf[128];
	char Writebuf[128];

	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(1);
	}
	if(argc != 3)
	{
		perror("argc");
		exit(1);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	//bind
	if(bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)) < 0)
	{
		perror("bind");
		exit(1);
	}

	//listen
	if(listen(s_fd,10) < 0)
	{
		perror("listen");
		exit(1);
	}

	//accept
	printf("wait connecting\n");
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	clen = sizeof(struct sockaddr_in);
	while(1)
	{
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1)
		{
			perror("accept");
			continue;
		}

		printf("connect success,%s\n",inet_ntoa(c_addr.sin_addr));

		//read
		if(fork() == 0)
		{
			close(s_fd);
			while(1)
			{
				nread = read(c_fd,Readbuf,128);
				if(nread <= 0)
				{
					close(c_fd);
					exit(0);
				}
				choosecmd(Readbuf,c_fd);
				memset(Readbuf,0,sizeof(Readbuf));
			}

		}
	}

	close(s_fd);

	return 0;
}

①命令匹配函数change()

复制代码
int change(char cmd[128])
{
	if(!strcmp("ls",cmd))          return 1;
	else if(!strcmp("ps",cmd))      return 2;
	else if(!strcmp("g",cmd))       return 3;
	else if(strstr(cmd,"cd") != NULL) return 4;
	else if(strstr(cmd,"get") != NULL) return 5;
	else if(strstr(cmd,"put") != NULL) return 6;

	return -1;
}

作用:接收客户端发来的命令字符串,判断是什么命令,返回一个数字编号。

  • ls → 1
  • ps → 2
  • g → 3
  • 包含 cd →4
  • 包含 get→5
  • 包含 put→6
  • 不认识 →-1

②命令执行框架choosecmd()

复制代码
void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);         // 接收命令编号
	switch(ret)
	{
		case 1: break; // ls
		case 2: break; // ps
		case 3: break; // g
		case 4: break; // cd
		case 5: break; // get
		case 6: break; // put
	}
}

作用 :根据 change() 返回的数字,执行对应命令 。你现在这里只是空框架,后面可以填充:

  • 执行 ls
  • 执行 cd
  • 文件上传 / 下载
  • 发送结果回客户端

③核心main函数(服务器灵魂)

1.变量定义

复制代码
int c_fd;         // 客户端套接字(和客户端通信的通道)
int s_fd;         // 服务器套接字(负责监听连接)
int clen;         // 客户端地址长度
int nread;        // read() 读到的字节数

char Readbuf[128];// 接收客户端命令的缓冲区
char Writebuf[128];// 发送数据缓冲区

struct sockaddr_in s_addr; // 服务器地址结构体
struct sockaddr_in c_addr; // 客户端地址结构体

2.创建套接字socket()

复制代码
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
	perror("socket");
	exit(1);
}
  • AF_INET:使用 IPv4
  • SOCK_STREAM:TCP 协议
  • 返回值:服务器监听套接字(用来等别人连接)

3.参数检查

复制代码
if(argc != 3){
	perror("argc");
	exit(1);
}

运行必须带IP+端口

4.配置服务器地址结构体

复制代码
memset(&s_addr,0,sizeof(struct sockaddr_in));

s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2])); // 端口号
inet_aton(argv[1],&s_addr.sin_addr);    // IP 地址
  • htons():主机字节序 → 网络字节序
  • inet_aton():字符串 IP → 网络格式 IP

5.绑定端口bind()

复制代码
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in))

把IP+端口绑定到套接字上

6.监听连接listen()

复制代码
listen(s_fd,10)
  • 让服务器进入监听状态
  • 10:最多等待连接的队列长度

7.循环接受连接accept()

复制代码
while(1){
	c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
	printf("connect success,%s\n",inet_ntoa(c_addr.sin_addr));
}
  • accept() 阻塞等待客户端连接
  • 连接成功后返回 c_fd(客户端通信套接字)
  • 可以用 inet_ntoa(c_addr.sin_addr) 打印客户端 IP

④多进程处理客户端

复制代码
if(fork() == 0)  // 创建子进程
{
	close(s_fd); // 子进程不需要监听套接字
	while(1)     // 循环接收客户端命令
	{
		nread = read(c_fd,Readbuf,128); // 读命令
		if(nread <= 0){
			close(c_fd);
			exit(0); // 客户端断开,子进程退出
		}
		choosecmd(Readbuf,c_fd); // 执行命令
		memset(Readbuf,0,sizeof(Readbuf));
	}
}

一个服务器可以同时服务多个客户端

  • 父进程:负责 accept () 等待新连接
  • 子进程:负责 和客户端通信、执行命令

流程:

  1. 客户端连接 → 父进程 fork 子进程
  2. 子进程关闭监听套接字
  3. 子进程循环 read 客户端命令
  4. 调用 choosecmd 执行命令
  5. 客户端断开 → 子进程退出

2、客户端基本框架

复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include <fcntl.h>   
#include <sys/stat.h> 

int change(char cmd[128])
{
	if(!strcmp("ls",cmd))
	{
		return 1;
	}
	else if(!strcmp("lls",cmd))
	{
		return 2;
	}
	else if(!strcmp("g",cmd))
	{
		return 3;
	}
	else if(strstr(cmd,"lcd") != NULL)
	{
		return 4;
	}
	else if(strstr(cmd,"cd") != NULL)
	{
		return 5;
	}
	else if(strstr(cmd,"get") != NULL)
	{
		return 6;
	}
	else if(strstr(cmd,"put") != NULL)
	{
		return 7;
	}
	
	return -1;
}

void choosecmd(char cmd[128],int c_fd)
{
	int ret;
	switch(ret)
	{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 7:
			break;
	}
	free(p);
}

int main(int argc,char **argv)
{
	int c_fd;
	struct sockaddr_in c_addr;
	int clen;

	char Writebuf[128];
	char Readbuf[1024];

	if(argc != 3)
	{
		perror("argc");
		exit(1);
	}

	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1)
	{
		perror("socket");
		exit(1);
	}
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	//connect
	clen = sizeof(struct sockaddr_in);
	if(connect(c_fd,(struct sockaddr *)&c_addr,clen) < 0)
	{
		perror("connect");
		exit(1);
	}

	//waited send
	printf("connect...\n");
	while(1)
	{
		fgets(Writebuf,sizeof(Writebuf),stdin);
		Writebuf[strcspn(Writebuf, "\n")] = '\0';
		printf("cmd:%s\n",Writebuf);

		write(c_fd,Writebuf,strlen(Writebuf));
		choosecmd(Writebuf,c_fd);
		printf("-----------------cmd--------------\n-");

		memset(Writebuf,0,sizeof(Writebuf));
	}


	return 0;
}

①命令匹配函数:change()

复制代码
int change(char cmd[128])
{
	if(!strcmp("ls",cmd))          return 1;
	else if(!strcmp("lls",cmd))     return 2;
	else if(!strcmp("g",cmd))       return 3;
	else if(strstr(cmd,"lcd") != NULL) return 4;
	else if(strstr(cmd,"cd") != NULL)  return 5;
	else if(strstr(cmd,"get") != NULL) return 6;
	else if(strstr(cmd,"put") != NULL) return 7;

	return -1;
}

作用:判断你输入的命令是什么,返回编号。

  • ls → 1(发给服务器,让服务器执行
  • lls →2(本地执行
  • g →3(退出)
  • lcd →4(本地切换目录
  • cd →5(发给服务器,让服务器切换目录
  • get→6(下载文件)
  • put→7(上传文件)

这就是客户端特有的逻辑 :一部分命令本地执行 ,一部分发给服务器

②命令执行框架:choosecmd()

复制代码
void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);
	switch(ret)
	{
		case 1: break;
		case 2: break;
		case 3: break;
		case 4: break;
		case 5: break;
		case 6: break;
		case 7: break;
	}
}

作用:根据命令编号,执行对应操作。

目前是空框架,后面可以填:

  • 接收服务器返回的 ls 结果
  • 本地执行 lls
  • 本地切换目录 lcd
  • 下载文件 get
  • 上传文件 put

③核心:main 函数(客户端灵魂)

  1. 变量定义

    int c_fd; // 客户端套接字(和服务器通信的通道)
    struct sockaddr_in c_addr; // 服务器地址结构体
    int clen; // 地址结构体长度

    char Writebuf[128]; // 发送命令缓冲区
    char Readbuf[1024]; // 接收数据缓冲区

  2. 参数检查

    if(argc != 3)
    {
    perror("argc");
    exit(1);
    }

运行必须带 服务器 IP + 端口

bash

运行

复制代码
./client 192.168.1.100 8888
  1. 创建客户端套接字

    c_fd = socket(AF_INET,SOCK_STREAM,0);
    if(c_fd == -1)
    {
    perror("socket");
    exit(1);
    }

  • 和服务器完全一样
  • 创建 TCP 套接字
  1. 配置服务器地址

    memset(&c_addr,0,sizeof(struct sockaddr_in));
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],&c_addr.sin_addr);

这里填的是服务器的 IP 和端口,客户端要知道连谁。

  1. 连接服务器:connect ()

    if(connect(c_fd,(struct sockaddr *)&c_addr,clen) < 0)
    {
    perror("connect");
    exit(1);
    }

客户端最核心函数

  • 主动连接服务器
  • 连接成功,才能通信
  1. 循环输入命令 + 发送 + 处理

    printf("connect...\n");
    while(1)
    {
    fgets(Writebuf,sizeof(Writebuf),stdin); // 1. 输入命令
    Writebuf[strcspn(Writebuf, "\n")] = '\0'; // 去掉换行
    printf("cmd:%s\n",Writebuf);

    复制代码
     write(c_fd,Writebuf,strlen(Writebuf));        // 2. 发给服务器
     choosecmd(Writebuf,c_fd);                    // 3. 处理命令(本地/接收)
     printf("-----------------cmd--------------\n");
     memset(Writebuf,0,sizeof(Writebuf));

    }

完整流程:

  1. 你输入 ls
  2. write 发给服务器
  3. choosecmd 接收服务器返回结果并打印

④客户端整体运行逻辑

  1. 启动客户端 → 创建 socket
  2. 配置服务器 IP / 端口 → connect 连接
  3. 进入循环:
    • 输入命令
    • 发送给服务器
    • 处理命令(本地执行 或 接收服务器结果)
  4. 直到输入 g 退出

客户端 vs 服务器 对应关系

服务器 客户端
socket() socket()
bind() 不需要
listen() 不需要
accept() connect()
read () 接收命令 write () 发送命令
执行命令 处理结果 / 本地执行

五、代码实现具体功能

1、ls,lls,g功能的实现

ls:服务器列出当前目录文件

lls:客户端列出当前目录文件

g:客户端退出连接

服务器端:

复制代码
void choosecmd(char cmd[128],int c_fd)
{
	int ret;
	FILE *fdb;
	char freadbuf[1024] = {0};
	ret = change(cmd);
	switch(ret)
	{
		case 1:
			fdb = popen("ls","r");
			fread(freadbuf,1024,1,fdb);
			freadbuf[strlen(freadbuf)] = '\0';
			write(c_fd,freadbuf,strlen(freadbuf));
			memset(freadbuf,0,sizeof(freadbuf));
			printf("ls secceed\n");
			pclose(fdb);
			break;
		case 2:
			system("ps");
			printf("ps succeed\n");
			break;
		case 3:
			read(c_fd,freadbuf,128);
			printf("%s\n",freadbuf);
			exit(1);
			break;

①case 1:ls 命令(服务器执行,返回结果给客户端)

复制代码
case 1:
	// 1. 执行系统命令 ls,打开管道读取结果
	fdb = popen("ls","r");

	// 2. 把 ls 的结果读到 freadbuf 里
	fread(freadbuf,1024,1,fdb);

	// 3. 确保字符串结束
	freadbuf[strlen(freadbuf)] = '\0';

	// 4. 把 ls 结果发送给客户端
	write(c_fd,freadbuf,strlen(freadbuf));

	// 5. 清空缓冲区
	memset(freadbuf,0,sizeof(freadbuf));

	// 6. 服务器打印日志
	printf("ls secceed\n");

	// 7. 关闭命令管道
	pclose(fdb);
	break;
  1. popen("ls","r")

    • 作用:执行系统命令 ls
    • 并且创建一个管道,"r"是把命令执行的结果读出来
    • 返回一个文件指针 fdb
  2. fread(缓冲区, 大小, 个数, 文件指针)

    • ls 执行出来的文件列表读到 freadbuf
  3. freadbuf[strlen(freadbuf)] = '\0'

    • 给字符串加结束符,保证不乱码
  4. write(c_fd, freadbuf, ...)

    • 把服务器执行 ls 的结果发给客户端
  5. pclose(fdb)

    • 关闭命令执行管道

②case 2:ps 命令(服务器自己打印进程信息)

复制代码
case 2:
	system("ps");       // 执行系统命令 ps
	printf("ps succeed\n");
	break;
  • system("ps")直接在服务器端屏幕 打印当前系统进程**不会发给客户端!**只有服务器自己能看到。

③case 3:g 命令(客户端退出)

复制代码
case 3:
	read(c_fd,freadbuf,128);  // 读取客户端发来的断开消息
	printf("%s\n",freadbuf);  // 服务器打印:away host
	exit(1);                  // 服务器子进程退出
	break;
  1. 客户端输入 g → 发送 "away host" 给服务器
  2. 服务器 read 收到这个消息
  3. 服务器打印出来
  4. exit(1) → 服务器子进程直接退出代表这个客户端连接结束

客户端:

复制代码
void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);
	char *p = (char *)malloc(8000);
	char *dir;
	switch(ret)
	{
		case 1:
			memset(p,0,8000);
			read(c_fd,p,8000);
			printf("%s\n",p);
			memset(p,0,1024);
			break;
		case 2:
			system("ls");
			break;
		case 3:
			printf("unconnecting\n");
			write(c_fd,"away host",strlen("away host"));
			close(c_fd);
			exit(-1);
			break;
        ...
    }
    free(p);
}

①case 1:ls 命令(客户端接收服务器发来的结果)

复制代码
case 1:
	memset(p,0,8000);        // 清空缓冲区,防止脏数据
	read(c_fd,p,8000);      // 从服务器读取 ls 结果
	printf("%s\n",p);       // 打印服务器发来的文件列表
	memset(p,0,1024);       // 再次清空
	break;
  1. **memset(p,0,8000)**把内存全部清零,保证接收数据干净不乱码。

  2. read(c_fd, p, 8000)

    • 核心作用阻塞等待服务器发来 ls 的结果
    • 从 socket 读取数据,存到 p 指针指向的内存
    • 这一步是客户端接收服务器消息的关键
  3. **printf("%s\n",p)**把服务器发来的文件列表打印在客户端屏幕。

  4. **memset(p,0,1024)**清空缓冲区,为下一次通信做准备。

②case 2:lls 命令(客户端本地自己列文件)

复制代码
case 2:
	system("ls");    // 本地执行 ls
	break;
  • 不与服务器通信
  • 直接在客户端自己 执行系统命令 ls
  • 结果只显示在客户端屏幕
  • 服务器完全不知道

③case 3:g 命令(客户端主动断开退出)

复制代码
case 3:
	printf("unconnecting\n");             // 打印断开提示
	write(c_fd,"away host", strlen("away host"));  // 告诉服务器:我要走了
	close(c_fd);                          // 关闭socket连接
	exit(-1);                             // 客户端程序退出
	break;
  1. **printf("unconnecting\n")**客户端屏幕输出:正在断开连接

  2. write(c_fd, "away host", ...) 给服务器发送一段消息:away host服务器收到后就知道客户端要断开了

  3. **close(c_fd)**关闭与服务器的通信套接字,断开 TCP 连接

  4. **exit(-1)**客户端程序直接退出

超级重要对应关系

服务器端 case1 ls

  • 执行 popen(ls)
  • 把结果发给客户端

客户端 case1 ls

  • read 接收结果
  • 打印显示

服务器端 看不到 lls

  • 客户端 lls本地执行
  • 服务器完全不知道

g 命令

  • 客户端:关闭连接 + 退出
  • 服务器:收到断开消息,子进程退出

2、cd,lcd功能的实现

cd:让服务器切换它自己的工作目录

lcd:让客户端自己切换本地目录

服务器端:

复制代码
char *getbehind(char cmd[128])
{
	static char p[128];
	memset(p,0,128);
	strtok(cmd," ");
	char *tmp = strtok(NULL," ");
	if(tmp) strcpy(p,tmp);
	return p;
}
void choosecmd(char cmd[128],int c_fd)
{
	char *dir;
	switch(ret)
	{
		case 4:
			dir = getbehind(cmd);
			if(dir[0] != '\0')
			chdir(dir);
			printf("cd %s succeed\n",dir);
			break;
	}
	free(p);
}

getbehind 函数 ------ 命令分割核心

复制代码
char *getbehind(char cmd[128])
{
	static char p[128];   // 静态数组,保存结果
	memset(p,0,128);      // 清空数组
	strtok(cmd," ");      // 第一次分割:取命令(cd)
	char *tmp = strtok(NULL," "); // 第二次分割:取参数(目录名)
	if(tmp) strcpy(p,tmp);        // 如果有参数,复制到 p
	return p;                     // 返回目录名
}

假设客户端发来命令:cmd = "cd test"

  1. strtok(cmd," ");
  • 作用 :按空格分割字符串
  • 第一次调用 ,传入 cmd → 分割出 cd
  • 函数内部会记住分割到的位置
  1. char *tmp = strtok(NULL," ");
  • 第二次调用 ,传 NULL → 继续往下分割
  • 分割出 test
  • tmp 指向 "test"
  1. if(tmp) strcpy(p,tmp);
  • "test" 复制到 p 数组里
  1. return p;
  • 返回 test

总结 getbehind 功能:

输入:cd test 输出:test(命令后面的参数)

这就是提取命令参数的函数!

②**cd 功能实现代码**

复制代码
case 4:
	dir = getbehind(cmd);  // 1. 拿到目录名(如 test)
	if(dir[0] != '\0')     // 2. 判断目录名不为空
	chdir(dir);            // 3. 系统调用:切换目录
	printf("cd %s succeed\n",dir); // 4. 打印成功
	break;
  1. dir = getbehind(cmd);

调用刚才的分割函数,拿到目录名 dir = "test"

  1. if(dir[0] != '\0')

判断用户有没有输入目录名

  • 输入 cd → dir 为空,不切换
  • 输入 cd test → dir 非空,执行切换
  1. chdir(dir);

Linux 系统函数 :切换进程的工作目录服务器调用后,服务器的目录变了

  1. printf("cd %s succeed\n",dir);

服务器端打印:cd test succeed

客户端:

复制代码
char *getbehind(char cmd[128])
{
    static char p[128];
    memset(p, 0, 128);
    strtok(cmd, " ");
    char *tmp = strtok(NULL, " ");
    if(tmp) strcpy(p, tmp);
    return p;
}
void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);
	char *dir;
	switch(ret)
	{
		case 4:
			dir = getbehind(cmd);
			if(dir[0] != '\0')
			{
				chdir(dir);
				printf(">> Local dir changed: %s\n", dir);
			}
			break;
		case 5:
			printf(">> Send cd command to server\n");
			break;
	}
}

同样的先进行代码分割

①case 4:lcd → 客户端本地切换目录

复制代码
case 4:
	dir = getbehind(cmd);       // 拿到目录名,如 test
	if(dir[0] != '\0')          // 目录名不为空
	{
		chdir(dir);             // 客户端自己切换目录
		printf(">> Local dir changed: %s\n", dir);
	}
	break;
  1. lcd 是客户端本地命令
  2. 不会发给服务器
  3. 调用 chdir(dir)改变客户端自己的工作目录
  4. 打印提示:本地目录已切换

②case 5:cd → 发送命令给服务器

复制代码
case 5:
	printf(">> Send cd command to server\n");
	break;
  1. cd 是远程命令
  2. 客户端不做任何切换操作
  3. 只是打印一句话:已发送cd命令到服务器
  4. 真正切换目录的动作,在服务器端执行

3、get,put功能实现

get:从服务器下载文件到客户端

put:从客户端上传文件到服务器

服务器端:

复制代码
char *getbehind(char cmd[128])
{
	static char p[128];
	memset(p,0,128);
    strtok(cmd," ");
	char *tmp = strtok(NULL," ");
	if(tmp) strcpy(p,tmp);
	return p;
}
void putmessage(char cmd[128],int c_fd)
{
	char *filename;
	int sfd;
	char readbuf[8000];
	filename = getbehind(cmd);

	if(filename[0] != '\0')
	{
		if(access(filename,F_OK) == -1)
		{
			printf("NO file");
		}
		else
		{
			sfd = open(filename,O_RDONLY);
			read(sfd,readbuf,8000);
			write(c_fd,readbuf,strlen(readbuf));
			printf("put %s succeed\n",filename);
			close(sfd);
		}
	}

}
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];
	char *filename = getbehind(cmd);
    int n = read(c_fd,readbuf,8000);

	if(filename[0] != '\0')
	{
		read(c_fd,readbuf,8000);
		int fd = open(filename,O_RDWR|O_CREAT,0666);
		write(fd,readbuf,n);
		printf("get %s succeed\n",readbuf);
		close(fd);
	}
}
void choosecmd(char cmd[128],int c_fd)
{
	ret = change(cmd);
	switch(ret)
	{
		case 5:
			putmessage(cmd,c_fd);
			break;
		case 6:
			getmessage(cmd,c_fd);
			break;
	}
}

①代码分割 getbehind()

复制代码
char *getbehind(cmd);

作用:把命令按空格切开,拿到后面的文件名 例如:get test.txt → 拿到 test.txt``put 123.c → 拿到 123.c

case5: putmessage 服务器发送文件(对应客户端 get)

复制代码
void putmessage(char cmd[128],int c_fd)
{
	char *filename;       // 文件名
	int sfd;              // 文件描述符
	char readbuf[8000];   // 数据缓冲区

	filename = getbehind(cmd); // 拿到文件名

	if(filename[0] != '\0')    // 文件名不为空
	{
		if(access(filename,F_OK) == -1)  // 判断文件是否存在
		{
			printf("NO file");
		}
		else
		{
			sfd = open(filename,O_RDONLY);  // 打开文件
			read(sfd,readbuf,8000);           // 把文件读到缓冲区
			write(c_fd,readbuf,strlen(readbuf)); // 发给客户端
			printf("put %s succeed\n",filename);
			close(sfd);                       // 关闭文件
		}
	}
}
  1. **filename = getbehind(cmd)**拿到客户端要下载的文件名。

  2. access(filename, F_OK) 判断服务器本地有没有这个文件,没有就打印 NO file

  3. **open(filename, O_RDWR, 0666)**打开要发送的文件。

  4. read(sfd, readbuf, 8000) 把文件内容读到内存缓冲区 readbuf

  5. write(c_fd, readbuf, ...) 通过 socket 把文件内容发给客户端

  6. 打印成功,关闭文件。

putmessage = 服务器读取文件 → 发给客户端(给客户端下载用)

case6: getmessage 服务器接收文件(对应客户端 put)

复制代码
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];
	char *filename = getbehind(cmd); // 拿到要保存的文件名
    int n = read(c_fd,readbuf,8000);

	if(filename[0] != '\0')
	{
		read(c_fd,readbuf,8000);     // 从客户端读取文件数据
		int fd = open(filename,O_RDWR|O_CREAT,0666); // 创建/打开文件
		write(fd,readbuf,n);          // 写入文件
		printf("get %s succeed\n",readbuf);
		close(fd);
	}
}
  1. **filename = getbehind(cmd)**拿到要保存的文件名。

  2. read(c_fd, readbuf, 8000) 阻塞等待客户端发送文件数据,读到内存。

  3. **open(filename, O_RDWR|O_CREAT, 0666)**打开文件(没有就创建)。

  4. **write(fd, readbuf, ...)**把客户端发来的数据写入文件。

  5. 打印成功,关闭文件。

getmessage = 服务器接收客户端数据 → 保存成文件(给客户端上传用)

choosecmd 里的调用

复制代码
case 5:
	putmessage(cmd,c_fd);  // 客户端 get → 服务器发文件
	break;
case 6:
	getmessage(cmd,c_fd);  // 客户端 put → 服务器收文件
	break;

总结

  • putmessage读文件 → 发给客户端 对应:客户端 get(下载)

  • getmessage读客户端数据 → 写文件 对应:客户端 put(上传)

客户端:

复制代码
char *getbehind(char cmd[128])
{
    static char p[128];
    memset(p, 0, 128);
    strtok(cmd, " ");
    char *tmp = strtok(NULL, " ");
    if(tmp) strcpy(p, tmp);
    return p;
}
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];
	char *filename = getbehind(cmd);
    int n = read(c_fd,readbuf,8000);
	if(filename[0] != '\0')
	{
		read(c_fd,readbuf,8000);
		int fd = open(filename,O_RDWR|O_CREAT,0666);
		write(fd,readbuf,n);
		printf("recrive %s succeed!\n",filename);
		close(fd);
	}
}
void putmessage(char cmd[128],int c_fd)
{
	char *filename;
	int sfd;
	char readbuf[8000];
	filename = getbehind(cmd);

	if(access(filename,F_OK) == -1)
	{
		printf("NO file\n");
	}
	else
	{
		sfd = open(filename,O_RDONLY);
		read(sfd,readbuf,8000);
		write(c_fd,readbuf,strlen(readbuf));
		printf("send %s succeed!\n",filename);
		close(sfd);
	}
}

void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);
	switch(ret)
	{
		case 6:
			getmessage(cmd,c_fd);
			break;
		case 7:
			putmessage(cmd,c_fd);
			break;
	}
}

①代码分割getbehind()

复制代码
char *getbehind(cmd);

作用:分割命令,拿到后面的文件名 例:get test.txt → 拿到 test.txt``put abc.c → 拿到 abc.c

case 6:getmessage 客户端 下载文件(get)

复制代码
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];           // 接收数据的缓冲区
	char *filename = getbehind(cmd); // 拿到要保存的文件名
    int n = read(c_fd,readbuf,8000);

	if(filename[0] != '\0')       // 文件名不为空
	{
		read(c_fd,readbuf,8000);  // 从服务器读取文件内容
		int fd = open(filename,O_RDWR|O_CREAT,0666); // 创建文件
		write(fd,readbuf,n);          // 写入本地文件
		printf("recrive %s succeed!\n",filename);    // 打印成功
		close(fd);
	}
}
  1. **read(c_fd, readbuf, 8000)**从服务器读取发来的文件内容

  2. **open(filename, O_CREAT)**本地创建一个文件,准备保存

  3. **write(fd, readbuf, ...)**把服务器发来的数据写入本地文件

  4. 打印下载成功

getmessage = 客户端接收服务器文件 → 保存到本地(下载)

case 7:putmessage 客户端 上传文件(put)

复制代码
void putmessage(char cmd[128],int c_fd)
{
	char *filename;
	int sfd;
	char readbuf[8000];
	filename = getbehind(cmd); // 拿到要上传的文件名

	if(access(filename,F_OK) == -1) // 判断本地文件是否存在
	{
		printf("NO file");
	}
	else
	{
		sfd = open(filename,O_RDONLY); // 打开本地文件
		read(sfd,readbuf,8000);           // 读取文件内容
		write(c_fd,readbuf,strlen(readbuf)); // 发给服务器
		printf("send %s succeed!\n",filename);
		close(sfd);
	}
}
  1. **access(filename, F_OK)**检查本地有没有这个文件,没有就打印 NO file

  2. open 打开文件 → read 读到缓冲区

  3. **write(c_fd, ...)**把文件内容通过 socket 发给服务器

  4. 打印上传成功

putmessage = 客户端读取本地文件 → 发给服务器(上传)

choosecmd 命令匹配

复制代码
case 6:
	getmessage(cmd,c_fd);  // get 命令 → 下载
	break;
case 7:
	putmessage(cmd,c_fd);  // put 命令 → 上传
	break;

总结

  • getmessage ← 下载收服务器的文件,保存到本地

  • putmessage → 上传读本地文件,发给服务器

六、最终实现

服务器端

复制代码
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<string.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <fcntl.h>   
#include <sys/stat.h>

int change(char cmd[128])
{
	if(!strcmp("ls",cmd))
	{
		return 1;
	}
	else if(!strcmp("ps",cmd))
	{
		return 2;
	}
	else if(!strcmp("g",cmd))
	{
		return 3;
	}
	else if(strstr(cmd,"cd") != NULL)
	{
		return 4;
	}
	else if(strstr(cmd,"get") != NULL)
	{
		return 5;
	}
	else if(strstr(cmd,"put") != NULL)
	{
		return 6;
	}

	return -1;
}
char *getbehind(char cmd[128])
{
	static char p[128];
	memset(p,0,128);
	strtok(cmd," ");
	char *tmp = strtok(NULL," ");
	if(tmp) strcpy(p,tmp);
	return p;
}
void putmessage(char cmd[128],int c_fd)
{
	char *filename;
	int sfd;
	char readbuf[8000];
	filename = getbehind(cmd);

	if(filename[0] != '\0')
	{
		if(access(filename,F_OK) == -1)
		{
			printf("NO file");
		}
		else
		{
			sfd = open(filename,O_RDONLY);
			read(sfd,readbuf,8000);
			write(c_fd,readbuf,strlen(readbuf));
			printf("put %s succeed\n",filename);
			close(sfd);
		}
	}

}
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];
	char *filename = getbehind(cmd);
    int n = read(c_fd,readbuf,8000);

	if(filename[0] != '\0')
	{
		read(c_fd,readbuf,8000);
		int fd = open(filename,O_RDWR|O_CREAT,0666);
		write(fd,readbuf,n);
		printf("get %s succeed\n",filename);
		close(fd);
	}
}
void choosecmd(char cmd[128],int c_fd)
{
	int ret;
	FILE *fdb;
	char freadbuf[1024] = {0};
	char *dir;
	char *readbuf = (char *)malloc(128);
	int sfd;
	ret = change(cmd);
	switch(ret)
	{
		case 1:
			fdb = popen("ls","r");
			fread(freadbuf,1024,1,fdb);
			freadbuf[strlen(freadbuf)] = '\0';
			write(c_fd,freadbuf,strlen(freadbuf));
			memset(freadbuf,0,sizeof(freadbuf));
			printf("ls secceed\n");
			pclose(fdb);
			break;
		case 2:
			system("ps");
			printf("ps succeed\n");
			break;
		case 3:
			read(c_fd,freadbuf,128);
			printf("%s\n",freadbuf);
			exit(1);
			break;
		case 4:
			dir = getbehind(cmd);
			if(dir[0] != '\0')
			if(chdir(dir) == 0)
            {
			    printf("cd %s succeed\n",dir);
			}
            else
            {
                perror("cd fail\n");
            }
            break;
		case 5:
			putmessage(cmd,c_fd);
			break;
		case 6:
			getmessage(cmd,c_fd);
			break;
	}
}
int main(int argc,char **argv)
{
	int c_fd;
	int s_fd;
	int clen;
	int nread;

	char Readbuf[128];
	char Writebuf[128];

	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(1);
	}
	if(argc != 3)
	{
		perror("argc");
		exit(1);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	//bind
	if(bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)) < 0)
	{
		perror("bind");
		exit(1);
	}

	//listen
	if(listen(s_fd,10) < 0)
	{
		perror("listen");
		exit(1);
	}

	//accept
	printf("wait connecting\n");
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	clen = sizeof(struct sockaddr_in);
	while(1)
	{
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1)
		{
			perror("accept");
			continue;
		}

		printf("connect success,%s\n",inet_ntoa(c_addr.sin_addr));

		//read
		if(fork() == 0)
		{
			close(s_fd);
			while(1)
			{
				nread = read(c_fd,Readbuf,128);
				if(nread <= 0)
				{
					close(c_fd);
					exit(0);
				}
				choosecmd(Readbuf,c_fd);
				memset(Readbuf,0,sizeof(Readbuf));
			}

		}
	}

	close(s_fd);

	return 0;
}

客户端

复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include <fcntl.h>   
#include <sys/stat.h> 

int change(char cmd[128])
{
	if(!strcmp("ls",cmd))
	{
		return 1;
	}
	else if(!strcmp("lls",cmd))
	{
		return 2;
	}
	else if(!strcmp("g",cmd))
	{
		return 3;
	}
	else if(strstr(cmd,"lcd") != NULL)
	{
		return 4;
	}
	else if(strstr(cmd,"cd") != NULL)
	{
		return 5;
	}
	else if(strstr(cmd,"get") != NULL)
	{
		return 6;
	}
	else if(strstr(cmd,"put") != NULL)
	{
		return 7;
	}
	
	return -1;
}

char *getbehind(char cmd[128])
{
    static char p[128];
    memset(p, 0, 128);
    strtok(cmd, " ");
    char *tmp = strtok(NULL, " ");
    if(tmp) strcpy(p, tmp);
    return p;
}
void getmessage(char cmd[128],int c_fd)
{
	char readbuf[8000];
	char *filename = getbehind(cmd);

	if(filename[0] != '\0')
	{
		read(c_fd,readbuf,8000);
		int fd = open(filename,O_RDWR|O_CREAT,0666);
		write(fd,readbuf,strlen(readbuf));
		printf("recrive %s succeed!\n",filename);
		close(fd);
	}
}
void putmessage(char cmd[128],int c_fd)
{
	char *filename;
	int sfd;
	char readbuf[8000];
    int n = read(c_fd,readbuf,8000);
	filename = getbehind(cmd);

	if(access(filename,F_OK) == -1)
	{
		printf("NO file\n");
	}
	else
	{
		sfd = open(filename,O_RDONLY);
		read(sfd,readbuf,8000);
		write(c_fd,readbuf,n);
		printf("send %s succeed!\n",filename);
		close(sfd);
	}
}

void choosecmd(char cmd[128],int c_fd)
{
	int ret = change(cmd);
	char *p = (char *)malloc(8000);
	char *dir;
	switch(ret)
	{
		case 1:
			memset(p,0,8000);
			read(c_fd,p,8000);
			printf("%s\n",p);
			memset(p,0,1024);
			break;
		case 2:
			system("ls");
			break;
		case 3:
			printf("unconnecting\n");
			write(c_fd,"away host",strlen("away host"));
			close(c_fd);
			exit(-1);
			break;
		case 4:
			dir = getbehind(cmd);
			if(dir[0] != '\0')
			{
				chdir(dir);
				printf(">> Local dir changed: %s\n", dir);
			}
			break;
		case 5:
			printf(">> Send cd command to server\n");
			break;
		case 6:
			getmessage(cmd,c_fd);
			break;
		case 7:
			putmessage(cmd,c_fd);
			break;
	}
	free(p);
}

int main(int argc,char **argv)
{
	int c_fd;
	struct sockaddr_in c_addr;
	int clen;

	char Writebuf[128];
	char Readbuf[1024];

	if(argc != 3)
	{
		perror("argc");
		exit(1);
	}

	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1)
	{
		perror("socket");
		exit(1);
	}
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	//connect
	clen = sizeof(struct sockaddr_in);
	if(connect(c_fd,(struct sockaddr *)&c_addr,clen) < 0)
	{
		perror("connect");
		exit(1);
	}

	//waited send
	printf("connect...\n");
	while(1)
	{
		fgets(Writebuf,sizeof(Writebuf),stdin);
		Writebuf[strcspn(Writebuf, "\n")] = '\0';
		printf("cmd:%s\n",Writebuf);

		write(c_fd,Writebuf,strlen(Writebuf));
		choosecmd(Writebuf,c_fd);
		printf("-----------------cmd--------------\n-");

		memset(Writebuf,0,sizeof(Writebuf));
	}


	return 0;
}
相关推荐
阳光普照世界和平2 小时前
实战指南|3类高频软件漏洞,从识别到修复一步到位
网络
j_xxx404_2 小时前
用系统调用从零封装一个C语言标准I/O库 | 附源码
linux·c语言·开发语言·后端
kyle~2 小时前
计算机网络----数据链路层(逻辑链路控制子层LLC、介质访问控制子层MAC)
网络·网络协议·计算机网络
计算机魔术师2 小时前
【AI面试八股文 Vol.1.1 | 专题3:State Schema 设计】State Schema设计:TypedDict / Pydantic类型约束
linux·人工智能·面试
j_xxx404_2 小时前
面试官灵魂拷问:Linux软链接与硬链接到底有什么区别?(附底层Inode级深度图解)
linux·运维·服务器
前端技术2 小时前
ICMP与ARP协议
网络·智能路由器
lThE ANDE8 小时前
最完整版Linux安装Redis(保姆教程)
linux·运维·redis
byoass9 小时前
企业云盘文件预览技术深度剖析:从10种常见格式到渲染架构实战
网络·安全·架构·云计算
TechWayfarer11 小时前
知乎/微博的IP属地显示为什么偶尔错误?用IP归属地查询平台自检工具3步验证
网络·python·网络协议·tcp/ip·网络安全