基于UDP的文件传输软件 (C#)

基于UDP的文件传输软件 (C#)

选了一门软件开发案例的选修课,得到一项多选一大作业,我们选择了“基于UDP的文件传输系统”这个作业,然后经过需求分析和概要设计,进入编码阶段,因为作业要求有界面,个人认为用C#编写桌面软件比较容易,就选择了C#而非Java做这个作业(主要是Java编写桌面软件我不会)。
这篇博文的目的在于解释大作业的结构,放上一些需求分析和概要设计的成果,结合代码,所以说踩过哪些坑,如何设计的软件结构,代码下载地址将会放在文章结尾。
说来惭愧,负责这个作业的分析和设计的,是我组长(我是副组长兼组员,笑),我负责上来就编码,这完全没有遵循软件开发的规范,事后,我觉得应该补救一下,所以打算用这篇文章完成一些分析与设计做的事情。

项目要求

完成一个局域网内的基于UDP的文件传输系统,该软件有客户端与服务端,服务端绑定IP、端口,监听接收文件的消息,客户端连接服务端,向其发送选中文件,发送文件过程中允许两端终端文件传输。同时需要支持多个客户端的文件发送。
附两张老师需求文档中界面的截图:
客户端

服务端

需求分析,划分功能点

在项目要求和图片中把很多点都列举出来了,这里列个表

  • 选择IP、端口,服务端需要绑定IP、端口,客户端需要连接到服务端
  • 通信,客户端和服务端可以通信,不仅是隐式的,还有显式的主动收发消息
  • 文件选择,客户端需要选择文件
  • 文件传输,客户端需要发送文件
  • 停止文件传输,客户端、服务端都可以停止文件的发送
  • 文件接收、写入磁盘,服务端需要接收文件,写入磁盘
  • 显示文件发送情况,客户端需要显示当前文件已发送多少,所占比例是多少
  • 多线程支持,一个服务端需要同时应对多个客户端

技术分析


  1. 基于UDP,服务端还绑定IP端口,当时我一想,钻了个牛角尖:使用同一个端口怎么可能支持多个客户端?期间还想起了学习计算机网络的知识,UDP的Socket只通过本机的IP和端口标识一个连接,这样的话,岂不是所有客户端的连接,只要接入同一个服务端的IP和端口,就只能被识别为一个连接了?数据怎么传,做不成吧。
    问了老师,老师只回答了用多线程,并没有解决UDP使用同一个IP端口的问题。
    后来做起来,渐渐想清楚了,绑定一个IP和端口肯定是不行的,这其实是让我们手动模拟TCP的Socket.accept函数,每次接到一个连接请求,就重新分配一个端口号用于传输。
  2. 从上面的需求界面可以看到,街面上需要显示当前传输文件的进度,进度是实时更新的。这其实是一个难点。因为传输肯定是要放到UI线程之外来做的,否则UI会卡死,但是UI控件的更新却只能由UI线程来做,这是矛盾的。自然,这种矛盾不仅我们遇到过,无数用C#写桌面程序的人都遇到过,查一下,就有了答案,通过使用SynchronizationContext,将希望UI线程更新的信息,借由同步上下文使用消息队列发送给UI线程,交由UI线程来做。
  3. 还是基于2中的问题,实时更新,其实是一个细粒度的事情,这表示底层Socket发送的过程中要向上层反馈消息,消息再由消息队列交付给UI线程,那么底层向上层发送消息怎么实现呢?自己写消息队列然后上层轮询,可以,但这个消息队列就要手写了。方法应该有很多种,我想到了C#的语法——委托。可以让上层实现消息交付给UI线程的函数,而底层持有这个函数的委托(相当于函数指针),这样底层既可以调用直接调用上层的函数了。

这个时候就突然想起了从哪儿看到的,C#常用于开发桌面应用,所以才有了委托这个语法。确实,委托就像抛向底层的钩子,UI的事情只能上层来做,通过这些钩子却可以让底层也能做。

系统设计与模块设计

这两部分其实是边写程序边修改的,程序写好了,设计也配套做好。原因是我想早早实践,这样的流程不符合软件开发的传统模型,比如瀑布流等,但是似乎符合敏捷开发,当然这是给自己贴金,不写文档不等于敏捷开发。

时序图
时序图

我组长总结的系统设计图:
辛苦我组长的系统架构图

上图是我组长画的系统设计图,整个程序可以分出Server和Client两个头,两个头界面和内部逻辑有些不同,相同的地方向下沉,沉到下一层的Control里,Control里有信息控制、文件发送、文件接收等主要3个类,分为3个模块。

  • 其中MsgCtrl自成一个模块,主管信息收发。
  • FileSendUDPClient两个类为一个功能,主管文件发送相关。FileSend控制UDPClient
  • FileRecUDPServer两个类为一个功能,主管文件接收相关。FileRev控制UDPServer
  • 此外还有一个Util工具类,负责定义常量抛出一些公用方法。

下面是各个类的介绍:

MsgCtrl

内部封装一个UDP的Sokcet,用于发送、接收消息
消息按类型分为系统、文件信息、文件控制、端口消息、用户消息,发送时将消息类型和内容拼接成字符串发送,接收后拆分。
函数介绍:

  • MsgCtrl():初始化
  • sendMsg(msg,flag):将类型与消息拼接,发送消息。
  • String[] reciveMsg()接收消息并且拆分消息类型与内容

UDPClient

用于发送一个文件,内部封装了一个Socket
函数介绍:

  • UDPClient(IP,Port...):初始化一个Socket,以及其他内部属性
  • sendFile():读取文件,分块,发送文件,并且借用构造器传入的委托(类似于函数指针)更新Client端界面显示
  • stopSend():停止发送文件
  • sendDatasendFile内部调用了它,用于分块发送数据

UDPServer

用于一个接收文件
函数介绍:

  • UDPServer(IP,Port):初始化Socket
  • reciveFile:接收文件,拼接文件,写入磁盘
  • reciveDatareciveFile内部调用了它,用于接收分块的一块数据
  • stopRev不同于UDPClient,UDPServer的stopRev用于UDP超时停止接收数据

FileSend

用于管理文件发送
函数介绍:

  • sendFile(UDPclient,filename,filePath):用于新开线程,发送指定文件
  • stopSend:用于取消发送最近发送的文件

FileRev

用于管理文件接收
函数介绍:

  • reciveFile(UDPserver,filename,filePath,fileSize):用于新开线程,接收指定文件
  • stopRev:用于停止接收最近接收的文件,内部其实是接收完后删除了指定文件

Util

用于规定一些公共变量,以及工具方法
函数介绍:

  • 各const String变量:用于分类msg的种类
  • checkIP(IP,prot):用于检测IP、port是否合法
  • newThread:用于开启新线程
  • randomPort:用于产生随机端口供新线程的UDP连接使用

至此呢,应该说需求分析和设计就算是完成了,编码工作也同步完成,下面说下不足,也就是编码没考虑周全的地方

待改进

  • 界面的问题:C#竟然没有官方的IP地址栏的控件,这导致界面上IP地址和端口的Input极丑还不好用,需要改进
  • 输入数据的检测:如果服务端填写的不是本机IP会怎样?肯定不行是吧,但是我没有做这方面的检测
  • 多线程管理:现在的程序,是开了线程就不管了,等着线程里的任务跑完或者超时杀死线程。其实在Util类里我是放过一个链表来收集线程的,并且有退出杀死链表中所有线程的函数,但细粒度的管理并不知道怎么做
  • 功能划分,或者模块划分:其实写起来还是有些东西不知道划分在哪儿,比如MsgCtrl,是客户端服务端使用同一个类,还是特化出两个不同类,代码结构还要改进;此外,MsgCtrl应该单拿出来的,我将其方法写在了界面代码里,增加了耦合。
  • 随机端口:随机端口部分,可以用,但是没有检测端口被抢占了的问题,应该将使用中的端口放入一个Set,每次随机的端口需要先判断是否已使用

好,上面的内容一看完,这个程序除了能跑通就没优点了。
其实这个程序已经重构过一次了,先前那一版,因为脑袋里没有全局的设计,导致有几个部分功能划分重叠,现在代码结构更清爽点。

源码地址

最后,等作业检查完放github地址:github:基于UDP的文件传输软件 (C#)

  • 10
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值