1.题目
1)模仿百度网盘实现一个文件上传、下载、浏览的终端网盘;
2)能够实现文件和目录的存储;
3)在终端下运行服务器和客户端,不对GUI做要求;
4)以用户的形式组织用户数据,不同用户“登录”可以访问相应的数据;
5)同一用户可以在不同位置进行登录,并且需要考虑访问冲突的问题:即用户在写一个文件的时候,该文件不能被伤处;
6)浏览方法的显示参考下面的方式进行显示,同时需要根据参数选择显示文件的属性;
7)写一个测试程序模拟在多进程环境下对服务器程序进行性能测试,需要的数据至少包括多少用户的情况下得到的传输率分别为多少;
8)服务端程序应以Daemon的方式予以实现。
2.运行截图
1)左上方为服务端程序,可以看到此时两个成功连接并已成功退出(执行结束之后才截的图)。其他两个为客户端。注册两个新用户,左下角的客户端上传了普通文件4.c以及目录文件test,然后演示下载目录文件test;右边的客户端上传了目录文件test以及普通文件3.c和4.c,演示了下载普通文件4.c。
2)左边的客户端输入浏览指令-a浏览该用户文件夹12345的全部文件,也就是之前上传的目录文件test以及普通文件4.c,输入具体的文件名称test浏览该文件的所有文件属性信息,右边的客户端也如此。
3)浏览指令处输入-m显示菜单,并输入4退出程序。
4)可以看到服务端以用户账号为文件夹名称新建的两个分别存放各自上传文件的文件夹。
5)文件夹12345有用户12345之前上传的目录文件test以及普通文件4.c。
6)打开test文件夹可以看到里面的两个普通文件。
7)文件夹23456同样存在用户23456上传的目录文件test以及普通文件3.c、4.c。
8)打开客户端存放的下载信息的文件加可以看到用户12345和23456下载的两个文件。
9)打开下载的test文件夹可以看到里面的两个文件。
10)这是服务端存放用户信息的文件,2代表用户总数,12345和12345分别是用户的账号和密码,下面的两行同样如此。
3.总体设计
服务端程序以守护进程的方式运行,传输层通信协议用的是面向连接、可靠的、基于字节流的TCP协议,服务端在主函数创建并绑定套接字之后,处于listen状态,将accept函数放在while无线循环里面,每有一个客户端connect则accept并输出显示连接的客户端的IP地址和PORT端口号,并利用pthread_create函数为该用户创建一个线程,进而实现多用户多线程的访问方式。
之后都是根据客户端的指令执行操作了,客户端和服务端是同步进行的,首先会从文件读入所有用户的账号和密码供验证,没有账户则自动创建,然后可以根据菜单选择上传文件、下载文件、浏览文件还是退出程序,可以对普通文件还是目录文件进行操作。服务端存放的用户文件放在由唯一的该用户的账号创建的文件夹里,用户也只可以对自己的文件夹里面的文件进行操作。由于考虑到C/S模式的性质,对下载的文件就没有必要根据用户账号进行存储了,都是直接存储。每执行一个操作结束之后均会再次显示该菜单,通过函数的形参传递用户的连接套接字、账号以及密码,客户端退出后服务端同样显示退出的客户端IP和PORT。
4.详细设计
这里首先会分别说明服务端和客户端特有的部分,接下来两者的通信公共部分会结合说明。
服务端:首先利用define定义网盘最大用户数MAX_USER以及MAX_ONLINE_CLIEN服务器最大同时在线客户端数、SERVER_PORT服务端口 、BUFFER_SIZE发送接收缓冲区大小。由于上传文件函数Upload、下载文件函数Download和浏览文件函数Browse是互相调用的,所以需要提前声明它们。接下来还有网盘的用户数目current_user、存储存储连接服务端的用户信息结构体User(包括用户账号和密码),两者都是要存入文件net_disk_userinfo.txt文件当中。
然后是服务端的写入数据到文件函数Write_to_file,利用fopen以w写的方式打开该文件,设置完全缓存模式之后利用fprintf函数分别将网盘用户数、用户信息结构体的信息写入文件,最后再利用fclose函数关闭文件。从文件读出数据函数Read_from_file基本同写入的一样,只不过是将fprintf改成fscanf,这里就不再赘述。
到服务端主函数部分利用库函数daemon(int nochdir, int noclose )函数创建守护进程,将两个参数都置为非0,表示保持当前执行目录不变以及表示不重定向标准输入、输出、错误到/dev/null(也就是不输出),保持不变。然后就是利用socket创建套接字,设置相关参数然后利用bind绑定,接着是调用listen使其处于监听状态,最大连接客户机数位MAX_ONLINE_CLIENT。接下来是while循环语句,客户机连接时候将accept然后输出连接的客户机的IP地址和端口号,然后利用pthread_create函数为其创建线程,跳转至用户起始执行函数Client_start_func并传入accept函数返回的客户套接字描述符cfd。
客户端:首先还是利用define定义服务器端口号SERVER_PORT、服务器IP地址SEERVER_IP、发送接收信息缓冲区大小,还有声明上传、下载、浏览三个函数。主函数部分利用socket创建套接字之后就设置相关参数然后connect连接服务端,成功后调用用户起始执行函数。
通信公共部分:用户首先输入五位的数账号然后发送给服务端,服务端创建线程后先从文件当中读入用户数据,然后接收用户账号。接着利用for循环验证是否存在该用户,如果存在也就是strcmp(Usr[i].id, buffer) == 0则等待接收用户发来的密码并验证,用户输入密码后发送给服务端,服务端验证该用户账号对应的密码正确后会发送true给客户,否则发送false然后客户端会利用goto语句返回重新输入密码。若服务端验证该用户不存在则自动为该用户注册账号,将网盘用户数current++,并且接收到的新用户账号密码写入文件,同时服务端调用mkdir且根据用户账号创建存储该用户的文件夹,用于存储用户上传的数据。然后客户端开始输入指令并发送指令给服务端,所输入错误指令则goto返回重新输入。输入1进入上传文件,同时将客户端套接字cfd,账号id以及密码password通过形参传入上传文件函数。客户端首先输入要上传的文件名,然后利用struct stat s_buf和stat(file_name, &s_buf)获取文件信息,并放到s_buf中。if(S_ISDIR(s_buf.st_mode))则上传的文件为目录,则将”dir”发送给服务端,由于send和recv不是一一对应的,所以若是没有输出参数的情况下连续出现send或者是recv,需要在它们的中间分别接收和发送一条无价值的信息,否则会导致接收或发送信息的重合,导致程序出错。
服务端接收到上传的文件是目录文件之后,会首先利用mkdir在该用户的文件夹下面以该目录文件名为文件名创建一个新文件夹。在服务端创建该文件夹的完整路径由客户端发送给服务端,(这里客户端应道basename函数,用于获取文件路径最后的目录文件名或者普通文件名字,不验证文件存在与否,用在客户端上传的文件不在当前目录,需要输入完整的路径名),然后客户端会利创建目录句柄,while((file = readdir(dir)) != NULL) {if((strcmp(file->d_name,"…") != 0) && (strcmp(file->d_name,".") != 0))total_file++;}统计上传的文件数目。然后利用sprintf(buffer, “%d”, total_file);将整型的文件数目变量转化为字符串发送给服务端,服务端接收到后会利用atoi函数再转回整型。
然后客户端接收服务端发送过来的无价值信息,统计完文件数目之后关闭目录重新打开目录使其再次从头读取。客户端从头读取除了“.”和”…”的文件,每次读取将读取到的文件名称连同服务端存储该文件的路径发送给服务端,服务端以写的打开方式打开文件,同时客户端以读的方式打开该文件。客户端读取并发送文件
do
{
r_size = fread(buffer, sizeof(char), BUFFER_SIZE, fp);
send(sfd, buffer, r_size, 0);
}while(r_size == BUFFER_SIZE);
fclose(fp);
服务端接收信息并将信息写入文件
do
{
r_size = recv(cfd, buffer, BUFFER_SIZE, 0);
fwrite(buffer, sizeof(char), r_size, fp);
}while(r_size == BUFFER_SIZE);
fclose(fp);
读完写完文件之后关闭文件,然后客户端接收服务端发送过来的无价值信息,接着进入下一次循环开始发送文件,发送接收文件结束之后会再输出输出操作菜单。如果发送的是文件则客户端直接进入将服务端存放该文件的路径以及文件名发到给服务端,接着如同上面发送目录文件一样发送该文件即可,发送结束后还是进入操作菜单。
输入2进入下载文件,基本思路同上传文件一样,只不过是将服务端和客户端的角色交换过来罢了,这里就觉得没必要进行讲解,要不然就显得冗余了。
输入3进入浏览文件,进入while(1)循环,客户端输入指令,输入-m则跳出循环,输出菜单;若输入-a则从服务端读该用户的目录获取该目录里面的所有文件名整合成字符串发送给客户端,客户端再输出;如果客户端输入具体的文件名或者目录名称,则服务端则根据struct stat输出该文件的的所有属性信息,同样在服务端将它们整合成字符串再发送给客户输出显示,会将文件属性的三个时间属性信息以strftime(abuffer, 64, “%Y-%m-%d %H:%M:%S \n”, accesstime)这种方式输出。
5.源码
5.1服务端
/*高级网络程序设计课程设计
1.模仿百度网盘实现一个文件上传、下载、浏览的终端网盘;
2.能够实现文件和目录的存储;
3.在终端下运行服务器和客户端,不对GUI做要求;
4.以用户的形式组织用户数据,不同用户“登录”可以访问相应的数据;
5.同一用户可以在不同位置进行登录,并且需要考虑访问冲突的问题:即用户在写一个文件的时候,该文件不能被伤处;
6.浏览方法的显示参考下面的方式进行显示,同时需要根据参数选择显示文件的属性;
7.写一个测试程序模拟在多进程环境下对服务器程序进行性能测试,需要的数据至少包括多少用户的情况下得到的传输率分别为多少;
8.服务端程序应以Daemon的方式予以实现;
9.源代码和报告提交时间:2020年7月20日。
此程序为服务端*/
#include <unistd.h> //read write close
#include <libgen.h> //basename
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <pthread.h> //pthread_crate
#include <sys/socket.h>//socket
#include <arpa/inet.h> //socket
#include <netinet/in.h>//socket
#include <sys/stat.h> //mkdir
#include <dirent.h> //readdir/opendir/closedir
#include <time.h> //strftime
#include <pthread.h> //pthread
#define MAX_USER 10 //网盘最大用户数
#define MAX_ONLINE_CLIENT 20//服务器最大同时在线客户端数
#define SERVER_PORT 8080 //服务端口
#define BUFFER_SIZE 1024 //发送接收缓冲区大小
void Upload(int cfd, char id[6], char password[6]);
void Download(int cfd, char id[6], char password[6]);
void Browse(int cfd, char id[6], char password[6]);
int current_user = 0;//网盘用户数
struct User //用户信息结构体
{
char id[6]; //账号
char password[6];//密码
}User[MAX_USER];
//写入数据到文件
void Write_to_file()
{
FILE *fp;
int i = 0;
if((fp = fopen("net_disk_userinfo.txt", "w")) == NULL)//以写的方式打开文件
perror("file open failed");
setvbuf(fp, NULL, _IOFBF, BUFSIZ); //设置完全缓存模式
fprintf(fp, "%d\n", current_user);
while(i < current_user)
{
fprintf(fp, "%s\n%s\n\n", User[i].id, User[i].password);
i++;
}
fclose(fp);
}
//从文件读出数据
void Read_from_file()
{
FILE *fp;
int i = 0;
if((fp = fopen("net_disk_userinfo.txt", "r")) == NULL)//以读的方式打开文件
perror("file open failed");
setvbuf(fp, NULL, _IOFBF, BUFSIZ); //设置完全缓存模式
fscanf(fp, "%d\n", ¤t_user);
while(i < current_user)
{
fscanf(fp, "%s\n%s\n\n", User[i].id, User[i].password);
i++;
}
fclose(fp);
}
//用户上传
void Upload(int cfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE];
int r_len = recv(cfd, buffer, BUFFER_SIZE, 0);//接收普通文件还是目录
buffer[r_len] = '\0';
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
if(strcmp(buffer, "dir") == 0)//上传目录
{
int total_file, i;
r_len = recv(cfd, buffer, BUFFER_SIZE, 0);//存放该目录文件的完整路径
buffer[r_len] = '\0';
mkdir(buffer, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);//为目录创建文件夹
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
recv(cfd, buffer, BUFFER_SIZE, 0);//接收的文件数目
total_file = atoi(buffer);
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
for(i = 0; i < total_file; i++)
{
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//存放该文件的完整路径以及文件名
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
FILE *fp = fopen(buffer, "w");//打开文件,准备写入
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = recv(cfd, buffer, BUFFER_SIZE, 0);
fwrite(buffer, sizeof(char), r_size, fp);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
}
}
else//上传普通文件
{
char file_name[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//接收文件存放完整路径
strcpy(file_name, buffer);
send(cfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
FILE *fp = fopen(file_name, "w");//打开文件,准备写入
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = recv(cfd, buffer, BUFFER_SIZE, 0);
fwrite(buffer, sizeof(char), r_size, fp);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
}
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//接收指令
if(strcmp(buffer, "1") == 0)
Upload(cfd, id, password);
else if(strcmp(buffer, "2") == 0)
Download(cfd, id, password);
else if(strcmp(buffer, "3") == 0)
Browse(cfd, id, password);
else
{
struct sockaddr_in addr;//获取下线的客户机的IP和PORT
int len = sizeof(addr);
getpeername(cfd, (struct sockaddr*)&addr, &len);
printf("客户机成功下线 IP: %s PORT: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
close(cfd);
}
}
//用户下载
void Download(int cfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE], file_name[BUFFER_SIZE], file_name1[BUFFER_SIZE];
recv(cfd, buffer, BUFFER_SIZE, 0);//接收下载的文件的完整路径
strcpy(file_name, buffer);
struct stat s_buf;//用于判断该文件是目录还是普通文件
stat(buffer, &s_buf);//获取文件信息,并放到s_buf中
if(S_ISDIR(s_buf.st_mode))//下载目录
{
send(cfd, "dir", 3, 0);//告诉客户端这是目录文件
recv(cfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
int total_file = 0;//目录里面的文件数
DIR *dir;
struct dirent *file;
if(!(dir = opendir(file_name)))
{
perror("open dir error");
exit(-1);
}
while((file = readdir(dir)) != NULL)//统计上传的文件数目
{
if((strcmp(file->d_name,"..") != 0) && (strcmp(file->d_name,".") != 0))
total_file++;
}
closedir(dir);
sprintf(buffer, "%d", total_file);
send(cfd, buffer, strlen(buffer), 0);//给客户端发送要下载的文件数目
recv(cfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
if(!(dir = opendir(file_name)))
{
perror("open dir error");
exit(-1);
}
while((file = readdir(dir)) != NULL)//开始发送目录文件
{
if((strcmp(file->d_name,"..") != 0) && (strcmp(file->d_name,".") != 0))
{
strcpy(file_name1, file_name);
strcat(file_name1, "/");
strcat(file_name1, file->d_name);//目录中要发送的文件的完整路径
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_clientinfo/");
strcat(buffer, basename(file_name));
strcat(buffer, "/");
strcat(buffer, file->d_name);//客户端存放该文件的完整路径以及文件名
send(cfd, buffer, strlen(buffer), 0);
recv(cfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
FILE *fp = fopen(file_name1, "r");
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = fread(buffer, sizeof(char), BUFFER_SIZE, fp);
send(cfd, buffer, r_size, 0);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
recv(cfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
}
}
closedir(dir);
}
else//下载普通文件
{
send(cfd, "file", 4, 0);//告诉客户端这是普通文件
FILE *fp = fopen(buffer, "r");
recv(cfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
if(NULL == fp)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = fread(buffer, sizeof(char), BUFFER_SIZE, fp);
send(cfd, buffer, r_size, 0);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
}
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, 1, 0);//接收指令
if(strcmp(buffer, "1") == 0)
Upload(cfd, id, password);
else if(strcmp(buffer, "2") == 0)
Download(cfd, id, password);
else if(strcmp(buffer, "3") == 0)
Browse(cfd, id, password);
else
{
struct sockaddr_in addr;//获取下线的客户机的IP和PORT
int length = sizeof(addr);
getpeername(cfd, (struct sockaddr*)&addr, &length);
printf("客户机成功下线 IP: %s PORT: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
close(cfd);
}
}
//用户浏览
void Browse(int cfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE], file_name[BUFFER_SIZE];
while(1)
{
bzero(buffer, BUFFER_SIZE);
bzero(file_name, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//接收来自客户端的指令
strcpy(file_name, buffer);
if(strcmp(buffer, "-a") == 0)//浏览所有该用户的文件
{
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, id);//服务端存放该用户文件的位置
DIR *dir;
struct dirent *file;
if(!(dir = opendir(buffer)))
{
perror("open dir error");
exit(-1);
}
strcpy(buffer, "/");
strcat(buffer, id);
strcat(buffer, "/");
strcat(buffer, "\n");
while((file = readdir(dir)) != NULL)
{
if((strcmp(file->d_name,"..") != 0) && (strcmp(file->d_name,".") != 0))
{
strcat(buffer, file->d_name);
strcat(buffer, "\n");
}
}
closedir(dir);
send(cfd, buffer, strlen(buffer), 0);//给客户端发送该用户的所有文件名
}
else if(strcmp(buffer, "-m") == 0)//跳出循环,显示菜单
break;
else//浏览指定文件的属性
{
strcpy(file_name, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(file_name, id);
strcat(file_name, "/");
strcat(file_name, buffer);//服务端存放该文件的位置
struct stat st;//将所有文件属性信息整合成字符串返回给客户端
stat(file_name, &st);
char tmp[BUFFER_SIZE];
strcpy(buffer, "file type & mode (permissions): ");
sprintf(tmp, "%d", st.st_mode);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "i-node number (serial number): ");
sprintf(tmp, "%ld", st.st_ino);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "device number (file system): ");
sprintf(tmp, "%ld", st.st_dev);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "device number for special files: ");
sprintf(tmp, "%ld", st.st_rdev);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "number of links: ");
sprintf(tmp, "%ld", st.st_nlink);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "user ID of owner: ");
sprintf(tmp, "%d", st.st_uid);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "group ID of owner: ");
sprintf(tmp, "%d", st.st_gid);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "size in bytes, for regular files: ");
sprintf(tmp, "%ld", st.st_size);
strcat(buffer, tmp);
strcat(buffer, "\n");
struct tm* accesstime = localtime(&(st.st_atime));
struct tm* modifytime = localtime(&(st.st_mtime));
struct tm* changetime = localtime(&(st.st_ctime));
char abuffer[64], mbuffer[64], cbuffer[64];
strftime(abuffer, 64, "%Y-%m-%d %H:%M:%S \n", accesstime);
strftime(mbuffer, 64, "%Y-%m-%d %H:%M:%S \n", modifytime);
strftime(cbuffer, 64, "%Y-%m-%d %H:%M:%S \n", changetime);
strcat(buffer, "time of last access: ");
strcat(buffer, abuffer);
strcat(buffer, "time of last modification: ");
strcat(buffer, mbuffer);
strcat(buffer, "time of last file status change: ");
strcat(buffer, cbuffer);
strcat(buffer, "best I/O block size: ");
sprintf(tmp, "%ld", st.st_blksize);
strcat(buffer, tmp);
strcat(buffer, "\n");
strcat(buffer, "number of disk blocks allocated: ");
sprintf(tmp, "%ld", st.st_blocks);
strcat(buffer, tmp);
strcat(buffer, "\n\n");
send(cfd, buffer, strlen(buffer), 0);
}
}
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, 1, 0);//接收指令
if(strcmp(buffer, "1") == 0)
Upload(cfd, id, password);
else if(strcmp(buffer, "2") == 0)
Download(cfd, id, password);
else if(strcmp(buffer, "3") == 0)
Browse(cfd, id, password);
else
{
struct sockaddr_in addr;//获取下线的客户机的IP和PORT
int length = sizeof(addr);
getpeername(cfd, (struct sockaddr*)&addr, &length);
printf("客户机成功下线 IP: %s PORT: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
close(cfd);
}
}
//用户起始执行函数
void *Client_start_func(void *argv)
{
int cfd = *(int*)(argv);
char buffer[BUFFER_SIZE], id1[6], password1[6];
Read_from_file();//从文件读入数据
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//接收用户账号
strcpy(id1, buffer);
int i;
for(i = 0; i < current_user; i++)//验证是否存在该用户
{
if(strcmp(User[i].id, buffer) == 0)
{
reconfirm:
recv(cfd, buffer, BUFFER_SIZE, 0);//接收该用户密码,并验证是否正确
strcpy(password1, buffer);
if(strcmp(User[i].password, buffer) == 0)
{
send(cfd, "true", 4, 0);//发送给客户端说明密码正确
break;
}
else
{
send(cfd, "false", 5, 0);//发送给客户端说明密码错误
goto reconfirm; //重新验证密码
}
}
}
if(i == current_user)//新用户自动注册
{
send(cfd, "true", 4, 0);//“说明密码正确”
current_user++;//用户数加1
strcpy(User[i].id, id1);
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, User[i].id);
mkdir(buffer, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);//为新用户创建专属文件夹
bzero(buffer, BUFFER_SIZE);
recv(cfd, buffer, BUFFER_SIZE, 0);//接收新用户密码
strcpy(password1, buffer);
strcpy(User[i].password, buffer);
Write_to_file();//向文件写入新用户账号、密码
}
bzero(buffer, BUFFER_SIZE);
int re = recv(cfd, buffer, BUFFER_SIZE, 0);//接收指令
if(strcmp(buffer, "1") == 0)
Upload(cfd, id1, password1);
else if(strcmp(buffer, "2") == 0)
Download(cfd, id1, password1);
else if(strcmp(buffer, "3") == 0)
Browse(cfd, id1, password1);
else
{
struct sockaddr_in addr;//获取下线的客户机的IP和PORT
int length = sizeof(addr);
getpeername(cfd, (struct sockaddr*)&addr, &length);
printf("客户机成功下线 IP: %s PORT: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
close(cfd);
}
}
//主函数
int main()
{
if(daemon(1, 1) == -1)
perror("daemom error");
int sfd = socket(AF_INET, SOCK_STREAM ,0);
if(sfd == -1)
{
perror("socket error");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t addrlen = sizeof(addr);
int ret = bind(sfd, (struct sockaddr*)(&addr), addrlen);
if(ret == -1)
{
perror("bind error");
return -1;
}
if(listen(sfd, MAX_ONLINE_CLIENT) == -1)
{
perror("listen error");
return -1;
}
printf("正在等待客户机连接...\n");
while(1)
{
struct sockaddr_in caddr;
pthread_t pthread_id;
socklen_t len = sizeof(caddr);
int cfd = accept(sfd, (struct sockaddr*)(&caddr), &len);
if(cfd == -1)
{
perror("accept error");
continue;
}
printf("客户机成功连接 IP: %s PORT: %hu\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
ret = pthread_create(&pthread_id, NULL, Client_start_func, (void*)&cfd);
if(ret != 0)
{
perror("pthread_create error");
continue;
}
}
}
5.2客户端
/*高级网络程序设计课程设计
1.模仿百度网盘实现一个文件上传、下载、浏览的终端网盘;
2.能够实现文件和目录的存储;
3.在终端下运行服务器和客户端,不对GUI做要求;
4.以用户的形式组织用户数据,不同用户“登录”可以访问相应的数据;
5.同一用户可以在不同位置进行登录,并且需要考虑访问冲突的问题:即用户在写一个文件的时候,该文件不能被伤处;
6.浏览方法的显示参考下面的方式进行显示,同时需要根据参数选择显示文件的属性;
7.写一个测试程序模拟在多进程环境下对服务器程序进行性能测试,需要的数据至少包括多少用户的情况下得到的传输率分别为多少;
8.服务端程序应以Daemon的方式予以实现;
9.源代码和报告提交时间:2020年7月20日。
此程序为客户端*/
#include <unistd.h> //read write close
#include <libgen.h> //basename
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <string.h> //string
#include <pthread.h> //pthread_crate
#include <sys/socket.h>//socket
#include <arpa/inet.h> //socket
#include <netinet/in.h>//socket
#include <dirent.h>
#include <sys/stat.h>
#define SERVER_PORT 8080 //服务器端口
#define SERVER_IP "192.168.61.173"//服务器IP地址
#define BUFFER_SIZE 1024 //发送接收缓冲区大小
void Upload(int sfd, char id[6], char password[6]);
void Download(int sfd, char id[6], char password[6]);
void Browse(int sfd, char id[6], char password[6]);
//上传
void Upload(int sfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE], file_name[BUFFER_SIZE];
printf("请输入你想要上传的文件:");//包括路径
scanf("%s", file_name);
struct stat s_buf;//用于判断该文件是目录还是普通文件
stat(file_name, &s_buf);//获取文件信息,并放到s_buf中
if(S_ISDIR(s_buf.st_mode))//如果是目录
{
send(sfd, "dir", 3, 0);//告诉服务端这是目录
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, id);
strcat(buffer, "/");
strcat(buffer, basename(file_name));//basename函数获取文件路径最后的文件名字,不验证路径存在与否
send(sfd, buffer, strlen(buffer), 0);//给服务端发送服务端存放该目录文件的完整路径
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
DIR *dir;
struct dirent *file;
if(!(dir = opendir(file_name)))
{
perror("open dir error");
exit(-1);
}
char file_name1[BUFFER_SIZE];
int total_file = 0;//目录里面的文件数
while((file = readdir(dir)) != NULL)//统计上传的文件数目
{
if((strcmp(file->d_name,"..") != 0) && (strcmp(file->d_name,".") != 0))
total_file++;
}
closedir(dir);
sprintf(buffer, "%d", total_file);//将整型转化为字符串
send(sfd, buffer, strlen(buffer), 0);//给服务端发送要接收的文件数目
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
if(!(dir = opendir(file_name)))
{
perror("open dir error");
exit(-1);
}
while((file = readdir(dir)) != NULL)//开始发送文件
{
if((strcmp(file->d_name,"..") != 0) && (strcmp(file->d_name,".") != 0))
{
strcpy(file_name1, file_name);
strcat(file_name1, "/");
strcat(file_name1, file->d_name);//目录中要上传的文件的完整路径
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, id);
strcat(buffer, "/");
strcat(buffer, basename(file_name));
strcat(buffer, "/");
strcat(buffer, file->d_name);//服务端存放该文件的完整路径以及文件名
send(sfd, buffer, strlen(buffer), 0);
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
FILE *fp = fopen(file_name1, "r");
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
bzero(buffer, BUFFER_SIZE);
do
{
r_size = fread(buffer, sizeof(char), BUFFER_SIZE, fp);
send(sfd, buffer, r_size, 0);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
}
}
printf("目录上传成功\n");
closedir(dir);
}
else//如果是普通文件
{
send(sfd, "file", 4, 0);//告诉服务端这是普通文件
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, id);
strcat(buffer, "/");
strcat(buffer, basename(file_name));
send(sfd, buffer, strlen(buffer), 0);//给服务端发送服务端存放该文件的完整路径以及文件名
recv(sfd, buffer, BUFFER_SIZE, 0);//无价值信息,仅仅是为了防止由于连续send导致发送的信息重合
FILE *fp = fopen(file_name, "r");
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = fread(buffer, sizeof(char), BUFFER_SIZE, fp);
send(sfd, buffer, r_size, 0);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
printf("文件上传成功!\n");
}
}
printf("\n1、继续上传文件\n2、下载文件\n3、浏览文件\n4、退出\n请输入指令:");
reinputorder:
scanf("%s", buffer);
if(strcmp(buffer, "1") == 0)
{
send(sfd, buffer, 1, 0);//发送指令给服务器
Upload(sfd, id, password);;
}
else if(strcmp(buffer, "2") == 0)
{
send(sfd, buffer, 1, 0);
Download(sfd, id, password);
}
else if(strcmp(buffer, "3") == 0)
{
send(sfd, buffer, 1, 0);
Browse(sfd, id, password);
}
else if(strcmp(buffer, "4") == 0)
{
send(sfd, buffer, 1, 0);
exit(0);
}
else
{
printf("请输入正确指令!");
goto reinputorder;//重新输入指令
}
}
//下载
void Download(int sfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE], file_name[BUFFER_SIZE], file_name1[BUFFER_SIZE];
printf("请输入你想要下载的文件名:");
scanf("%s", file_name);
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_serverinfo/");
strcat(buffer, id);
strcat(buffer, "/");
strcat(buffer, file_name);
send(sfd, buffer, strlen(buffer), 0);//下载的文件在服务端的完整路径
int r_len = recv(sfd, buffer, BUFFER_SIZE, 0);//下载的文件是普通文件还是目录
buffer[r_len] = '\0';
if(strcmp(buffer, "dir") == 0)//如果是目录
{
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_clientinfo/");
strcat(buffer, file_name);
mkdir(buffer, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);//为下载的目录文件创建目录
send(sfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
int total_file = 0, i;//目录里面的文件数
recv(sfd, buffer, BUFFER_SIZE, 0);
total_file = atoi(buffer);
send(sfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
for(i = 0; i < total_file; i++)
{
bzero(buffer, BUFFER_SIZE);
recv(sfd, buffer, BUFFER_SIZE, 0);//存放该文件的完整路径以及文件名
send(sfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
FILE *fp = fopen(buffer, "w");//打开文件,准备写入
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = recv(sfd, buffer, BUFFER_SIZE, 0);
fwrite(buffer, sizeof(char), r_size, fp);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
send(sfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
}
}
else//如果是普通文件
{
send(sfd, "1", 1, 0);//无价值的信息,仅仅是为了防止由于连续recv导致接收的信息重合
strcpy(buffer, "/home/zhuhezhang/UNIX课程/net_disk_clientinfo/");
strcat(buffer, file_name);
FILE *fp = fopen(buffer, "w");
if(fp == NULL)
perror("file open failed");
else
{
int r_size = 0;
do
{
r_size = recv(sfd, buffer, BUFFER_SIZE, 0);
fwrite(buffer, sizeof(char), r_size, fp);
}while(r_size == BUFFER_SIZE);
fclose(fp);
fp = NULL;
}
printf("文件下载成功!\n");
}
printf("\n1、上传文件\n2、继续下载文件\n3、浏览文件\n4、退出\n请输入指令:");
reinputorder:
scanf("%s", buffer);
if(strcmp(buffer, "1") == 0)
{
send(sfd, buffer, 1, 0);//发送指令给服务器
Upload(sfd, id, password);
}
else if(strcmp(buffer, "2") == 0)
{
send(sfd, buffer, 1, 0);
Download(sfd, id, password);
}
else if(strcmp(buffer, "3") == 0)
{
send(sfd, buffer, 1, 0);
Browse(sfd, id, password);
}
else if(strcmp(buffer, "4") == 0)
{
send(sfd, buffer, 1, 0);
exit(0);
}
else
{
printf("请输入正确指令!");
goto reinputorder;//重新输入指令
}
}
//浏览
void Browse(int sfd, char id[6], char password[6])
{
char buffer[BUFFER_SIZE];
while(1)
{
bzero(buffer, BUFFER_SIZE);
printf("请输入浏览指令:");//-a显示所有文件,-m显示菜单,输入文件名返回该文件属性信息
scanf("%s", buffer);
send(sfd, buffer, strlen(buffer), 0);
if(strcmp(buffer, "-a") == 0)//浏览所有文件
{
recv(sfd, buffer, BUFFER_SIZE, 0);
printf("%s\n", buffer);
}
else if(strcmp(buffer, "-m") == 0)
break;
else//浏览指定文件属性
{
recv(sfd, buffer, BUFFER_SIZE, 0);//接收文件属性信息
printf("%s", buffer);
}
}
bzero(buffer, BUFFER_SIZE);
printf("\n1、上传文件\n2、下载文件\n3、继续浏览文件\n4、退出\n请输入指令:");
reinputorder:
scanf("%s", buffer);
if(strcmp(buffer, "1") == 0)
{
send(sfd, buffer, 1, 0);//发送指令给服务器
Upload(sfd, id, password);
}
else if(strcmp(buffer, "2") == 0)
{
send(sfd, buffer, 1, 0);
Download(sfd, id, password);
}
else if(strcmp(buffer, "3") == 0)
{
send(sfd, buffer, 1, 0);
Browse(sfd, id, password);
}
else if(strcmp(buffer, "4") == 0)
{
send(sfd, buffer, 1, 0);
exit(0);
}
else
{
printf("请输入正确指令!");
goto reinputorder;//重新输入指令
}
}
//用户起始执行函数
void Client_start_func(int sfd)
{
char buffer[BUFFER_SIZE], id[6], password[6];//缓冲区、账号、密码
printf("欢迎进入朱和章的模拟百度网盘系统\n");
printf(" 账号:");
scanf("%s", id);
send(sfd, id, 5, 0);
reconfirm:
printf(" 密码:");
scanf("%s", password);
send(sfd, password, 5, 0);
bzero(buffer, BUFFER_SIZE);
recv(sfd, buffer, BUFFER_SIZE, 0);//验证密码是否正确
if(strcmp(buffer, "true") == 0)//密码正确
{
printf("\n1、上传文件\n2、下载文件\n3、浏览文件\n4、退出\n请输入指令:");
reinputorder:
scanf("%s", buffer);
if(strcmp(buffer, "1") == 0)
{
send(sfd, buffer, 1, 0);//发送指令给服务器
Upload(sfd, id, password);
}
else if(strcmp(buffer, "2") == 0)
{
send(sfd, buffer, 1, 0);
Download(sfd, id, password);
}
else if(strcmp(buffer, "3") == 0)
{
send(sfd, buffer, 1, 0);
Browse(sfd, id, password);
}
else if(strcmp(buffer, "4") == 0)
{
send(sfd, buffer, 1, 0);
exit(0);
}
else
{
printf("请输入正确指令!");
goto reinputorder;//重新输入指令
}
}
else//密码错误
{
printf("密码错误!请重新输入!\n");
goto reconfirm;//重新输入密码
}
}
//主函数
int main(void)
{
int sfd;
struct sockaddr_in addr;
if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket error");
exit(-1);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_IP);
if(connect(sfd, (const struct sockaddr*)(&addr), sizeof(addr)) == -1)
{
perror("connect error");
exit(-1);
}
Client_start_func(sfd);//用户起始执行函数
return 0;
}