基于Qt实现的Ftp客户端和服务端程序

Reindeer

发布日期: 2020-06-18 12:24:54 浏览量: 813
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

一、设计目的

文件传送是各种计算机网络都实现的基本功能,文件传送协议是一种最基本的应用层协议按照客户/服务器的模式进行工作,提供交互式的访问,是INTERNET使用最广泛的协议之一。

本实验的目的是,学会利用已有网络环境设计并实现简单应用层协议,掌握TCP/IP 网络应用程序基本的设计方法和实现技巧。

二、设计内容和功能

2.1 设计内容

我们的计算机网络实验环境建立在TCP/IP 网络体系结构之上。各计算机除了安装TCP/IP 软件外,还安装了TCP/IP 开发系统。实验室各计算机具备Windows环境中套接字socket 的编程接口功能,可为用户提供全网范围的进程通信功能。本设计实现了一个简单的文件传送协议。

2.2 具体功能

用socket 编程接口编写两个程序,分别为客户程序和服务器程序,该程序应能实现了下述命令功能:

  • get:取远方的一个文件

  • put:传给远方一个文件

  • pwd:显示远主当前目录

  • dir:列出远方当前目录

  • cd:改变远方当前目录

  • :显示你提供的命令

  • ls :列出当前目录

  • quit :退出返回

其中支持多连接,并限制了只能有三个并发客户端。

三、设计平台与语言

  • 平台:LINUX

  • 语言:C 和 C++

  • 界面设计: qt

四、设计具体步骤

4.1 总体方案设计

服务器

服务器中一直在阻塞地等待客户端的连接。而每个连接一个客户端就会创建一一条TCP通道,并用一线程和处理这条TCP通道的命令传输。每当需要进行文件传输时,就会再创建一条TCP通道进行文件传输,传输完毕后释放这条通道。

客户端

首先通过TCP连接到服务器产生一条TCP通道,并创建一线程来处理服务发来的信息,然后通过服务器提供的命令和服务器进行交互,当需要获取或上传文件时,再创建一条TCP通道进行文件传输,传输完毕后释放这条通道,且可以保持和服务器进行交互。

4.2 服务器模块设计

此服务器可连接多个客户端,其中线程1是唯一的,一直在阻塞地等待客户端的连接。而每个连接一个客户端就会创建一个线程2来处理客户端的工作。每当需要进行文件传输时,就会创建一个线程3来进行文件传输。

4.3 客户端模块设计

首先线程1用来连接服务器,之后等待终端输入命令,接下来启动线程2,线程2用来等待服务器发来的信息,并启动线程3来等待数据TCP的连接。在发收文件传输命令后,服务器响应则启动线程4来进行文件传输,传输完毕则自动断开连接,并结束线程。

4.4 文件传输实现

  • READY_T信号的确认是为了确认要传输的文件是否存在,如果不存在即打开失败,会发送FAILL_T信号告诉接收端结束传输,如果成功,则可以继续

  • OK_T回应信号是为了确保接收端有权限创建要接收的文件,如果创建成功则发送OK_T表示可以继续,否则发送FAIL_T信号结束传输

  • 如果双方都准备好,则发送方可将文件总大小发给接收方

  • 可以进行文件传输

  • 发送方完毕后,等接收方接收完毕

  • 最后再一次确认文件已传完毕,双方结束传并释放TCP通道

4.5 代码实现

各确认标识的设计

  1. enum{READY_T=200,OK_T=202, FAILL_T=204};

文件传输TCP通道的连接

客户端(等待连接):

  1. /***********等待数据TCP连接***************/
  2. void *data_routine(void *arg)
  3. {
  4. socklen_t len;
  5. len = sizeof(data_adr);
  6. while(1)
  7. {
  8. //等待服务器连接
  9. datafd = accept(dfd, (struct sockaddr *)&(data_adr), &len);
  10. is_data = 1;
  11. }
  12. }

服务器(创建连接):

  1. //连接数据连接TCP通道
  2. if(clt->dfd == -1)
  3. {
  4. clt->dfd = socket(AF_INET, SOCK_STREAM, 0);
  5. connect(clt->dfd, (struct sockaddr *)&clt->data_adr, sizeof(clt->data_adr));
  6. }
  7. //连接成功则进行文件传输
  8. if(clt->dfd > 0)
  9. {
  10. //设置为发送模式
  11. ts->cmd = GET;
  12. //获取要发送的文件名
  13. stpcpy(ts->file_name , r_buf+1);
  14. //发送准ready信号
  15. ack = READY_T;
  16. send(clt->fd, &ack, sizeof(ack), 0);
  17. //创建文件传输线程
  18. r = pthread_create(&tid, NULL, &transfer_routine, (void*) ts);
  19. if(r == -1) perror("transfer err");
  20. }

文件传输的线程创建

服务器:

  1. /*********命令传输结构体***************/
  2. struct Transfer
  3. {
  4. int cmd;
  5. struct client *clt;
  6. char file_name[100];
  7. };
  8. /*******************文件传输的线程********************/
  9. void *transfer_routine(void *arg)
  10. {
  11. struct Transfer *ts = (struct Transfer *)arg;
  12. printf("transfer_routine -- %d, %s\n", ts->cmd, ts->file_name);
  13. char cur_file[200];
  14. //把文件名拼接到镜像目录后面
  15. strcpy(cur_file, ts->clt->dir);
  16. strcat(cur_file, "/");
  17. strcat(cur_file, ts->file_name);
  18. t_count = 1;
  19. switch(ts->cmd)
  20. {
  21. case GET: //发送文件
  22. put_file(&ts->clt->dfd, cur_file);
  23. break;
  24. case PUT: //接收文件
  25. dowload(&ts->clt->dfd, cur_file);
  26. is_fchge = 1;
  27. break;
  28. }
  29. //关闭tcp
  30. t_count = 0;
  31. shutdown(ts->clt->dfd, SHUT_RDWR);
  32. close(ts->clt->dfd);
  33. ts->clt->dfd = -1;
  34. printf("tranfer close\n");
  35. }

客户端:

  1. /*******************文件传输的线程********************/
  2. void *transfer_routine(void *arg)
  3. {
  4. //判断发送还是接收
  5. switch(flag)
  6. {
  7. case PUT: //发送文件
  8. put_file(file_name);
  9. break;
  10. case GET: //接收文件
  11. dowload(file_name);
  12. break;
  13. }
  14. //输入提示
  15. write(0,"myftp>", sizeof("myftp>"));
  16. }

文件传输(发送与接收)

发送文件(客户端为例):

  1. /******************上传文件到服务器*******************/
  2. int put_file(char *file)
  3. {
  4. int ffd; //file 文件描述符
  5. char buf[256]; //缓存区
  6. int total_c; //文件总大小
  7. int sent_c = 0; //已经发送的字节数
  8. int snd_c; //发送字节数
  9. int ret; //处理返回值
  10. int ack; //信号确认
  11. /*打开要发送的文件*/
  12. ffd = open(file, O_RDONLY);
  13. if(ffd == -1)
  14. {
  15. printf("open %s failled\n", file);
  16. goto SNDERR;
  17. }
  18. /*发送READY_T的信号*/
  19. ack = READY_T;
  20. ret = send(datafd, &ack, 4, 0);
  21. /*等待对方发送确认的应答*/
  22. ret = recv(datafd, &ack, 4, MSG_WAITALL);
  23. if(ack == FAILL_T)
  24. goto SNDERR;
  25. /*发送文件总大小*/
  26. total_c = lseek(ffd, 0, SEEK_END);
  27. lseek(ffd, 0, SEEK_SET);
  28. printf("have %.2f KB to send\n", (float)total_c/1024);
  29. ret = send(datafd, &total_c, sizeof(total_c), 0);
  30. /*开始发送文件*/
  31. printf("sending %s .......\n", file);
  32. snd_c = 255;
  33. while(1)
  34. {
  35. bzero(buf, sizeof(buf));
  36. //从文件中读取snd_c字节到缓存
  37. ret = read(ffd, &buf, snd_c);
  38. //发送snd_c个字节
  39. ret = send(datafd, buf, snd_c, 0);
  40. //总字节数-已发送字节数
  41. total_c -= ret;
  42. sent_c += ret;
  43. if(total_c < snd_c)
  44. snd_c = total_c;
  45. if(total_c <= 0)
  46. break;
  47. }
  48. /*发送发送完成信号*/
  49. ack=OK_T;
  50. ret = send(datafd, &ack, 4, 0);
  51. close(ffd);
  52. /*等待对方接收完成的应答*/
  53. ret = recv(datafd, &ack, 4, 0);
  54. printf("send %.2f KB\n", (float)sent_c/1024);
  55. printf("send %s success\n", file);
  56. return 0;
  57. //错误处理
  58. SNDERR:
  59. //打印错误信息
  60. perror("send err");
  61. //发送接收错误信号
  62. ack = FAILL_T;
  63. ret = send(datafd, &ack, 4, 0);
  64. //关闭文件
  65. close(ffd);
  66. return -1;
  67. }

接收文件(服务器为例):

  1. /******************下载客户端传过来的文件******************/
  2. int dowload(int *tfd, char *file)
  3. {
  4. int file_c;
  5. int get_c;
  6. int got_c = 0;
  7. char buf[256];
  8. int ret;
  9. int ffd;
  10. int ack;
  11. /*等待对方发送READY_T的信号*/
  12. ret = recv(*tfd, &ack, 4, MSG_WAITALL);
  13. printf(" %d\n", ack);
  14. if(ack == FAILL_T)
  15. {
  16. printf("file donot exist\n");
  17. return -1;
  18. }
  19. /*打开或创建文件*/
  20. ffd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  21. if(ffd == -1)
  22. {
  23. printf("open file failled\n");
  24. goto RECVERR;
  25. }
  26. /*发送确认信号*/
  27. ack = OK_T;
  28. ret = send(*tfd, &ack, 4, 0);
  29. /*等待对方发送文件总大小*/
  30. ret = recv(*tfd, &file_c, 4, MSG_WAITALL);
  31. printf("have %.2f KB to download\n", (float)file_c/1024);
  32. printf("downloading %s.......\n", file);
  33. /*开始接收*/
  34. get_c = 255;
  35. while(1)
  36. {
  37. bzero(buf, sizeof(buf));
  38. if(file_c < get_c)
  39. get_c = file_c;
  40. ret = recv(*tfd, buf, get_c, MSG_WAITALL);
  41. if(ret > 0)
  42. {
  43. ret = write(ffd, buf, ret);
  44. file_c -= ret;
  45. got_c += ret;
  46. }
  47. else if(ret == 0)
  48. {
  49. printf("download %s success\n", file);
  50. break;
  51. }
  52. else if(ret == -1)
  53. {
  54. printf("internet err!\n");
  55. break;
  56. }
  57. }
  58. /*等待发送完毕确认信号*/
  59. ret = recv(*tfd, &ack, 4, 0);
  60. /*发送以接收完毕确认信号*/
  61. ack = OK_T;
  62. ret = send(*tfd, &ack, 4, 0);
  63. printf("download %.2f KB\n", (float)got_c/1024);
  64. close(ffd);
  65. return 0;
  66. /**错误处理**/
  67. RECVERR:
  68. perror("download err");
  69. ack = FAILL_T;
  70. ret = send(*tfd, &ack, 4, 0);
  71. close(ffd);
  72. return -1;
  73. }

4.6 界面设计

用QT进行设计的显示界面。界面功能:

  • 显示当前IP地址

  • 当前客户连接数

  • 可查看连接客户的ip信息

  • 消息提示

  • 显示当前工作路径和当前目录的内容

  • 可查看服务器磁盘的所有文件

  • 改变当前工作目录。

  • 查看已连接客户的IP

显示当前IP(QT的接口函数实现)

  1. ui->label_ip->setText(QNetworkInterface::allAddresses().at(2).toString());

当前客户连接数

  1. //clt_count是全局变量,有客户连接和断开都会改变
  2. count = clt_count;
  3. ui->count_lb->setText(QString::number(count));

可查看连接客户的ip信息

  1. void FtpDialog::on_pushButton_usr_clicked()
  2. {
  3. int i=0;
  4. QString clients="";
  5. //已经限制最多3个客户连接
  6. for(;i < 3; i++)
  7. {
  8. //clientip数组是全局变量
  9. if(clientip[i]!=NULL)
  10. {
  11. clients += QString::number(i+1)+"、"+ QString::fromUtf8(clientip[i])+"\n" ;
  12. }
  13. }
  14. //对话框显示
  15. QMessageBox::information(this,"已连接的客户",clients);
  16. }

消息提示

  1. //有客户断开、连接
  2. if(count != clt_count)
  3. {
  4. if(count < clt_count)
  5. ui->msg_lb->setText("有新客户连接");
  6. else
  7. ui->msg_lb->setText("有客户断开连接");
  8. }
  9. // is_fchge全局变量,接收到新文件会置为1
  10. if(is_fchge == 1)
  11. {
  12. is_fchge = 0;
  13. ui->msg_lb->setText("接收到新文件");
  14. }

显示当前工作路径和当前目录的内容

  1. //当前目录改变/接收到新文件: 刷新显示列表
  2. if(path != current_path || is_fchge == 1)
  3. {
  4. //显示当前目录
  5. path = current_path;
  6. ui->dir_lb->setText(path);
  7. //显示目录内容
  8. dir = new QDir(path);
  9. QStringList filter;
  10. int c, i;
  11. filter<<"*";
  12. dir->setNameFilters(filter);
  13. file_list =new QList<QFileInfo>(dir->entryInfoList((filter)));
  14. c = file_list->count();
  15. ui->file_lw->clear();
  16. for(i = 0; i < c; i++)
  17. {
  18. if(file_list->at(i).fileName() != "." && file_list->at(i).fileName() != "..")
  19. ui->file_lw->addItem(file_list->at(i).fileName());
  20. }
  21. delete dir;
  22. }

可查看服务器磁盘的所有文件

  1. //浏览文件的按键事件
  2. void FtpDialog::on_pushButton_open_clicked()
  3. {
  4. QString path = QFileDialog::getOpenFileName(this, tr("scan file"), " ", tr("Allfile(*.*)"));
  5. }

改变当前工作目录

  1. //改变当前目录的按键事件
  2. void FtpDialog::on_pushButton_cddir_clicked()
  3. {
  4. QString dir = QFileDialog::getExistingDirectory(this, tr("select dir"));
  5. if(dir != "")
  6. {
  7. current_path = dir;
  8. cd_dir(current_path.toLocal8Bit().data());
  9. }
  10. }

查看已连接客户的IP

  1. void FtpDialog::on_pushButton_usr_clicked()
  2. {
  3. int i=0;
  4. QString clients="";
  5. for(;i < 3; i++)
  6. {
  7. if(clientip[i]!=NULL)
  8. {
  9. clients += QString::number(i+1)+"、"+ QString::fromUtf8(clientip[i])+"\n" ;
  10. }
  11. }
  12. QMessageBox::information(this,"已连接的客户",clients);
  13. }

六、课程设计心得

由于FTP程序的各功能的关系非常密切,分工也比较困难,而且必须要把整个框架搭好才能给各功能进行调试,而且LINUX的FTP实现在网上并没有太多的参考,只有多原理出发来进行设计。

本设计的最大的难点就是文件传输的实现,传输机制给过多修改后才能够稳定传输,刚开始没有进行发送前后的确认,造成其中一方出现错误,就认另一方一直阻塞。而且客户端出现错误有可能会服务器整个程序崩溃,对于一个服务器来说,为种情况是不允许发生的,服务器会给多个客户端服务,不能因为其中一个客户端出问题而结束程的。完善传输机制后,只要收接客户端的错误信息就会中终止任务,等待重新请求,这样保证的服务器的正确运行。

设计的为了可靠传输,命令的传输机制也经过了多次修改,达到效果满意为止。

有时候小细节会造成大麻烦,程序完善到最后一直有一个问题,程序启动后第一次传输文件肯定会失败,这个问题看了很久才找出原因,原来是忘记初始化数据传输的TCP套接字为-1,造第一次传不会去连接传输TCP。

界面的设计是后来才加上的,刚开始的设计中并没有界面,只是两个.c和程序,服务器没有交互的操作。所以才想到要加个界面进行交互。

由于界面要用C++实现,并且用QT的库实现,面QT库没有原始的socket接口,都是已经封装好的类,要进行移植改动会很大,也不符合设计的要求,因此,想到了C和C++混合使用。文件传输的实现依然用LINUX的C库,而界面设计则用QT库。虽然看起来有点奇怪,但是实现起来并不会也现什么问题,而且也使得ftp服务器功能的实现和界面设独立开来,也方便了代码的查看和修改。

本设计的实现过程中,学习了FTP 文件传输的工作原理的实现方法和TCP socket 编程,巩固了C和C++语言的编写和QT的使用。同时再次感受到程序员的艰辛。

七、参考文献

  • [1] 《计算机网络 第6版 》 谢希仁 电子工业出版社

  • [2] 《TCP/IP 详解 》卷3 (美)W.Richard Stevens 机械工业出版社

上传的附件 cloud_download 基于Qt实现的Ftp客户端和服务器程序.7z ( 1.43mb, 17次下载 )
error_outline 下载需要12点积分

发送私信

看见你笑眼一弯呀,所有天上的星星,就被叮叮当当地摇响

10
文章数
13
评论数
最近文章
eject