Socket网络编程--小小网盘程序(3)
接上一小节,这次增加另外的两张表,用于记录用户是保存那些文件。增加传上来的文件的文件指纹,使用MD5表示。
两张表如下定义:
1 create table files( 2 fid int, 3 filename varchar(64), 4 md5 varchar(64) 5 ); 6 7 create table relations( 8 uid int, 9 fid int 10 );
表与表之间的关系如下:
client.cpp 在上一小节基础上增加了一个md5的功能,传输给服务器,用于作为文件的唯一标识
...
42 struct File 43 { 44 int uid; 45 char filename[64]; 46 char md5[64]; 47 }; 48 struct FileList 49 { 50 char list[1024]; 51 }; 52 53 void print_time(char *ch);//打印时间 54 int file_push(struct Addr addr,struct User user,char *filenames); 55 int check_login(struct Addr addr,struct User * user); 56 int md5sum(char *filename,unsigned char *md5); 57 int file_pull(struct Addr addr,struct User user,char *filenames); 58 59 60 int main(int argc,char *argv[]) 61 { ... 131 return 0; 132 } 133 134 //验证成功时返回大于0的uid号码,错误返回-1 135 int check_login(struct Addr addr,struct User * user) 136 { ...
186 } 187 188 int file_push(struct Addr addr,struct User user,char *filenames) 189 { ... ... 193 struct File file;//文件指纹 194 int sockfd; 195 FILE *fp; 196 char md5[64]; 197 unsigned char md5tmp[64]; 198 ... ...
220 //计算MD5 221 memset(md5,0,sizeof(md5)); 222 md5sum(filenames,md5tmp); 223 printf("计算得到的MD5:"); 224 for(int i=0;i<16;i++) 225 { 226 sprintf(&md5[i*2],"%02X",md5tmp[i]); 227 } 228 printf("%s\n",md5); 229 230 //打开文件 231 if((fp=fopen(filenames,"rb"))==NULL) 232 { 233 perror("文件打开失败"); 234 exit(-1); 235 } 236 //这里传输控制信号 237 control.control=FILE_PUSH; 238 control.uid=user.uid; 239 if(send(sockfd,(char *)&control,sizeof(struct Control),0)<0) 240 { 241 perror("控制信号发送失败"); 242 exit(-1); 243 } 244 //发送文件指纹 245 strcpy(file.filename,filenames); 246 strcpy(file.md5,md5); 247 file.uid=user.uid; 248 if(send(sockfd,(char *)&file,sizeof(struct File),0)<0) 249 { 250 perror("文件指纹发送失败"); 251 exit(-1); 252 } 253 char buffer[BUFFER_SIZE]; 254 bzero(buffer,BUFFER_SIZE); 255 printf("正在传输文件"); 256 int len=0; 257 //不断的读取文件直到文件结束 258 while((len=fread(buffer,1,BUFFER_SIZE,fp))>0) 259 { 260 if(send(sockfd,buffer,len,0)<0) 261 { 262 perror("发送数据失败"); 263 exit(-1); 264 } 265 bzero(buffer,BUFFER_SIZE); 266 printf(".");//1K打印一个点//如果要实现百分比,就要计算文件大小,然后再处理即可 267 } 268 269 printf("传输完毕\n"); 270 fclose(fp);//关闭文件流 271 close(sockfd);//关闭socket连接 272 273 return 0; 274 } 275 276 int md5sum(char *filename,unsigned char *md5) 277 { 278 MD5_CTX ctx; 279 char buffer[1024]; 280 unsigned char outmd[16]; 281 int len=0; 282 int i; 283 FILE *fp=NULL; 284 memset(outmd,0,sizeof(outmd)); 285 memset(buffer,0,sizeof(buffer)); 286 fp=fopen(filename,"rb"); 287 if(fp==NULL) 288 { 289 perror("打开文件失败"); 290 } 291 MD5_Init(&ctx); 292 while((len=fread(buffer,1,sizeof(buffer),fp))>0) 293 { 294 MD5_Update(&ctx,buffer,len); 295 memset(buffer,0,sizeof(buffer)); 296 } 297 MD5_Final(outmd,&ctx); 298 for(i=0;i<16;i++) 299 { 300 md5[i]=outmd[i]; 301 } 302 fclose(fp); 303 return 0; 304 } 305
server.cpp
... ...
40 struct File 41 { 42 int uid; 43 char filename[64]; 44 char md5[64]; 45 }; 46 struct FileList 47 { 48 char list[1024]; 49 }; 50 51 void print_time(char *ch);//打印时间 52 int MAX(int a,int b); 53 int mysql_check_login(struct User user); 54 int mysql_file_in(struct File file); 55 56 int mysql_get_max_fid() 57 { 58 MYSQL conn; 59 MYSQL_RES *res_ptr; 60 MYSQL_ROW result_row; 61 int res;int row;int column;int mfid; 62 char sql[256]; 63 strcpy(sql,"select max(fid) from files;"); 64 mfid=0; 65 mysql_init(&conn); 66 if(mysql_real_connect(&conn,"localhost","root","","filetranslate",0,NULL,CLIENT_FOUND_ROWS)) 67 { 68 res=mysql_query(&conn,sql); 69 if(res) 70 { 71 perror("SELECT SQL ERROR!"); 72 exit(-1); 73 } 74 else 75 { 76 res_ptr=mysql_store_result(&conn); 77 if(res_ptr) 78 { 79 column=mysql_num_fields(res_ptr); 80 row=mysql_num_rows(res_ptr)+1; 81 if(row<=1) 82 { 83 ;//没有数据 84 } 85 else 86 { 87 result_row=mysql_fetch_row(res_ptr); 88 printf("最大的fid是:%s\n",result_row[0]); 89 if(result_row[0]==NULL) 90 mfid=0; 91 else 92 mfid=atoi(result_row[0]); 93 } 94 } 95 else 96 { 97 printf("没有查询到匹配的数据\n"); 98 } 99 } 100 } 101 else 102 { 103 perror("Connect Failed!"); 104 exit(-1); 105 } 106 mysql_close(&conn); 107 return mfid; 108 } 109 110 int mysql_file_in(struct File file) 111 { 112 int mfid; 113 MYSQL conn; 114 int res; 115 char sql[256]; 116 char tmp[32]; 117 mfid=mysql_get_max_fid()+1; 118 printf("获取到的最大fid为:%d\n",mfid); 119 mysql_init(&conn); 120 if(mysql_real_connect(&conn,"localhost","root","","filetranslate",0,NULL,CLIENT_FOUND_ROWS)) 121 { 122 //insert into files values(mfid,file.filename,file.md5); 123 //insert into files values(file.uid,mfid); 124 memset(sql,0,sizeof(sql)); 125 strcpy(sql,"insert into files values("); 126 sprintf(tmp,"%d",mfid); 127 strcat(sql,tmp); 128 strcat(sql,",'"); 129 strcat(sql,file.filename); 130 strcat(sql,"','"); 131 strcat(sql,file.md5); 132 strcat(sql,"');"); 133 printf("插入的sql语句: %s\n",sql); 134 res=mysql_query(&conn,sql); 135 if(res) 136 printf("Insert Error!\n"); 137 else 138 printf("Insert Success!\n"); 139 140 memset(sql,0,sizeof(sql)); 141 strcpy(sql,"insert into relations values("); 142 sprintf(tmp,"%d",file.uid); 143 strcat(sql,tmp); 144 strcat(sql,","); 145 sprintf(tmp,"%d",mfid); 146 strcat(sql,tmp); 147 strcat(sql,");"); 148 printf("插入的sql语句: %s\n",sql); 149 res=mysql_query(&conn,sql); 150 if(res) 151 printf("Insert Error!\n"); 152 else 153 printf("Insert Success!\n"); 154 } 155 else 156 { 157 perror("Connect Failed!"); 158 exit(-1); 159 } 160 return 0; 161 } 162 163 164 int main(int argc,char *argv[]) 165 { ... 201 while(1) 202 { 203 clientfd=accept(sockfd,(struct sockaddr *)&client_addr,&length); 204 if(clientfd==-1) 205 { 206 perror("accept 失败"); 207 continue; 208 } 209 printf(">>>>>%s:%d 连接成功,当前所在的ID(fd)号: %d \n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),clientfd); 210 print_time(ch); 211 printf("加入的时间是:%s\n",ch); 212 213 //来一个连接就创建一个进程进行处理 214 pid=fork(); 215 if(pid<0) 216 { 217 perror("fork error"); 218 } 219 else if(pid==0) 220 { 221 recv(clientfd,(char *)&control,sizeof(struct Control),0); 222 printf("用户 %d 使用命令 %d\n",control.uid,control.control); 223 switch(control.control) 224 { 225 case USER_CHECK_LOGIN: 226 { 227 //身份验证处理 ... ...
239 break; 240 } 241 case FILE_PUSH: 242 { 243 char buffer[BUFFER_SIZE]; 244 int data_len; 245 FILE * fp=NULL; 246 struct File file; 247 //获取文件指纹 248 recv(clientfd,(char *)&file,sizeof(struct File),0); 249 printf("获取到的用户名ID: %d 文件名:%s MD5:%s\n",file.uid,file.filename,file.md5); 250 bzero(buffer,BUFFER_SIZE); 251 if((fp=fopen("data","wb"))==NULL) 252 { 253 perror("文件打开失败"); 254 exit(-1); 255 } 256 //循环接收数据 257 int size=0;//表示有多少个块 258 while(data_len=recv(clientfd,buffer,BUFFER_SIZE,0)) 259 { ... ...
278 } 279 if(size>0) 280 { 281 printf("\n%s:%d的文件传送完毕\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 282 //如果文件传输成功那么就可以写入数据库了 283 mysql_file_in(file); 284 } 285 else 286 printf("\n%s:%d的文件传送失败\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 287 fclose(fp); 288 rename("data",file.md5);//这里可以修改文件的名字 289 exit(0); 290 break; 291 } 292 case FILE_PULL: 293 { 294 break; 295 } 296 case FILE_LIST: 297 { 298 break; 299 } 300 case FILE_DELECT: 301 { 302 break; 303 } 304 default: 305 { 306 break; 307 } 308 } 309 close(clientfd);//短连接结束 310 exit(0);//退出子进程 311 } 312 } 313 314 return 0; 315 } 316 317 318 //函数定义 319 int mysql_check_login(struct User user) 320 { ... ...
377 } 378 ... ...
上面已经介绍了如何计算MD5值并写入数据库,接下来要做的事就是如何判断要上传的文件是否在服务器中已经存在,如果存在就可以不用上传,而是在relations表中记录,写入uid-fid关系,这样就可以实现网盘的秒传功能了。而如何区分文件是否相同,我这里使用的是以MD5作为文件指纹。话说秒传功能也不过如此吧。所以现在应该知道为什么有的文件可以秒传,有的不可以了吧。关于具体的解释可以参考杜鑫先生在知乎中的回到。传送门在第一小节中有。
已加入验证和秒传功能的网盘程序
client.cpp修改如下
... ... 188 int file_push(struct Addr addr,struct User user,char *filenames) 189 { ... ...
238 //发送文件指纹 239 strcpy(file.filename,filenames); 240 strcpy(file.md5,md5); 241 file.uid=user.uid; 242 if(send(sockfd,(char *)&file,sizeof(struct File),0)<0) 243 { 244 perror("文件指纹发送失败"); 245 exit(-1); 246 } 247 char ch[64]; 248 if(recv(sockfd,ch,64,0)<0) 249 { 250 perror("error"); 251 } 252 if(ch[0]=='y')//表示已经存在 253 { 254 printf("该文件在服务器中存在,正使用秒传功能。\n"); 255 printf("传输完毕\n"); 256 return 0; 257 } 258 //打开文件 259 if((fp=fopen(filenames,"rb"))==NULL) 260 { 261 perror("文件打开失败"); 262 exit(-1); 263 } 264 char buffer[BUFFER_SIZE]; 265 bzero(buffer,BUFFER_SIZE); 266 printf("正在传输文件"); 267 int len=0; 268 //不断的读取文件直到文件结束 269 while((len=fread(buffer,1,BUFFER_SIZE,fp))>0) 270 { 271 if(send(sockfd,buffer,len,0)<0) 272 { 273 perror("发送数据失败"); 274 exit(-1); 275 } 276 bzero(buffer,BUFFER_SIZE); 277 printf(".");//1K打印一个点//如果要实现百分比,就要计算文件大小,然后再处理即可 278 } 279 280 printf("传输完毕\n"); 281 fclose(fp);//关闭文件流 282 close(sockfd);//关闭socket连接 283 284 return 0; 285 } 286 ... ...
server.cpp修改如下
...... 61 int main(int argc,char *argv[]) 62 { ...... 110 //来一个连接就创建一个进程进行处理 111 pid=fork(); 112 if(pid<0) 113 { 114 perror("fork error"); 115 } 116 else if(pid==0) 117 { 118 recv(clientfd,(char *)&control,sizeof(struct Control),0); 119 printf("用户 %d 使用命令 %d\n",control.uid,control.control); 120 switch(control.control) 121 { 122 case USER_CHECK_LOGIN: ... ...
138 case FILE_PUSH: 139 { 140 char buffer[BUFFER_SIZE]; 141 int data_len; 142 FILE * fp=NULL; 143 struct File file; 144 //获取文件指纹 145 recv(clientfd,(char *)&file,sizeof(struct File),0); 146 printf("获取到的用户名ID: %d 文件名:%s MD5:%s\n",file.uid,file.filename,file.md5); 147 //对文件进行验证,如果文件已经存在就不用进行接收了 148 int t=mysql_check_md5(file); 149 char ch[64]={0}; 150 printf("t=%d\n",t); 151 if(t!=0) 152 { 153 printf("该文件存在,使用秒传功能\n"); 154 strcpy(ch,"yes"); 155 send(clientfd,ch,64,0); 156 mysql_file_in(file.uid,t); 157 continue; 158 } 159 strcpy(ch,"no"); 160 send(clientfd,ch,64,0); 161 printf("md5验证后得到的fid:%d\n",t); 162 bzero(buffer,BUFFER_SIZE); ... ... ...
202 break; 203 } ... ...
220 } 221 close(clientfd);//短连接结束 222 exit(0);//退出子进程 223 } 224 } 225 226 return 0; 227 } 228 229 230 //函数定义 231 int mysql_check_md5(struct File file) 232 { 233 MYSQL conn; 234 MYSQL_RES * res_ptr; 235 MYSQL_ROW result_row; 236 int res;int row;int column;int value=0; 237 char sql[256]={0}; 238 strcpy(sql,"select fid from files where md5='"); 239 strcat(sql,file.md5); 240 strcat(sql,"';"); 241 printf("查询的sql:%s\n",sql); 242 243 mysql_init(&conn); 244 if(mysql_real_connect(&conn,"localhost","root","","filetranslate",0,NULL,CLIENT_FOUND_ROWS)) 245 { 246 res=mysql_query(&conn,sql); 247 if(res) 248 { 249 perror("Select Sql Error!"); 250 exit(-1); 251 } 252 else 253 { 254 res_ptr=mysql_store_result(&conn); 255 if(res_ptr) 256 { 257 column=mysql_num_fields(res_ptr); 258 row=mysql_num_rows(res_ptr)+1; 259 if(row<=1) 260 { 261 ; 262 } 263 else 264 { 265 result_row=mysql_fetch_row(res_ptr); 266 value=atoi(result_row[0]); 267 } 268 } 269 else 270 { 271 printf("没有查询到匹配的数据\n"); 272 } 273 } 274 } 275 else 276 { 277 perror("Connect Failed!"); 278 exit(-1); 279 } 280 mysql_close(&conn); 281 return value;//返回fid 282 } 283 284 int mysql_file_in(int uid,int fid) 285 { 286 MYSQL conn; 287 int res; 288 char sql[256]; 289 char tmp[32]; 290 mysql_init(&conn); 291 if(mysql_real_connect(&conn,"localhost","root","","filetranslate",0,NULL,CLIENT_FOUND_ROWS)) 292 { 293 //insert into files values(uid,fid); 294 memset(sql,0,sizeof(sql)); 295 strcpy(sql,"insert into relations values("); 296 sprintf(tmp,"%d",uid); 297 strcat(sql,tmp); 298 strcat(sql,","); 299 sprintf(tmp,"%d",fid); 300 strcat(sql,tmp); 301 strcat(sql,");"); 302 printf("插入的sql语句: %s\n",sql); 303 res=mysql_query(&conn,sql); 304 if(res) 305 printf("Insert Error!\n"); 306 else 307 printf("Insert Success!\n"); 308 } 309 else 310 { 311 perror("Connect Failed!"); 312 exit(-1); 313 } 314 return 0; 315 } 316 ... ...
运行时的截图
由上图可以看出如果该文件在服务器中存在的话,那么下一次上传同一个文件的话就会跳过上传的步骤,而是把数据库中的标识号给用户uid即可。具体关系可以看下面数据库数据。
好了,现在的上传功能已经很完善了。下一节将实现下载功能了。
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |