Linux系统下C语言实现百度网盘
- Linux操作系统下用C语言写一个网盘
- 完整代码:
- 服务器
- 客户端
Linux操作系统下用C语言写一个网盘
本次实验完成了完整的网盘功能(查询文件,下载文件,上传文件,刷新界面,和退出系统),包括附加功能 (查询特定目录下的文件进行下载或者上传)。
- 初始文件分配,在根目录下的初始文件是dd.txt ee.txt 而./download里面的文件是a.txt b.txt c.txt.。 分别用于客户下载和客户上传文件。
- 刷新网盘的界面: 当用户输入4,重新输入网盘的可视化界面,并且让用户选择执行的操作。
void net_disk_ui() //网盘可视化页面
{
printf("======================TCP网盘客户端==================\n");
printf("=========================功能菜单====================\n");
printf("\t\t\t1.查询文件\n");
printf("\t\t\t2.下载文件\n");
printf("\t\t\t3.上传文件\n");
printf("\t\t\t4.刷新界面\n");
printf("\t\t\t0.退出系统\n");
printf("------------------------------------------------------\n");
printf("请选择你要执行的操作: \n");
}
case '4':
{
net_disk_ui();
}
- 查询文件: 当用户输入1,输出home/drj/目录下的文件列表
case '1': // 要让服务器给我们发送目录信息->客户端也要创捷线程
// 这个while循环本身也是死循环,我们要让客户端也创建线程,让接受服务器的数据的代码放进线程中
send_msg.type = MSG_TYPE_FILENAME;
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send msg error: ");
}
memset(&send_msg, 0, sizeof(MSG));
break;
- 下载文件: 根据给定被下载文件的路径以及需要下载的指定位置,连续下载两个文件,并且下载路径和内容无误
- 当用户输入2的时候,先显示当前目录/home/drj目录下有哪些内容,让用户进行选择下载哪一个文件
- 经过下载,./download目录下由dd.txt 和 ee.txt这两个文件,并且核对文件内容无误,下载成功操作。
// 客户端
case '2':
{
send_msg.type = MSG_TYPE_DONWLOAD;
struct dirent* dir = NULL;
printf("there are files in /home/drj : \n");
DIR* dp = opendir("/home/drj");
while (1)
{
dir = readdir(dp);
if (NULL == dir)
{
break;
}
if (dir->d_name[0] != '.')
printf("%s ", dir->d_name);
}
printf("\ninput download filename: \n");
scanf("%s", down_file_name);
strcpy(send_msg.fname, down_file_name);
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send msg error: ");
}
memset(&send_msg, 0, sizeof(MSG));
break;
}
// 服务器
else if (recv_msg.type == MSG_TYPE_DONWLOAD)
{
strcpy(down_file_name, recv_msg.fname);
printf("要下载的文件是%s\n", down_file_name);
server_file_download(acpt_socket);
memset(&recv_msg, 0, sizeof(MSG));
}
- 上传文件: 将download下文件a.txt b.txt c.txt上入 /home/drj目录下,传递完成,服务器和客户端都没有错误,并且上传的字节数和内容无误。
- 输入3 ,给用户展示当前文件夹先有的文件,并且让用户选择一个进行上传。给出上传文件的路径以及当前文件的大小。
- 连续上传三个文件,然后在根目录下检查文件是否上传成功,以及文章内容是否正确等。可以看到,三个文件全部上传成功并且文件内容全部正确。
// 客户端
case '3': {
send_msg.type = MSG_TYPE_UPLOAD;
struct dirent* dir = NULL;
printf("there are files in ./download : \n");
DIR* dp = opendir("./download");
while (1)
{
dir = readdir(dp);
if (NULL == dir)
{
break;
}
if (dir->d_name[0] != '.')
printf("%s ", dir->d_name);
}
printf("\ninput upload filename: \n");
scanf("%s", up_file_name);
// 发送一个数据包告诉服务器,准备上传文件了
strcpy(send_msg.fname, up_file_name);
// f发送数据给服务器
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send upload packege error");
continue; //不走下面了
}
memset(&send_msg, 0, sizeof(MSG));
// 由于考虑到上传文件是需要时间的,如果文件很大,那么就需要非常长的时间
// 如果这个时候写在这里会导致其他功能卡住,因此需要把发送文件内容的代码放进线程里面,因此需要创建线程
// 因此我们客户都拿还需要创建一个新的线程,专门处理文件的上传任务;
pthread_create(&pthread_send_id, NULL, upload_file_thread, &client_socket); //创建一个线程
break;
}
// 服务器
else if (recv_msg.type == MSG_TYPE_UPLOAD) //如果收到的是上传包,说明准备接受客户端发来的文件数据
{
printf("进入upload\n");
//从数据包的文件名获取文件名信息并创建文件,默认创建的文件夹在家目录下
strcpy(up_file_name, recv_msg.fname);
char path[20] = "/home/drj/";
strcat(path, up_file_name);
printf("当前要下载到的文件位置是 %s \n", path);
// 创建文件,在家目录下,下需要定义文件名描述符
fd = open(path, O_CREAT | O_WRONLY, 0666);
if (fd < 0)
{
perror("create up file error");
}
}
else if (recv_msg.type == MSG_TYPE_UPLOAD_DATA)
{
// 写的字节数是recv_msg.bytes
printf("文件开始开始上传\n");
res = write(fd, recv_msg.buffer, recv_msg.bytes);
if (recv_msg.bytes < sizeof(recv_msg.buffer))
{
//说明是最后一个包,这部分数据是文件的最后数据了
printf("client up file ok\n");
close(fd);
}
memset(&recv_msg, 0, sizeof(MSG));
}
完整代码:
服务器
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
char down_file_name[20] = { 0 };
#define MSG_TYPE_LOGIN 0
#define MSG_TYPE_FILENAME 1
#define MSG_TYPE_DONWLOAD 2
#define MSG_TYPE_UPLOAD 3
#define MSG_TYPE_UPLOAD_DATA 4
typedef struct msg
{
int type; // 协议类型 0 表示登录协议,1表示文件传输包 2 表示文件下载
int flag;
char buffer[1024]; //存放除文件名之外的内容
char fname[50]; //如果type是1 就是文件名传输包,这个结构体会添加新的字段;
int bytes; // 这个字段用来记录传输文件时候每个数据包实际的文件数
}MSG;//这个结构体会根据业务需求的不断变化,会添加新的字段。
void search_server_dir(int accept_socket)
// 通过打开服务器中的某个文件,并使用 socket 网络发送给客户端 至于什么文件我们先顶一个。。。e
//可以根据实际情况定义
{
struct dirent * dir = NULL; //定义目录的结构体;
MSG info_msg = { 0 };
int res = 0;
//opendir 是打开Linux目录的api函数
DIR* dp = opendir("/home/drj");
info_msg.type = MSG_TYPE_FILENAME;
if (NULL == dp)
{
perror("open dir error: ");
return;
}
while (1)
{
dir = readdir(dp);
if (NULL == dir) //目录遍历完成,函数返回空值,表示目录全部读取完成
{
break;
}
if (dir->d_name[0] != '.') //过滤掉.隐藏文屏蔽
{
printf("name = %s\n", dir->d_name);
memset(info_msg.fname, 0, sizeof(info_msg.fname));
strcpy(info_msg.fname, dir->d_name);
res = write(accept_socket, &info_msg, sizeof(MSG)); //把每个文件名拷贝到
// info_msg结构体中,通过该套接字发送出去。
if (res < 0)
{
perror("send client error: ");
return;
}
}
}
}
void server_file_download(int accept_socket)
{
MSG file_msg = { 0 };
int res = 0;
int fd; //文件描述符 linux系统下很重要的概念,linux认为所有设备都是文件描述符。对文件的 打开对设备。
// 都可以使用文件描述符概念。
char path[20] = "/home/drj/";
strcat(path, down_file_name);
fd = open(path, O_RDONLY);
if (fd < 0)
{
perror("file open error: ");
return;
}
// 在读取文件并把文件传到客户端,这个时候msg结构体中的buffer 就是存放文件的内容,
// 但是一般来说文件都超过1024,所以要发送多个包;
file_msg.type = MSG_TYPE_DONWLOAD;
strcpy(file_msg.fname, "ee.txt");
while ((res = read(fd, file_msg.buffer, sizeof(file_msg.buffer))) > 0)
// read用于读取文件的时候,当文件读到末尾的时
// res是实际读到的字节
{
file_msg.bytes = res;
res = write(accept_socket, &file_msg, sizeof(MSG));
if (res <= 0)
{
perror("server send file error: ");
}
memset(file_msg.buffer, 0, sizeof(file_msg.buffer));
}
}
void* thread_fun(void* arg)
{
int acpt_socket = *((int*)arg);
int res;
char buffer[50] = { 0 };
char up_file_name[20] = { 0 };
MSG recv_msg = { 0 };
int fd = -1; //定义一个打开文件的描述符
//read函数就是接受客户端发来的数据,返回实际从客户端那边收到的字节数。
//buffer: 收到客户端数据后把数据存放的地址 sizeof(buffer) 就是希望读取的字节数
//search_server_dir(acpt_socket);
printf("目录发送客户端完成!\n");
while (1)
{
// todo #################################sizeof(recv_msg)
res = read(acpt_socket, &recv_msg, sizeof(recv_msg));
if (res == 0) //说明客户端已经断开,read默认情况下他是堵塞模式,回答问题才能说出下一句话
{
printf("客户端已经断开");
break; // 线程结束
}
if (recv_msg.type == MSG_TYPE_FILENAME) //说明客户端发过来的是目录
{
search_server_dir(acpt_socket);
memset(&recv_msg, 0, sizeof(MSG));
}
else if (recv_msg.type == MSG_TYPE_DONWLOAD)
{
strcpy(down_file_name, recv_msg.fname);
printf("要下载的文件是%s\n", down_file_name);
server_file_download(acpt_socket);
memset(&recv_msg, 0, sizeof(MSG));
}
else if (recv_msg.type == MSG_TYPE_UPLOAD) //如果收到的是上传包,说明准备接受客户端发来的文件数据
{
printf("进入upload\n");
//从数据包的文件名获取文件名信息并创建文件,默认创建的文件夹在家目录下
strcpy(up_file_name, recv_msg.fname);
char path[20] = "/home/drj/";
strcat(path, up_file_name);
printf("当前要下载到的文件位置是 %s \n", path);
// 创建文件,在家目录下,下需要定义文件名描述符
fd = open(path, O_CREAT | O_WRONLY, 0666);
if (fd < 0)
{
perror("create up file error");
}
}
else if (recv_msg.type == MSG_TYPE_UPLOAD_DATA)
{
// 写的字节数是recv_msg.bytes
printf("文件开始开始上传\n");
res = write(fd, recv_msg.buffer, recv_msg.bytes);
if (recv_msg.bytes < sizeof(recv_msg.buffer))
{
//说明是最后一个包,这部分数据是文件的最后数据了
printf("client up file ok\n");
close(fd);
}
memset(&recv_msg, 0, sizeof(MSG));
}
// printf("client read %s\n", buffer);
// write(acpt_socket, buffer, res);
memset(&recv_msg, 0, sizeof(MSG));
//服务器收到客户端数据后,原封不动的发给客户端
}
}
int main()
{
char buffer[50] = { 0 };
int res = 0;
int server_socket; //这个是socket网络描述符,也叫套接字描述符。
int accept_socket;
// 第一步:创建套接字描述符 --“买一部手机”
pthread_t thread_id; //线程编号
printf("开始创建tcp服务器\n");
server_socket = socket(AF_INET, SOCK_STREAM, 0);//我们想要向网络发送数据都使用
//server_socket这个套接字描述符
if (server_socket < 0)
{
perror("socket create failed:");
return 0;
}
// 第二步:要告诉这个服务器 我的ip地址和端口号 我们要有一个保存ip地址和端口的变量
// --“买电话卡”
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // 表示ipv4地址协议
server_addr.sin_addr.s_addr = INADDR_ANY; //inaddr_any 存服务器ip地址,告诉系统自动绑定网卡ip地址
server_addr.sin_port = htons(1046); //网络地址转换,把主机字节顺序转换成网络字节顺序
// 端口号1024以上即可。
//
//
// 如果服务器程序退出之后,又立刻打开服务器,系统会提示地址已经被使用
//这是因为ip地址和端口号是系统资源,必须设置为端口号为重复使用。
int optvalue = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optvalue, sizeof(optvalue));
// 第三步:把我们设定好的ip地址和端口号绑定到我们的server_socket描述符上。 --“把电话卡插入手机上”
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
perror("server bind error:");
return 0;
}
// 第四步:我们调用listen 开始监听程序 --“把电话放在身上,电话铃响听到声音”
if (listen(server_socket, 10) < 0)
{
perror("server listen error:");
return 0;
}
// 第五步:以上4个步骤都ok后,我们就可以等待客户端连接过来了。
// accept 函数有一个特点,当我们程序调用这个函数的时候,如果没有客户端连接到我们的服务器,
// 那么这个函数将堵塞(程序停下不走了),直到有客户端连接到服务器,这个函数将解开,并
// 并且返回一个新的套接字描述符。那么后期和客户端的通讯都交给这个新的套接字描述符来负责。
printf("TCP服务器准备完成,等待客户端连接!\n");
while (1)
{
accept_socket = accept(server_socket, NULL, NULL);
printf("有客户端连接到服务器!\n");
//创建一个新的线程,创建成功之后,系统就会执行‘thread_fun’代码,这里就是多线程代码。
pthread_create(&thread_id, NULL, thread_fun, &accept_socket); // 第三个参数 函数指针 真正执行函数的指针
// 移动到最前面函数里面去了。
}
printf("%s 向你问好!\n", "LINUX_C_SAMPLE");
return 0;
}
客户端
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
// 编写客户端
#define MSG_TYPE_LOGIN 0
#define MSG_TYPE_FILENAME 1
#define MSG_TYPE_DONWLOAD 2
#define MSG_TYPE_UPLOAD 3
#define MSG_TYPE_UPLOAD_DATA 4
char down_file_name[20] = { 0 };
char up_file_name[20] = { 0 };
typedef struct msg
{
int type; // 协议类型 0 表示登录协议,1表示文件传输包
int flag;
char buffer[1024]; //存放除文件名之外的内容
char fname[50]; //如果type是1 就是文件名传输包,这个结构体会添加新的字段;
int bytes; // 这个字段用来记录传输文件时候每个数据包实际的文件数
}MSG;//这个结构体会根据业务需求的不断变化,会添加新的字段。
// 全局变量 用来打开文件进行读写的文件描述符,默认情况下为0
int fd = -1;//进行读写的文件标识符
void net_disk_ui()
{
printf("======================TCP网盘客户端==================\n");
printf("=========================功能菜单====================\n");
printf("\t\t\t1.查询文件\n");
printf("\t\t\t2.下载文件\n");
printf("\t\t\t3.上传文件\n");
printf("\t\t\t4.刷新界面\n");
printf("\t\t\t0.退出系统\n");
printf("------------------------------------------------------\n");
printf("请选择你要执行的操作: \n");
}
// 根据网盘客户端业务需求,客户端想要查看下服务器这边目录下的文件信息。因此
// 服务器必须设计一个功能,把某个目录下的文件名信息全部获取出来发给客户端。
// 默认情况下服务器的目录,用户的目录设置为家home目录
// 在Linux下如何对文件和目录进行读取并获取文件名
void * thread_func(void* arg)
{
int client_socket = *((int*)arg);
MSG recv_msg = { 0 };
int res;
char pwd[100] = { 0 } ;
while (1)
{
//用来接受服务器发过来的数据
res = read(client_socket, &recv_msg, sizeof(MSG));
if (recv_msg.type == MSG_TYPE_FILENAME)
{
printf("server path filename=%s\n", recv_msg.fname);
memset(&recv_msg, 0, sizeof(MSG));
}
else if (recv_msg.type == MSG_TYPE_DONWLOAD) // 做好接受准备,一定是一个文件
{
// 1.创建一个目录
if (mkdir("download", S_IRWXU) <0 )
{
if (errno == EEXIST)
{
printf("download dir exist continue!!\n");
}
else
{
perror("mkdir error: ");
}
}
// 目录创建没有问题,就要开始创建文件了。
if (fd == -1) //表示文件还没打开过,若非0 已经打开过了
{
//printf("\ncurrent = %s\n", getcwd(NULL,0));
char path[20] = "./download/";
strcat(path, down_file_name);
printf("download path = %s\n", path);
fd = open(path, O_CREAT | O_WRONLY, 0666); //打开过肯定会有个文件描述返回 0666读写可执行
if (fd < 0)
{
perror("file open error: ");
}
}
// 通过上面的创建目录以及文件描述的判断,通过后就可以从MSG结构体里面的buffer取数据了。
// recv_msg.buffer 存放的就是文件的部分内容。 recv_msg.bytes就这个部分文件的字节。
res = write(fd, recv_msg.buffer, recv_msg.bytes);
if (res < 0)
{
perror("file write error : ");
}
// 那么我们判断文件的内容都全部发完了吗?通过recv_msg.bytes 如果小于recv_buffer的1024
if (recv_msg.bytes < sizeof(recv_msg.buffer))
{
printf("file download finish!!\n");
close(fd);
fd = -1;
}
}
}
}
void* upload_file_thread(void * args)
{
// 客户端实现上传文件到服务器逻辑
// 1.打开文件
MSG up_file_msg = { 0 };
int client_socket = *((int*)args);
int fd = -1;
int res = 0; //实际读入的文件名
char buffer[1024] = { 0 }; //用来保存读取文件的缓冲区数据
char path[20] = "./download/";
strcat(path, up_file_name);
printf("客户端上传文件的路径是%s \n", path);
fd = open(path, O_RDONLY);
if (fd < 0)
{
perror("client open up file error:");
return NULL;
}
up_file_msg.type = MSG_TYPE_UPLOAD_DATA;
//2.读取文件
while ((res = read(fd, buffer, sizeof(buffer)) )> 0)
{
// 要把文件数据拷贝到MSG结构体中的buffer中
printf("传入的字节数目:%d 字节\n", res);
memcpy(up_file_msg.buffer, buffer, res);
up_file_msg.bytes = res;
res = write(client_socket, &up_file_msg, sizeof(MSG));//写的字节数一定是读的总数据
memset(buffer, 0, sizeof(buffer));
memset(up_file_msg.buffer, 0, sizeof(up_file_msg.buffer));
}
}
int main()
{
int client_socket;
//todo 定义
pthread_t pthread_id; //线程编号
pthread_t pthread_send_id;
char c;
struct sockaddr_in server_addr; //用来连接到服务器的ip地址和端口号
char buffer[50] = { 0 };
client_socket = socket(AF_INET,SOCK_STREAM,0);
int res;
MSG recv_msg = { 0 };
MSG send_msg = { 0 };
if (client_socket < 0)
{
perror("client socket faile: ");
return 0;
}
server_addr.sin_family = AF_INET; //ipv协议 且自动匹配绑定
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 两种方法如果服务器和客户端都在同一台电脑中,ip地址可以设置为127.0.0.1
// 现在就是服务器和客户端中在同一个机子上,因此可以127.0.0.1.
server_addr.sin_port = htons(1046);
printf("这是客户端\n");
// 创建好套接字之后,客户端要连接到服务器,使用conect()函数
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
perror("connect error: ");
return 0;
}
printf("客户端连接服务器成功\n");
pthread_create(&pthread_id, NULL, thread_func, &client_socket);
net_disk_ui();
// 我们用户在客户端中连续输入字符,回车表示把数据发出去,把buffer里的数据发出去;服务器有固定ip地址即可,不一定在一个主机上
while (1)
{
c = getchar();
switch (c)
{
case '1': // 要让服务器给我们发送目录信息->客户端也要创捷线程
// 这个while循环本身也是死循环,我们要让客户端也创建线程,让接受服务器的数据的代码放进线程中
send_msg.type = MSG_TYPE_FILENAME;
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send msg error: ");
}
memset(&send_msg, 0, sizeof(MSG));
break;
case '2':
{
send_msg.type = MSG_TYPE_DONWLOAD;
struct dirent* dir = NULL;
printf("there are files in /home/drj : \n");
DIR* dp = opendir("/home/drj");
while (1)
{
dir = readdir(dp);
if (NULL == dir)
{
break;
}
if (dir->d_name[0] != '.')
printf("%s ", dir->d_name);
}
printf("\ninput download filename: \n");
scanf("%s", down_file_name);
strcpy(send_msg.fname, down_file_name);
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send msg error: ");
}
memset(&send_msg, 0, sizeof(MSG));
break;
}
case '3': {
send_msg.type = MSG_TYPE_UPLOAD;
struct dirent* dir = NULL;
printf("there are files in ./download : \n");
DIR* dp = opendir("./download");
while (1)
{
dir = readdir(dp);
if (NULL == dir)
{
break;
}
if (dir->d_name[0] != '.')
printf("%s ", dir->d_name);
}
printf("\ninput upload filename: \n");
scanf("%s", up_file_name);
// 发送一个数据包告诉服务器,准备上传文件了
strcpy(send_msg.fname, up_file_name);
// f发送数据给服务器
res = write(client_socket, &send_msg, sizeof(MSG));
if (res < 0)
{
perror("send upload packege error");
continue; //不走下面了
}
memset(&send_msg, 0, sizeof(MSG));
// 由于考虑到上传文件是需要时间的,如果文件很大,那么就需要非常长的时间
// 如果这个时候写在这里会导致其他功能卡住,因此需要把发送文件内容的代码放进线程里面,因此需要创建线程
// 因此我们客户都拿还需要创建一个新的线程,专门处理文件的上传任务;
pthread_create(&pthread_send_id, NULL, upload_file_thread, &client_socket); //创建一个线程
break;
}
case '4':
{
net_disk_ui();
}
}
}
/*
while (1)
{
res = read(client_socket, &recv_msg, sizeof(MSG));
if (recv_msg.type == MSG_TYPE_FILENAME)
{
printf("server path filename = %s\n", recv_msg.fname);
memset(&recv_msg, 0, sizeof(MSG));
}
}
*/
/*
//用户在客户端中连续输入字符,回车发送数据
while (fgets(buffer, sizeof(buffer), stdin) != NULL)
{
res = write(client_socket, buffer, sizeof(buffer));
printf("send bytes =%d\n", res);
memset(buffer, 0, sizeof(buffer));
res = read(client_socket, buffer, sizeof(buffer));
printf("recv from server info : % s\n", buffer);
memset(buffer, 0, sizeof(buffer));
}
*/
printf("%s 向你问好!\n", "linux_02");
return 0;
}