Socket网络编程--小小网盘程序(3)
接上一小节,这次增加另外的两张表,用于记录用户是保存那些文件。增加传上来的文件的文件指纹,使用MD5表示。
两张表如下定义:
create table files(
fid int,
filename varchar(64),
md5 varchar(64)
); create table relations(
uid int,
fid int
);
表与表之间的关系如下:
client.cpp 在上一小节基础上增加了一个md5的功能,传输给服务器,用于作为文件的唯一标识
...
42 struct File
{
int uid;
char filename[];
char md5[];
};
struct FileList
{
char list[];
}; void print_time(char *ch);//打印时间
int file_push(struct Addr addr,struct User user,char *filenames);
int check_login(struct Addr addr,struct User * user);
int md5sum(char *filename,unsigned char *md5);
int file_pull(struct Addr addr,struct User user,char *filenames); int main(int argc,char *argv[])
{
...
return ;
} //验证成功时返回大于0的uid号码,错误返回-1
int check_login(struct Addr addr,struct User * user)
{
...
} int file_push(struct Addr addr,struct User user,char *filenames)
{
... ...
struct File file;//文件指纹
int sockfd;
FILE *fp;
char md5[];
unsigned char md5tmp[]; ... ...
//计算MD5
memset(md5,,sizeof(md5));
md5sum(filenames,md5tmp);
printf("计算得到的MD5:");
for(int i=;i<;i++)
{
sprintf(&md5[i*],"%02X",md5tmp[i]);
}
printf("%s\n",md5); //打开文件
if((fp=fopen(filenames,"rb"))==NULL)
{
perror("文件打开失败");
exit(-);
}
//这里传输控制信号
control.control=FILE_PUSH;
control.uid=user.uid;
if(send(sockfd,(char *)&control,sizeof(struct Control),)<)
{
perror("控制信号发送失败");
exit(-);
}
//发送文件指纹
strcpy(file.filename,filenames);
strcpy(file.md5,md5);
file.uid=user.uid;
if(send(sockfd,(char *)&file,sizeof(struct File),)<)
{
perror("文件指纹发送失败");
exit(-);
}
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
printf("正在传输文件");
int len=;
//不断的读取文件直到文件结束
while((len=fread(buffer,,BUFFER_SIZE,fp))>)
{
if(send(sockfd,buffer,len,)<)
{
perror("发送数据失败");
exit(-);
}
bzero(buffer,BUFFER_SIZE);
printf(".");//1K打印一个点//如果要实现百分比,就要计算文件大小,然后再处理即可
} printf("传输完毕\n");
fclose(fp);//关闭文件流
close(sockfd);//关闭socket连接 return ;
} int md5sum(char *filename,unsigned char *md5)
{
MD5_CTX ctx;
char buffer[];
unsigned char outmd[];
int len=;
int i;
FILE *fp=NULL;
memset(outmd,,sizeof(outmd));
memset(buffer,,sizeof(buffer));
fp=fopen(filename,"rb");
if(fp==NULL)
{
perror("打开文件失败");
}
MD5_Init(&ctx);
while((len=fread(buffer,,sizeof(buffer),fp))>)
{
MD5_Update(&ctx,buffer,len);
memset(buffer,,sizeof(buffer));
}
MD5_Final(outmd,&ctx);
for(i=;i<;i++)
{
md5[i]=outmd[i];
}
fclose(fp);
return ;
}
server.cpp
... ...
struct File
{
int uid;
char filename[];
char md5[];
};
struct FileList
{
char list[];
}; void print_time(char *ch);//打印时间
int MAX(int a,int b);
int mysql_check_login(struct User user);
int mysql_file_in(struct File file); int mysql_get_max_fid()
{
MYSQL conn;
MYSQL_RES *res_ptr;
MYSQL_ROW result_row;
int res;int row;int column;int mfid;
char sql[];
strcpy(sql,"select max(fid) from files;");
mfid=;
mysql_init(&conn);
if(mysql_real_connect(&conn,"localhost","root","","filetranslate",,NULL,CLIENT_FOUND_ROWS))
{
res=mysql_query(&conn,sql);
if(res)
{
perror("SELECT SQL ERROR!");
exit(-);
}
else
{
res_ptr=mysql_store_result(&conn);
if(res_ptr)
{
column=mysql_num_fields(res_ptr);
row=mysql_num_rows(res_ptr)+;
if(row<=)
{
;//没有数据
}
else
{
result_row=mysql_fetch_row(res_ptr);
printf("最大的fid是:%s\n",result_row[]);
if(result_row[]==NULL)
mfid=;
else
mfid=atoi(result_row[]);
}
}
else
{
printf("没有查询到匹配的数据\n");
}
}
}
else
{
perror("Connect Failed!");
exit(-);
}
mysql_close(&conn);
return mfid;
} int mysql_file_in(struct File file)
{
int mfid;
MYSQL conn;
int res;
char sql[];
char tmp[];
mfid=mysql_get_max_fid()+;
printf("获取到的最大fid为:%d\n",mfid);
mysql_init(&conn);
if(mysql_real_connect(&conn,"localhost","root","","filetranslate",,NULL,CLIENT_FOUND_ROWS))
{
//insert into files values(mfid,file.filename,file.md5);
//insert into files values(file.uid,mfid);
memset(sql,,sizeof(sql));
strcpy(sql,"insert into files values(");
sprintf(tmp,"%d",mfid);
strcat(sql,tmp);
strcat(sql,",'");
strcat(sql,file.filename);
strcat(sql,"','");
strcat(sql,file.md5);
strcat(sql,"');");
printf("插入的sql语句: %s\n",sql);
res=mysql_query(&conn,sql);
if(res)
printf("Insert Error!\n");
else
printf("Insert Success!\n"); memset(sql,,sizeof(sql));
strcpy(sql,"insert into relations values(");
sprintf(tmp,"%d",file.uid);
strcat(sql,tmp);
strcat(sql,",");
sprintf(tmp,"%d",mfid);
strcat(sql,tmp);
strcat(sql,");");
printf("插入的sql语句: %s\n",sql);
res=mysql_query(&conn,sql);
if(res)
printf("Insert Error!\n");
else
printf("Insert Success!\n");
}
else
{
perror("Connect Failed!");
exit(-);
}
return ;
} int main(int argc,char *argv[])
{
...
while()
{
clientfd=accept(sockfd,(struct sockaddr *)&client_addr,&length);
if(clientfd==-)
{
perror("accept 失败");
continue;
}
printf(">>>>>%s:%d 连接成功,当前所在的ID(fd)号: %d \n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),clientfd);
print_time(ch);
printf("加入的时间是:%s\n",ch); //来一个连接就创建一个进程进行处理
pid=fork();
if(pid<)
{
perror("fork error");
}
else if(pid==)
{
recv(clientfd,(char *)&control,sizeof(struct Control),);
printf("用户 %d 使用命令 %d\n",control.uid,control.control);
switch(control.control)
{
case USER_CHECK_LOGIN:
{
//身份验证处理
... ...
break;
}
case FILE_PUSH:
{
char buffer[BUFFER_SIZE];
int data_len;
FILE * fp=NULL;
struct File file;
//获取文件指纹
recv(clientfd,(char *)&file,sizeof(struct File),);
printf("获取到的用户名ID: %d 文件名:%s MD5:%s\n",file.uid,file.filename,file.md5);
bzero(buffer,BUFFER_SIZE);
if((fp=fopen("data","wb"))==NULL)
{
perror("文件打开失败");
exit(-);
}
//循环接收数据
int size=;//表示有多少个块
while(data_len=recv(clientfd,buffer,BUFFER_SIZE,))
{
... ...
}
if(size>)
{
printf("\n%s:%d的文件传送完毕\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
//如果文件传输成功那么就可以写入数据库了
mysql_file_in(file);
}
else
printf("\n%s:%d的文件传送失败\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
fclose(fp);
rename("data",file.md5);//这里可以修改文件的名字
exit();
break;
}
case FILE_PULL:
{
break;
}
case FILE_LIST:
{
break;
}
case FILE_DELECT:
{
break;
}
default:
{
break;
}
}
close(clientfd);//短连接结束
exit();//退出子进程
}
} return ;
} //函数定义
int mysql_check_login(struct User user)
{
... ...
} ... ...
上面已经介绍了如何计算MD5值并写入数据库,接下来要做的事就是如何判断要上传的文件是否在服务器中已经存在,如果存在就可以不用上传,而是在relations表中记录,写入uid-fid关系,这样就可以实现网盘的秒传功能了。而如何区分文件是否相同,我这里使用的是以MD5作为文件指纹。话说秒传功能也不过如此吧。所以现在应该知道为什么有的文件可以秒传,有的不可以了吧。关于具体的解释可以参考杜鑫先生在知乎中的回到。传送门在第一小节中有。
已加入验证和秒传功能的网盘程序
client.cpp修改如下
... ...
int file_push(struct Addr addr,struct User user,char *filenames)
{
... ...
//发送文件指纹
strcpy(file.filename,filenames);
strcpy(file.md5,md5);
file.uid=user.uid;
if(send(sockfd,(char *)&file,sizeof(struct File),)<)
{
perror("文件指纹发送失败");
exit(-);
}
char ch[];
if(recv(sockfd,ch,,)<)
{
perror("error");
}
if(ch[]=='y')//表示已经存在
{
printf("该文件在服务器中存在,正使用秒传功能。\n");
printf("传输完毕\n");
return ;
}
//打开文件
if((fp=fopen(filenames,"rb"))==NULL)
{
perror("文件打开失败");
exit(-);
}
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
printf("正在传输文件");
int len=;
//不断的读取文件直到文件结束
while((len=fread(buffer,,BUFFER_SIZE,fp))>)
{
if(send(sockfd,buffer,len,)<)
{
perror("发送数据失败");
exit(-);
}
bzero(buffer,BUFFER_SIZE);
printf(".");//1K打印一个点//如果要实现百分比,就要计算文件大小,然后再处理即可
} printf("传输完毕\n");
fclose(fp);//关闭文件流
close(sockfd);//关闭socket连接 return ;
} ... ...
server.cpp修改如下
......
int main(int argc,char *argv[])
{
......
//来一个连接就创建一个进程进行处理
pid=fork();
if(pid<)
{
perror("fork error");
}
else if(pid==)
{
recv(clientfd,(char *)&control,sizeof(struct Control),);
printf("用户 %d 使用命令 %d\n",control.uid,control.control);
switch(control.control)
{
case USER_CHECK_LOGIN:
... ...
case FILE_PUSH:
{
char buffer[BUFFER_SIZE];
int data_len;
FILE * fp=NULL;
struct File file;
//获取文件指纹
recv(clientfd,(char *)&file,sizeof(struct File),);
printf("获取到的用户名ID: %d 文件名:%s MD5:%s\n",file.uid,file.filename,file.md5);
//对文件进行验证,如果文件已经存在就不用进行接收了
int t=mysql_check_md5(file);
char ch[]={};
printf("t=%d\n",t);
if(t!=)
{
printf("该文件存在,使用秒传功能\n");
strcpy(ch,"yes");
send(clientfd,ch,,);
mysql_file_in(file.uid,t);
continue;
}
strcpy(ch,"no");
send(clientfd,ch,,);
printf("md5验证后得到的fid:%d\n",t);
bzero(buffer,BUFFER_SIZE);
... ... ...
break;
}
... ...
}
close(clientfd);//短连接结束
exit();//退出子进程
}
} return ;
} //函数定义
int mysql_check_md5(struct File file)
{
MYSQL conn;
MYSQL_RES * res_ptr;
MYSQL_ROW result_row;
int res;int row;int column;int value=;
char sql[]={};
strcpy(sql,"select fid from files where md5='");
strcat(sql,file.md5);
strcat(sql,"';");
printf("查询的sql:%s\n",sql); mysql_init(&conn);
if(mysql_real_connect(&conn,"localhost","root","","filetranslate",,NULL,CLIENT_FOUND_ROWS))
{
res=mysql_query(&conn,sql);
if(res)
{
perror("Select Sql Error!");
exit(-);
}
else
{
res_ptr=mysql_store_result(&conn);
if(res_ptr)
{
column=mysql_num_fields(res_ptr);
row=mysql_num_rows(res_ptr)+;
if(row<=)
{
;
}
else
{
result_row=mysql_fetch_row(res_ptr);
value=atoi(result_row[]);
}
}
else
{
printf("没有查询到匹配的数据\n");
}
}
}
else
{
perror("Connect Failed!");
exit(-);
}
mysql_close(&conn);
return value;//返回fid
} int mysql_file_in(int uid,int fid)
{
MYSQL conn;
int res;
char sql[];
char tmp[];
mysql_init(&conn);
if(mysql_real_connect(&conn,"localhost","root","","filetranslate",,NULL,CLIENT_FOUND_ROWS))
{
//insert into files values(uid,fid);
memset(sql,,sizeof(sql));
strcpy(sql,"insert into relations values(");
sprintf(tmp,"%d",uid);
strcat(sql,tmp);
strcat(sql,",");
sprintf(tmp,"%d",fid);
strcat(sql,tmp);
strcat(sql,");");
printf("插入的sql语句: %s\n",sql);
res=mysql_query(&conn,sql);
if(res)
printf("Insert Error!\n");
else
printf("Insert Success!\n");
}
else
{
perror("Connect Failed!");
exit(-);
}
return ;
} ... ...
运行时的截图
由上图可以看出如果该文件在服务器中存在的话,那么下一次上传同一个文件的话就会跳过上传的步骤,而是把数据库中的标识号给用户uid即可。具体关系可以看下面数据库数据。
好了,现在的上传功能已经很完善了。下一节将实现下载功能了。
本文地址: http://www.cnblogs.com/wunaozai/p/3891062.html
Socket网络编程--小小网盘程序(3)的更多相关文章
- Socket网络编程--小小网盘程序(5)
各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中的文件列表,还有删除用户在服务器中的文件,最后的可以共享文件给好友. 列出用户在服务器中的文件 ...
- Socket网络编程--小小网盘程序(4)
在这一小节中实现了文件的下载,具体的思路是根据用户的uid和用户提供的文件名filename联合两张表,取得md5唯一标识符,然后操作这个标识符对应的文件发送给客户端. 实现下载的小小网盘程序 cli ...
- Socket网络编程--小小网盘程序(2)
这一节将不会介绍太多的技术的问题,这节主要是搭建一个小小的框架,为了方便接下来的继续编写扩展程序.本次会在上一小节的基础上加上一个身份验证的功能. 因为网盘程序不像聊天程序,网盘是属于主动向服务器拉取 ...
- Socket网络编程--小小网盘程序(1)
这个系列是准备讲基于Linux Socket进行文件传输.简单的文件传输就是客户端可以上传文件,可以从服务器端下载文件.就这么两个功能如果再加上身份验证,就成了FTP服务器了,如果对用户的操作再加上一 ...
- linux下C语言socket网络编程简例
原创文章,转载请注明转载字样和出处,谢谢! 这里给出在linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到client的连接后,发送数据给client:clie ...
- 5.3linux下C语言socket网络编程简例
原创文章,转载请注明转载字样和出处,谢谢! 这里给出在Linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端:客户端在接受到数据后 ...
- Socket网络编程--聊天程序(9)
这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...
- Socket网络编程系列教程序
C语言的用途相当多,可以用在数据结构.数据库.网络.嵌入式等方面,历经40多年不衰,真是厉害!最近一直想从某一应用方面写一个系列教程,好好地把某一方面讲深讲透. 正好博主对网络方面的编 ...
- Socket网络编程--FTP客户端
Socket网络编程--FTP客户端(1)(Windows) 已经好久没有写过博客进行分享了.具体原因,在以后说. 这几天在了解FTP协议,准备任务是写一个FTP客户端程序.直接上干货了. 0.了解F ...
随机推荐
- 在VS代码中编辑Python
在VS代码中编辑Python Python扩展提供了许多用于在Visual Studio代码中编辑Python源代码的功能: 自动完成和智能感知 在终端中运行选择/行(REPL) 格式化 重构 也见L ...
- HDU1532 网络流最大流【EK算法】(模板题)
<题目链接> 题目大意: 一个农夫他家的农田每次下雨都会被淹,所以这个农夫就修建了排水系统,还聪明的给每个排水管道设置了最大流量:首先输入两个数n,m ;n为排水管道的数量,m为节点的数量 ...
- ubuntu安装nodejs出现./config.gypi错误
报错的内容如下: xxx@xxx [/usr/local/src/node-v0.8.3]# ./configure { 'target_defaults': { 'cflags': [], 'def ...
- IdentityServer4-主题
一.Startup 二.定义Resources 三.定义Clients 四.登录 五.使用外部身份提供商登录 六.Windows身份验证 七.登出 八.注销外部身份提供商 九.联合注销 十.联合网关 ...
- grpc使用客户端技巧
grpc 使用技巧,最近在做的项目是服务端是go语言提供服务使用的是grpc框架. java在实现客户端的时候,参数的生成大部分采用创建者模式.java在接受go服务端 返回数据的时候,更多的是通过p ...
- u3d 楼梯,圆环,椭圆,直线运动。世界坐标。点击。U3d stair, ring, ellipse, linear motion.World coordinates.Click .
u3d 楼梯,圆环,椭圆,直线运动.世界坐标.点击. U3d stair, ring, ellipse, linear motion.World coordinates.Click . 作者:韩梦飞沙 ...
- wamp memcache 的安装与扩展(Windows 64)
一.windows操作系统下的memcache安装 1.此处提供32位的安装包链接,如果需要64位的应该可惜查得到.将下载的压缩包解压到自己确定的安装目录,我的参考如下: 2.为了安装顺利,所以需要以 ...
- treap(树堆)
一棵treap是一棵修改了结点顺序的二叉查找树,如图,显示一个例子,通常树内的每个结点x都有一个关键字值key[x],另外,还要为结点分配priority[x],它是一个独立选取的随机数. 假设所有的 ...
- 发布Docker 镜像到dockerhub
公有仓库 docker提供了一个类似于github的仓库dockerhub, 网址 https://hub.docker.com/ 需要注册使用 注意要保证image的tag是账户名,如果镜像名字不对 ...
- VMware workstation12 密匙
VMware workstation12 密匙:5A02H-AU243-TZJ49-GTC7K-3C61N