分类

课内:
不限
类型:
不限 毕业设计 课程设计 小学期 大作业
汇编语言 C语言 C++ JAVA C# JSP PYTHON PHP
数据结构与算法 操作系统 编译原理 数据库 计算机网络 软件工程 VC++程序设计
游戏 PC程序 APP 网站 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019

资源列表

  • 基于Node.js中间层的微信图书借阅平台网站的设计与实现

    1 引言步入信息时代以来,互联网给人们的生活带来了翻天覆地的变化,互联网也不再简单地仅仅通过提供便利快捷的资讯服务来丰富我们的生活。互联网的出现打破了许多传统行业垄断的格局,互联网以其接入面广、信息即使、人人可参与等等性质,迅速融入到了人们的生活中,并且已经成为了整个社会不可缺少的一部分。大约5年以前的互联网是属于PC的时代,那时人们的网络生活包括逛贴吧、看资讯、写博客、下载多媒体文件以及在线网购等等。互联网是一个时刻都在发展的行业,其发展速度之快是大部分传统领域望尘莫及的,一方面是因为互联网虽然已有近30年的念头,但相较于传统行业,这个年份几乎还是跟刚出生的婴儿差不多。因此互联网有极为广阔的发展空间,另一方面由于互联网本身就技术性质而言,属于一门科学领域,大量国内外的高等技术人才都在不断完善互联网领域的科学根基。从小到微机硬件性能一代又一代地提升,再到大型路由交换网络环境的接入覆盖范围越来越广、越来越快,再到现如今机器学习、人工智能、分布式计算、分布式存储的铺开,互联网领域逐渐形成了一个包含计算机科学、软件工程等等领域的超大集合。以前人无法做到的事情已经逐渐慢慢在通过机器来实现。对于互联网领域而言,只有针对用户有意义的应用才是有价值的。随着互联网的发展和移动设备的普及,目前移动端市场正在以突飞猛进的速度渗透到用户群当中去。人们通过随手可得的智能手机、智能平板,甚至电子阅读器等等诸多设备都可以便捷的通过移动4G网络随时接入互联网。移动4G和公共Wifi网络的速度提升和逐步普及为移动互联网深入人们的生活打下了良好的开端。越来越多的人开始使用移动端设备接入互联网丰富日常生活。从在线购物、在线订餐,订房、订票,到日常信息的获取和交流。
    移动互联网是传统互联网的一个分支,但抛开其在移动端运行这一点而言,移动互联网跟传统互联网没有任何区别。然而仅仅是在移动端运行这一点而言,就足以体现出移动互联网与传统互联网的巨大区别。首先考虑用户的使用场景,一般而言,传统互联网的接入方式一般是PC浏览器,用户端座位上,面对着电脑屏幕获取互联网资讯。而移动互联网的用户没有这么多时间,他们可能是上班的路上拿出了手机,可能是吃饭时想发一张自拍,最常见的是与好友们的即使通讯,分享自己某一刻的心情等等。这些事情的共同点是操作时间短,使用地点自由。移动互联网因为其便捷的接入性完全满座以上需求。用户在使用智能设备访问移动互联网是没有端坐在桌面上通过PC浏览器访问桌面网页时那么有耐心,移动互联网让用户们得以随时随地享受快速的服务。
    然而重新提到移动互联网和传统互联网的共同点时,就又要回归到技术层面上,那就是虽然互联网的接入模式在变,但是基于的技术架构仍然没有任何变化,当用户在移动设备上访问互联网时,和在PC上通过浏览器访问后端服务没有任何差别。因此此前在传统互联网领域所没有克服的技术问题也就顺势带到了移动互联网开发领域。这十分让人苦恼,在之前,由于传统互联网的接入方式通常是宽带接入,网络稳定性和速度有保证,于是开发者们可以牺牲一些新能和速度以换取用户的良好体验。然而当面临移动互联网时,开发者们要面临移动端网络环境不稳定、速度有延迟等无法避免的事情。又由于移动端通常不具备PC端良好的处理性能和存储空间的优势,因此移动端开发时需要对性能有更高的要求。除此之外,还有一个难以调和的矛盾,就是移动端涌入我们的生活了,对于用户而言是好事,他们可以通过更多种途径获取更多种便捷的服务。但是服务和业务规模的扩增对开发者而言却是让人头疼的难事,因为传统互联网一直没能很好地解决前后端耦合的问题。前端由于接入设备种类的增加以及业务规模地愈发复杂化,导致我们强烈需要把前端从后端中解耦出来,让前后端不近从运行平台上解开,更重要的是从代码设计和架构上实现完全的解耦。
    在这些基础上,前人为了实现前后端解耦总结了许多实践方法,但每解决一个问题往往会引入新的问题。本课题就是试图引入一种新的模式,从而从根本上实现前后端的彻底解耦。通过微信公众号开发实现一个图书借阅平台的真实例子,从而验证所提出的新模式的可行性与架构方法。
    1.1 项目研究背景Web软件研发这一行业,从互联网诞生的那一刻开始诞生,开发模式也在不断演进。从最初既做设计图又要写代码,不分前后端端一人通吃的蛮荒时代,到现在逐渐分工明确各司其职的现代化开发时代,Web行业正在以历史上最快的速度向前发展。在这个历史进程中,Web研发模式经历了如下阶段。
    最初期可称为Web初期,那时不区分前后端工程师,对于大部分公司而言,所懂得技术的人才并不多,然而当初的业务成都也没有现在这么复杂。通常所有工作由一个小团队即可完成,HTML内容直接由JSP或者PHP等后端语言直接输出,交给浏览器来呈现,所有关于页面的展示逻辑都由浏览器来负责。浏览器端也很少参与业务逻辑,内容完全由服务器端程序来决定。这种模式的方便之处就是业务逻辑十分简单,业务核心依赖服务器端,代码结构简单清晰,利于调试和编写。然而一旦随着业务规模的增长,过多参杂了View层逻辑的服务器端代码就会不断增长,不单影响性能、效率,还严重制约程序开发的进度。服务器端的复杂度越来愈大,为了解决问题,单纯靠填补服务器端开发人员的数量也难以应对庞大的服务器端代码。
    这期间遇到的一些典型问题令许多人深有感触,随着服务逐渐增长,API调用关系变复杂,服务器端程序部署成为问题。后端人员对代码做出修改后上线需要进行上线部署,而前端人员对页面的细微调整也需要与后端人员进行沟通,提交代码给后端进行处理,完成本地开发,代码合并,后端部署等诸多环节。这导致前端的任务严重依赖于后端,没有后端的前端程序完全无法独立运行,制约着前后端之间开发进度的衔接。
    为了解决这种混乱不堪的开发状况,诞生了一种耦合式的前后端解决方案:即前端人员书写完页面代码之后,交给后端人员处理与数据库的对接,让前端人员给后端人员打下手。并且严格按照这种开发流程来执行。虽然这种方式解开了前后端人员之间的工作任务,但是并没有解开前后端之间耦合的代码。
    到了Web2.0时期,浏览器所呈现的内容愈发丰富,各种Web应用层出不穷,开始强调客户端丰富的用户交互。此时再把前后端耦合在一起显然不合适了。所以为了降低复杂度,开始在Web后端领域实践MVC开发模式。这一阶段涌现出了一些非常优秀的后端框架,比如RubyonRails、JavaSpring、Django等等。后端MVC强调模型、视图和控制器职责的分离,而一直以来前端的工作都是与View层紧密相连的,所以后端可以完全专注于Model和Controller的开发工作,而前端则专注于View层页面的开发。当前端编写完页面模板之后,交给后端去渲染Model层的数据。虽然前后端各自的关注点分离了,但是依然没有解决代码耦合的问题。对于后端而言,Controller层和Model层纠缠不清,Model层又和View层纠缠不清。虽然后端MVC是一种非常优秀的开发模式,但是前后端耦合这些根源问题依然没有解决。
    随着Google推动Ajax技术的发展,现在几乎所有网站都有Ajax的身影,异步的JavaScript和XML技术给Web开发模式提供了全新的思路。服务器端单纯以服务的模式提供数据,View层完全独立于后端的服务,前端所需的数据都通过Ajax异步请求向服务器端获取,页面内容的渲染工作全部由前端完成。这种模式下,前端与后端实现了完全的分离,交互的关键点就是Ajax接口,但是此时,服务器端的业务的虽然降低了,但是前度的复杂度却增加了。一切总归是平衡的,后端所减轻的负担,现在全部施加到了前端上。这个阶段,是前端工作人员最痛苦的时期,JavaScript走向了之前JSP和PHP走过的路,前端代码陡增,前端尝试引入框架来解决代码逻辑问题,但前端的压力依然很大。
    到了最近一个阶段,也是目前业界正在广泛使用的一个模式,即在前端也部署MVC甚至MVVM这种大型软件的构建模式。这一阶段前后端之间的代码耦合已经降低到最低了,几乎解除了Web开发之间的代码耦合问题。后端专注于业务逻辑的开发,通过RESTful接口输出数据,前端只要准从设计模式的标准,也可以应对复杂的交互逻辑了。但是方便的背后,也带来了一些其他问题。比如全部异步的请求模式给前端编码带来难度,全部通过请求接口来渲染数据对搜索引擎也十分不友好,不方便爬取页面数据。而且对于移动设备而言,尤其是手机,在网络状况不好的情况下,前端页面需要等待网络请求返回到结果才能呈现。对于前端SPA应用,最重要的路由问题难以和后端调谐,长此以往路由都是交给后端处理的,但是现实的情况是前端对路由的依赖更加严重。
    展望未来,业界正在积极寻找一种新的解决方案,一个新的模式。既能解除Web开发面临的耦合问题,又能让前后端关注自己的职责,并且彼此的功能划分地更加细致。让前端可以脱离于后端,让后端可以专注于业务服务。随着Node.js技术的发展,再一次为Web研发模式带来了新的思路。Node.js与前端共享开发语言,可以与前端实现高度的代码重用,也赋予了JavaScript在服务器端执行的能力。这种新的模式就是在传统后端和前端之间,引入一个Node.js中间层来调和矛盾和彼此的缺失。以往所面临的问题,随着Node.js的引入都可以得到解决,所带来的代价便是需要前端工程师对后端有更深入的理解,需要他们设计更多后端的知识,已经引入一个新层之后的通信效率略微下降。更多的制约可能是面临新的技术解决方案时的担忧,缺乏对新技术的实践经验,无法短期内改善大量的历史代码。
    1.2 项目研究内容本课题所要研究的是长久以来Web研发领域前、后端耦合的问题,进而提出一个全新的解决方案,并通过开发一个基于微信的图书借阅系统展示新方案的可行性。在此以前,我们所面临的耦合问题的广义层面含义包含很多中,下面逐一分析这些问题的具体内容。
    从代码的耦合来看,Web研发领域在初始时期由于代码规模较小,并不注重代码分离。导致出现了不区分前后端的混合式编程模式。比如页面数据从数据库获取之后直接写入到页面当中输出给浏览器端展示。衍生出的问题包括代码糅杂和混乱不堪,难以区分服务端代码和客户端代码,不容易做分离,代码的灵活性不强,不利于代码复用和拓展。业界要想实现中到大型规模的软件产品,与团队协作的重要性不可分。需要由不同的团队负责不同的模块代码开发,所以解耦的第一步就是要解开代码层面的耦合,以适应大规模的代码量。
    从业务的耦合来看,以电商网站为例,用户完成一个完整的购物流程包括浏览商品,搜索商品,购物车下单,支付,等待收货,完成评价。既然作为一个整体的流程,那么每个环节之间必然会出现耦合,比如搜索功能依赖展示功能呈现结果,支付功能依赖购物车功能计算总价,评价系统依赖用户信息系统。安装业务的属性来分,展示功能属于浏览器端,搜索和支付属于服务器端,但脱离前、后任何一端,任何功能都无法完成。传统Web研发领域由于前后端耦合的问题,导致前后端的职责并没有很好地区分开来,虽然从业务职责上各个模块是分离的,但是前后端确实耦合的,这就给开发带来了难度,依旧不能解决随着业务规模扩展带来的开发压力。
    从服务的耦合来看,随着目前移动互联网的火热,用户接入互联网的方式不再局限于电脑。使用安卓智能手机、苹果智能手机、各类平板电脑、所占据的用户比例甚至已经超过了传统使用PC浏览器的上网比例。这又给Web研发提出了新的要求,如果统一提供跨平台的服务。传统Web研发由于后端服务与前端服务耦合,不方便做统一地跨平台支持。同样的,随着用户设备多样性的增加,需要提供越来越多的服务需求,开发压力也将陡增。
    本课题通过研究Web领域历史以来的演变过程中所诞生的各种技术的优缺,通过探索新的模式,提出新的方案来解决耦合这个历史问题。以达到让前后端的代码更分离、职责更清晰、服务更统一、分工更合理高效的目的。
    要解决问题需要首先分析问题出现的原因,传统Web开发带来的制约源于后端没有纯粹地负责数据处理和存取功能,而是过度耦合了前端呈现和用户交互。后端应该不区分端,只负责提供统一地服务,然后各个端之间负责利用后端提供的服务接口完成与用户的交互和业务逻辑。这样就找到了解决问题的方法。
    所尝试探索的途径是通过引入Node.js中间层技术,在Web服务器上同时部署服务器程序和Node.js中间层,当用户请求前端页面时,前端首先访问Node.js中间层进行页面关键数据的渲染,当后续需要数据时即可跳过Node.js中间层直接请求后端。当用户进行页面跳转时,使用Node.js中间层和前端同步页面路由,对于用户的状态管理等与业务逻辑不是强相关的服务也由Node.js来提供。依然会依赖Ajax技术来处理大量的数据依赖,除了引入Node.js之外,其他的前后端技术依然保持不变,变的是它们之间的组合关系。本课题所要实现的,也正是这样一种合理利用Node.js中间层解决前后端耦合的研发模式。
    1.3 项目研究意义就软件工程领域而言,近些年发展最快的莫过于Web开发。绝大部分互联网公司采用Node.js、PHP、Python、JavaWeb、Ruby、Go、HTML/CSS/JavaScript等等技术为用户提供丰富便捷的互联网服务。这些服务涵盖我们日常生活的方方面面,从衣食住行到社交通讯,到处都需要Web开发提供服务程序。对于一个完整的应用声明周期而言,从开发到测试再到上线再到版本迭代,这期间会不停地伴随着功能增加和业务规模的扩增,因此Web开发对程序设计的要求需要具备灵活的拓展性。程序等于数据结构加算法,这是一条基本常理,无论多么复杂的程序,归根结底就是在做这样两件东西。相比之下,软件架构的设计也离不开另一个基本的常理,那就是高内聚低耦合。无论是命令式编程的独立函数还是面向对象式编程的设计模式,无论是MVC还是MVVM架构,在设计软件架构的时候,都要时刻考虑这两个基本原则。因为只有软件的模块、类、功能、服务彼此之间尽量独立,尽量不要依赖别的部分,自己内部的状态尽量保持封闭这些都能做到时,才能保证模块可以随意增删以及单独测试等等。研究耦合这个问题,就像研究数据结构与算法对于程序设计的意义一样,是Web应用程序架构的基础。对于Web开发这种功能快速变化,版本快速迭代的模式而言,如何实现高度的解耦是功能业务能否快速拓展以及保证前后端开发人员各司其职高效配合的重要基石。
    研究Web研发新模式的意义,不是一味地标新立异,而是真正着眼于当下Web开发领域所面临的开发效率低效这个切实问题。契合当今前、后端独立发展,开发人员职责合理分配,让团队之间的协作更加高效,并让产品的开发速度,软件服务的可拓展性、灵活性、独立性提升的需求,解放生产力。
    2 技术选型与开发环境本微信图书借阅平台是一个完整的B/S架构的实现,在服务器端通过Nginx监听用户请求并分发请求给StrongLoop进程管理器,StrongLoop创建多个slave进程维护Node.js实例来处理用户请求,Node.js作为中间层可以构建在任何其他语言的后端之上,通过基于RESTful规则设计的接口可以建立Node.js中间层与其他后端语言的数据通信。数据库采用MySQL、通过Jade模板引擎实现服务端HTML预渲染、在客户端通过微信内置的Blink内核的WebView作为与用户交互的入口、UI效果展示采用Bootstrapcss界面库快速制作移动端友好的响应式界面。
    2.1 技术选型2.1.1 Node.js介绍Node.js是一个基于事件驱动的JavaScript异步运行环境,从一开始设计之初就是针对互联网这种节点灵活、易于扩展、高性能的场景而生的。Node.js处理并发的性能非常好,下面有个简单的实例,展示Node.js如何用简短的代码实现一个HTTP服务器,并且可以承受极高的并发请求量。当有HTTP请求到达服务器端时,Node.js迅速处理调度事件循环处理请求,当没有HTTP请求到达服务器是,Node.js就自动挂起进程,节约CPU和内存资源。
    示例:用Node.js创建一个HTTP服务器
    consthttp=require('http');consthostname='127.0.0.1';constport=3000;constserver=http.createServer((req,res)=>{res.statusCode=200;res.setHeader('Content-Type','text/plain');res.end('HelloWorld\n');});server.listen(port,hostname,()=>{console.log(`Serverrunningathttp://${hostname}:${port}/`);});
    Node.js在设计上受到了RubyEventMachine和PythonTwisted的影响,但对于事件模型的处理上Node.js更进一步,Node.js不依赖第三方库,在运行时自动创建一个事件循环处理任务。一般在别的时间循环系统当中,需要在代码起始执行的地方显式地通过阻塞方法开启一个事件循环,然而这些在Node.js当中都是不需要的,Node.js在执行代码文件的伊始就自动开启了事件循环。这种模式跟浏览器端JavaScript脚本的执行非常类似,异步操作都是浏览器在幕后完成的。
    HTTP是Node.js中非常重要的一部分,因为Node.js的初衷就是针对互联网上大规模可灵活拓展的HTTP服务而设计的。因此Node.js作为提供HTTP服务的基础服务层十分适合。
    2.1.2 异步编程介绍如上所述,可以看出Node.js的异步处理模型与常见的基于系统线程实现并发处理的模型有很大反差。基于线程的网络模型唯一的优势就在于用最低的资源开销实现并发处理,但是多线程编程非常不利于控制和管理,相比之下,单线程的编程模式就简单直接多了。虽然Node.js引入的是异步编程,但目前已经有良好的异步编程解决方案,比如Async/await+Promise的解决方案使得异步编程可以像同步编程一般方便直观。反观多线程编程,多个线程之间的任务如果是非相关的,那么影响倒不是很大,然而一旦多个线程依赖同一个数据源,就会导致资源争夺而无法合理分配的情况,需要引入锁机制来防止死锁。而且多线程编程还有一个致命问题在于一旦其中一个线程出现异常,但是这个异常没有被正确处理(这十分常见),就会导致主线程崩溃的情况,最终导致所有线程崩溃。所以为了提升多线程程序的鲁棒性,必须依赖经验丰富的程序员通过巧妙的机制来合理处理资源争夺、数据不同步、异常崩溃等问题。仅仅是为了节省资源而引入多线程编程,在大多数情况下并没有这个必要。
    示例:Node.jsAsync/await+Promise解决方案
    function_fetchbooks(){ returnnewpromise((resolve,reject)=>{ //ajaxoperation if(ajax.success){ resolve(ajax.response); }else{ reject(ajax.error); } });}asyncfunctionfetchbooks(){ constbooks=await_fetchbooks(); console.log(books); returnbooks;}
    而Node.js的开发者几乎不需要考虑进程死锁的问题,因为单线程的执行环境中并没有锁这个概念。而且Node.js中几乎所有函数都是异步I/O函数,因此Node.js的主进程永远不会阻塞,又因为不会发生阻塞,所以扩展系统业务的规模也十分方便。虽然Node.js被设计成不依赖线程编程,但并不意味着Node.js不能发挥CPU多核心的性能。通过child_process.fork()API可以灵活地复制出子进程,而且它们彼此之间的通讯也经过了特殊设计,非常方便。基于内置的cluster模块,可以在本地的多进程之间共享sockets,充分发挥多核心的优势。
    因为随着计算机的普及和计算机硬件行业的发展,服务器上所采用的硬件在处理能力上正在发生翻天覆地的变化,绝大多数公司在考虑后端技术选型和服务器硬件配置时,都是首先考虑技术的复杂度,服务器能承受的并发压力,未来产品迭代的速度等等。除非是顶尖的互联网软件行业对性能要求极高的公司,对于大部分公司而言,Node.js所提供的这种简洁轻快、高效方便的优势都会对快速产品迭代开发带来极大的优势。
    2.1.3 阻塞和非阻塞介绍阻塞是指当执行JavaScript代码时,Node.js进程必须等待非JavaScript操作结束,之所以发生这种事情是因为事件循环在阻塞操作发生时无法继续执行。比如说读取一个大文件,当Node.js执行读取文件的代码时,必须等待操作系统将整个文件读取完毕,否则Node.js的事件循环将无法继续工作。因此在Node.js中面临CPU密集型任务时,JavaScript通常会表现出很差的性能,但是在处理I/O密集型任务时,则表现出优异的性能。Node.js标准库中基于libuv的同步方法一般都调用阻塞操作,而异步方则调用非阻塞操作并且接收回调函数用来处理异步的结果。通常Node.js会提供两个版本的函数,默认是异步方法,方法名后面加上sync的是同步方法。
    示例:Node.js阻塞方法
    constfs=require('fs');constdata=fs.readFileSync('/file.md');//blockshere
    示例:Node.js非阻塞方法
    constfs=require('fs');fs.readFile('/file.md',(err,data)=>{ if(err)throwerr;});
    在上面的阻塞方法示例中,Node.js会等待文件全部读取完毕才能继续往下执行,这将导致Node.js进程阻塞无法继续执行,等待非JavaScript操作也就是操作系统读取完毕后,方可继续执行。而在非阻塞方法的示例中,当执行readFile之后程序会立即结束,继续往下执行,但是读取文件的任务并没有终止而是放在了事件循环中去做,当文件读取完毕后回调函数会被出发。
    2.1.4 MySQL数据库介绍MySQL从最初开始就被设计为一款支持SQL查询语言的关系型数据库管理系统,最初MySQL以开源的形式向外界发放。它的兼容性非常好,支持Linux系统、Windows系统、Unix系统。由于其广泛的支持度,MySQL被用于各种类型的应用程序的开发之中,当然MySQL的最大用途还是作为在线Web应用的数据库,也就是我们所常用的网络应用数据库服务器。MySQL不光以其强大的兼容性风靡各大平台,其中广为熟知的LAMP一站式打包服务更是为MySQL在Linux上的统治地位奠定了强有力的基础。配合Linux内核操作系统、Apache应用服务器、MySQL数据库服务器、PHP(Python或Perl)网络开发语言,无数中小企业通过这些技术向用户提供各种丰富的网络服务。
    虽然MySQL最初以开源的方式向外界授权,后来被瑞典MySQLAB公司收购,后者2008年又被甲骨文公司收购,甲骨文也因此获得了MySQL的版权,收购MySQL之后的甲骨文公司宣称MySQL面向普通用户将永远免费,只有企业用户需要获得授权才能使用。也正因此导致MySQL发展出继续开源的MariaDB分支,MariaDB保持跟MySQL完全的兼容性,但仍会以开源的性质继续发布。
    MySQL的特点:

    MySQL是一款完善的关系型数据库管理系统。MySQL基于Client/Server架构,只需要一个Server端,就可以支持大量的Client端接入。对于集群式应用开发,只需要部署一台MySQLServer服务器即可。相比于Oracle、DB2、SQLServer等商业服务器而言,MySQL对个人用户完全免费,也正因为如此吸引了大量的用户。MySQL性能优异,安全性、跨平台兼容性都非常优异。MySQL对SQL的支持度很好,并且支持SQL的最新标准。MySQL功能完善,支持外键约束、索引、子查询、视图、过程、触发器、Unicode字符、全文搜索等实用功能。MySQL支持事务回滚,数据操作失败时根据数据库读写日志恢复到出错前的状态。MySQL社区用户活跃,社区对于一款开源软件的影响将起到主导作用。
    MySQL操作:
    MySQL可以在Windows、Linux和Unix下很好地工作,推荐在Linux命令行中安装MySQL,过程快捷便利。以Ubuntu操作系统为例,执行如下命令即可安装MySQL服务器端和客户端:
    $sudo apt-get install mysql
    执行如下命令即可进入MySQL数据库进行数据库操作:
    $mysql -u root -pmysql> CREATETABLEmytable;
    除了命令行方式以外,MySQL支持多种语言的API接入,MySQL服务端默认监听服务器的3306端口,等待来自外界的TCP连接,常见的Web开发语言通过各自版本的MySQL连接驱动即可成功连接MySQL数据库执行数据库操作。
    2.1.5 Nginx服务器介绍Nginx是一个高性能的Web和反向代理服务器,它具有有很多非常优越的特性:

    作为Web服务器:相比Apache,Nginx使用更少的资源,支持更多的并发连接,体现更高的效率,这点使Nginx尤其受到虚拟主机提供商的欢迎。能够支持高达50,000个并发连接数的响应,Nginx支持epoll、kqueue等多种开发模型.作为负载均衡服务器:Nginx既可以在内部直接支持Rails和PHP,也可以支持作为HTTP代理服务器对外进行服务。Nginx用C编写,不论是系统资源开销还是CPU使用效率都比Apache等其他服务器要好很多。作为邮件代理服务器:Nginx同时也是一个非常优秀的邮件代理服务器。
    Nginx安装非常的简单,配置文件非常简洁(还能够支持perl语法),Bugs非常少的服务器:Nginx启动特别容易,并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。还能够在不间断服务的情况下进行软件版本的升级。
    2.1.6 StrongLoop进程管理器介绍StongLoop是Node.js集群工作环境下最完善的运行时管理控制和监控程序,StrongLoop在吸取了Node.js社区一些现有的进程管理器(forever、pm2)的基础上又扩增了独有的功能,StrongLoop支持的功能如下:

    图形化操作界面;集成nginx负载均衡;支持远程管理;自动化多站点部署;集成运行监控和性能评估;多机器、多进程分布式实时控制。
    2.1.7 微信公众号平台介绍微信公众号是腾讯公司所推出的即时聊天工具“微信”中,面向非普通用户即具备推广能力的个人用户和企业用户所推出的推广平台。通过开放授权和实名审查,吸引有开发和推广能力的人员使用。普通用户可以关注微信公号,获取资讯和服务。微信公众号的本质是提供一个第三方平台打通内容推广者与大众用户的桥梁。
    申请个人公众订阅号和商业公众企业号的用户,首先需要首先到微信官网注册账号,个人公众订阅号用户需要提供证件申请实名认证,商业公众企业号用户需要提供商业资质证明。等待申请结果成功后,用户们便可以通过微信搜索功能搜索公众号名字,查看详情后关注该微信公众号。开发者在微信后台配置微信与自己的服务器之间的连接,从而打通从用户到微信服务器再到个人服务器之间的消息传递,并将处理后的结果回传给微信服务器,最终转发到用户的微信客户端上。开发者有两种方式与用户进行交互,一是通过聊天信息和推送小时与用户进行信息交互,二是通过微信公号底部的自定义菜单栏引导用户触发指定的功能,与用户完成交互。微信提供OAuth2.0认证机制,帮助开发者在经过用户的授权之后获取用户的一些非隐私性个人信息,比如用户id、头像、昵称、地域等等信息。帮助开发者给用户提供更加精准、有效的服务。
    3 需求分析自从移动互联网兴起以来,开发人员面临了更大的挑战,要求也更加严格。传统的仅基于PC浏览器平台的Web应用模式无法满足现如今各大移动终端平台的需求。面临更多的平台,需要提供针对不同平台的服务来满足用户需求,Web应用的定制性越来越强,代码规模越来越大。为了满足新的业务需求,提升开发效率,节约开发成本。需要将前、后端开发的人员独立开来,让彼此各司其职。在这个需求下,我们需要达到如下几点:对于后端而言,仅负责业务逻辑和数据接口的开发,一套服务和接口的独立性尽量高,从而可以被多套业务重用。而对于前端而言,负责保持用户的状态,为用户展示界面效果,与用户进行逻辑交互,一套界面组件的独立性尽量高,从而可以被多套业务重用。
    3.1 技术需求其实Web开发解耦这个话题的讨论从若干年前就早已开始了,只是随着Web研发一个阶段一个阶段的推进,业界虽然探索出了一些比较实用的方案,但是没有从根本上解决问题。另一方面因为Web领域的受众用户基础量太大,一旦采用新模式重构旧代码,会导致伤筋动骨,之前大量的业务和逻辑需要重写。所有很多时候,历史问题比技术问题更难解决。
    当我们讨论前、后端解耦,我们在讨论什么?对于前、后端解耦,大家对于其认知不尽相同。对于大部分初步接触解耦的人而言,比较认同的解耦模型是SPA(Single Page Application)模式,这种模式的技术特点是,后端处理所有的业务逻辑和数据,但不处理任何与视图界面和用户交互方面的问题。后端仅通过RESTFul、SOAP或普通的HTTP请求对前端发出的请求给予响应。到了用户这一端都是前端在进行处理,除了页面上硬编码的HTML结构,所有的动态数据都通过接口请求后端获得,然后动态渲染视图展示给用户,所有与用户交互的操作都有前端完成。这种架构是一种非常彻底的解耦模型,但是它太过于彻底了,以至于前后端之间充斥着大量的接口请求操作,以完成视图界面的创建。对于前端而言,又由于请求都是异步操作,会带来编码上的复杂性。
    除了SPA模式之外,目前业界没有其他更成熟的前、后端解耦方案。在PC浏览器时代,由于物理带宽得到稳定保证,因此浏览器发出的HTTP请求通常可以得到快速响应,而且上网花销比较低。然后随着移动互联网的普及,流量费用是一笔不小的开销。而且移动网络的稳定性并没有传统物理宽带那样稳定,SPA架构的Web应用被这两个问题频繁困扰,不通过请求获取数据,页面上将会呈现大片空白区域,网络速度不稳定,加载的数据不完整,频发发送请求,对用户的流量也是一个比较大的负担。再之,SPA的可用范围比较窄,对于以内容为主的Web应用,比如新闻类和分享类的网站,需要被搜索所收录,SEO对于此类网站而言尤为重要。而SPA应用的网页内容都是通过JavaScript异步请求后端接口获得数据后动态插入到页面当中的,搜索引擎无法执行JavaScript,因此无法收录网站的内容。这将会对企业造成极大损失,因此SPA只能在有限的场景下发挥作用。以上便是我们所熟知的前后端解耦。
    为什么要探索前、后端解耦的新模式?对于目前现有的几种Web研发模式而言,它们都有各自适合的开发场景,没有谁能够完全取代别彼此。这就造成了开发人员为了兼容多套不同的平台业务,需要采用多种方案来实现,学习成本高,维护的代价也大,需要大量的开发人员来维护多套解决方案代码。基于Node中间层的解耦方案,可以实现前、后端解耦架构的大一统,可拓展性、灵活性都非常强。不管是垂直业务深度拓展,还是水平平台种类覆盖方面,Node中间层都能以一个灵巧的胶水的角色拟合它们的差异,实现极高的重用和复用。
    对于现今Web研发的开发效率而言,前、后段人员的分工,团队之间的配合,代码的整合与部署,新产品的迭代,出错后的容灾回滚等等都受到现有开发模式的限制。因为前、后端的工作内容没有分离开,为了解决分分合合的代码问题,将会浪费大量的时间处理非技术性问题。
    另一方面,前端人员所能做的事情一直受到后端的制约,前端所开发出来的应用无法再脱离后端的情况下运行,难以调试、部署。过渡依赖于后端环境提供前端运行环境的支撑。又因为前端需要耦合到后端当中,也就意味着后端参与了很多本应该前端做的事情,为了给后端做出让步,前端不能充分发挥自身的能力,需要把功能做得尽量简单,转而通过后端来实现复杂的逻辑。后端又因为需要处理与用户交互先关的内容而叫苦连天。
    还有十分重要的一点,就Web应用的性能而言,服务器在承受着巨大并发压力的情况下,如果参与了过多关于视图界面相关的工作,会影响CPU的处理能力和内存资源的占用。而将这些工作搬到前端之后,后端的职责更加清晰,仅需要执行纯粹的数据处理的请求处理就可以了。
    以上这些问题是Web研发领域面临的迫切需求,只有进行良好的设计解除前、后端之间的耦合关系,充分发乎前后端开发人员的能力,才能更加高效地推动行业的发展。
    3.2 功能需求微信图书借阅平台提供添加图书、借阅图书、归还图书、浏览图书等功能。用户首次向书架贡献自己的图书时,使用添加图书功能;当用户需要查看当前书架图书信息时,使用浏览图书功能;当用户需要从书架上借阅图书时,使用图书借阅功能;当用户阅读完毕图书时,使用归还图书功能。用户用例图如图3.1所示。

    3.3 系统操作流程微信图书借阅平台的使用流程有三个步骤。第一步,用户关注微信图书借阅平台公众号。第二步,用户使用微信图书借阅平台的各个功能,包括添加图书、浏览图书、借阅图书、归还图书。第三步,扫描图书条形码,完成图书信息读取,并确认图书的添加、借阅、归还等功能。系统流程图如图3.2所示。
    3.3.1 关注微信图书借阅平台目前微信平台使用的广泛度已经家喻户晓,作为即时通讯软件,它还提供了开发平台共开发者使用。因此微信图书借阅平台基于微信,不需要下载任何软件,只要用户关注“微信图书借阅平台”公众号,就可以使用平台的图书相关功能。“微信图书借阅平台”作为一个公众号,可供任何拥有微信号的用户搜索并关注,点击微信界面右上角“+”,在搜索框输入“微信图书借阅平台”,点击进入平台主页,点击关注按钮,便完成了关注过程。
    3.3.2 功能菜单微信图书借阅平台公众号的主界面是由三个一级菜单组成,包括借阅图书、归还图书、管理图书。其中管理图书菜单下还有两个二级菜单,包括添加图书、浏览图书。用户第一次使用本平台可能是要贡献图书的贡献者用户或者是借阅图书的借阅者用户,对于贡献者,点击管理图书菜单下的添加图书,并将图书放入书架;对于需要借阅书籍的用户,点击借阅图书菜单。用户需要借阅书籍时,可以点击浏览图书,查看当前书架上有哪些图书,点击具体的某本图书,查看图书相关信息。
    3.3.3 扫码微信图书借阅平台并没有图书馆手持款图书条形码扫描仪,但是,平台使用微信的“扫一扫”功能,完成对图书条形码的扫描,从而获取实体图书的图书信息。添加图书、借阅图书、归还图书的功能都需要获取图书信息,通过点击功能菜单会自动转到扫一扫界面。
    3.3.4 确认操作微信图书借阅平台的操作流程是关注“微信图书借阅平台”公众号、选择功能菜单、进入扫码、完成操作确认。系统操作流程图如图3.2所示。

    4.系统设计概要设计是建立在需求分析的基础上的,概要设计的主要任务是,通过仔细分析软件规格说明,适当的对软件功能进行分解,从而把软件划分为模块,并设计出能完成预订功能的模块结构。
    4.1概要设计概要设计是软件设计具体实施阶段的第一个环节,通过给出核心功能点和设计要素的概括性描述,指导设计开发的方向。在这个阶段的主要任务一方面是概括地描述出整个软件的整体设计。另一方面是将工程概括的拆分出主要的核心模块描述各个模块大致的实现方案。
    4.1.1 整体设计1.微信公众号自定义菜单
    微信公众号是用户使用该图书分享平台的入口,公众号底部的自定义菜单是引导用户进行功能操作的入口,需要设置简洁直观的入口让用户快速了解菜单对应的功能。
    2.前端交互
    图书分享和借阅应该是一个非常便捷的操作,不应当涉及过多的交互步骤,使得各个功能一触即达。
    3.Node.js中间层
    中间层的职责是前端与后端的桥梁,负责从后端获取数据并渲染出页面或进一步处理数据后传递给前端。
    4.1.2 模块设计1.借阅图书模块
    图书借阅模块实现了图书的借阅操作,读者从书架上拿到图书,通过扫描图书背面的条形码确认借阅完成借书操作,取走图书。
    2.归还图书模块
    图书归还模块实现了图书的归还操作,当读者阅读完毕后,扫描图书条形码后确认图书归还,然后将图书归回原位。
    3.添加图书模块
    图书添加模块实现了图书的入库操作,读者拿出自己的图书,扫描图书条形码后确认图书归还,然后将图书放入书架。
    4.浏览图书模块
    浏览图书模块实现了浏览图书信息的功能,用户可以远程查看书架上的书籍信息,包括书籍名等基本信息、书籍数量、书籍可借阅数量。
    4.2详细设计用户进入微信后,关注公号进入公号界面,通过公众号主页底部的自定义菜单进入相关操作界面。点击“借阅图书”自动弹出二维码让用户扫描书籍自动借阅。点击“归还图书”自动弹出二维码让用户扫面书籍自动归还,点击“添加图书”自动弹出二维码让用户扫描书籍完成添加,点击“浏览图书”跳转到设定的网页浏览当前书库的所有书目。
    4.2.1 微信公众号设置申请个人公众订阅号和商业公众企业号的用户,首先需要首先到微信官网注册账号,个人公众订阅号用户需要提供证件申请实名认证,商业公众企业号用户需要提供商业资质证明。等待申请结果成功后,用户们便可以通过微信搜索功能搜索公众号名字,查看详情后关注该微信公众号。
    在微信后台配置微信与自己的服务器之间的连接,从而打通从用户到微信服务器再到个人服务器之间的消息传递,并将处理后的结果回传给微信服务器,最终转发到用户的微信客户端上。
    微信图书借阅平台通过企业公众号底部的自定义菜单栏,分别引导用户至不同的功能,与用户完成交互。菜单栏分为一级菜单和二级菜单,本平台使用借阅图书、归还图书、管理图书三个一级菜单,管理图书下有添加图书、浏览图书两个二级菜单。
    4.2.2 获取图书信息实现一个图书数据库的工作量非常庞大,设计到市面上所有已有图书的信息采集入库操作。为了便利地获取图书信息,采用豆瓣开放的公共图书API接口实现图书信息查询功能。用户首先打开微信进入公众号界面,通过公众号底部菜单中的“添加图书”菜单,点击后自动转至图书条形码扫描界面,用户扫描图书背面的条形码后,微信将识别出条形码中包含的ISBN号并回传给前端程序。前端程序根据将ISBN回传给Node.js中间层服务器,Node.js向豆瓣开放API服务器发送RESTful请求,附带着图书的ISBN信息,取得图书信息(图书名、图书封面图片链接、图书内容摘要、作者、出版社等)。
    4.2.3 存储图书信息所有图书的信息都通过自豆瓣的开放API接口服务获得,每当采集到一本图书的信息,便会执行一次图书信息的存储任务,将图书信息存入服务器本地数据库中。这样做的原因是,一方面便于再次获取图书信息时,可以快速查询图书结果。另一方面将初次获取后的信息按照关系存入本地数据库有利于信息的统计和变更。
    在取得图书信息后,用户点击确认按钮,图书的书名、ISBN号、作者、出版社、封面、摘要等信息会存储到数据库中,并附加与借阅相关的图书信息,比如图书库存量,借阅次数,当前借阅者的信息等等。
    4.2.4 展示图书信息Node.js中间层在取得图书信息后,根据预先定义的Jade模板渲染出图书界面的HTML内容。微信用户内置的Webview控件在收到HTML内容后,渲染内容,加载图书封面等静态资源,经过CSS样式表处理后渲染出图书展示界面。图书信息的展示工作完全由Node.js中间层负责,当用户在图书页面之间跳转时,均直接请求Node.js中间层服务器,交由Node.js选择指定的Jade模板渲染内容。
    系统功能模块图

    5 数据库设计微信图书借阅系统的正常使用需要有正确的数据库读取操作,数据库设计的优劣与否关系到系统能否运行流畅。微信图书借阅系统的数据库设计主要分为概念结构设计和物理结构设计,概念结构设计主要分析实体关系,以E-R图形象的展示实体关系。物理结构设计将实体关系转化成数据库中表的关系,以及表结构中各个字段的设置。
    5.1 概念结构设计微信图书借阅系统的实体是各个用户,用户之间的地位平等,每个用户都可以贡献图书、借阅图书、查看图书借阅情况。本系统的数据库E-R图如图5.1所示。

    5.2物理结构设计本系统使用的数据库是MySQL数据库,设计的充分考虑了程序处理的要求、应用环境的需求,斟酌数据库表结构和字段设置,考虑数据库各表中的字段类型和长度,尽可能提前预估到可能出现的问题,避免程序设计过程中带来不必要的麻烦。MySQL数据库中,凡字段为varchar或char类型时,需要指定编码方式为UTF-8,本系统对以上两种类型数据,全部设置为UTF_8编码方式。
    微信图书借阅系统共设计了3张表,分别是图书表(Books)、用户表(Users)、借阅表(Borrowing)。
    1.图书表
    图书表存储图书的相关信息,包括字段:图书id、ISBN号、名称、封面、作者、出版社、摘要、总数、可借阅数、贡献者。图书表结构如表5.1所示。



    字段名
    数据类型
    宽度
    是否可空
    说明




    id
    integer


    图书id,主键,自增


    ISBN
    varchar
    255

    ISBN号


    title
    varchar
    255

    名称


    cover
    varchar
    255

    封面


    author
    varchar
    255

    作者


    press
    varchar
    255

    出版社


    excerpt
    text


    摘要


    total_count
    integer


    总数


    avail_count
    integer


    可借阅数


    owner_id
    integer


    贡献者



    2.用户表
    用户表存储系统用户的相关信息,包括字段:用户id、用户标识、用户名。用户表结构如表5.2所示。



    字段名
    数据类型
    宽度
    是否可空
    说明




    id
    integer


    用户id,主键,自增


    uuid
    integer


    用户标识


    name
    integer


    用户名



    3.借阅表
    借阅表存储用户借阅信息,包括字段:借阅id、图书id、用户id、借阅时间。借阅表结构如表5.3所示。



    字段名
    数据类型
    宽度
    是否可空
    说明




    id
    integer


    借阅id,主键,自增


    book_id
    integer


    图书id


    user_id
    integer


    用户id


    borrow_time
    date


    借阅时间



    6 图书借阅平台的实现微信图书借阅平台是一个完整的B/S架构的实现,在服务器端通过Nginx监听用户请求并分发请求给StrongLoop进程管理器,StrongLoop创建多个slave进程维护Node.js实例来处理用户请求,Node.js作为中间层会可以构建在任何其他语言的后端之上,通过基于RESTful规则设计的接口可以建立Node.js中间层与其他后端语言的数据通信。数据库采用MySQL、通过Jade模板引擎实现服务端HTML预渲染、在客户端通过微信内置的Blink内核的WebView作为与用户交互的入口、UI效果展示采用Bootstrap、CSS界面库快速制作移动端友好的响应式界面。
    6.1 配置微信图书借阅平台是一个微信公众号,用户使用之前需要先关注公众号。用户登录微信,点击右上角的添加按钮,点击添加朋友菜单,输入“微信图书借阅平台”,关注公众号。成功关注公众号以后,用户便可以正常使用平台的各项功能。
    6.2 主界面主界面是微信图书借阅平台的功能入口界面,提供借阅图书、归还图书、管理图书等三个一级菜单,其中管理图书分为添加图书、浏览图书两个二级菜单。主界面如图6.1所示。

    6.2添加图书微信图书借阅平台是一个提供用户共享书籍的平台,用户有闲时不看的图书便可以自己的图书添加到公共书架上。在平台上,即点击图书管理下的二级菜单添加图书,便进入“扫一扫”界面。通过扫码图书,平台读取图书ISBN号,获取图书信息,最后跳转到贡献图书确认界面。
    添加图书菜单按钮提供了用户添加图书的功能入口,点击添加图书,便进入扫一扫界面,可收集图书的信息,如图6.2所示。

    获取图书ISBN号后,经过处理,获取图书信息并展示出来,用户可以对图书贡献进行确认,如图6.3所示。

    6.3借阅图书微信图书借阅平台是一个提供用户共享书籍的平台,用户可以通过扫码借阅公共书架上的书籍。在平台上,即点击借阅图书菜单,进入“扫一扫”界面。通过扫码图书,平台获取图书ISBN号,获取图书信息,最后跳转到借阅图书确认界面。
    借阅图书界面显示当前书籍信息(若要凑字,可以多描述一下),图书总量、已借阅、剩余等信息,如果当前书架上有可以借阅的书籍,确认借阅按钮可用,如图6.4所示;否则,确认按钮不可用,如图6.5所示。



    借阅图书(可借)
    借阅图书(不可借)









    6.4归还图书微信图书借阅平台是一个提供用户共享书籍的平台,用户阅读完书籍可以通过扫码图书,归还公共书架上的书籍。在平台上,即点击归还图书菜单,进入“扫一扫”界面。通过扫码图书,平台获取图书ISBN号,获取图书信息,最后跳转到归还图书确认界面。
    归还图书菜单按钮提供了用户归还图书的功能入口,点击归还图书,便进入扫一扫界面,可收集图书的信息。点击确认归还按钮,可以归还图书,如图6.6所示。

    6.5 浏览图书微信图书借阅平台是一个提供用户共享书籍的平台,用户可以添加书籍到公共书架、借阅书籍、归还书籍、浏览书籍。浏览书籍功能菜单直接跳转到书籍列表,展示当前书架上都有哪些书籍,点击书记项进入书籍详情页,查看书籍详细信息。
    在平台上,点击浏览图书菜单,进入图书列表界面,如图6.7所示。通过点击书籍项目,进入详情页。详情页展示书籍详细信息,包括书籍名称、作者、图书ISBN号等书籍信息,还包括书籍当前借阅情况,例如总量、已借阅、剩余,如图6.8所示。



    图书列表
    图书详情









    参考文献[1] NicholasS.Williams.Java Web高级编程.北京:清华大学出版社,2015
    [2] 列旭松,陈文.PHP核心技术与最佳实践.北京:机械工业出版社,2012
    [3]王志刚.PHP5应用实例详解:使用Zend Framework&Smarty构筑真正的MVC模式应用.北京:电子工业出版社,2010
    [4] JezHumble,David Farley.持续交付:发布可靠软件的系统方法.北京:人民邮电出版社,2011
    [5] 王彦平,吴盛峰.网站分析实战.北京:电子工业出版社,2013
    [6] 秦小波.设计模式之禅(第2版).北京:机械工业出版社,2014
    [7] 许勇.Ruby on Rails 程序设计深入剖析与范例应用.北京:清华大学出版社,2013
    [8] CraigWalls.Spring实战(第3版).北京:人民邮电出版社,2013
    [9] Harry J.W.Percival.Python Web开发:测试驱动方法.北京:人民邮电出版社,2015
    [10] MarkSimon.Introduction to Ajax:Client Server Communications on the Web.O’Reilly Media,2016
    [11] 司徒正美.JavaScript 框架设计.北京:人民邮电出版社,2014
    [12] 张欣毅.XML简明教程.北京:清华大学出版社,2009
    [13] the5fire.Backbone.js入门实战:WEB端MVC框架开发单页应用实战.北京:机械工业出版社,2014
    [14] YakovFain,Anton Moiseev.Angular 2 Development with TypeScript.Manning Publications,2016
    [15] LeonardRichardson,Mike Amundsen,Sam Ruby.RESTful Web APIs.O’Reilly Media,2013
    [16] SemmyPurewal.Learning Web AppDevelopment.O’Reilly Media,2014
    [17] EthanBrown.Web Development with Nodeand Express.O’Reilly Media,2014
    1 评论 7 下载 2018-10-02 21:05:13 下载需要14点积分
  • VC++实现的仿QQ和飞秋并支持语音视频白板屏幕共享的即时聊天软件

    第2章 系统分析及开发技术说明2.1 需求分析2.1.1 功能需求分析

    用户端的基本聊天信息发送,这些基本聊天信息包括文本和图片。文本和图片聊天是聊天软件最基础的功能。用户通过输入IP来查找用户,并申请加为好友,在对方同意加为好友后,在线用户列表就会更新用户,把加入的用户添加到用户列表中。这样,两个用户之前就可以实现通信了。在信息传输中,预计利用TCP/IP协议中的UDP协议,这是面向无连接的协议,但发送速度快,用于聊天信息传输用适合。用户端的音视频数据传输,这是本设计的扩展功能。用户可以正常通信后,就可以选择是否进行语音或视频聊天。本功能也将采用UDP协议,UDP协议可能会丢失数据,但对于音视频聊天需要传输大量数据但又允许丢失少量数据的情况下,UDP的快速发送信息的特点就得到很好的体现。用户端文件传输的功能,用户之间可以断点续传文件。在传文件之前,首先创建一信息文件,记录文件传送的一些信息,并根据传输的数据量实时修改。如果没有传完,下次就可以继续打开这个信息文件,接着上次的进度传输。因为文件传输入要求数据不能出错,因此此模块采用TCP协议。用户端之间白板和共享屏幕的功能,这个功能有些类似视频的传输,因些并不是很难,可以在视频传输的功能上加以修改。用户之间后台的连接,每个用户隔指定时间会向用户列表中的每一用户发送消息,查看用户是否在线,如果不在线,就更新用户列表,删除用户。
    2.1.2 数据需求分析
    客户端之间聊天信息。在控件上显示时格式化,更易于用户的查看自己发送或接收到的信息。在线用户列表信息。服务器端存放在适当的空间中,在发送给客户端时,对信息列表进行格式化,便于客户端提取信息。客户端向服务器端发送的确认在线信息。包括客户端刚刚启动时的初始化信息和在使用过程中的确认在线信息。其它程序内部可能需要设计的数据结构体。
    2.1.3 性能需求分析
    可靠性高,能在由于系统问题或其它原因产生错误后,作出相对应处理,比如网络初始化失败、服务器不在线等,可以提示用户安全退出本程序,在出现不可知的错误以后,可以尽量安全的退出程序。在程序的设计过程中,要求能尽可能多的设想到用户使用过程中可能发生的事件,并能在判断事件后做出相应的处理,使程序具有较高的容错性能。宜操作性,程序简单易懂,容易上手使用。设计界面是,简化界面的复杂性,模拟QQ等现有即时通讯工具的界面,使用户能很容易看懂并使用。开发文档易理解,保证以后自己二次开发或他人接手开发时,能够清晰的理解整个系统的设计思路和实现细节。模块化设计此软件的功能,不同的模块实现不同的功能,使得软件易于以后的维护与扩展,在以后可以更好的完善本软件的功能,更方便于在工作中的应用。
    2.1.4 运行需求分析
    用户界面:程序较小,启动速度快,无启动界面。在本地局域网中使用,所以无需进行用户登录,无需认证界面,启动后的应用界面要清爽,设计要简单明了,要具有较高的易用性。故障处理:在遇到可预知的故障与情况时,能提示用户并自动退出;在遇到不可预知的故障时能安全退出。
    2.4 Winsock网络编程Windows Sockets是从Berkeley Sockets扩展而来的,其在继承Berkeley Sockets的基础上,又进行了新的扩充。这些扩充主要是提供了一些异步函数,并增加了符合WINDOWS消息驱动特性的网络事件异步选择机制。
    Windows Sockets由两部分组成:开发组件和运行组件。

    开发组件:Windows Sockets 实现文档、应用程序接口(API)引入库和一些头文件。运行组件:Windows Sockets 应用程序接口的动态链接库(WINSOCK.DLL)。
    2.4.1 Socket套接字(Socket)最初是由加利福尼亚大学Berkeley分校为UNIX操作系统开发的网络通信接口,随着UNIX操作系统的广泛使用,套接字成为当前最流行的网络通信应用程序接口之一。90年代初,由SunMicrosystems,JSBCorporation,FTP software,Microdyne和Microsoft等几家公司共同制定了一套标准,即Windows Sockets规范。
    Windows Sockets API是Microsoft Windows的网络程序设计接口,它在继承了Berkeley Sockets主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合Windows消息驱动特性的网络事件异步选择机制。这些扩充有利于应用程序开发者编制符合Windows编程模式的软件,它使在Windows下开发高性能的网络通信程序成为可能。
    Socket实际上是指一个通信端点,借助于它,用户所开发的Socket应用程序,可以通过网络与其它Socket应用程序进行通信。
    近年来,随着计算机网络与Windows 95的流行,许多用户所开发的应用程序需要实现网络间的数据通信。
    2.4.2 开发Windows Sockets网络通信程序的软、硬件环境所采用的操作系统软件可以是Windows 95,2000,XP,也可以是Windows NT,因为它们都支持Windows Sockets API,在以下的介绍中,我们将以在Windows XP环境下的开发为例。
    所采用的编程语言一般可选目前较流行使用的可视化和采用面向对象技术的Microsoft Visual C++ 6.0。Visual C++ 6.0可在Windows XP或Windows NT环境下运行,其开发系统增加了全面集成的基于Windows 的开发工具以及一个基于传统C/C++开发过程的“可视化”用户界面驱动模型。Visual C++ 6.0中的Microsoft基类(MFC,即MicrosoftFoundation Class)库是一系列C++类,其中封装着为Microsoft Windows操作系统系列编写应用程序的各种功能 。在有关套接字方面,Visual C++ 6.0对原来的Windows Sockets库函数进行了一系列封装,继而产生了CSocket 、CSocketFile等类,它们封装着有关Socket的各种功能。
    所采用的网络通信协议一般是TCP/IP。Windows XP和Windows NT都带有该协议。但是,所开发的网络通信应用程序并不能直接与TCP/IP核心打交道,而是与网络应用编程界面Windows Sockets API打交道。Windows Sockets API则可直接与TCP/IP核心进行沟通。TCP/IP核心协议连同网络物理介质(如网卡)一起,都是提供网络应用程序间相互通信的设施。
    2.4.3 CSocket类编程模型使用CSocket对象涉及CArchive和CSocketFile 类对象。以下介绍的针对字节流型套接字的操作步骤中,只有第三步对于客户方和服务方操作是不同的,其他步骤都相同。

    构造一个CSocket对象。 使用这个对象的Create()成员函数产生一个socket对象。在客户方程序中,除非需要数据报套接字,Create()函数一般情况下应该使用默认参数。而对于服务方程序,必须在调用Create时指定一个端口。需要注意的是,Carchive类对象不能与数据报(UDP)套接字一起工作,因此对于数据报套接字,CAsyncSocket和CSocket 的使用方法是一样的。 如果是客户方套接字,则调用CAsyncSocket∷Connect()函数与服务方套接字连接;如果是服务方套接字,则调用CAsyncSocket∷Listen()开始监听来自客户方的连接请求,收到连接请求后,调用CAsyncSocket∷Accept()函数接受请求,建立连接。请注意Accept()成员函数需要一个新的并且为空的CSocket对象作为它的参数,解释同上。 产生一个CSocketFile对象,并把它与CSocket 对象关联起来。为接收和发送数据各产生一个CArchive对象,把它们与CSocketFile对象关联起来。切记CArchive是不能和数据报套接字一起工作的。 使用CArchive对象的Read()、Write()等函数在客户与服务方传送数据。通讯完毕后,销毁CArchive、CSocketFile和CSocket对象。
    2.4.4 用VC6.0进行Windows Sockets程序开发的技术要点
    同常规编程一样,无论服务器方还是客户方应用程序都要进行所谓的初始化处理,这部分工作仍可采用消息驱动机制来先期完成。一般情况下,网络通信程序是某应用程序中的一模块。在单独调试网络通信程序时,要尽量与采用该通信模块的其它应用程序开发者约定好,统一采用一种界面形式,即单文档界面SDI、多文档界面MDI和基于对话框界面中的一种(这在使用AppWizard形成项目[Project]文件时有提示),尽管这并非必须,但可使通信模块在移植到所需的应用程序时省时省力,因为Visual C++ 6.0这种可视化语言在给我们提供方便的同时,也给我们带来某些不便,譬如所形成的项目文件中的许多相关文件与所采用的界面形式密切联系,许多消息驱动功能,随所采用的界面形式不同而各异。当然,也可将通信模块函数化,并形成一个动态连接库文件(DLL文件),供主程序调用。 以通信程序作为其中一个模块的应用程序往往不是在等待数据发送或接收完之后再做其它工作,因而在主程序中要采用多线程(Multithreaded)技术。即将数据的发或收,放在一个具有一定优先级(一般宜取较高优先级)的辅助线程中,在数据发或收期间,主程序仍可进行其它工作,譬如利用上一个周期收到的数据绘制曲线。Visual C++ 6.0中的MFC提供了许多有关启动线程、管理线程、同步化线程、终止线程等功能函数。在许多情况下,要求通信模块应实时地收、发数据。譬如调用之的主程序以0.5秒为一周期,在这段时间内 ,要进行如下工作:接收数据,利用收到的数据进行运算,将运算结果发送到其它计算机节点,周而复始。我们在充分利用Windows Sockets的基于消息的网络事件异步选择机制,用消息来驱动数据的发送和接收的基础上,结合使用其他措施,如将数据的收和发放在高优先级线程,在软件设计上,安排好时序,尽量避免在同一时间内,双方都在向对方发送大量数据的情况发生,保证网络要有足够的带宽等,成功地实现了数据传输的实时性。
    第3章 详细设计本章将从各个方面介绍本系统的设计。先从基本框架的设计出发,然后逐步介绍好友管理、聊天模块、聊天室模块、传送文件模块、共享屏幕模块、白板模块、音、视频模块和调试模块,所以本章是本论文的重点。
    3.1 基本框架设计本节内容将介绍除各个功能模块外的设计,包括界面上的处理、保持好友在线列表等的处理。有些内容可能并不属性框架设计,但这些内容也不具有单独使用一节来介绍的必要,所以把这些内容也一并放到这些节来介绍。这也是为了区分设计周围的处理与各个功能模块的处理。
    3.1.1 宏和数据结构的定义程序中用到了很多宏和数据结构,这些宏和数据结构在多个模块中都有用到,因此程序中专门新建一个头文件Global.h,此头文件里是程序中很到的宏和数据结构的定义。在StdAfx.h文件包含Global.h,在程序其他地方都可以使用Global.h中的宏和定义的数据结构。这样处理还有一个好处,如果需要修改某些宏的值,可以直接在Global.h中修改,而不用到处去找宏的定义,方便和快捷。
    3.1.2 程序配置文件程序中很多信息需要保存,比如用户名和热键,因些程序用到了配置文件,默认的配置文件名为conf.ini。程序用读取和写入配置文件系列函数来管理此配置文件。
    3.1.3 主界面初始化用过QQ的人都知道,QQ主面板总是处于其他程序的上面,而且QQ在任务栏没有图标,而是把图标放到了托盘区,另外,我们还可以按Ctrl+Alt+Z默认的快捷键隐藏和显示QQ主面板。不但QQ是这样处理的,很多聊天软件都采用此种处理方式。本设计也不例外,同样也要达到这样的目的。
    下面从各个方面来说明本设计的处理方式:
    1.不在任务栏显示图标
    CDialog dlgParent;dlgParent.Create( IDD_DIALOG_BG );dlgParent.ShowWindow( SW_HIDE );CInstantMessagingDlg dlg( &dlgParent );m_pMainWnd = &dlg;ModifyStyleEx( WS_EX_APPWINDOW, 0 );
    上面代码就达到了程序主界面不在任务显示的目的。首先,我们创建一个对话框,并隐藏此对放框,然后把这个对话框作为主界面对话框的父窗口,然后在主界面对话框的初始化函数中修改其风格,去掉WS_EX_APPWINDOW风格。这样,主界面就不会出现在任务了。
    2.将主界面放在最上层
    将程序放到顶层,很多程序都有这功能,比如金山词霸等,实现起来其实很简单,只用一条语句就可以达到目的:
    SetWindowPos( &wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE );
    第一个参数就是将程序放到所有非顶层窗口的顶层,如果有多个程序都是顶层窗口,那么他们谁在上面,就要看当前谁是激活的窗口。最后一个参数,是用位或|组后起来的,从字面意思上我们就能理解到这是不移动不改变大小的意思,忽略了当中的4个参数。
    3.热键的处理
    设计中默认的热键是Ctrl+Alt+Z,当然程序允许用户自己定义热键,自定义的热键将保存在conf.ini文件中。热键的功能可以隐藏、显示主界面,有消息到达时,按热键也可以打开聊天对话框。
    ::RegisterHotKey( m_hWnd, IDHOTKEY, m_wModifiers, m_wVirtualKeyCode );
    使用全局函数RegisterHotKey可以注册热键,如果注册的热键没有被其他程序占用,那么注册成功。注册成功后,如果按热键,那么程序就会接受到WM_HOTKEY消息,因此我们还需要自己处理WM_HOTKEY消息:
    void OnHotkey( WPARAM wParam, LPARAM lParam );BEGIN_MESSAGE_MAP(CInstantMessagingDlg, CDialog) … ON_MESSAGE( WM_HOTKEY, OnHotkey ) … //}}AFX_MSG_MAPEND_MESSAGE_MAP()
    在消息映射中,我们用OnHotkey()函数来处理WM_HOTKEY消息。
    void CInstantMessagingDlg::OnHotkey( WPARAM wParam, LPARAM lParam ){ if( this->IsWindowVisible() ) { ShowWindow( SW_HIDE ); } else { ShowWindow( SW_SHOW ); ::SetForegroundWindow( m_hWnd ); }}
    在OnHotkey()函数中判断主界面是否是可见的,如果是可见的那么隐藏起来,否则显示,并且把主界面设为前景窗口。
    4.最小化和关闭按钮的处理
    我们希望单击程序右上角的最小化按钮时,程序隐藏起来,而单击关闭按钮时,程序会提示是否退出,而不会悄无声息的退出。
    void CInstantMessagingDlg::OnSysCommand(UINT nID, LPARAM lParam){ …if( nID == SC_MINIMIZE ) { this->ShowWindow( SW_HIDE); } else { CDialog::OnSysCommand(nID, lParam); }}void CInstantMessagingDlg::OnCancel() { if( IDOK == MessageBox( "要退出吗?", "退出", MB_OKCANCEL | MB_ICONINFORMATION | MB_DEFBUTTON2 ) ) { … DestroyWindow(); }}
    在程序中处理OnSysCommand()函数和OnCancel()函数就实现了我们要的功能。
    5.托盘图标的显示
    至此,程序已不在任务栏显示图标,已是最顶层窗口,而且也已有热键功能,但是还没有实现托盘图标的显示。实现托盘图标的代码如下:
    NOTIFYICONDATA m_nid;HICON hIcon = AfxGetApp()->LoadIcon( STATE_ONLINE );m_nid.hIcon = hIcon;m_nid.hWnd = m_hWnd;m_nid.cbSize = sizeof( NOTIFYICONDATA );m_nid.uCallbackMessage = WM_SHELLNOTIFY;m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;m_nid.uID = IDR_MENU1;sprintf( m_nid.szTip, "即时聊天软件" );Shell_NotifyIcon( NIM_ADD, &m_nid );
    这样我们就在托盘区显示了STATE_ONLINE的图标,把鼠标移动到图标上一会,还会出现“即时聊天软件”的提示框。如果你们对图标有单击和双击等操作,程序会收到WM_SHELLNOTIFY消息,因此,我们还必须处理WM_SHELLNOTIFY消息。
    void OnShellNotifyProc( WPARAM wParam, LPARAM lParam );BEGIN_MESSAGE_MAP(CInstantMessagingDlg, CDialog) //{{AFX_MSG_MAP(CInstantMessagingDlg) … ON_MESSAGE( WM_SHELLNOTIFY, OnShellNotifyProc ) … //}}AFX_MSG_MAPEND_MESSAGE_MAP()void CInstantMessagingDlg::OnShellNotifyProc( WPARAM wParam, LPARAM lParam ){ if( lParam == WM_LBUTTONDBLCLK ) { … } else if( lParam == WM_RBUTTONUP ) { … }}
    与热键处理一样,OnShellNotifyProc()函数响应我们对图标的操作,其中lParam参数表示消息号,在本设计中只处理左键双击(显示主界面)和右键单击(弹出菜单)。
    在托盘添加图标,退出程序前,如果没有从托盘删除图标,那么托盘区的图标会一直保留下来,直到鼠标移过托盘区引起托盘区的重绘,这当然不是我们所希望的结果。
    void CInstantMessagingDlg::OnDestroy() { // 删除在托盘建立的图标 ::Shell_NotifyIcon( NIM_DELETE, &m_nid ); CDialog::OnDestroy();}
    以上代码在程序退出时调用,从托盘从删除图标。
    6.只允许运行唯一实例
    这点与QQ不同,在一台机子上可以运行多个QQ,但本程序只允许运行一个实例。只允许运行一个实例,有多种方法,本设计采用的是创建命名事件的方法:
    HANDLE hEvent = ::CreateEvent( NULL, FALSE, FALSE, "InstantMessaging" );if( hEvent ){ if( ERROR_ALREADY_EXISTS == GetLastError() ) { return FALSE; }}
    事件与普通变量不一样,普通变量只在运行的当前程序中有效,而事件在整个系统中都有效。当首次运行程序时,会创建一个名为“InstantMessaging”的事件,这个事件在系统范围内有效,当再次运行程序时,程序会尝试着创建同名的事件,因为之前已经创建了这个事件,因此系统会返回之前创建事件的句柄,但GetLastError()会返回ERROR_ALREADY_EXISTS,表明需创建的事件之前已经创建,为了保证只允许一个实例,这个实例就不再允许运行,直接返回,退出程序。
    3.1.3 主界面布局程序主界面如下:

    左上角显示的是自己的头像、状态和昵称;右上角的列表框是查找IP输入框,下面是添加按钮;在下面一点的列表框是自己的址列表框,显示了自己的所有IP;主界面中央是用户列表框;最下面是4个功能按钮。
    用户列表框是一列一列的显示添加的好友,最左边是好友的头像;中部上边是好友的昵称,下面是好友的IP;右下角是删除好友按钮和摄像头按钮,当然好友必须有摄像头才会显示摄像头按钮。
    有两种方式添加联系人:

    在右上解的查找IP输入框里输入IP,然后单击下面的添加按钮。也可以从IP输入框里选择以前加过的好友IP。程序允许保存10个最近联系人的IP,当新添加联系人时,如果已保存了10个联系人的IP,程序会按照时间的先后顺序覆盖之前的IP。在列表框展开下拉列表后,可以按DELETE键删除选定的IP。选定一个自己的IP,然后单击“网段”按钮。此功能可以向选定IP的IP段发送添加请求的消息,这相当于批量添加好友的功能。
    在主界面任何地点单击左键不放开,可以拖动程序;单击右键,会弹出菜单,用户选中相应的菜单项,可以执行相应的功能;在任何地点双击左键,可以打开“个人设置”对话框,如下:

    最后一项“允许别人直接将我加为联系”的意思是别人添加我为好友时,不会弹出请求对话框而直接加为好友。
    单击保存后,此对话框里的内容会保存到config.ini配置文件中。运行程序后,会从config.ini读取用户信息,并在主界面中作相应的设置。
    在主界面,添加、聊天室、传送文件、共享屏幕和白板按钮都是自绘按钮,可以显示图片,有提示能力,当鼠标移动到其上一会儿后,会弹出提示框,而且这些按钮都具有XP风格,既鼠标滑过时会显示不同的状态。用户列表框也是自绘的,普通的列表控件无法显示我们所需的信息。自绘按钮和自绘列表框会作为一个单独的模块来介绍,这儿就不作过多的介绍。
    3.1.4 自绘按钮VC++6.0自带的按钮控件不具有XP风格,而且也不能显示图像,作为一款好的软件,应该有个好的界面。在程序的主界面上,主要的按钮都采用了自绘按钮,而不使用自带的按钮控件。
    AdvButton.h和AdvButton.cpp是自绘按钮类的头文件和实现文件。
    在自绘按钮类中定义了如下成员变量:
    int m_nState; // 按钮的状态CBitmap m_bmpNormal; // 正常图标CBitmap m_bmpHover; // 焦点图标CBitmap m_bmpDown; // 按下图标CBitmap m_bmpDisable; // 无效图标CToolTipCtrl m_pToolTipCtrl; // 提示类
    m_nState表示当前按钮的状态,可以为宏:NORMAL,HOVER,DOWN,DISABLE,分别表示按钮正常状态、处于焦点状态、按下状态、无效状态,这4个宏的定义在实现文件。4个CBitmap的变量分别存储4种状态下的图像。m_pToolTipCtrl是提示工具控件类,既是鼠标在其上时,会弹出提示窗口。
    要实现按钮自绘,必须更新按钮的风格为自绘,可以在按钮的属性中更改,也可以使用代码更改。重载PreSubclassWindow(),在这个函数中更改按钮风格并初始化m_pToolTipCtrl。
    void CAdvButton::PreSubclassWindow() { ModifyStyle( 0, BS_OWNERDRAW ); CButton::PreSubclassWindow(); m_pToolTipCtrl.Create( this, TTS_ALWAYSTIP ); m_pToolTipCtrl.SetDelayTime( 100 ); CString strText; GetWindowText( strText ); m_pToolTipCtrl.AddTool( this, strText ); }
    VC++6.0中的ClassWizard不能为我们添加鼠标离开的消息,只能为我们添加鼠标移动、单击等消息,我们得自己为自绘按钮添加上鼠标离开的消息。
    TRACKMOUSEEVENT tme;tme.cbSize = sizeof( TRACKMOUSEEVENT );tme.hwndTrack = m_hWnd;tme.dwFlags = TME_LEAVE;::_TrackMouseEvent( &tme );
    以上代码告诉系统,当鼠标离开m_hWnd窗口时,向这个窗口发送一条WM_MOUSELEAVE消息。下面的处理方式与热键和托盘通知消息的处理方式一样,自定义这个消息处理函数就行了。
    自绘按钮必须重载DrawItem()函数,在DrawItem()函数中根据m_nState的值可以贴上不同的图,表示按钮的一不同状态。
    void CAdvButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { if( lpDrawItemStruct->itemState & ODS_DISABLED ) { m_nState = DISABLE; } switch( m_nState ) { case NORMAL: DrawNORMAL(); break; case HOVER: DrawHOVER(); break; case DOWN: DrawDOWN(); break; case DISABLE: DrawDISABLE(); break; default: break; }}
    DrawNORMAL() 、DrawHOVER ()、DrawDOWN() 、DrawDISABLE()分别画按钮的4种状态。当鼠标滑过或单击按钮时,更改m_nState的值,然后调用Invalidate(),强制按钮重绘。要使按钮无效,必须调用EnableWindow(FALSE )函数来更改按钮的状态,我们也就无法更改m_nState的值。lpDrawItemStruct->itemState的值表示了当前按钮的状态,可以检测lpDrawItemStruct->itemState,如果按钮是无效状态,则设置m_nState为DISABLE,否则不作改变。
    在画按钮的状态时,使用到了TransparentBlt()函数,这个函数可以贴透明位图。在TransparentBlt()最后一个参数中指定掩码色,贴图时掩码色就不会贴出来。要使用此函数,必须导入 msimg32.dll,在程序使用如下语句导入:
    #pragma comment( lib, "MSIMG32.LIB" )
    3.1.5 自绘好友列表框普通的列表控件无法满足程序的要求,程序要求好友列表框可以显示好友的头像、好友昵称、好友IP和删除、摄像头按钮。
    FriendsListCtrl.h和FriendsListCtrl.cpp是自绘列表框的头文件和实现文件。
    定义的成员变量如下:
    CInstantMessagingDlg *m_pMainDlg; // 主对话框CImageList m_imageList; // 头像图像列表int m_nCamera; // 摄像头激活的序号int m_nDelIcon; // 删除按钮激活的序号int m_nCurSel; // 当前选中用户序号
    与自绘按钮类似,在PreSubclassWindow()函数中更改列表框的风格为自绘:
    void CFriendsListCtrl::PreSubclassWindow() { ModifyStyle( 0, LVS_OWNERDRAWFIXED ); … CListCtrl::PreSubclassWindow();}
    重载MeasureItem()函数更改列表框每一项的高度:
    void CFriendsListCtrl::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct ){ lpMeasureItemStruct->itemHeight = DEFAULT_FRIENDSLIST_HEIGHT;}
    宏DEFAULT_FRIENDSLIST_HEIGHT在Global.h文件定义,表示好友列表框每项的高度。
    列表框中的鼠标离开消息与自绘按钮的实现是同一个原理,这儿就不再赘述。
    在向好友列表框中添加好友时,主对话框调用好友列表框的AddUser()函数,参数为USER结构体,这个参数作为列表项的额外数据,这样重绘的时候再读取出这个额外数据就可以得到这一项的用户信息。
    鼠标在好友列表框上移动时,判断鼠标是否在删除或摄像头(如果有摄像头)按钮范围内,如果在,就设置m_nDelIcon或m_nCamera为当前项的序号,否则就设置m_nCurSel为当前项的序号。
    用户双击鼠标时,调用主对框的相应函数,并把项的序号传给此函数。如果是单击,先判断m_nDelIcon、m_nCamera值,如果不为-1,则选中了删除或摄像头按钮,调主对框相应函数执行相应操作。
    在DrawItem()函数中,先得到额外的附加数据,既是添加项时作为参数传递的USER类型的变量,然后再根据m_nCurSel、m_nDelIcon和m_nCamera的值自绘。在自绘时,为了防止闪烁,程序用到了双缓冲技术。
    双缓冲技术,就是先创建一个与目标设备兼容的内存设备上下文,在内存设置上下文中画图或进行其他处理,操作完成了,再一并把内存设备上下文的内容贴到目标设备上,这样就可以有效的防止闪烁。
    3.2 好友管理好友管理包括添加好友、删除好友以及与好友保持连接。
    3.2.1 添加好友添加好友的两种方式4.1节已经介绍过了,这一节介绍的是具体的实现。
    首先然主对话框定义监听Socket并初始化:
    CListeningSocket *m_pLisSocket;m_pLisSocket = new CListeningSocket( this );m_pLisSocket->Create( DEFAULT_PORT, SOCK_DGRAM );
    在添加按钮的响应函数中先判断IP地址是否合法,是否是自己的IP,是否已经添加此好友。如果可以添加此好友,则向此好友发起请求加为好友的请求:
    // 定义数据包DATAPACKET dataPacket;dataPacket.command = REQUEST_ADD;// 设置请求的用户结构USER user;user.bHasCamera = m_bCamera;user.nFace = m_wFace;memcpy( user.strName, m_strNickName.GetBuffer( MAXNICKNAMELENGTH + 1 ), MAXNICKNAMELENGTH + 1 );m_strNickName.ReleaseBuffer();// 分配空间UINT uDataLength = sizeof( DATAPACKET ) + sizeof( USER );BYTE *pSendData = new BYTE[ uDataLength ];memcpy( pSendData, &dataPacket, sizeof( DATAPACKET ) );memcpy( pSendData + sizeof( DATAPACKET ), &user, sizeof( USER ) );// 发送请求m_pLisSocket->SendTo( pSendData, uDataLength, DEFAULT_PORT, strFriendIP );delete pSendData;
    以上代码中的宏都可以在Global.h头文件中找到。向好友发起请求的数据中,还包括自己的USER数据。
    向对方发起请求后,对方的m_pLisSocket就会调用OnReceive()函数,程序中重载了CListeningSocket类的OnReceive()函数,在OnReceive()中调用主对对话框的OnListeningReceive()函数来接收网络数据。
    在OnListeningReceive()函数中,根据DATAPACKET的command值来进行相应的处理,这儿是添加为好友的请法度,先得到发起请求的USER,再调用AddRequest()函数做相应处理。
    在AddRequest()函数中,先进行相应的判断,如果具备加为好友的条件,根据是否允许直接加为好友的值是否弹出提示对话框。如果拒绝加为好友,则向发送者发送拒绝的消息。否则先把发起者的USER信息加入到好友列表中,再向请求者发送允许加为好友的消息,消息中包括自己的USER信息。
    请求者收到对方的拒绝消息,会弹出对话框提示对方拒绝加为好友;如果收到的是允许加为好友的消息,则再得到一同发送过来的USER信息,再加入到列表中。
    在InstantMessaging.h头文件中定了如下变量:
    CArray< USER, USER > m_arrFriends; // 好友列表
    此数组保存的是已连接的好友,添加好友时会向这个变量里添加USER信息,并把USER添加到好友列表框中。
    请求者添加好友流程图如下:

    好友收到请求的流程图如下:

    3.2.2 删除好友删除好友比较简单,在服务器端向要删除的好友发送命令为OFFLINE的消息,然后从好友数组和好友列表框中删除此好友。
    在客户端接受到OFFLINE的网络消息时,得到发送此消息的IP,然后从好友数据和好友列表框中删除与此IP相等的好友。
    另外,当用户改变自己的状态为下线状态或关闭程序时,会调用SendOffLineMessage()函数向所有好友发送下线消息,并删除所有的好友。
    3.2.3 与好友保持连接此功能用到了定时器,间隔一定时间向所有好友发起请求保持连接的消息,并把发送过此消息的好友添加到一个临时好友列表中。好友收到请求保持连接的消息后,会发送回应保持连接的消息。程序收到回应保持连接的消息后,会从临时好友列表中删除对应的好友。在下一次定时器到的时候,程序检查临时好友列表,在临时好友列表中的好友都是没有回应的的好友,这些好友可能是因为程序不正常关闭而未向其他好友发送下线通知,程序就可以将这些好友删除。在程序初始化时设置一定时器:
    SetTimer( TIMER_CONNECT, DEFAULT_REFRESH_TIME, NULL );
    在OnTimer()函数中进行如下处理:
    // 保持接连if( TIMER_CONNECT == nIDEvent ) { // 删除没有回应的联系人 for( int nIndex = 0; nIndex < m_arrFriends.GetSize(); nIndex++ ) { USER userDel = m_arrFriends.GetAt( nIndex ); if( m_strlstKeepConnent.Find( userDel.strIP ) ) { m_listCtrlFriends.DeleteUser( nIndex ); m_arrFriends.RemoveAt( nIndex ); nIndex--; } } m_strlstKeepConnent.RemoveAll(); // 分别发送保持连接的消息,将发送过的IP加入到m_strlstKeepConnent for( nIndex = 0; nIndex < m_arrFriends.GetSize(); nIndex++ ) { USER user = m_arrFriends.GetAt( nIndex ); DATAPACKET dataPacket; dataPacket.command = REQUEST_KEEPCONNECT; // 分配空间 UINT uDataLength = sizeof( DATAPACKET ); BYTE *pSendData = new BYTE[ uDataLength ]; memcpy( pSendData, &dataPacket, sizeof( DATAPACKET ) ); m_pLisSocket->SendTo( pSendData, uDataLength, DEFAULT_PORT, user.strIP ); delete pSendData; m_strlstKeepConnent.AddTail( user.strIP ); }}
    3.3 聊天模块聊天包括文字聊和图片聊天,本系统用到了Microsoft Rich Textbox Control 6.0控件,此控件支持RTF(Rich TextFormat)格式的内容,包括不同颜色、不同字体字号的文本和图片。要使用此控件,系统中必须注册了此控件,如果未注册此控件,那么打开聊天对话框时程序会死掉。为了解决这个问题,本设计在程序中检查系统是否注册过richtx32.ocx控件,如果没有注册,程序会先注册,代码如下:
    HKEY hKey;if( RegOpenKeyEx( HKEY_CLASSES_ROOT, "RICHTEXT.RichtextCtrl\\CLSID", 0, KEY_READ, &hKey ) != ERROR_SUCCESS ) { HINSTANCE hLib = LoadLibrary( "RICHTX32.OCX" ); // 控件不存在 if( !hLib ) { MessageBox( "RICHTX32.OCX控件未找到" ); } else { // 获取注册函数DllRegisterServer地址 FARPROC lpDllEntryPoint; lpDllEntryPoint = GetProcAddress( hLib, "DllRegisterServer" ); // 注册richtx32.ocx控件 lpDllEntryPoint(); }}
    聊天对话框如下:

    聊天对话框用的Socket是主对话框的m_pLisSocket,当要发送消息时,调用主对话框的SendPreChatMessage()函数,主对话框接收到聊天消息时调用聊天对话框的ReceiveMessage()函数。
    聊天对话框中定的发送和接收富文本框变量为:
    CRichText m_rtReceived;CRichText m_rtSend;
    发送消息的主要代码如下:
    void CChatDlg::OnSend() { CTime time = CTime::GetCurrentTime(); CString strTime = time.Format( "%H:%M:%S" ); … CString strSend = m_rtSend.GetTextRTF(); // 发送聊天消息 m_pMainDlg->SendPreChatMessage( m_userChat, strTime, strSend );}
    单击“发送”按钮或CTRL+ENTER键,程序会调用OnSend()函数,首先得到发送的时间和发送的内容,得到的是RTF格式的内容,因此strSend中还包括了文字的格式和图片信息。然后调用主对话框的SendPreChatMessage()函数向m_userChat好友发送聊天消息。
    接收到的消息主要代码如下:
    void CChatDlg::ReceiveMessage( LPCSTR szTime, LPCSTR szMessage ){ CString strText; strText.Format( "%s(%s) %s\r\n ", m_userChat.strName, m_userChat.strIP, szTime ); // 设置接收框 m_rtReceived.SetSelStart( m_rtReceived.GetTextRTF().GetLength() ); m_rtReceived.SetSelText( strText ); m_rtReceived.SetSelRTF( szMessage ); …}
    因为聊天用的Socket是主对话框的m_pLisSocket,因此是主对话框接收到的聊天消息。主对话框接收到聊天消息后,根据消息发送的来源IP把处理消息。如果与此IP的聊天窗口打开的,就直接调用ReceiveMessage()把消息放入接收文本框中,如果聊天窗口没有打开,则把消息追加到CMapStringToOb类型的变量m_mapIPToChat中,并在托盘区动态显示用户的头像。
    聊天消息包括文字格式和图像信息,因此发送的数据可能很大,会超过Socket发送的最大数据长度,这样如果直接发送,会因为数据长度过大,而导致发送失败。所以在发送这种大数据量的消息时,本程序采用的分包发送的方式。
    上面提到了发送的最大数据长度,m_pLisSocket是创建的UDP套接字,不像TCP一样可以发送随意大小的数据,UDP套接字只能发送小于一定大小的数据。UDP可以发送的最大数据量可以由下面的代码得到:
    WSADATA wsaData;if (!AfxSocketInit( &wsaData )){ AfxMessageBox(IDP_SOCKETS_INIT_FAILED); return FALSE;}
    初始化WinSocket后,WSADATA结构中就有我们需要的信息,wsaData.iMaxUdpDg就是UDP可以发送的最大数据量。主对话框定义了一变量,设置成wsaData.iMaxUdpDg的值,在用UDP发送数据的时候,就可以根据这个值的大小来确定分包的大小。
    聊天对话框发送消息时调用主对话框的SendPreChatMessage()函数,这个函数并不是一下子将消息发送出去,而是将消息保存起来,只发送一个通知消息告诉对方有数据需要发送,这个通知消息中带有消息的时间的消息的长度。
    对方接收到这个通知消息时,会根据接收到的消息长度分配一个接收消息的临时空间,并把接收到的消息时间也保存。然后向消息发送者发送请求接收第一个数据包的请求。
    消息发送者收到请求发送消息的消息后,会把相应的数据包发送给聊天对方。
    对方接收到消息数据包后,把消息放入到接收消息的临时空间,并判断数据是否接收完毕。如果没有接收完毕,继续请求发送下一个消息数据包。如果接收完毕,播放声音,并判断与此IP对应的聊天窗口是否打开,是则调用ReceiveMessage()把消息放入到接收消息文本框,否则把消息保存起来,并动态显示托盘的图标,等待用户打开聊天窗口时再把消息放到接收文本框。
    聊天对话框还有记录和打开聊天记录的功能。勾选上“关闭时保存聊天记录”,关闭聊天对话框会聊天记录会自动保存在形如“2011-03-11.102920(192.168.1.3)测试.rtf”的文件中,文件名由聊天开始时间、IP、昵称组成。打开“聊天记录”按钮,可以选择保存的聊天记录,在接收文本框内会显示打开选择的聊天记录。
    3.4 聊天室模块聊天室分为服务器和客户端,每个用户只能创建一个聊天室,也只能加入一个聊天室。聊天室也只能发送普通文本消息,所以聊天室的开发相比聊天来说,简单的多。聊天室服务器如下图:

    服务器右边两个列表框分别表示在聊天室里的好友、不在聊天室里的好友;客户端右边的列表框表示在聊天室中的好友。
    用户创建聊天室可以把自己的所有好友都加入到聊天室,这些好友发的消息可以被所有在聊天室中的好友共享,即使这些好友之间可能并不能互相访问。这也是聊天室的一个好处。
    聊天室服务器和客户端各使用一个SOCKET,创建的是UDP套接字,有自己的端口号。
    用户创建聊天室时,创建端口为CHATROOM_SERVER_PORT的UDP套接定,把自己加入到聊天室好友列表里,同时也把自己添加的所有好友加入到未进入聊天室好友列表中。
    在未进入聊天室好友列表中选定要添加进入聊天室的好友,单击向上的按钮,会向这些选定好的好友发送加入聊天室的请求。
    SendUserCommandToIP( CHATROOM_ADDREQUEST, user.strIP, DEFAULT_PORT, &userSelf );
    以上代码就是向好友user发送加入聊天室请求,因为在加入聊天室之前,这些好友并没有创建聊天室客户端SOCKET,所以必须向主对话框的m_pLisSocket套接字发送消息,这从DEFAULT_PORT端口号就可以看出来。
    如果好友接受了请求,接受请求的好友就会从未进入聊天室聊表框移到聊天室好友列表框。
    单击向下的按钮,会删除聊天室好友列表框中选定的好友,并向他们发送被踢出聊天室的消息:
    SendUserCommandToIP( CHATROOM_CLIENT_KICKED, user.strIP, CHATROOM_CLIENT_PORT, NULL );
    这儿是发往CHATROOM_CLIENT_PORT端口的消息,因为将要删除的好友已经打开了聊天室,而且创建了聊天室客户端的SOCKET,可以接收发往这个端口的消息。
    当聊天室服务器关闭时,会向所有聊天室好友发送关闭聊天室的消息:
    for( int nIndex = 1; nIndex < m_listCtrlInChat.GetItemCount(); nIndex++ ){ USER user = m_arrFriendsInChat.GetAt( nIndex ); SendUserCommandToIP( CHATROOM_SERVER_CLOSED, user.strIP, CHATROOM_CLIENT_PORT, NULL );}
    聊天室好友列表里第的是自己,不必要给自己发送消息,因此nIndex是从1开始的。
    当创建聊天室的用户发送聊天消息时,服务器将文本消息发给所有聊天室好友。在服务器创建的时候,限制过发送文本框的字数:
    m_editSend.SetLimitText( MAXDATAPACKETLENGTH - sizeof( DATAPACKET ) - sizeof( CHATROOMMESSAGEINFO ) );
    因此,发送的消息长度不会超过UDP可以发送的数据最大值,发送消息时直接一次发送就行:
    for( int nIndex = 1; nIndex < m_arrFriendsInChat.GetSize(); nIndex++ ){ USER user = m_arrFriendsInChat.GetAt( nIndex ); SendTextToIP( user.strIP, CHATROOM_CLIENT_PORT, strSend, "" );}
    聊天室服务器接收到消息时,会将接收到的消息加入到聊天室接收文本框,并将消息发给所有在聊天室中的好友,除了向服务器发送消息的好友外:
    for( int nIndex1 = 1; nIndex1 < m_arrFriendsInChat.GetSize(); nIndex1++ ){ USER userSend = m_arrFriendsInChat.GetAt( nIndex1 ); if( 0 == strcmp( userSrc.strIP, userSend.strIP ) ) { continue; } SendTextToIP( userSend.strIP, CHATROOM_CLIENT_PORT, strSend, userSrc.strIP );}
    以上就是服务器的主要功能。
    客户端的处理比服务器要简单一些,客户端只有在聊天室列表中的好友,客户端也只向服务器发送消息,不用向其他聊天室好友发送消息。
    聊天室客户端如下图:

    首先,主对话框接受到CHATROOM_ADDREQUEST消息,弹出对话框询问用户是否进入好友创建的聊天室,如果用户拒绝加入则不用处理此消息,如果用户同意加入聊天室,那么就弹出聊天室客户端。
    在客户端打开的同时,限制发送文本框的最大字数,创建客户端SOCKET,端口号为CHATROOM_CLIENT_PORT的UDP。
    当服务器踢出客户端,或服务器关闭时,弹出对话框提示用户,并清空聊天室好友,设置发送文本框不可用。
    客户端关闭时,会向服务器发送客户端关闭的消息。
    客户端发送消息时,先将发送的消息加入到接收消息文本框,然后将消息发往服务器,由服务器将消息发到各个聊天室好友。
    客户端接到服务器发来的聊天消息时,处理也很简单,直接将聊天消息加入到接收消息文本框。
    以上就是聊天室的设计方法。服务器在当中起到了中转的作用,所有消息都经过服务,服务器向所有好友发送消息,就算聊天室中的好友不能互相连接,只要能连接到服务器,他们就可以一起聊天。
    3.5 传送文件模块正如任务书上写的,文件传送支持断点续传,这是本程序的一个亮点。文件传送模块用到了多线程,可以实现多个文件、多个用户之间的传输。一个线程负责将一个文件传送给一个好友。
    传送文件服务器如下图所示:

    单击添加按钮会打开选择文件对话框,选择好要发送的文件后,会弹出一个好友列表框,要求选择需要发送的好友,选择了好友后,程序会把发送文件信息添加到传输入文件对话框中,并向接收者发起传送文件的请求,如果接收者拒绝接收文件,在传输文件对话框的相应项的速度列就会显示“拒绝”,如果接收者接受文件,文件传送正式开始。下面详细介绍服务器端的工作过程:
    传送文件用到的是TCP连接,因为传送文件必须保证传输的数据不能丢失也不能有误。用户打开文件传输功能,弹出传输文件对话框,在初始对话框的时候,除了初始化传输文件列表框,还会创建一个端口号为SENDFILESSERVER_PORT的TCP套按字m_pSFServerSocket,并设置此套接字为监听状态。
    当用户选择好传送的文件和传送的好友后,传送信息会加入到列表框,并调用主对话框的SendFilesNotify()函数向这些好友发送传送文件的消息,此消息中还包括文件名和文件长度。
    如果接收者拒绝接收文件,列表框的相应项的速度列会被设置为“拒绝”。
    如果接收者接收文件,接收者会先向pSFServerSocket发起连接,程序重载pSFServerSocket的OnAccept()函数,在此函数中调用传输文件服务器对话框的OnAccept()函数:
    void CSendFilesServerDlg::OnAccept(){ CSendFilesSocket sfSocket; m_pSFServerSocket->Accept( sfSocket ); CSendFilesServerThread *pSFSThread = ( CSendFilesServerThread *)AfxBeginThread( RUNTIME_CLASS( CSendFilesServerThread ), 0, 0, CREATE_SUSPENDED, NULL ); pSFSThread->SetSendFilesServerDlg( this ); pSFSThread->AttachSocket( sfSocket.Detach() ); pSFSThread->ResumeThread(); m_arrSendThread.Add( pSFSThread );}
    在此函数中先定义一个CSendFilesSocket类型的变量sfSocket,m_pSFServerSocket接收此WinSocket,就与接收都连接起来了。然后此函数再创建了一个线程,并把此线程加入到线程列表中。在线程中也有一个CSendFilesSocket类型的成员变量,将这儿的sfSocket分离掉套接字句柄,附加到线程中的相同类型的成员变量上。发送数据的功能就由这个线程来处理了。
    在传输入文件对话框中选中某些传输信息,单击“删除”按钮,会弹出删除提示框询问用户是否删除这些传输信息,如果用户确定删除这些传输信息,传略文件对话框会删除这些传输信息,如果文件正在传输入,会关闭线程,停止传输文件。
    在传输文件对话框中设置了一个定时器,用于刷新列表框。每当定时器时间到时,程序调用RefreshListBox()函数刷新列表框,更新文件传输的进度和速度。
    如果接受者停止了接收文件或者文件传输完成,相应的线程也会调用RefreshListBox()函数及时的刷新列表框。
    CSendFilesServerThread类用于发送文件数据,接收者连接到m_pSFServerSocket后,接收者就可以与CSendFilesServerThread类中的CSendFilesSocket成员变量m_sendFilesSocket通信了。
    因为程序支持断点续传,因此接收者首先会通知CSendFilesServerThread线程要发送的文件、大小、已经发送的大小,之后线程会打开需传送的文件,并定位到已传送过的位置,然后向接收者发送可以开始传送文件的消息。
    接收者收到可以开始传送文件的消息后,会向服务器发起请求传输数据的消息,此消息中包含已传输的大小。
    传输线程收到此消息后,会根据文件的大小、已传输的大小、以及包的大小向接收者发送数据包。传输线程只要接收到传输数据的消息,就会传输数据,当接收到传输完毕后,此线程作一些结尾工作,通知传输入文件对话框,然后结束线程。
    接收文件对话框如下:

    服务器向接收者发送文件时,主对话框会弹出对话框询问是否接受文件,如果不接受则不作处理。如果接收,则打开接收文件对话框,并让用户选择保存接收文件的位置,然后定义CReceiveFilesSocket类型的变量rfSocket,向服务器发起连接,创建CSendFilesClientThread线程,把线程加入到线程列表,并分离rfSocket的套接字句柄,附加到CSendFilesClientThread线程中的CReceiveFilesSocket类型的成员变量中。
    接收文件对话框的其他功能,如删除、刷新列表框等与传输文件对话框无太大的区别,在此就不再累赘。接收文件的操作就由CSendFilesClientThread线程来处理。
    断点续传功能主要在是接收端实现的,接收端接受文件时会创建一配置文件,配置文件记录了源文件在服务器的路径、大小以及传输过的大小。当接收者接收到一个数据包并把数据保存起来后,会更新此配置文件,当文件传输完成后,删除此配置文件。
    接收开始时,接收线程会首先寻找在接收文件目录下是否存在配置文件,如果配置文件存在,验证配置文件中记录的源文件在服务器的路么是否一致,大小是否相等增,接收文件是否存在,大小是否比配置文件中读取大小的值大。如果这些条件都成立,说明可以接着传输,否则从头开始传。
    判断出是否续传信息后,接收线程会将这些信息发送给服务器,服务器收到信息后会作相应的设置,然后发消息通知接收者可以传输数据了。接收者收到信息后,就会向服务器发出请求传输数据的消息,消息中包含已传输的大小。服务器收到消息后就会向接收者发送数据包。
    接收者接收到数据包后,更新已传输的大小,并把文件数据追加到接收文件后面。如果已传输大小等于文件的大小,接收者会向服务器发送传输完毕的消息,然后作结尾工作,通知接收对话框接收完毕,结束线程。如果还未传输完成,接收者会再次向服务器发起传输数据的消息,直到文件传输完毕。
    3.6 共享屏幕模块共享屏幕指的是用户可以让其他好友观看自己的屏幕。
    共享屏幕服务器如下:

    共享屏幕界面的设置与聊天室相似,这里不再累赘。
    共享屏幕模块同样使用UDP套接字,客户端同意加入共享屏幕后,服务器所做的功能就是先把自己屏幕的尺寸发送给好友,然后反复把自己桌面图像发送给好友。
    发送桌面图像给好友之前需要截取桌面图像,为了减小图像大小,方便传输,程序截取桌面图像后把图像处理成8位的位图,宏SHARESCREEN_BITCOUNT定义了截取后的位图颜色位数,默认为8位。另外,在共享屏幕中还用到了数据压缩,这儿使用了网上成熟的Zlib压缩。因为并不是所有的数据都能压缩得更小,因此压缩后还必须判断压缩是否在效,如果无效就不压缩,直接传输入原数据:
    // 压缩数据DWORD dwMaxCompressLength = compressBound( dwDIBLength );BYTE *pCompress = new BYTE[ dwMaxCompressLength ];DWORD dwCompressLength;compress( pCompress, &dwCompressLength, pDIB, dwDIBLength );// 压缩有效,则使用压缩后的数据,否则使用原数据if( dwCompressLength < dwDIBLength ){ BYTE *pTmp = pDIB; pDIB = pCompress; dwDIBLength = dwCompressLength; delete pTmp;}else{ delete [] pCompress;}
    以上代码可以看出只有当压缩后的数据大小小于原数据大小时,才使用压缩数据。这样我们得到的数据其实很小了,已经在UDP传输数据允许范围内,因此程序没有再使用分包发送,而是直接一次性发送出去。
    截取出来的图像没有鼠标信息,因此还必须在截取的图像上画上鼠标:
    // 画鼠标CPoint mouse;GetCursorPos( &mouse );::DrawIcon( hdc, mouse.x - 10, mouse.y - 10, ::GetCursor() );
    共享屏幕客户端如下:

    客户端的处理很简单,直接将接收到的图像数据经过解压后贴到对话框的客户区就行。客户端没有什么难点,做法都很正常,因此就不再介绍。
    3.7 白板模块白板服务器如下:

    白板服务器与聊天室服务器类似,相同的地方就不一一介绍。服务器对话框左边是工具和属性按钮,这些按钮都是自绘按钮,自绘按钮前面已经做过介绍,这儿就再说明。中间的画板是重载CStatic后得到的自绘CCanvasStatic。服务器对话框有三个主要的成员变量:
    TOOL m_emTool; // 工具int m_nWidth; // 线宽COLORREF m_clDrawColor; // 颜色
    左边的工具和属性按钮对这三个变量相对应,这些按钮控制着画图的属性。
    绘图操作在CCanvasStatic中处理,绘画结果再传给服务器对话框,服务器对话框然后把绘图信息发送给所有在白板中的好友。同样的,服务器收到绘图信息后,会反绘图信息传递给画板类CCanvasStatic,让绘出图形,然后服务器再将接收到的绘图信息发送给其他好友。
    白板客户端如下:

    客户端与服务器功能相似,而且更简单,客户端绘画后把只把绘图信息发送给服务器,在接收到服务器发送过来的绘图信息后再把绘图信息反应到画板上。
    3.8 音、视频模块这个模块重点介绍如何解决音频的连续和音、视频同步的问题。
    界面设计如下:

    要和好友视频聊天,自己或好友必须至少一方有摄像头,在主对话框初始化的时候进行判断是否有摄像头:
    m_hWndVideo = capCreateCaptureWindow( NULL, WS_CHILD, 0, 0, 1, 1, m_hWnd, IDD_CAPTUREVIDEO );if( capDriverConnect( m_hWndVideo, 0 ) ){ m_bCamera = TRUE; capDriverDisconnect( m_hWndVideo );}
    思路是通过与序号为第一个摄摄头连接,如果连接成功,说明有摄像头,否则不存在摄像头。测试是否有摄头,有更好的代码的,但程序为了方便开发,只是简单的认为一台电脑上只有一个摄像头,以下都是如此处理的,不再说明。
    从以上代码可以看出,视频聊天代码中使用capCreateCaptureWindow()函数,这是VFW中的函数,本程序中的视频聊天都是采用VFW模式来设计的。关于VFW的使用,本论文限于篇幅,就不再介绍。下面介绍视频聊天的构架。
    本程序允许同多个好友进行视频聊天,而一个摄像头只能被连接一次,所以一个摄像头供所有视频聊天对话框共享,出于此,只能在主对话框中连接摄像头,把摄像头捕获到的数据分发给所有视频聊天对话框。
    当向好友发起视频聊天请求或好友向自己发起视频聊天请求时,首先会判断自己是否有摄像头,如果有,再判断之前是否已连接摄像头,如果没有,这时先连接摄像头,然后处理其他的:
    if( !m_bConnectCamera ){ if( capDriverConnect( m_hWndVideo, 0 ) ) { // 设置视频的大小 BITMAPINFO bmpInfo; capGetVideoFormat( m_hWndVideo, &bmpInfo, sizeof( BITMAPINFO ) ); bmpInfo.bmiHeader.biWidth = VIDEOCHAT_VIDEO_WIDTH; bmpInfo.bmiHeader.biHeight = VIDEOCHAT_VIDEO_HEIGHT; … capSetVideoFormat( m_hWndVideo, &bmpInfo, sizeof( BITMAPINFO ) ); … // 连接上视频 m_bConnectCamera = TRUE; }}
    连接上视频后,首先设置摄像头的分辨率,这儿使用的是宏VIDEOCHAT_VIDEO_WIDTH和VIDEOCHAT_VIDEO_HEIGHT定义的大小,默认为320x240。
    连接上视频后,程序设置摄像头为流模式捕获画面,摄像头定时捕获画面,捕获到画面后,会调用回调函数EncodeCallback():
    LRESULT WINAPI EncodeCallback( HWND hWnd, LPVIDEOHDR lpVHdr ){ if( lpVHdr->dwFlags & VHDR_DONE ) { … // 通过主对对话框更新所有视频聊天对话框画面 pDlgMain->UpdateVideoPicture( ::GetTickCount(), bmpInfo, lpVHdr->lpData, lpVHdr->dwBufferLength ); } return 1;}
    在此回调函数中,调用主对话框的UpdateVideoPicture()函数,UpdateVideoPicture()函数只是简单把捕获到的视频数据分发给所有的视频聊天对话框,视频聊天对话框把视频数据显示到自己视频区域,然后向好友发送视频数据。好友接收到视频数据后,再把视频数据显示到对应的区域。不断的发送数据、接收数据、更新区域,这样就达到了视频聊天的目的。
    音频聊天需要处理连续的问题,因为在服务器端是录音,再发送到客户端播放,如果录完音处理发送,再接着录音,这样中间就会有空隙,造成录的音断断续续。所以音频聊天本程序使用的是双缓冲录音,多缓冲接收录音数据的方式。
    音频聊天采用的是WaveX系列API函数来捕获音频数据,在主对话框初始化的时候就准备两个缓冲区来接收录音数据:
    m_pWaveHdr1 = ( PWAVEHDR )new char[ sizeof( WAVEHDR ) ];m_pWaveHdr2 = ( PWAVEHDR )new char[ sizeof( WAVEHDR ) ];…waveInPrepareHeader( m_hWaveIn, m_pWaveHdr1, sizeof( WAVEHDR ) );waveInPrepareHeader( m_hWaveIn, m_pWaveHdr2, sizeof( WAVEHDR ) );
    一个缓冲区录音完毕,程序会收到WaveInData消息,程序就可以调用函数来处理音频数据,同时系统会继续录音,因为另一缓冲区还可以接收数据:
    void CInstantMessagingDlg::WaveInData( WPARAM wParam, LPARAM lParam ){ DWORD dwTickCount = m_dwTickTime - 1200; /// 得到录音的时间 m_dwTickTime = ::GetTickCount(); /// 每个视频窗口发送声音 int nVideoChatDlgIndex = 0; for( nVideoChatDlgIndex = 0; nVideoChatDlgIndex < m_arrVideoChatDlg.GetSize(); nVideoChatDlgIndex++ ) { m_arrVideoChatDlg.GetAt( nVideoChatDlgIndex )->SendAudioData( dwTickCount,( ( PWAVEHDR )lParam )->lpData,( ( PWAVEHDR )lParam )->dwBufferLength ); } /// 将此WAVEHDR再添加进录音设备 waveInAddBuffer( m_hWaveIn, ( PWAVEHDR )lParam, sizeof( WAVEHDR ) ); return; }
    处理完接收到音频数据的缓冲区后,继续把此缓冲区添加到录音中,这样就可以循环录音。
    在客户端采用多缓冲区来接收音频数据,这些自缓冲区组成一个首尾相连的圆圈缓冲区,用两个指针来表示接收和播放的缓冲区序号:
    // 申请音频数据的缓冲区for( int nIndex = 0; nIndex < AUDIO_BUFFER_BLOCK; nIndex++ ){ char *pAudioBuffer = new char[ AUDIO_BUFFER_SIZE + sizeof( DWORD ) ]; m_arrAudioBuffer.Add( pAudioBuffer );}m_nReceive = 0;m_nPlay = 0;
    缓冲区的块数是AUDIO_BUFFER_BLOCK宏定义,刚开始时接收和播放的指针都为0。
    下面是接收到音频数据的代码:
    // 接收到音频数据,保存起来void CVideoChatDlg::SaveAudioData( char *pData, DWORD dwDataLength ){ CDebug debug( "SaveAudioData" ); // 保存接收到的音频数据 char *pSaveData = m_arrAudioBuffer.GetAt( m_nReceive ); memcpy( pSaveData, pData, dwDataLength ); // 新的接收区块号 m_nReceive = ( m_nReceive + 1 ) % AUDIO_BUFFER_BLOCK; // 接收追上播放,播放往前走 if( m_nReceive == m_nPlay ) { m_nPlay = ( m_nPlay + 1 ) % AUDIO_BUFFER_BLOCK; } if( !m_bPlaying && m_bReceiveAudio ) { m_bPlaying = TRUE; char *pPlayData = m_arrAudioBuffer.GetAt( m_nPlay ); memcpy( &m_dwRecordAudioTickTime, ( BYTE * )pPlayData, sizeof( DWORD ) ); m_dwPlayAudioTickTime = ::GetTickCount(); m_pWaveHdr1->lpData = pPlayData + sizeof( DWORD ); waveOutPrepareHeader( m_hWaveOut, m_pWaveHdr1, sizeof( WAVEHDR ) ); waveOutWrite( m_hWaveOut, m_pWaveHdr1, sizeof( WAVEHDR ) ) ; m_nPlay = ( m_nPlay + 1 ) % AUDIO_BUFFER_BLOCK; }}
    接收到音频数据先保存起来,再判断两个指针的位置关系,如果接收到的指针追上了播放指针,则播放指针往前走,如果客户端允许播放声音,而且之前接收到的音频数据已播放完毕,那么接收到数据这时就可以继续播放了。
    至此,视频和音频可以可以正常的显示和播放了。但还有个大问题,就是视频和音频不同步,视频可以说是实时的,但视频需要录制完毕后发送到客户端才播放的,所以音频比视频要慢上几秒。
    为了解决这个问题,程序在每个视频数据和音频数据上都加上了服务器端的时间标签。在显示视频画面时比较时间标签决定视频画面处理。具体的处理如下所述:
    开始播放音频时,记下客户端播放的时间和此音频数据在服务器端录制的时间,在视频数据到达时,记下此时客户端时间,计算出音频已播放的时长,然后在音频数据在服务器端录制的时间上加上这个时长,就得到标准的画面在服务器端时间,然后用这个时间比接收到的视频数据中的时间相比较:

    如果视频时间比标准时间大,说明视频画面还有播放音频后面,把些画面保存起来,不显示在对话框的好友区域;如果视频时间比标准时间小,说明音频播放到视频的后面了,此视频画面可以丢弃,也不显示,但必须从保存起来的视频画面中找到与标准时间最接近的视频画面来显示;如果视频时间与标准时间相等,或者视频时大于标准时间大但是已经进行过第2种情况的处理,则可以显示此视频数据。注意此种情况与第1种情况的区别,第1种情况是没有进行过第2种情况的处理。
    用以上的方法来处理音频和视频数据,就可以达到音、视频的同步,主要代码如下:
    CString strVideoData( ( LPCTSTR )pVideoReceiveData, dwDataLength );m_lstVideoData.AddTail( strVideoData );CString strData;BITMAPINFO bmpInfo;BYTE *pRGBData;// 如果没有播放音频,则直接贴图if( !m_bPlaying ){ strData = m_lstVideoData.GetHead(); m_lstVideoData.RemoveHead(); BYTE *pVideoData = ( BYTE * )strData.GetBuffer( strData.GetLength() ); memcpy( &bmpInfo, pVideoData + sizeof( DWORD ), sizeof( BITMAPINFO ) ); pRGBData = new BYTE[ bmpInfo.bmiHeader.biSizeImage ]; YUY2_RGB( pVideoData + sizeof( DWORD ) + sizeof( BITMAPINFO ), pRGBData, bmpInfo.bmiHeader.biSizeImage * 4 / 6 ); strData.ReleaseBuffer( -1 );}// 正在播放音频,需要对视频数据进行处理else{ DWORD dwStandardVideoTime = ::GetTickCount() - m_dwPlayAudioTickTime + m_dwRecordAudioTickTime; DWORD dwVideoTime; BOOL bSmallerThanStandardVideoTime = FALSE; // 循环判断每个视频时间与标准播放时间的距离 int nVideoDataCount = m_lstVideoData.GetCount(); for( int nIndex = 0; nIndex < nVideoDataCount; nIndex++ ) { strData = m_lstVideoData.GetHead(); BYTE *pVideoData = ( BYTE * )strData.GetBuffer( strData.GetLength() ); memcpy( &dwVideoTime, pVideoData, sizeof( DWORD ) ); // 如果视频时间与标准时间相等或大于但已经过了一个小于的 if( ( dwVideoTime > dwStandardVideoTime && bSmallerThanStandardVideoTime ) ||dwVideoTime == dwStandardVideoTime ) { m_lstVideoData.RemoveHead(); memcpy( &bmpInfo, pVideoData + sizeof( DWORD ), sizeof( BITMAPINFO ) ); pRGBData = new BYTE[ bmpInfo.bmiHeader.biSizeImage ]; YUY2_RGB( pVideoData + sizeof( DWORD ) + sizeof( BITMAPINFO ), pRGBData, bmpInfo.bmiHeader.biSizeImage * 4 / 6 ); strData.ReleaseBuffer( -1 ); break; } // 如果视频时间小于标准时间,丢弃视频数据 else if( dwVideoTime < dwStandardVideoTime ) { bSmallerThanStandardVideoTime = TRUE; m_lstVideoData.RemoveHead(); strData.ReleaseBuffer( -1 ); continue; } // 如果视频时间大于标准时间,则不作处理,直接返回 else if( dwVideoTime > dwStandardVideoTime ) { strData.ReleaseBuffer( -1 ); return; } }}
    3.9 调试模块为了方便调试,在程序中添加了一个用于调试的类CDebug,它具有的成员变量和成员函数如下:
    class CDebug {private: CString m_strMessage; // 信息public: CDebug(); CDebug( CString strMessage ); virtual ~CDebug();};// 构造和析构函数如下CDebug::CDebug( CString strMessage ){ m_strMessage = strMessage; CString strTrace; strTrace = "run in : " + m_strMessage + "\n"; TRACE( strTrace );}CDebug::~CDebug(){ CString strTrace; strTrace = "run out : " + m_strMessage + "\n"; TRACE( strTrace );}
    利用类的构造、析构函数和类的生命周期,我们可以很方便的设计如上的CDebug类,用法如下:
    { CDebug debug( “test” );}
    在函数或语句组的开始处定义一个CDebug变量,当程序运行到变量定义处的时候,会打印出run in : test,当程序执行完函数或语句组时,会打印出run out : test,这样我们就可以知道程序运行到何处,极大的方便了我们对程序的调试。
    1 评论 25 下载 2018-10-02 20:36:20 下载需要16点积分
  • CG树顶端节点集群的设计与实现

    摘要本文描述了一个CG树顶端节点集群的设计与实现,主要内容有:

    详细阐述了顶端节点集群的设计方案。该方案维持集群节点间的通信,当集群内节点失效时能及时发现;负载均衡器(LB)能够将客户端的请求通过一定的调度策略转发给下面的真实服务器(RS);能够保证所有真实服务器上的数据库的一致性。
    实现了一个顶端节点集群的系统原型,技术分析和实验结果表明,该集群系统具有稳定性和高可用性。


    搭建了一个测试系统,对客户端的请求进行分析处理,返回客户端需要的信息,并测试顶端节点集群的性能。根据测试结果分析顶端节点和顶端节点集群之间的性能差异,证明设计方案的有效性。
    [关键词] 集群 CG树 LVS 顶端节点集群
    AbstractThis paper describes the design and implementation for the top node cluster in CG-Tree. The major contents include:

    A detail design solution for the top node cluster is described. It can maintain the communications among cluster nodes and detect node faults whenever cluster fails. The load balancer (LB) can forward the client’s request to the real server (RS) by selected strategy. The databases on all real servers are kept consistency.A prototype of the top nodecluster is implemented. The technical analysis and experimental result showthat the cluster is with stability and highly availability.A test system is built to analyze and process the request from clients, return the required information to clients, and test the performance of the top node cluster. Accorded to the test results, the difference of the performance between the top node and the top node cluster is analyzed to show the effectiveness of the design solution.
    [Keywords] Cluster, CG-Tree, LVS, Top Node Cluster
    第一章 引言1.1 研究背景1.1.1 集群技术Internet 的飞速发展给网络带宽和服务器带来巨大的挑战。从网络技术的发展来看,网络带宽的增长远高于处理器速度和内存访问速度的增长。热门网站引发前所未有的访问流量,很多网络服务因为访问次数爆炸式地增长而不堪重负。一些新兴的网络技术如视频点播,动态网页,CGI 等带来更大的网络带宽需求。这时单一的计算机系统,如单处理器或者SMP 系统,往往因为巨大的网络负载而不堪重负,其存在着诸多的问题,主要表现在:扩展能力差并且扩展的代价昂贵;升级导致的服务中断会带来巨大的商业损失,并造成原有计算资源的浪费;单点故障发生的概率较高导致无法提供持续的可靠服务。解决网络服务的可伸缩性和可靠性已是非常紧迫的问题。
    通过高性能网络或局域网互联的服务器集群[1]正成为实现高可伸缩的、高可用网络服务的有效结构。这种松耦合结构的服务器集群系统有下列优点:

    提高性能
    一些计算密集型应用,如:天气预报、核试验模拟等,需要计算机有很强的运算处理能力。这时,一般都使用计算机集群技术,集中几十台甚至上百台计算机的运算能力来满足要求。提高处理性能一直是集群技术研究的一个重要目标之一。随着网络的普及和计算机硬件性能的不断提高,集群系统的应用领域越来越广,目前集群系统主要应用于Web服务、Cache服务、媒体服务、科学计算以及数据库应用等领域。
    降低成本
    组成集群系统的 PC 服务器或RISC 服务器和标准网络设备因为大规模生产降低成本,价格低,具有很高的性能价格比。若整体性能随着节点数的增长而接近线性增加,该系统的性能价格比接近于PC 服务器。所以,这种松耦合结构比紧耦合结构的多处理器系统具有更好的性能价格比。
    提高可扩展性
    用户若想扩展系统能力,不得不购买更高性能的服务器,才能获得额外所需的CPU和存储器。如果采用集群技术,则只需要将新的服务器加入集群中即可,对于客户来看,服务无论从连续性还是性能上都几乎没有变化,好像系统在不知不觉中完成了升级。
    增强可靠性
    集群技术使系统在故障发生时仍可以继续工作,将系统停运时间减到最小。集群系统在提高系统的可靠性的同时,也大大减小了故障损失。

    目前集群系统因其诸多的优点,已被广泛应用于 Web 服务,Cache 服务,媒体服务,科学计算及数据库等领域。
    1.1.2 CG树简介对于视频点播这种大流量的服务需求,服务器这一端的带宽将在很大程度上影响媒体服务的质量,如果将服务器节点限制在单个局域网内,媒体集群将受到网关流量的制约而提供很有限的服务。
    CG树[2,3]是适用于分布式集群的一种模型,CG树将集群的节点分级分层,组织成以前端调度节点为根节点,服务器池的所有节点分组管理的树形结构。该模型下前端负载均衡器不必跟服务器池的所有节点通信,有效减少了前端节点的负载。集群节点采用分级分层的结构,能有效减轻集群前端负载均衡器的负担,并能有效地减少跨网络集群节点间的通信开销,具有较高的实用价值,是跨网络集群的一个较好的解决方案。
    如图1-1是一棵CG 树的结构图。

    在以往的CG树的实现中,顶端节点(如图1-2)需要负责处理客户端的请求,数据库的查询,CG森林的维护,资源的调度等任务,导致顶端节点的压力非常大,成为了整个系统的瓶颈。其中,数据库的查询操作占据了节点的绝大多数的资源。对于此,本文对顶端节点的性能做出过测试,在千兆局域网内选择了1台计算机作为顶端节点,其详细配置为:

    操作系统:Ubuntu10.10Linux内核版本:Linux 2.6.35CPU:Intel® Core™ 2 Quad CPU Q8400 2.66GHz(四核)内存:2GB数据库:MySQLServer 5.1
    然后客户端不断地发送请求,由客户端记录发送请求和收到响应的数量。实验结果表明,顶端节点在满负荷的条件下,每秒钟只能执行数据库查询一万次左右,即每秒钟可以处理客户端的一万次左右的资源请求。如果系统面向全世界服务,其性能显然无法满足客户的需求,因此,顶端节点成为了整个系统的瓶颈。

    1.1.3 系统的提出为了解决这一问题,本文提出了一种将顶端节点修改为顶端节点集群(如图1-3)的做法。这种做法的思路是,用分布的做法让多台服务器组成顶端节点集群来分担原来单个顶端节点的工作,从而提高系统的整体性能。顶端节点集群由两层计算机组成,第一层仅仅负责请求的转发,第二层由多台计算机组成,作为真实服务器,负责请求的处理,其数量取决于整个CG树的规模。由于顶端节点仅仅在选择真实服务器的时候需要与客户端通讯,真正提供服务时是由真实服务器与客户端直接进行通讯的,因此,基于CG树结构的服务器在管理规模上几乎没有限制。

    1.2 研究工作本文设计与实现了一种CG树顶端节点集群的设计方案,主要包括以下内容:

    集群整体架构的设计:设计了一种可用的集群方案,引入了心跳机制,通过心跳的传递使负载均衡器了解每个节点的运行状态,同时通过TCP消息的传递,保证了各个节点数据库的一致性。调度策略的设计:实现了轮转调度、源地址哈希调度、加权轮转调度三种种可用的调度策略。集群的实现:使用UNIX/Linux下Socket编程技术以及多线程技术,实现了本文设计的集群系统,包括负载均衡器和真实服务器两大模块的实现以及数据库连接池的实现。性能测试:模拟了一个基于CG树的视频点播系统,在系统正常工作的条件下,测试了网络的丢包率,测试了顶端节点和顶端节点集群的性能,分析并比较了顶端节点集群与以往顶端节点之间的性能差异,并且对数据库的一致性进行了简单的测试。最后,本文对顶端节点集群的设计方案和测试结果给出了结论,提出了系统存在的缺点,对未来的研究方向做出了展望。
    1.3 论文结构本论文共有七章。

    第一章为引言。本章首先分析了以往CG树所存在的问题,从而引出了将CG树的顶端节点改为顶端节点集群的想法。第二章为相关技术介绍。主要介绍了本文中用到的相关技术。第三章为系统设计。本章主要介绍了CG树顶端节点集群的设计方案。第四章为系统实现。本章主要介绍了CG树顶端节点集群的具体实现。第五章为实验部分。在校园网的实验环境下进行了相关测试,并对实验结果进行了分析。第六章为总结与展望。提出了本文研究工作的结论和不足,并对课题给予展望。最后是参考文献与致谢。
    第二章 相关实现技术简介2.1 Socket通信简介所谓socket[4]通常也称作“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求。有两种常用的Socket类型:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式Socket是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

    用户数据报协议(UDP)
    UDP是一个简单的传输层协议,应用进程往一个UDP套接字写入一个消息,该消息随后被封装到一个UDP数据报,该UDP数据报进而又被封装到一个IP数据报,然后发送到目的地。UDP不能保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数据报只到达一次。
    传输控制协议(TCP)
    TCP不同于UDP,它提供客户与服务器之间的连接,TCP客户首先与服务器建立一个连接,然后通过该连接与服务器交换数据,然后终止这个连接。TCP提供了可靠的数据传输和流量控制,但是其开销要大于UDP。

    在本文的设计方案中,心跳信息的传递使用UDP协议,数据库更新信息和节点之间的其他信息的传递使用TCP协议。
    2.2 多线程技术简介由于本文涉及的系统涉及到多个功能模块的并发操作,对于并发操作主要有两种方式实现:多进程结构和多线程结构。在多进程结构中,程序通过创建子进程来实现并发操作,如此可以充分发挥多核CPU的性能。然而创建子进程的开销是非常昂贵的,每创建一个子进程,需要把父进程的内存映像复制到子进程,且进程间的通信非常复杂。而线程则可以解决上述问题,线程的创建和切换的开销要比进程小得多。同一进程内的所有线程可以共享相同的全局内存,使得线程之间易于共享信息,随之而来的同步问题则可以通过互斥量来解决。
    为了提高系统运行的效率,本系统将采用多线程技术实现任务的并发操作。本系统使用的是POSIX线程[5],也称为Pthread。
    2.3 MySQL数据库简介MySQL[6]是最流行的开放源码关联数据库管理系统。关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大的仓库内。这样就增加了速度并提高了灵活性。MySQL的SQL指得是“结构化查询语言”。SQL是用于访问数据库的最常用标准化语言,它是由ANSI/ISO SQL标准定义的。MySQL数据库服务器具有快速、可靠和易于使用的特点。
    第三章 系统设计3.1 整体结构3.1.1 整体结构的设计以往的CG树由一个顶端节点负责控制一个CG森林,顶端节点需要负责整个集群系统的维护、客户端请求的处理以及资源的调度等等,这样使得顶端节点的负担很重,成为整个系统的瓶颈,为了解决这一问题,本文提出了将顶端节点改为顶端节点集群的办法,其结构图如图3-1所示。

    顶端节点集群为两层结构,第一层是一台负载均衡器,第二层由若干台真实服务器组成。负载均衡器负责接收第二层节点的心跳信息,保存它们的相关信息。第二层的每台服务器都维持一个同样内容的数据库,保存CG树叶子节点上相关资源的信息。由负载均衡器接受客户端的请求,然后按照一定的转发策略转发给第二层的真实服务器,真实服务器通过数据库查询操作得到保存客户端请求资源的CG树节点,将查询结果发送给客户端。
    3.1.2 与LVS的比较LVS[7]是基于IP层负载均衡技术的典型代表。用户通过虚拟IP地址(Virtual IP Address)访问服务时,访问请求的报文会到达负载均衡器,由它进行负载均衡调度,从一组真实服务器选出一个,将报文的目标地址Virtual IP Address改写成选定服务器的地址,报文的目标端口改写成选定服务器的相应端口,最后将报文发送给选定的服务器。真实服务器的回应报文经过负载均衡器时,将报文的源地址和源端口改为Virtual IP Address和相应的端口,再把报文发给用户;或者真实服务器直接将回应报文的源地址和源端口改为Virtual IP Address和相应的端口,发给用户。
    以LVS为代表的典型集群在结构上一般分为三层:负载调度器(load balancer),它作为整个集群系统对外的前端,负责将客户的请求发送到后台的一组服务器上执行,客户不需要知道真实服务器的IP地址;服务器池(server pool),位于后台的一组真正提供应用程序服务的服务器,他们之间通过传递心跳信息来维持可用的服务器列表;共享存储(shared storage),它为服务器提供一个共享的存储区,这样很容易使得服务器池拥有相同的内容,提供相同的服务。
    图2-1展示了一个LVS系统的基本结构。

    LVS集群由负载调度器、服务器池和共享存储三大部分组成,本文的设计方案借鉴了LVS,同样有一个负载调度器,一组真实服务器。因为CG树顶端节点处理客户请求仅仅需要查询数据库,因此,不需要设置共享存储。
    对于MySQL分布式数据库,LVS的一般做法是使用高冗余的同步集群(MySQL Cluster)或者相对简单的异步集群(MySQL replication)来实现。同步集群的特点是配置和管理方便,不会丢失数据,但是其需要较多的内存且速度一般。异步集群的特点是使用主从(mater/slave)模式,速度较快,但是往往会导致主数据库的压力过大且可能会丢失数据。无论哪种数据库集群,其都会产生较大的数据冗余。
    在本系统的应用中,客户端的请求是查询所需资源的地址,而数据库的更新操作只有在CG树叶子节点上的资源更新时才会发生。因此,本文根据这个特点设计了一种简单的数据库的维护方式,即每台真实服务器保留一个数据库的完整副本,从而提高数据库的查询效率。
    3.2 心跳的设计心跳机制[8]是高可用集群的重要技术之一。心跳周期性的检测集群中节点机器的工作状态,当节点机器的工作状态发生改变时,能够通知集群软件的其他部件。
    本集群将采用心跳机制来维持系统的高可用性,由第二层的真实服务器周期性地向第一级的负载均衡器发送心跳信息,以告知负载均衡器其最新的状态。负载均衡器接收到心跳信息后,更新节点的状态信息,记录节点的心跳时间,同时设置一个心跳超时时间,其一般为心跳周期的2到3倍,若负载均衡器在超时时间内没有接收到真实服务器的心跳信息,则认为该节点发生故障,将节点状态设为故障。在实际应用上,可在心跳信息中捎带其他信息,如节点的工作负载等。
    因为心跳的发送频繁,为了减少网络通信的开销,本文使用UDP协议来进行心跳的传递。
    3.3 数据库一致性的保持因为本文的设计方案中,集群中第二层的所有节点使用内容相同的数据库,因此保持数据库的一致性就是我们要关心的问题之一。
    在本文的设计方案中,负载均衡器和真实服务器都要创建一个用于数据库更新的线程,CG树节点向负载均衡器发出数据库更新消息,当负载均衡器接受到数据库更新消息后,通过TCP将更新消息转发给每一台真实服务器,由真实服务器执行数据库的更新操作,从而保证每台服务器数据库的一致性。
    3.4 调度策略的设计本文为了提供整个集群的可用性,共实现了三种请求的转发调度策略[9],分别是:
    3.4.1 轮转调度轮转调度是最简单的调度策略,当负载均衡器接收到客户端的请求时,将请求轮询式的转发给第二层的节点。使用这种调度策略,负载均衡器的负担最小,同时,当客户端请求资源少而频繁时,此调度策略具有非常高的效率。但是,当请求服务时间变化比较大时,轮转调度算法容易导致服务器间的负载不平衡。
    // 轮转调度算法// 假设有一组服务器S = {S0, S1, …, Sn-1},一个指示变量i表示上一次选择的服务器,W(Si)表示服务器Si的权值,大于0表示服务器可用。变量i被初始化为n-1,其中n > 0j = i;do { j = (j + 1) mod n; if (W(Sj) > 0) { i = j; return Si; }} while (j != i);return NULL;
    3.4.2 源地址哈希调度通过源调度哈希转发即通过哈希函数将客户端的IP映射到唯一的一台服务器上。该调度算法可以使请求得到较均匀的分布。
    // 源地址哈希调度算法// 假设有一组服务器S = {S0, S1, …, Sn-1},W(Si)表示服务器Si的优先级,0表示服务器不可用。ServerNode[]是一个有256个桶(大小可调整)的Hash表,变量i表示上一次选择的服务器,变量ip表示客户端IP,getNext表示使用轮转获取下一个可用节点。算法的初始化是将所有服务器顺序、循环地放置到ServerNode表中j = hash(ip);if (W(Sj) == 0) { j = getNext(i);}Return Sj;hash(unsigned int ip) { return (ip * 2654435761) & HASH_TAB_MASK;}// 其中,2654435761UL是2到2^32 (4294967296)间接近于黄金分割的素数。// 2654435761 / 4294967296 = 0.618033987
    3.4.3 加权轮转调度为了解决集群第二层服务器性能可能存在差异的问题,可以使用加权轮转调度。其具体实现就是根据每台服务器的性能为每个节点设置一个优先级,性能越好,优先级越高。当服务器向负载均衡器注册时,同时告知优先级信息。在客户端发来请求时,负载均衡器根据优先级转发给第二层的真实服务器。
    // 加权轮转调度算法// 假设有一组服务器S = {S0, S1, …, Sn-1},W(Si)表示服务器Si的优先级,变量i表示上一次选择的服务器,变量cw表示当前调度的权值,max(S)表示集合S中所有服务器的最大权值,gcd(S)表示集合S中所有服务器优先级的最大公约数。变量i初始化为-1,cw初始化为零。while (true) { i = (i + 1) mod n; if (i == 0) { cw = cw - gcd(S); if (cw <= 0) { cw = max(S); if (cw == 0) return NULL; } } if (W(Si) >= cw) return Si;}
    例如,有三个服务器A、B和C分别有权值4、3和2,则在一个调度周期内调度序列为AABABCABC。当第二层服务器性能差异较大时,相对于轮询转发,此转发策略可以提高每台服务器的使用效率。
    第四章 系统实现4.1 LB模块的实现LB模块运行于集群第一层的负载均衡器上,负责转发客户端的请求、维持集群的运作、转发数据库更新消息等。
    4.1.1 LB模块的主要数据结构LB模块的主要数据结构为:
    class TopNode{ /**< 存放节点信息的容器 */ vector<Node *> nodeVector; /**< 转发请求时,下一个节点的序号 */ vector<Node *>::size_type m_nextNode; /**< m_nextNode的互斥锁 */ pthread_mutex_t m_nextNodeLock; /**< property的互斥锁 */ pthread_mutex_t m_propertyLock; /**< threadNum的互斥锁 */ pthread_mutex_t m_threadNumLock; /**< 数据库更新的互斥锁 */ pthread_mutex_t m_dbUpdateLock; /**< 接收心跳的端口 */ int m_heartbeatPort; /**< 接收消息的端口 */ int m_messagePort; /**< 接受数据库更新消息的端口 */ int m_dbPort; /**< 对外提供服务的端口 */ int m_servicePort; /**< 客户端请求转发策略 1:轮转调度 2: 源地址哈希转发 3:加权轮转调度 */ int m_policy; /**< 提供服务的套接字 */ int m_sockfd; /**< 接受请求计数 */ int m_count; /**< 二层服务器的数量 */ int m_rsNum; /**< 数据库连接池 */ ConnPool *m_pool; };/** @brief the arg of the function thread_handleRequest */struct requestArgs{ /**< 指向TopNode */ TopNode *topNode; /**< 客户端的地址结构*/ struct sockaddr_in cliaddr; /**< 客户端的socket套接字*/ int connfd; /**< 客户端的请求内容*/ string *request; };class Node{ /**< store node's priority information */ s_propertyMesg m_sProMesg; /**< 数据库更新的套接字 */ int m_sockdbfd; /**< 节点ID */ string ID; /**< 节点状态 */ int status; /**< 节点接收请求的端口 */ int m_servicePort; /**< 节点上次发出心跳的时间 */ time_t heartBeatTime; /**< 节点接收请求的地址结构 */ struct sockaddr_in m_serviceAddr; /**< 节点状态的互斥锁 */ pthread_mutex_t statusLock; /**< 节点心跳时间的互斥锁 */ pthread_mutex_t heatBeatTimeLock; };
    4.1.2 LB模块的接口RS模块提供的接口为:
    class TopNode{ /**< 初始化 */ void init(); /**< 轮询获得下个可用节点 */ Node *getNextNode(); /**< 读取配置文件 */ void readConf(char *); /**< 对外提供服务 */ void *serve(void *arg); /**< 接收心跳 */ void *receiveheartbeat(void *arg); /**< 接收消息 */ void *receiveMessage(void *arg); /**< 检查各字节点状态 */ void *changeStatus(void *arg); /**< 对请求进行处理 */ void *handleRequest(int, const struct sockaddr_in *, string *); /**< 对消息进行处理 */ void *handleMesg(int, const struct sockaddr_in *); /**< 通过IP获得下个可用节点 */ Node *getNextNodeByIP(uint32_t); /**< 通过负载获得下个可用节点*/ Node *getNextNodeByLoad(); /**< 通过优先级获得下个可用节点*/ Node *getNextNodeByProperty(); /**< 处理数据库更新消息*/ void *handledbUpdate(int); /**< 接受数据库更新消息*/ void *dbUpdate(void *arg);};class Node{ /**< 连接服务器,返回套接字 */ int connectServer(); /**< 关闭套接字 */ void closeServer(int); /**< 向服务器发送消息 */ void sendtoServer(int, char *, size_t);};
    4.1.3 LB的启动流程
    读取配置文件。从配置文件中读取服务端口等相关信息,同时读取数据库的相关配置。初始化数据。初始化程序运行所需要的数据,包括网络地址结构、数据库连接池等。创建提供服务的线程。该线程用于处理客户端发过来的请求,并将请求的内容转发给第二层的真实服务器。创建接受数据库更新消息的线程。该线程用于处理CG树发送过来的数据库更新消息,并将消息转发给每一台服务器。创建接受消息的线程。该线程用于接受所有节点发送过来的消息,并对消息进行处理。创建控制线程。该线程用于控制整个程序的运行,包括日志更新,节点状态的检测等功能。创建接受心跳的线程。该线程接受真实服务器发送的心跳信息,并记录真实服务器的状态。
    4.2 RS模块的实现RS模块运行于集群第二层的真实服务器上,负责处理客户端的请求,并把处理结果发送给客户端。
    4.2.1 RS模块的主要数据结构RS模块的数据结构为:
    class RealServer{ /**< 负载均衡服务器的地址 */ char m_lbIP[20]; /**< 负载均衡服务器的接收心跳的端口 */ int m_lbHbPort; /**< 负载均衡服务器的接收消息的端口 */ int m_lbMessagePort; /**< 节点ID */ string m_ID; /**< 提供服务的端口 */ int m_servicePort; /**< 接收数据库更新的端口 */ int m_dbPort; /**< 数据库连接池 */ int m_property; /**< 数据库连接池 */ ConnPool *m_pool; /**< 处理请求数 */ int m_connNum; /**< 处理数据库更新次数 */ int m_dbNum; /**< 提供服务的套接字 */ int m_sockfd; /**< 日志文件 */ FILE *m_log; };/**< 用于创建线程时传递参数 */struct requestArgs{ /**< 指向RealServer */ RealServer *realServer; /**< 客户端的地址结构*/ struct sockaddr_in cliaddr; /**< 客户端的socket套接字*/ int connfd; /**< 客户端的请求内容*/ string *request; };
    4.2.2 RS模块的接口RS模块提供的接口为:
    /**< 发送心跳 */void *heartbeat(void *arg);/**< 向LB注册 */int registerToTopNode();/**< 初始化服务器 */void init(); /**< 对外提供服务 */void *serve();/**< 处理客户端请求 */void *handleRequest(int, const struct sockaddr_in *, string *); /**< 读取配置文件 */void readConf(char *); /**< 数据库更新 */void *dbUpdate(void *);
    4.2.3 RS的启动流程
    读取配置文件。从配置文件中读取负载均衡器的IP和端口的相关信息,同时读取数据库的相关配置。初始化数据。初始化程序运行所需要的数据,包括网络地址结构、数据库连接池等。创建提供服务的线程。该线程用于处理负载均衡器转发过来的客户端请求,并将处理结果发送给客户端。创建接受数据库更新消息的线程。该线程用于处理负载均衡器发送过来的数据库更新消息,对数据库进行更新。向负载均衡器注册。向负载均衡器发送注册消息,告知负载均衡器自己的服务端口、优先级等信息。创建发送心跳信息的线程。该线程按照一定的周期向第一层的负载均衡器发送心跳信息。
    4.3 数据库连接池的实现数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。在高并发的条件下,数据库连接池可以明显提高数据库操作的效率。因此,为了提高效率,本系统实现了一个简单的数据库连接池。
    4.3.1 数据库连接池的数据结构数据库连接池的数据结构为:
    /** @brief 存放每个连接的地址和状态*/typedef struct _sConStatus{ /**< 数据库连接地址 */ MYSQL *connAddr; /**< 数据库连接状态 0:空闲 1:使用中*/ int status; }sConStatus;class connPool { /**< 数据库地址 */ string m_strHost; /**< 数据库用户名 */ string m_strUser; /**< 密码 */ string m_strPwd; /**< 数据库名 */ string m_strDbName; /**< 数据库服务器端口 */ int m_intMysqlPort; /**< 最大连接数 */ int m_intConnNum; /**< 存放连接的容器 */ vector<sConStatus *> m_vectorConn; /**< 从连接的地址,快速找到索引 */ map<sConStatus *, int> m_mapVI; /**< 从连接快速找到状态 */ map<MYSQL *, sConStatus *> m_mapMysqlScs; /**< 互斥锁 */ pthread_mutex_t m_mutexScs; };
    4.3.2 数据库连接池的接口数据库连接池对外提供的接口为:
    /**< 初始化数据库连接池 */int init(char *strHost, char *strUser, char *strPwd, char *strDbName, int intMysqlPort, int intConnNum);/**< 创建一个数据库连接 */MYSQL *createOneConn();/**< 从连接池取一个连接 */MYSQL *getOneConn();/**< 将连接放回连接池。以便其他人用*/void retOneConn(MYSQL *pMysql);
    第五章 实验及其结果分析5.1 实验环境在千兆局域网内选择了6台计算机,其详细配置为:

    操作系统:Ubuntu10.10Linux内核版本:Linux 2.6.35CPU:Intel® Core™ 2 Quad CPU Q8400 2.66GHz(四核)内存:2GB数据库:MySQLServer 5.1
    5.2 实验测试与分析5.2.1 丢包率测试测试方法:由三台计算机作为客户端,不断的发送UDP请求并记录发出的请求次数,服务器记录收到的请求次数。
    UDP数据报格式:

    测试结果:当发送速率在40万次/秒、50万次/秒、60万次/秒、65万次/秒的条件下,服务器收到包的成功率分别为100%、99.89%、93.19%、84.12%。



    发送速率(万/秒)
    接受成功率




    40
    100%


    50
    99.89%


    60
    93.19%


    65
    84.12%



    结果分析:在丢包率允许的范围内,服务器每秒钟最大可以接受50万次左右的请求,未来整个系统的搭建应该考虑网络的上限。
    5.2.2 顶端节点性能测试测试方法:选择一台计算机作为CG树的顶端节点,同时建立一个数据库,其规模为200万条记录,记录资源的相关信息。客户端不断的发送请求,每次请求1到10个资源,服务器负责查询数据库,将保存资源的CG树节点IP告知客户端。客户端记录收到的响应数,服务器端记录处理的请求数以及数据库查询操作的次数。
    测试结果:在满负荷的条件下,服务器平均每秒钟处理客户端请求1790次,数据库查询操作9836次。
    图5-1记录了顶端节点的CPU、内存以及网络使用等负载情况。

    结果分析:从图5-1中可以看出,CPU的使用率已经达到了90%以上,而每秒钟数据库的查询操作只有不到一万次,整个系统的性能会收到严重的制约。
    5.2.3 顶端节点集群性能测试测试方法:类似于上小节的测试方法,将顶端节点改为顶端节点集群,一台计算机作为负载均衡器,分别选择2、3、4台计算机作为真实服务器,进行同样的测试。
    测试结果:当真实服务器的数量分别为2、3、4台时,整个集群平均每秒钟处理的请求数分别为3746次、5346次、7670次,平均每秒钟数据库查询次数分别为20600次、29405次、42190次。
    满负荷运行时,真实服务器的负载情况如图5-2所示。

    当挂载四台真实服务器时,整个集群最大负荷条件下,负载均衡器的负载情况如图5-3所示。

    结果分析:将顶端节点改为顶端节点集群后,整个系统的处理能力大大加强。表5-1给出了性能测试的统计结果。



    真实服务器数量
    客户端请求数(次/秒)
    数据库查询次数(次/秒)
    性能




    0(即顶端节点)
    1790
    9836
    1


    2
    3746
    20600
    2.09


    3
    5346
    29405
    2.99


    4
    7670
    42190
    4.28



    根据表中数据可以看出,整个系统的性能几乎是和真实服务器的数量成正比的。
    另外,由图5-3所示,因为负载均衡器仅仅负责转发,所以在真实服务器满负荷的条件下,负载均衡器的CPU使用率只有15%左右,在网络条件允许的情况下完全可以挂载更多的服务器,从而继续增强整个系统的处理能力。
    5.2.4 数据库一致性测试测试方法:使用五台计算机组成顶端节点集群,由另外一台计算机向集群发出插入、更新、删除记录的消息,由每台真实服务器统计执行操作的次数,并人工检查几个数据库是否保持了一致性。
    测试结果

    首先测试数据库插入,每次插入一条数据,整个集群的插入效率是每秒钟插入18500条记录。然后测试更新语句的执行,每次更新一条记录,整个集群的更新效率是每秒钟更新16000条记录。
    最后测试删除语句的执行,每次删除一条记录,整个集群的删除效率是每秒钟删除15000条记录。(以上的测试数据会随着数据库的规模改变以及查询条件的改变而有所波动)
    最后检查所有数据库的内容,可以保证所有数据库内容一致。

    结果分析
    由于每台服务器上都有一个数据库,所以数据库更新时,所有的真实服务器都需要进行更新,真实服务器的增加不会使整个集群的效率增加,反而会增加网络的负担。但是由于CG树数据库更新操作较少,大部分的数据库操作都是查询操作,因此数据库一致性保持上所损失的性能是可以接受的。
    5.2.5 数据库更新与查询综合测试
    测试方法:使用五台计算机组成顶端节点集群,由另外的计算机分别发出客户端请求和数据库更新消息,统计满负荷下集群处理客户请求的数量。
    测试结果:当以每秒钟1000次左右的速率发送数据库更新消息时,整个集群每秒钟可以处理客户请求7600次左右,数据库查询42000次左右。
    结果分析:当数据库更新操作数量有限时,对整个集群客户端请求处理能力影响不大。考虑到CG树节点更新频率不高,因此本文的集群方案是可行的。

    第六章 总结与展望本章对本文的工作做了一个全面的总结,并指出了不足之处和下一步的研究内容。
    6.1 工作总结
    本文首先介绍了一种适用于分布式集群的模型——CG树,并且分析了CG树顶端节点存在的问题,然后引出了将顶端节点改为顶端节点集群的想法。
    然后本文介绍了一种顶端节点集群的设计方案,包括心跳设计、数据库一致性的保持以及调度策略等内容。
    随后,本文给出了顶端节点集群的详细设计,并利用socket编程以及多线程技术实现了顶端节点集群。
    最后,本文利用顶端节点集群搭建了一个简单服务器,并对其性能进行了测试,并与以往的顶端节点做出了比较。实验结果表明,顶端节点集群处理客户端请求的能力明显提高,具有一定的可行性。

    6.2 课题展望本文的实现方案中,第二层的所有服务器都使用相同的数据库,有较大的数据冗余,同时增加了数据库同步的开销,如果数据库的规模达到一定的程度,可以考虑将第二层的服务器分组,从而进步一减轻每台服务器的负担。
    另外,本文的系统建立于数据库更新操作不频繁的基础上,如果要应用于数据库更新频繁的平台,数据库的更新策略需要修改。
    本文的实现只针对Linux操作系统,在系统的实现中大量用到了Linux系统相关的API,今后如果有意把系统用在面向跨平台应用上,这些部分都需要进行扩充和修改。
    参考文献[1] William Stallings. 操作系统——精髓与设计原理(第五版)[M],北京:电子工业出版社,2006.
    [2] 刘维峰. 分布式媒体集群的设计与实现[D],厦门:厦门大学,2005.
    [3] 吴国才. 基于CG树的分布式服务器集群的设计与实现[D].,厦门:厦门大学,2008.
    [4] W.Richard Stevens,UNIX网络编程卷一:套接字联网API(第三版)[M],北京:人民邮电出版社,2010.
    [5] W.Richard Stevens,UNIX环境高级编程(第二版)[M],尤晋元等译,北京:人名邮电出版社,2006.
    [6] MySQL官方网站,http://www.mysql.com/.
    [7] 章文嵩. LVS项目介绍[Z],http://www.linuxvirtualserver.org/zh/lvs1.html,2002.
    [8] 李大夜. 基于Linux的集群和心跳设计[D],哈尔滨:哈尔滨工业大学,2006.
    [9] 章文嵩. LVS集群的负载调度[Z],http://www.linuxvirtualserver.org/zh/lvs4.html,2002.
    致谢语大学本科生活即将结束,回首总结这四年的求学生涯,许多老师、同学和朋友给予了我真诚、无私的指导和帮助,在此我要表示我最真挚的感激和谢意。
    首先,我要衷心感谢指导我完成毕业设计的老师。老师对我们认真负责,严格要求,从课题确定到最后论文的定稿,为我们倾注了许多的心血与汗水。在毕业设计期间,老师每周都抽出时间与我们讨论,了解我们毕设的进展,并提出了许多宝贵的建议和意见。正是由于老师的严格要求和悉心指导,本文才得以顺利完成。
    其次,我要感谢在这四年里教导我的所有老师们,是你们的辛苦付出让我在计算机学科道路上不断成长,不断成熟。
    感谢实验室里的几位学长,在毕业设计中给了我很大的支持。
    最后,特别感谢我的父母和家人,一直以来你们给予了我最大的关爱和帮助。在这二十年的学习生活中,正是因为有了你们作为我的坚强后盾,我才能在人生道路上一往无前,也正是有了你们,在我遇到挫折的时候,你们给了我避风的港湾。
    1 评论 1 下载 2018-10-02 20:20:25 下载需要15点积分
  • 基于拉普拉斯平滑算法的视频去雾系统的实现

    摘 要视频去雾一直是图像处理领域一个备受关注的题目,其主要目的是将被大雾天气所影响的视频尽可能地还原成没有被大雾情况下的真实图像。相比于雾天下的视频,去雾后的视频还原了一部分被雾天所模糊的图像细节。它减少了雾天视频对实际生活和工作中对人们带来的影响,如交通出行,户外监测系统等。举例说,早年间由于视频去雾技术受限,许多户外监测系统在大雾天气下无法正常工作,影响到了人们的正常出行和生产,所以人们希望利用有效的方法将这些雾天影响下的视频尽可能真实地还原。本文旨在探究一种不同于现有的最小化能量函数,基于拉普拉斯平滑项建立一个新的能量函数模型。对于待处理的视频帧序列,先生成每一帧的深度图,然后通过初试的深度图去分别计算对应的颜色一致性项,几何相关项,平滑项和拉普拉斯平滑项。其中,我们提出了新的颜色一致性项和几何相关项,它们考虑到大气散射效应的影响;同样,拉普拉斯平滑项可以更好地保存物体的细节,避免失真和模糊。最后,通过迭代化解能量函数最小方程,得到对应的立体重建后的去雾视频。本文最终展示的,是基于拉普拉斯平滑项的立体重建后的图像,与原待测试视频帧序列对比后所能达到的最好效果。
    【关键词】 视频去雾;立体重建;拉普拉斯平滑;场景深度
    ABSTRACTVideo defogging has always been a topic of concern in the image processing field. The main purpose of the video defogging is to restore as much as possible the video that is affected by fog weather to real images without being affected by heavy fog. Compared to the video in the foggy world, the post-fog video restores a part of the blurred image details of fog.It reduces the impact of foggy video on people in real life and work, such as traffic and outdoor monitoring systems. For example, due to the limited video defogging technology in the early years, many outdoor monitoring systems were unable to work properly under heavy fog, affecting people’ s normal travel and production. So people want to use effective methods to restore the video under the influence of these fogs as realistically as possible.
    This paper aims to explore a new energy function model based on the Laplacian smoothing term that is different from the existing minimum energy function. For the sequence of video frames to be processed, a depth map of each frame is generated, and then the corresponding depth map is used to separately calculate the corresponding color consistency item,geometric correlation item, smooth item, and Laplacian smoothing item. Among them, we propose new color consistency terms and geometric correlation terms that take into account the effects of atmospheric scattering effects.Similarly, Laplacian smoothing items can better preserve the details of objects and avoid distortion and blurring. Finally, by iteratively solving the minimum equation of the energy function, a corresponding stereoscopically reconstructed defogging video is obtained. The final display of this paper is based on the stereoscopic reconstruction of Laplacian smoothing terms, and the best results that can be achieved after comparing with the original video frame to be tested.
    [Keywords]: video defogging; stereoscopic reconstruction; Laplacian smoothing; scene depth
    第1章 引言视频去雾是数字图像处理中一个重要的课题,应用面极其广泛。在本章节中,我们首先将介绍视频去雾,和到目前为止的研究背景、应用的领域及其具体的实际意义,并且概括性地描述本文所做的工作。
    1.1 研究背景和意义随着城市化现象的加快,人口密度的急剧增长以及全球变暖等气候变化,城市的大雾现象变得越来越普遍。大雾天气给城市交通的监测和治安的监管等方面带来了极大的影响,严重时甚至导致城市安全系统完全失效。海港,河床,岸边也会因为水汽的蒸发,而出现难以消散的雾气,这样一种雾气弥漫的情况使得这些区域的监控难以顺利进行。森林和大面积的植被也会由于呼吸作用,在清晨和黄昏时分常常会形成大面积的雾气,这通常会使得使用长焦距摄像镜头的摄像机系统丧失原有的功能,这可能对森林火灾的安全防范造成极大的影响。尤其在当今由于人类破坏环境的加剧,导致了各种极端天气现象的发生频率升高,因此变化多端的天气对目前主要依赖于智能机器设备的现代生产生活的影响比以前更加大。大雾天气则是其中一种比较常见而且影响又比较大的一种天气现象,而且对此的有效措施也比较少,视频去雾的技术研究可以给实际生产生活中带来更多的效率提升和防范危害。
    视频户外系统在被大雾影响的环境下拍摄的实际图像,因为受到雾气的影响,容易出现图像分辨率降低、质量退化、失真等现象,这就使得户外的视频系统不能稳定正常地工作,因而对人们的生活造成了严重的影响,如图1-1所示为中国除雾无人机在城市上空拍摄到的图像。因此,为了增强户外视频系统的稳定性和适用性,使其在天气条件很糟糕的情况下也可以被正常地使用,就很有必要研究有雾环境下视频图像的去雾技术。针对有雾环境对户外视频系统造成的诸多不良影响,我们需要着力研究视频图像的去雾技术。

    这不仅能对其他图像清晰化技术的研究起到一定的推动作用,还可以尽可能地削弱外界天气条件对图像采集造成的不良影响。近年来,在图像处理技术和计算机视觉领域,图像去雾技术已逐渐成为研究的热点问题,而现有的去雾算法多关注于单一图像的去雾,有关视频去雾的理论却很少。视频去雾与单一图像去雾有所不同,因为视频的帧与帧之间存相关性,且视频处理过程更注重自动性和实时性,这就使得视频去雾更具有挑战性。
    目前,视频图像去雾技术还处在研发起步阶段,各方面可供参考的文献并不多。相较国内,国外的研究工作起步较早,进展相对快一些。由于有雾环境下图像的去雾技术本身的复杂性,虽然这方面已经取得了很大的进展,但所提出的研究算法仍存在一些有待完善的地方,需要我们以此为基础继续研究。
    1.2 视频去雾问题的描述视频去雾是将因为将由于大雾导致可见度下降的视频还原出真实的物理场景的图像处理技术。它不仅是数字图像处理方面一个具有挑战性、前景优秀且相当活跃的课题之一,同时也在社会价值上,有着不可估量的潜在经济利益。视频去雾可以有效地降低因大雾天气对户外视频系统的影响,提高户外视频系统的可用性和适应性。如图1-2为某车载视觉系统下的雾天透视效果。

    图像的去雾方法大体上可以分为两种,一种是基于图像增强的去雾方法,另一种则是基于图像复原的去雾方法。而基于图像增强,多是指通过一些图像增强算法,例如,自适应直方图均衡化,自适应色阶和对比度,和多尺度Retinex等算法,它们主要是通过场景反照率,场景深度等图像,视频因素来对图像进行还原,增强。该方法能在一定程度上提高图像的对比度,使图像的细节更加突出,使图像的视觉效果更好,但可能对突出的那部分信息造成一定损失。而基于图像复原的去雾方法则是指根据物理模型的去雾方法,在提出大气衰减的物理模型的基础上,提取物理信息,然后去除无关信息而得到去雾图像的方法。基于大气退化物理模型的单幅图像去雾是通过合理的数学推演和假设,还原清晰、高品质的图像,得到近似的最优退化还原图像。这种方法有较强的针对性,一般不会造成信息的损失,得到的除雾图像也比较自然,模型中参数的估计是该类方法的关键点。在应用中,需要结合所拍摄的有雾图像的具体特点,不同方法的适用条件以及实际需要来选择合适的去雾方法,以达到较理想的去雾效果。
    在图像去雾的基础上,再通过一系列的帧组成的图像,在光照,雾,色彩等因素了提供了一些列的共性,我们可以通过帧与帧之间的联系得到更为有用的信息。
    而本文涉及到的视频的去雾以及立体重建,在去雾过程中,有对于物理模型的因素考虑而进行的计算工作,而在此基础上也有基于视频的有序序列特征进行的图像增强。
    1.3 本文的工作在本文中,我们联合研究立体视觉和去雾问题,设计一种同时估计场景深度和立体重建的算法。 我们的方法是基于这样一个观察结果,即立体和雾的厚度的深度线索是相互补充的(即对于附近的物体立体信息更可靠,并且雾的厚度信息对于远处的物体更可靠)。首先介绍了了一个雾模型,以及一个传统的对于视频的立体重建的能量函数,然后在该能量函数使用了基于散射效应的颜色一致性项,新的平滑项,基于传输的排序约束条件的平滑项,并且上加入拉普拉斯平滑项,使用这个新的能量函数,最后通过迭代地最小化该能量函数,得到最优的每一帧的深度图,从而完成立体重建。
    1.4 本章小结在本章节中,主要阐述了图像去雾,视频去雾等的研究背景和意义,描述了视频去雾的价值和意义,做了相应的分类和一些概念的介绍。此外,还梳理了本文的工作,大致的思想流程和会涉及到的一些方法,对后续章节的编写做一个引导和铺垫。
    第2章 综述在初步了解了视频去雾的工作背景和研究意义之后,在本章中将进一步对立体视觉,图像增强,场景深度等一些相关领域的研究做更深入的介绍,并且在介绍的过程中,会提及到对于本文有借鉴参考意义的文章或步骤,从而让读者对后续文章的实现部分有一个先前认知。
    2.1 传统的视频去雾方法视频去雾是计算机视觉领域的一个经典问题。对于一幅在雾天干扰下的图片,通过图像处理的方法尽可能地去除雾,还原图片,得到清晰的图片。基于图像增强的去雾方法是指通过改变明亮程度和对比度来改善图像的视觉效果,该方法能在一定程度上提高雾天图像的质量。但该方法在去除随空间变化的雾时,其作用会受限。基于图像复原的去雾方法基本是根据物理模型的去雾方法,多是指提出在大气衰减的物理模型的基础上,提取物理信息,去除无关信息而还原真实图像的方法。基于大气退化物理模型的单幅图像去雾是通过合理的数学推演和假设,还原清晰、高品质的图像,得到近似的最优还原图像。
    下面是几种经典的基于图像增强的去雾方法
    1.直方图均衡化
    直方图均衡化的基本思想是通过灰度的映射来修正图像的直方图,使其分布更加均衡,该方法是一种简单而有效的图像增强方法。雾天退化图像往往会体现出比较低的对比度,其直方图的分布也常集中在某个区域,因此可以通过均衡化的方法来调整直方图的分布,从而增强图像的对比度,实现有雾图像的清晰化。全局直方图均衡化则是基于整幅图像的,即使得整幅有雾图像的直方图满足均匀分布,为了使雾天图像的对比度得到整体上的增强,可以增加像素的灰度值的动态范围。这种方法的计算量不大,且易于实现,但对于一些细节的增强还不够。而局部的直方图均衡化,则对于被处理图像的所有局部区域,都要采用直方图均衡化,并且为了使图像的局部信息得到自适应地增强,需要在局部范围内对运算进行叠加。

    2.小波变换
    近十几年来,小波变换的信号处理方法也逐渐发展起来。该方法可以理解为对一系列的小波函数进行缩放和平移,并用它们代替傅立叶变换中的余弦和正弦函数,再进行傅立叶变换得到的结果。小波和局部信号的相关程度可以通过小波系数反映出来,而它的计算要通过对母小波的缩放和平移操作来实现。信号的时间信息可以通过平移母小波来得到,而信号的频率特征则可以通过缩放母小波的宽度获取。小波变换具有提供局部分析和细化的能力,这是小波变换的主要优点,小波变换良好的局部特性体现在时域和频域上,在高频区域,要想聚焦到所要分析的对象所具有的任意细节,可以利用逐渐精细的时域,也可以利用空域的步长。该方法既能较好地保留图像的细节特征,又能有效地去除噪声。

    3.Retinex算法
    Retinex理论是由Edwin.H.Land提出的一种经典的图像增强算法,该理论是基于颜色恒常理论。颜色恒常可以体现人眼对环境光线的变化的反应,也就是说,即使环境光线发生了变化,人眼也可以对物体表面反射光线的强度的增强或减弱进行辨别,还可以保证所看到的物体的表面颜色不发生变化。而Retinex理论,即视网膜大脑皮层理论则认为:当人的眼睛感受到亮度变化的时候,人的视觉系统只会对可以体现物体本身属性的信息有印象,所以可以在一定程度上不被外界环境光线的变化所影响,而保证所看到的物体颜色恒定。因为Retinex既具有颜色不变的特性,又具有动态范围压缩的特点,并且它是一种用来描述颜色不变性的模型,所以采用该方法对那些由于光照不均而造成的低对比度的彩色图像进行增强处理时,也会取得很明显的效果。在图像去雾过程中,可以把雾天作为环境照度的变化,利用Retinex算法可以很好地保持物体原本具有的色彩,从而获得具有较好视觉效果的无雾图像。一般来说,应用Retinex处理彩色图像是分别对R,G,B通道进行Retinex处理将得到的结果作为R,G,B通道。这种方式对于三个通道比较均衡的图像来说效果比较好。但是有的图像就只有某一通道,如R通道分量特别小,然后经过Retinex强行将R通道调整到[0,255]区间,这样效果不会太好。
    而基于物理模型对有雾图像进行复原的方法有多种,大体可分为以下3类,分别是基于偏微分方程的、基于深度关系的、以及基于先验信息的。这些方法常常是基于两种大气散射模型,一类是McCartney提出的单色大气散射物理模型,一类是Narasimhan等人从RGB色彩空间出发,推导出的二色大气散射模型。而单纯地基于物理模型实现去雾,由于天气的复杂多变性导致物理模型拟合的困难,而且传统立体算法中的光密度测量不考虑光传播过程中的散射和吸收现象,从而产生系统的匹配误差。

    2.2 基于拉普拉斯平滑的视频去雾方法雾对立体重建算法提出了挑战,同时也带来了补偿优势。从计算的观点来看,众所周知,立体重建对远距离图无法很好地工作。进一步的深度平滑会导致表面细节损失,例如细长的结构和孔洞。雾信息包含的深度信息在性质上是不同的,因为较厚的雾与较大的距离相关联。
    在本文中,联合研究立体视觉和去雾问题,实现了一种同时估计场景深度和对输入图像除雾的算法。在立体视觉融合技术中,分为局部的和全局的立体视觉技术。局部的立体视觉技术围绕个别的像素周围的灰度值或者边缘模式进行匹配,但是其忽略了可能连接邻近点的约束。而立体视觉的全局方法,是将立体视觉问题建模为一个基于近邻像素的序列或者平滑约束的能量函数,并对其进行最小化。本文在基于传统的雾模型和能量函数上,使用了包含更加复杂的颜色一致性项,拉普拉斯平滑项,基于传输的排序约束的平滑项的新能量函数。在本文公式中,立体匹配和雾信息的深度线索相互加强,基于在计算机视觉广泛使用的散射模型,使用这个新的能量函数,可以同时实现立体重建和去雾。
    立体匹配有五个约束条件:极线约束,顺序性约束,连续性约束(也称作表面平滑约束),唯一性约束,相似性约束。提出的衡量函数在一定程度上衡量了立体匹配的连续性约束和相似性约束。该方法包括四个关键特征。首先,我们改进了立体重建中的颜色一致性项以结合散射效应。当从不同的角度评估两个像素的一致性时,我们明确地模拟了由于雾而造成的外观变化。其次,我们直接从场景深度计算每个像素处的雾的传输。这确保了我们的立体效果和除雾效果相互一致。第三步,我们将有效的雾传输的先验结合到立体重建除雾公式中。具体而言,由于雾的传输可以直接从深度计算,所以我们在场景深度处加入了拉普拉斯平滑约束,这个约束有助于捕捉深度图中的细节。最后,我们还利用基于传输的排序深度信息中的可靠信息,采用了成对的场景深度排序约束。
    共同优化新的能量函数中的分项有助于为立体深度恢复和除雾带来双赢,通过定义新的颜色一致性项和拉普拉斯项,有助于保存深度图中的细节,这也是由于它与光谱图像分割密切相关。
    2.3 本章小结在本章中,主要介绍了视频去雾和立体重建等问题的研究现状,总结了其优缺点,并从实际待解决的问题出发,分析了有借鉴意义的方法,提出了本文理论模型的大致框架,这也是接下来后续章节待实现和实验的概括纲要和方向。
    第3章 视频去雾和立体重建方法3.1 颜色一致性项的构建我们知道,在视频序列中,每个点和相邻帧的同一个点的物理信息应该是近似不变的,而像素点的颜色则是最基本的一个物理性质。
    3.1.1 评估深度图的颜色一致性在立体重建中,颜色一致性f(p, V)是一种标量函数,用于测量给定三维重建p与一组图像的视觉兼容性。在3D点p处的简单颜色一致性函数被定义如下:将p投影到V中的每个可见图像中,并且将它们投影附近的图像纹理的相似度计算为颜色一致性。不是比较每个图像中的单个像素颜色,而是比较每个局部图像区域中的一组像素颜色的差异大小。
    (比如说下图就是比较3*3的局部像素区域的差异)

    为了评估立体重建图像和实际视频邻近帧的视觉差异,我们使用颜色一致性项。
    假设我们的视频有n个连续的帧I={It|t=1,…,n},根据任意的标准structure from-motion (Sfm)算法,我们可以得到照相机参数C=(Kt,Rt,tt),其中Kt是内在矩阵,Rt是旋转矩阵,tt是传输向量。简而言之,Sfm算法是一种基于各种收集到的无序图片进行三维重建的离线算法,首先进行特征检测和特征匹配。当所有的两两匹配图像对被确定以后,就可以考虑把多个图像中都出现的共同特征匹配点连接起来,就能形成轨迹了。一旦符合的轨迹都找到后,就构造图像连接图,包含每个图像的节点,和有共同轨迹的图像边缘。最后通过不断地进行Bundle Adjustment,不断加入3D点,得到摄像机参数和场景几何信息。
    也可以根据zhang等人提出的根据视频进行立体重建的方法,近似得到每一帧的深度图 D={Dt|t=1,…,n}, 因此也可以通过求倒数得到其逆深度图Z={Zt|t=1,…,n}。
    为了定义颜色一致性项,按照多视图立体重建的原理,对于第i帧的每一个像素点x,其逆深度为Di(x),那么可以通过以下公式将其投影到第j帧,得到其在第j帧的相应位置

    因此,我们由初始近似得到的逆深度图和邻近帧中的深度信息,可以去估计当前的逆深度图中的立体重建的效果,使用颜色一致性项Ep(Dt) :

    其中N(t)是第t帧的邻近帧数,|N(t)|是邻近帧集合的大小。
    3.1.2 基于散射效应的颜色一致性项的计算由于传统的基于视频的立体重建方法并没有考虑到雾的因素,由于雾天环境下存在着散射效应,因此我们提出了基于雾天散射效应的新的颜色一致性项。
    在计算机视觉中,广泛使用的一个散射模型是:

    其中I:是在散射介质中观察到的图像,IJ是不受散射效应影响的真实图像,A是大气光,a是媒介传输参数,a决定了没有散射并且到达到摄像机的光的比例。当A是同质时,a可以简单的表示如下:

    其中B是散射系数,其决定了传输媒介的密度,而z是该点和摄像机中心的距离,为了简化该公式,我们假设该点的场景深度如那样的方法大致估算。
    为了得到A和B,为了简化该问题,我们使用的方法去估算A。根据得到的A以及一些第i帧和第j帧中匹配的像素点(这也是根据公式3.1),

    根据方程3.3,它们满足于

    由于Xk和Yk其实是对应于同一个场景点,可知:

    因此,我们有:

    其中Zi(xt)是根据zhang G等人提出的获取深度图信息的方法获得的深度信息。根据公式3.6,我们能获得每一对匹配点之间估算的B值,舍弃一些逆深度差别小于某个阈值(通常取0.001)的匹配像素点。为了简化问题,可以简单的取得到的B集合的平均值作为B值。
    我们定义

    其中,

    是Ri的行。

    是ti的行。现在我们使用投影

    来计算在具有媒介传输参数ai(x)的第i帧的像素点x在第y帧的媒介传输参数a:

    其中,

    是矩阵的最后一行。由公式3.4,我们可以从Zi(x)得到ai(x)。因此,我们可以使用基于雾的散射效应的颜色一致性项来评估立体重建的效果:

    其中,

    这可以根据公式3.3简单推导得到。注意到利用公式3.4我们可以利用D(x)来表示,a(x) 因此在公式3.8中,深度D(t)是唯一的自变量。
    3.2 几何相关项的构建几何相关项专门设计用于基于视频的立体重建,以确保恢复的深度图的时间一致性和处理遮挡。我们借鉴zhang等人在2009年提出的几何相关项的计算方法。与典型的多视角立体重建方法不同,该方法不仅强加颜色一致性约束,以统计方式将几何相关性与多个帧明确关联。它们有助于可靠地减少图像噪声和多帧数据遮挡的影响,从而使我们的优化不受过度平滑的影响。它会检查相邻帧中每一像素点与所对应的共轭像素的距离。所谓共轭像素,其实也就是我们之前所说的一个像素点在相邻帧中所对应的像素点。

    其中,

    是第t帧中具有逆深度Dt(x)像素点x的共轭像素在t帧中的位置,与公式3.8提到的相同。让计算机自动建立多幅图像之间的匹配关系其实是立体重建最困难的一个问题。可以看出,这项是为了评估立体重建后的深度图中,每个像素点和邻近帧所对应的像素点的距离关系,在此,我们可以简单的使用街区距离来衡量,当然也可以使用欧式距离。因为如果在相邻帧中出现了较大的像素位移,而照相机显然是在非常缓慢移动的过程中,这个位移较大的点往往是误差导致的。所以我们用几何相关项来评估这个误差的。
    3.3 平滑项的构建平滑的本义是指:重新分配概率,即使没出现的事件也会赋予一个概率。而体现在数学上的平滑法,就是对不断获得的实际数据和原预测数据给以加权平均,使预测结果更接近于实际情况的预测方法,又称光滑法或递推修正法。对于实际数据接近于平稳不变的情况,可以应用一次平滑法,以消除偶然因素的影响。
    3.3.1 朴素的平滑在同一帧的图像中,邻近的像素的深度变化往往变化非常微弱,因此在相邻的像素之间,大部分情况下深度应该是近似不变的。在实际情况中,基于像素点的匹配代价并不能完全正确地反映两幅图像中两个点匹配的正确性;比如噪声、大范围的相似区域等,其结果是错误匹配的代价常常会小于正确匹配代价,从而影响算法在该点的深度估计。因此,必须增加一些额外的平滑约束到能量的定义中,这种约束通常是采用对深度或者灰度的变化的惩罚,以抑制噪声对匹配结果的影响。为了衡量立体匹配的正确性,我们在我们的能量函数中引入了平滑项。
    通常,在立体重建中,平滑项被定义为如下:

    考虑鲁棒性,f(Dt(x),Dt(y))通常是截断的l1函数,即:

    其中t1是截断参数,w(x,y)是权重函数,表示像素点x和像素点y应该拥有相同逆深度的可能性。为了鼓励深度不连续性与颜色变化一致,w(x,y)通常基于相邻像素的色差来定义。在此处,我们简单地采用zhang等人提出的方法去定义平滑项权重系数w(x,y)。
    3.3.2 基于传输的排序约束的平滑之前介绍的朴素的平滑项,通常已经能够较好地在一般的立体重建中衡量立体匹配的效果。然而,我们的立体重建是对于受到大雾天气影响的视频进行的。与绝对深度值的传输相比,雾传输对点与点之间的深度顺序有更可靠的约束。我们可以进一步的利用雾这方面的信息。更具体地说,假设x和y是两个相邻像素,如果at(x)>at(y),我们期望Dt(x)>=Dt(y)。 理论上,公式3.4通过简单的推导可以得出该结论。
    因此,当这个条件被违反时,我们分配一个大的惩罚t2。 在数学上,我们将公式3.10中的f(Dt(x),Dt(y))修改为

    见图3-2,这是我们专门分别对某一帧的利用朴素的平滑项和基于传输的排序约束的平滑项实验的效果,可以看出,朴素的平滑项明显的有很多边缘模糊的现象,而使用基于传输的排序约束的平滑项可以比较好地保持物体的边缘细节。不过在某一些帧的效果上可能现象没有那么明显,这也可能跟某一帧的场景中的物体细节和边缘部分是否比较多有关。因此我们挑出对比效果较为明显的其中一帧的对比效果,原图中场景深度较小的物体拥有比较多的边缘和细节部分,而基于传输的排序约束的实验结果也较好地保存下了细节部分。

    3.4 拉普拉斯平滑项的构建在实际情况中,很有可能出现一种零概率问题,就是在计算某概率时,由于某个分子项为0,而导致整个项的结果也为0。这个问题也常出现在人工智能领域的文本分类上。这是不合理的,不能因为某个分子项为0而认为整个项的最后结果都是0。为了解决零概率问题,法国数学家拉普拉斯最早提出用加1的方法估计没有出现过的现象的概率,所以拉普拉斯平滑也叫做加法平滑。
    雾的出现也开启了丰富重建深度细节的可能性。为此,我们将拉普拉斯约束作为细节保留平滑项。雾透射图应该满足拉普拉斯平滑先验。考虑到该点,我们发现拉普拉斯先验不仅可以重新定义传输的图像,而且有助于保存深度图中的细节部分,可能是因为其和光谱图像分割有密切的联系。拉普拉斯项定义为如下:

    其中vec(at)将at转化成向量形式,It是拉普拉斯矩阵,与Levin提出的一致,它的第i行第j列的元素定义为如下:

    由图3-2可知,相比之下,使用拉普拉斯平滑项能够较好地保留深度图中的细节。

    3.5 立体重建深度图的构建之前提出了若干个评估立体重建中邻近帧之间差异的项,这些在一定程度上都体现了立体重建的实际效果。为了能够同时评估上述项,我们构建了新的能量方程,并且在各项前加入权重系数。此时的能量方程的唯一自变量就是逆深度图Dt,通过能量方程的最优化,可以使得我们选择到对应的最优逆深度图,从而得到立体重建后的深度图,由此,便最后可得到立体重建后的视频去雾图像。
    3.5.1 能量方程的构建在有雾的视频中测量颜色一致性通常很困难,因为场景辐射的衰减与不同的相机位置不同。为了克服这个困难,我们提出一个更复杂的颜色一致性项来考虑散射效应。同时,雾的出现也为丰富重建深度的细节提供了可能性。为此,我们利用传输提供的排序约束,在逆深度施加平滑度。最后,我们将拉普拉斯约束作为细节保留平滑项。
    在目前传统的已有的立体重建方法中,该能量方程已经较为准确地评估了颜色一致性,几何相干项,平滑项所表示的立体重建的效果。在本文中,我们参考HeK等人提出的方法,新加入了拉普拉斯平滑项,以满足拉普拉斯先验,并且提出了优化之后的颜色一致性项,基于传输约束的平滑项,因此综上,我们可以得到一个新的衡量立体重建效果的能量方程,如下:

    其中Eg(Dt)是不变的几何相干项,Eps(Dt),Eso(Dt)和Elop(Dt)分别对应于新的颜色一致性项,新的基于排序约束条件的平滑项和以及拉普拉斯平滑项n 和p,^是平衡这些项的参数。共同优化这些项有助于为立体重建深度恢复和除雾带来共同利益。特别是,远距离物体的模糊性被解除,并且深度和颜色细节都被更好地恢复。
    3.5.2 能量方程的最优化参考zhang等人的方法,我们也采用了两步优化策略。我们通过忽略第一步中的几何相关项来初始化深度图,然后在第二步中迭代地求解方程3.14的完整版本。由于拉普拉斯项的存在,方程3.14并不能轻易求解。因此,我们基于引入一个辅助变量来解耦术语和交替更新的想法。这种策略在许多计算机视觉算法中被广泛使用,并表现出良好的性能。
    我们将函数分成两部分:能量函数可以重新写成:

    我们迭代地最小化新的目标函数,直到收敛或迭代次数超过最大限制。在每次迭代中,我们在固定at时求解Dt,然后在固定Dt时求解at。因此,2个新的子问题是:

    由于方程(3.16)中的最后一项是一元的,并且具有排序约束的Eso(Dt)不会带来额外的困难,因此方程(3.16)可以通过图像分割来求解。第二个子问题方程(3.17)是一个无约束的凸问题并且具有封闭形式的解:

    通过最小化能量方程,我们最终得到了最优情况下的逆深度图,继而可以得到深度图,从而实现了立体重建。
    3.6 本章小结在本章中,主要分部分介绍了整个立体重建的过程,是模糊概念转到实现原理的过程。分别对颜色一致性项,几何相关项,平滑项,拉普拉斯平滑项进行了阐述,通过这些分项的和来共同评估立体重建的效果。每一小节都对具体的项所对应的算法或详或略进行了介绍,下一章中将对输出结果进行展示。
    第4章 实验和分析4.1 实验结果展示我们使用了ZhuwenLi等人用以测试视频去雾效果的视频序列来对我们的算法进行测试和展示,并且将中间过程产生的拉普拉斯平滑图像也展示出来,以对实验的过程和效果有一个感知。下列为其中的部分结果,每组结果上方彩色图为原图,左下方为中间过程的拉普拉斯平滑图像,右下方的是对应的去雾后的图像。

    4.2 实验结果与问题分析从得到的结果可以看出,整个程序能较好地通过拉普拉斯平滑得到细节保存较为完整的深度图,从而从深度图中恢复出对应的立体重建和去雾之后的实际图片。
    但是,也有一些明显的问题和缺点:

    算法运行的使用时间较长,这是因为我们在迭代求优化的最小值的时候,尽可能地迭代多次以达到最优效果,因此处理完整个帧序列的运行时间会稍微比较长。部分图像的细节存在比较严重的失真现象,这可能是由于我们认为的大气光是一种同质的介质,简化了计算媒介传输参数与像素点的场景深度的方法。但是在实际中,大气光往往不是同质的,而且构成也比较复杂,尤其是在一些空气环境不够单一的情况下会更为复杂。部分图像的可见边变得模糊,对比度减小,且有一定的色彩失真,可能是由于我们在计算时,没有按照等人的比较准确但是计算量较大的方法,而是为了降低算法复杂度,简单地取一个平均值,所以随之效果也会有一部分的衰减,导致了边缘的部分模糊。视频图像的帧与帧之间具有一定的相关性,即使单帧图像的处理效果很好,但如果不能保证相邻两帧亮度的相似性,就会使处理后的视频序列中出现频闪,忽明忽暗,亮度不均等不稳定的现象,会对视觉效果有一定影响。这个可能跟我们对于颜色一致性项的权重系数和局部窗口的大小有关,当颜色一致性项的权重系数较小或者局部窗口较小时,相邻帧之间的相关性会被一定程度的弱化,就会导致上述的一些现象。
    4.3 本章小结本章中展示了部分基于拉普拉斯平滑的立体重建和视频去雾的效果图,总结了实际立体重建和视频去雾中出现的一些很明显的问题和缺陷,并对每一个问题和缺陷可能出现在什么地方、哪一个步骤中进行了原因分析和解释,为后续工作总结、展望改进指明了方向。
    第5章 总结与展望至此,我们实现了一种基于拉普拉斯平滑的视频去雾和立体重建的方法,该方法相较于其他的着色方法有优势也有不足,并且自身也存在一定的缺陷,今后的工作将围绕着改善立体重建和视频去雾结果、优化运算方法、 减少程序运行时间花费的方向进行。
    5.1 研究成果总结本文提出的视频去雾方法,理论上只需要指定对应路径的视频帧序列,就可以通过对应分别计算对应的项,最小化能量函数而得到对应的立体重建后的图像,从而实现视频去雾的效果。
    区别于传统的视频去雾方法,本文主要是提出新的颜色一致性项,基于排序约束的平滑项和拉普拉斯平滑项,从而得出一个有别于传统的能量函数的新的表达式,继续可以通过最小化该能量函数获得立体重建的最佳效果。同时相较于现有的视频去雾方法,虽然本文的方法在实验结果上仍有不足,但是对于对应的视频帧序列,大部分的物体细节都能够较好地保留下来,而没有因为立体重建的影响导致失真或者模糊的现象。然而本文的方法是建立在能量函数的迭代最小化基础上,迭代的次数在理论上也会影响最终实验的结果,那么不可避免的一个带来的问题就是程序运行时间可能会比较长。那么为了在一定程度的减短程序运行时间,可能会使得处理的效果还没有达到最优。
    但是如上一章中提到的,本文的方法在实际实现的过程中,还存在着许多的问题和缺陷,这些问题由不同步骤中使用的算法产生。例如, 由于我们认为的大气光是一种同质的介质,简化了计算媒介传输参数与像素点的场景深度的方法。但是在实际中,大气光往往不是同质的,而且构成也比较复杂,尤其是在一些空气环境不够单一的情况下会更为复杂;在计算时,没有按照等人的比较准确但是计算量较大的方法,而是为了降低算法复杂度,简单地取一个平均值。所以随之效果也会有一部分的衰减,导致了边缘的部分模糊;能量函数的各个子项的权重系数的权衡等等。
    5.2 本章小结本章中将本文算法与一些传统视频去雾算法进行比较,并大致总结了本文算法的优劣所在,同时对算法的薄弱的地方提出了可能有效的方法和设想,今后将着重围绕这些部分进行修改和完善。
    参考文献[1] Zhang G, Jia J, Wong T T, et al. Consistent Depth Maps Recovery from a Video Sequence[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 2009, 31(6):974-988.
    [2] 涂雅瑗. 雾天降质图像的对比度增强方法研究[D];大连海事大学;2009
    [3] Bobick A F, Intille S S. Large Occlusion Stereo[J]. International Journal of ComputerVision, 1999, 33(3):181-200.
    [4] Boykov Y, Veksler O, Zabih R. Fast approximate energy minimization via graph cuts[C]// The Proceedings of the Seventh IEEE International Conference on Computer Vision. IEEE, 2002:377-384 vol.1.
    [5] 纪松.多视匹配策略与优化方法研究[D].郑州:信息工程大学测绘学院,2012.
    [6] 李学明 . 基于Retinex 理论的图像增强算法[J]. 计算机应用研究, 2005(02):235-237.
    [7] Caraffa L, Tarel J P. Stereo reconstruction and contrast restoration in daytime fog[C]// Asian Conference on Computer Vision. Springer-Verlag, 2012:13-25.
    [8] Carr P, Hartley R. Improved Single Image Dehazing Using Geometry[C]// Digital Image Computing: Techniques and Applications. IEEE, 2010:103-110.
    [9] Cozman F, Krotkov E. Depth from Scattering[C]// Computer Vision and Pattern Recognition, 1997. Proceedings. 1997 IEEE Computer Society Conference on. IEEE, 1997:801-806.
    [10] Fattal R. Single image dehazing[C]// ACM SIGGRAPH. ACM, 2008:72.
    [11] Felzenszwalb PF, Huttenlocher D P. Efficient Belief Propagation for Early Vision[J].International Journal of Computer Vision, 2006, 70(1):41-54.
    [12] 赵莹.基于单幅图像的去雾算法研究 [D].天津大学,2009.
    [13] 董长虹, 高志, 余啸海.Matlab小波分析工具箱原理与应用.北京:国防工业出版社, 2004
    [14] Geman D, Reynolds G. ConstrainedRestoration and the Recovery of Discontinuities[M]. IEEE Computer Society,1992.
    [15] 潘泉, 张磊, 梦晋丽, 等.小波滤波方法及应用[M]. 北京: 清华出版社, 2004: 40−78.
    [16] 董卫军, 周明全, 黎晓, 耿国华. 基于小波分析的边缘检测技术研究[J]. 计算机工程与应用 2004(25)
    [17] He K, Sun J, Tang X. Single ImageHaze Removal Using Dark Channel Prior[J]. IEEE Transactions on Pattern Analysis& Machine Intelligence, 2011, 33(12):2341-2353.
    [18] Jiang N, Cui Z, Tan P. A Global Linear Method for Camera PoseRegistration[C]// IEEE International Conference on Computer Vision. IEEE,2014:481-488.
    [19] Kopf J, Neubert B, Chen B, et al. Deep photo: model-basedphotograph enhancement and viewing[J]. Acm Transactions on Graphics, 2008,27(5):1-10.
    [20] Krishnan D, Fergus R. Fast image deconvolution usinghyper-Laplacian priors[C]// International Conference on Neural InformationProcessing Systems. Curran Associates Inc. 2009:1033-1041.
    [21] Levin A, Lischinski D, Weiss Y. A closed-form solution to naturalimage matting.[C]// IEEE Computer Society Conference on Computer Vision andPattern Recognition. IEEE Computer Society, 2006:61-68.
    [22] 纪松, 范大昭, 戴晨光, 等. 线阵影像GC3多视匹配及其扩展模型研究[J]. 测绘科学技术学报,2009,26(6): 44-47.
    [23] Zhuwen Li, Tan, Ping, Tan,Robby T, et al. Simultaneous video defogging and stereo reconstruction[J].2015:4988-4997.
    [24] 陈永亮, 灰度图像的直方图均衡化处理研究[D];安徽大学;2014[25] Narasimhan S G, Nayar S K. Contrast restoration of weather degradedimages[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence,2003, 25(6):713-724.
    [26] 肖燕峰, 基于Retinex理论的图像增强恢复算法研究[D];上海交通大学;2007
    [27] 张璇, Retinex理论及其在压缩域图像增强中的应用研究[D];合肥工业大学;2010
    1 评论 6 下载 2018-10-01 22:48:38 下载需要13点积分
  • 基于SMTP协议的E-MAIL电子邮件发送客户端软件C#实现

    摘 要电子邮件在当今社会中扮演了一个很重要的角色。越来越多的人在使用它。而且用它的人数势必会继续增加。虽然,现在已经有很多的邮件收发软件例如著名的FoxMail 但是对于大多数的非专业的人来说它还是有点难度稍嫌负责。因此,我们就利用SMTP和Pop协议从底层开发了这个软件。SMTP全称是简单邮件传输协议,它专门用来发送邮件用的。Pop全称是邮局协议,是专门用于接收邮件的。我主要是负责如何实现发送邮件功能的。MailSend命名空间是我整个程序的核心。它包括两个类。在SmtpMail的类中包含了一个SendMail的方法,它从底层详细地实现了和服务器的交互操作。你既可以用它发送一个纯文本邮件,也可以发送一个带有附件的邮件,理所当然地,你也可以使用不同的SMTP服务器。经过测试,证实此软件是一个支持多收信人,多附件的群发软件。虽然它没有FoxMail那么强大的功能,但是它容易掌握和使用。
    关键词:SMTP,命名空间,类,附件
    AbstractE-Mail play a veryimportant role in modern times.More and more people are using it,and the numberof it will larger and larger.Though there are a lot of software for sending andreceiving letters such as FoxMail which are also multifunctional,it isdifficult and complicated to the Most of people who are curbstone.For thisreason,we do this software with the rock-bottom protocol of SMTP and Pop. Thefull name of SMTP is Simple Mail Transfer Protocol.It is Used to sendingletters.The full name of Pop is Post Office Protocol which is Special to receiveletters.I basically take charge to how to realize the function of sendingletters. A namespace which is named MailSend is the soul of my programe.Itincludes two classes.A method named sendmial which realize the fuction step bystep belongs to the class of SmtpMail. It detailedly note the track of clientexchange to the server. You can use the software to send either a text E –Mailor a textE-mail with Attachments.You also can Send a letter to many addressee.In the nature of things,you can use a different SMTP service. The software Idid support multiletters and multisender after I test.It is simplier thanFoxMail and other professional softwares,but it is easy to hold and use.
    Key Words: SMTP, nameSpace, Class, Attachment
    1 引言1.1 电子邮件介绍电子邮件(简称E-mail)又称电子信箱、电子邮政,它是一种用电子手段提供信息交换的通信方式。它是全球多种网络上使用最普遍的一项服务。这种非交互式的通信,加速了信息的交流及数据传送,它是一个简易、快速的方法。通过连接全世界的Internet,实现各类信号的传送、接收、存贮等处理,将邮件送到世界的各个角落。到目前为止,可以说电子邮件是Internet资源使用最多的一种服务,E-mai1不只局限于信件的传递,还可用来传递文件、声音及图形、图像等不同类型的信息。
    电子邮件不是一种“终端到终端”的服务,是被称为“存贮转发式”服务。这正是电子信箱系统的核心,利用存贮转发可进行非实时通信,属异步通信方式。即信件发送者可随时随地发送邮件,不要求接收者同时在场,即使对方现在不在,仍可将邮件立刻送到对方的信箱内,且存储在对方的电子邮箱中。接收者可在他认为方便的时候读取信件,不受时空限制。在这里,“发送”邮件意味着将邮件放到收件人的信箱中,而“接收”邮件则意味着从自己的信箱中读取信件,信箱实际上是由文件管理系统支持的一个实体。因为电子邮件是通过邮件服务器(mai1server)来传递档的。通常mailserver是执行多任务操作系统UNIX的计算机,它提供24小时的电子邮件服务,用户只要向 mail server管理人员申请一个信箱账号,就可使用这项快速的邮件服务。
    电子邮件的工作原理:

    电子邮件系统是一种新型的信息系统,是通信技术和计算机技术结合的产物。电子邮件的传输是通过电子邮件简单传输协议(Simple Mail Transfer Protocol,简称SMTP)这一系统软件来完成的,它是Internet下的一种电子邮件通信协议。 电子邮件的基本原理,是在通信网上设立“电子信箱系统”,它实际上是一个计算机系统。系统的硬件是一个高性能、大容量的计算机。硬盘作为信箱的存储介质,在硬盘上为用户分一定的存储空间作为用户的“信箱”,每位用户都有属于自己的—个电子信箱。并确定—个用户名和用户可以自己随意修改的口令。存储空间包含存放所收信件、编辑信件以及信件存盘三部分空间,用户使用口令开启自己的信箱,并进行发信、读信、编辑、转发、存档等各种操作。系统功能主要由软件实现。电子邮件的通信是在信箱之间进行的。用户首先开启自己的信箱,然后通过键入命令的方式将需要发送的邮件发到对方的信箱中。邮件在信箱之间进行传递和交换,也可以与另—个邮件系统进行传递和交换。收方在取信时,使用特定账号从信箱提取。
    1.2 开发背景当前流行的各大邮件客户端软件的除了最主要的收发信件之外,功能越来越复杂,但是人们平常真正用到的功能很少,很多功能尤其对于那些计算机知识相对缺乏的人来说,更加显得太过于华丽而不太实用。有鉴于此,在了解RFC底层协议的基础上,我们开发了这个各种功能相对简单实用的邮件客户端程序,简化了很多不必要的功能。
    1.3 开发环境及运行环境1.3.1 开发环境
    AMD Athlon(TM),512M内存,80G硬盘Microsoft Windows XPProfessionalMicrosoft VisualStudio 2003(C #)Microsoft DeveloperNetwork for Visual Studio.NET 2003
    1.3.2 运行环境
    Intel® Pentium® 2及以上处理器,32M以上内存,4G以上硬盘Microsoft® Windows™9X/NT操作系统800*600或以上的屏幕分辨率确保机器上安装有.Net FrameWork 1.0或者以上版本
    2 软件架构及系统用例图2.1系统架构软件的总体架构如图2.1:

    2.2 系统总体用例
    2.3 程序功能框图
    2.4 发送邮件类是发送邮件的核心,类名为SmtpMail,隶属于命名空间MailSend。封装了发送邮件的具体实现方法,也是具体的RFC用代码实现的过程。而用户通过具体的操作接口,接口与SmtpMail类通过交互操作来实现用户发送信件的操作。
    2.5 附加小功能类是获取一些诸如系统时间,当前用户名,以及本机IP之类的类,类名为AddExtra,隶属于命名空间MailSend。
    3 SMTP协议的研究由于要开发的是邮件客户端程序,就不得不用到SMTP协议和POP协议。而我个人负责的是邮件发送功能的实现,因此就必然会涉及到SMTP(Simple Mail Transfer Protocol)协议。SMTP被用来在因特网上发送邮件,该协议规定了一些基本的命令和方法使客户端与服务器进行交互,以达到发送邮件的目的。
    3.1 SMTP协议简介及工作原理3.1.1 介绍简单邮件传输协议(SMTP)的目标是可靠高效地传送邮件,它独立于传送子系统而且仅要求一条可以保证传送数据单元顺序的通道。
    SMTP的一个重要特点是它能够在传送中接力传送邮件,传送服务提供了进程间通信环境(IPCE),此环境可以包括一个网络,几个网络或一个网络的子网。理解到传送系统(或IPCE)不是一对一的是很重要的。进程可能直接和其它进程通过已知的IPCE通信。邮件是一个应用程序或进程间通信。邮件可以通过连接在不同IPCE上的进程跨网络进行邮件传送。更特别的是,邮件可以通过不同网络上的主机接力式传送。
    3.1.2 SMTP模型SMTP设计基于以下通信模型:针对用户的邮件请求,发送SMTP建立与接收SMTP之间建立一个双向传送通道。接收SMTP可以是最终接收者也可以是中间传送者。SMTP命令由发送SMTP发出,由接收SMTP接收,而应答则反方面传送。
    一旦传送通道建立,SMTP发送者发送MAIL命令指明邮件发送者。如果SMTP接收者可以接收邮件则返回OK应答。SMTP发送者再发出RCPT命令确认邮件是否接收到。如果SMTP接收者接收,则返回OK应答;如果不能接收到,则发出拒绝接收应答(但不中止整个邮件操作),双方将如此重复多次。当接收者收到全部邮件后会接收到特别的序列,如果接收者成功处理了邮件,则返回OK应答。
    SMTP提供传送邮件的机制,如果接收方与发送方连接在同一个传送服务下时,邮件可以直接由发送方主机传送到接收方主机;或者,当两者不在同一个传送服务下时,通过中继SMTP服务器传送。为了能够对SMTP服务器提供中继能力,它必须拥有最终目的主机地址和邮箱名称。
    MAIL命令参数是回复路径,它指定邮件从何处来;而RCPT命令的参数是转发路径的,它指定邮件向何处去。向前路径是源路径,而回复路径是返回路径(它用于发生错误时返回邮件)。
    当同一个消息要发往不同的接收者时,SMTP遇到了向不同接收者发送同一份数据的复制品的问题,邮件命令和应答有一个比较奇怪的语法,应答也有一个数字代码。在下面,例子中可以看到哪些使用实际的命令和应答。完整的命令和应答在第四节。
    命令与应答对大小写不敏感,也就是说,命令和应答可以是大写,小写或两者的混合,但这一点对用户邮件名称却不一定是对的,因为有的主机对用户名大小写是敏感的。这样SMTP实现中就将用户邮箱名称保留成初始时的样子,主机名称对大小写不敏感。
    命令与应答由ASCII字母表组成,当传送服务提供8位字节传送通道,每7位字符正确传送,而最高位被填充为0。当指定一般的命令或应答格式后,参数会由一些类似于语言的字符串表示出来,如”\<string\>“或”\<reverse-path\>“,这里尖括号表示这是一种类似于语言的变量。
    3.2 SMTP协议的命令和应答3.2.1 SMTP协议的命令SMTP命令定义了邮件传输或由用户定义的系统功能。它的命令是由\<CRLF\>结束的字符串。而在带有参数的情况下,命令本身由\<SP\>和参数分开,如果未带参数可以直接和\<CRLF\>连接。邮箱的语法格式必须和接收站点的格式一致。下面讨论SMTP命令和应答。
    发送邮件操作涉及到不同的数据对象,它们由不同的参数相互连接。回复路径就是MAIL命令的参数,而转发路径则是RCPT命令的参数,邮件日期是DATA命令的参数。这些参数或者数据对象必须跟在命令后。这种模式也就要求有不同的缓冲区来存储这些对象,也就是说,有一个回复路径缓冲区,一个转发路径缓冲区,一个邮件内容缓冲区。特定的命令产生自己的缓冲区,或使一个或多个缓冲的内容被清除。
    HELLO (HELO)
    此命令用于向接收SMTP确认发送SMTP。参数域包括发送SMTP的主机名。接收SMTP通过连接确认命令来向发送SMTP确认接收SMTP。引命令和OK响应确认发送和接收SMTP进入了初始状态,也就是说,没有操作正在执行,所有状态表和缓冲区已经被子清除。
    MAIL (MAIL)
    此命令用于开始将邮件发送到一个多个邮箱中。参数域包括回复路径。返回路径中包括了可选的主机和发送者邮箱列表。当有主机列表时,它是一个回复路径源,它说明此邮箱是由在表中的主机一一传递发送(第一个主机是最后一个接收到此邮件的主机)过来的。此表也有作向发送者返回非传递信号的源路径。因为每个传递主机地址都被加在此表起始处,它就必须使用发送IPCE而不是接收IPCE(如果它们不是一个IPCE的话)清楚的名称。一些出错信息的回复路径可能就是空的。
    此命令清除回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区,并且将此命令的回复路径信息插入到回复路径缓冲区中。
    RECIPIENT (RCPT)
    此命令用于确定邮件内容的唯一接收者;多个接收者将由多个此命令指定。转发路径中包括一个可选的主机和一个必须的目的邮箱。当出现主机列表时,这就是一个源路径,它指明邮件必须向列表中的上一个主机发送。如果接收SMTP未实现邮件的传递发送,就会返回如未知本地用户(550)的信息给用户。
    当邮件被传递发送时,传递主机必须将自己的名称由转发路径的开始处移至回复路径的结束处。当邮件最终到达目的地时,接收SMTP将以它的主机邮件格式自己的名称插入目标邮件中。例如,由传递主机A接收的带有如下参数的邮件时:
    FROM:USERX@HOSTY.ARPA  TO:<@HOSTA.ARPA,@HOSTB.ARPA:USERC@HOSTD.ARPA>将会变成如下形式:
      FROM:<@HOSTA.ARPA:USERX@HOSTY.ARPA>  TO:<@HOSTB.ARPA:USERC@HOSTD.ARPA>.此命令导致它的转发路径参数加入转发路径缓冲区中。
    DATA (DATA)
    接收者将跟在命令后的行作为邮件内容。此命令导致此命令后的邮件内容加入邮件内容缓冲区。邮件内容可以包括所有128个ASCII码字符。邮件内容由只包括一个句号的行结束,也就是如下的字符序列:”\<CRLF\>.\<CRLF\>“,它指示了邮件的结束。
    邮件内容的结束指示要求接收者现在就处理保存的邮件内容。此过程将回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区的内容全部清空。如果操作成功,接收者必须返回OK应答;如果失败也必须返回失败应答。
    当接收SMTP收到一条信息时,无论是用作转发还是此邮件已经到达目的地,它都必须在邮件内容的开始处加上时间戳这一行,这一行指示了接收到邮件主机和发出此邮件主机的标识,以及接收到邮件内容的时间和日期。转发的信件将有多行这样的时间戳。当接收SMTP作最后一站的传送时,它将返回路径信息行插入邮件中。此行包括了发送命令中的\<reverse-path\>的信息。在这里,最后一站的传送的意思是邮件将被送到目的用户手中,但在一些情况下,邮件可能需要更进一步的加工并由另外的邮件系统传送。
    可能在返回路径中的邮箱与实际发送的邮件不一致,这个情况可能发生在需要传送一个特定的错误处理信箱而不是信件发送者那里。上面所述说明了,最后的邮件内容由一个返回路径行,和在其后的一个或多个时间戳行构成。这些行后面是邮件内容的头和体信息。
    当处理后面的邮件数据指示部分成功时就需要特定的说明。这种情况可能发生在发送SMTP发现当邮件需要传送给多个用户时,只能够成功地向其中的一部分发送信息这种情况下。在这种情况下,必须对DATA命令发送OK应答,而接收SMTP组织并发送一个”不可传递邮件”信息到信息的发送者。在此信息中或者发送一个不成功接收者的列表,或者每次发送一个不成接收者,而发送多次。所有不可传递邮件信息由MAIL命令发送。
    返回路径和接收时间戳例子:
    Return-Path: <@GHI.ARPA,@DEF.ARPA,@ABC.ARPA:JOE@ABC.ARPA>  Received: from GHI.ARPA by JKL.ARPA ; 27 Oct 81 15:27:39 PST  Received: from DEF.ARPA by GHI.ARPA ; 27 Oct 81 15:15:13 PST  Received: from ABC.ARPA by DEF.ARPA ; 27 Oct 81 15:01:59 PST  Date: 27 Oct 81 15:01:01 PST   From: JOE@ABC.ARPA   Subject: Improved Mailing System Installed   To: SAM@JKL.ARPA   This is to inform you that ...SEND(SEND)
    此命令用于开始一个发送命令,将邮件发送到一个或多个终端上。参数域包括了一个回复路径,此命令如果成功就将邮件发送到终端上了。
    回复路径包括一个可选的主机列表和发送者邮箱。当出现主机列表时,表示这是一个传送路径,邮件就是经过这个路径上的每个主机发送到这里的(列表上第一个主机是最后经手的主机)。此表用于返回非传递信号到发送者。因为每个传递主机地址都被加在此表起始处,它就必须使用发送IPCE而不是接收IPCE(如果它们不是一个IPCE的话)清楚的名称。一些出错信息的回复路径可能就是空的。
    此命令清除回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区,并且将此命令的回复路径信息插入到回复路径缓冲区中。
    SEND OR MAIL (SOML)
    此命令用于开始一个邮件操作将邮件内容传送到一个或多个终端上,或者传送到邮箱中。对于每个接收者,如果接收者终端打开,邮件内容将被传送到接收者的终端上,否则就送到接收者的邮箱中。参数域包括回复路径,如果成功地将信息送到终端或邮箱中此命令成功。
    回复路径包括一个可选的主机列表和发送者邮箱。当出现主机列表时,表示这是一个传送路径,邮件就是经过这个路径上的每个主机发送到这里的(列表上第一个主机是最后经手的主机)。此表用于返回非传递信号到发送者。因为每个传递主机地址都被加在此表起始处,它就必须使用发送IPCE而不是接收IPCE(如果它们不是一个IPCE的话)清楚的名称。一些出错信息的回复路径可能就是空的。
    此命令清除回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区,并且将此命令的回复路径信息插入到回复路径缓冲区中。
    SEND AND MAIL (SAML)
    此命令用于开始一个邮件操作将邮件内容传送到一个或多个终端上,并传送到邮箱中。如果接收者终端打开,邮件内容将被传送到接收者的终端上和接收者的邮箱中。参数域包括回复路径,如果成功地将信息送到邮箱中此命令成功。
    回复路径包括一个可选的主机列表和发送者邮箱。当出现主机列表时,表示这是一个传送路径,邮件就是经过这个路径上的每个主机发送到这里的(列表上第一个主机是最后经手的主机)。此表用于返回非传递信号到发送者。因为每个传递主机地址都被加在此表起始处,它就必须使用发送IPCE而不是接收IPCE(如果它们不是一个IPCE的话)清楚的名称。一些出错信息的回复路径可能就是空的。
    此命令清除回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区,并且将此命令的回复路径信息插入到回复路径缓冲区中。
    RESET (RSET)
    此命令指示当送邮件操作将被放弃。任何保存的发送者,接收者和邮件内容应该被抛弃,所有缓冲区和状态表应该被清除,接收方必须返回OK应答。
    VERIFY (VRFY)
    此命令要求接收者确认参数是一个用户。如果这是(已经知道的)用户名,返回用户的全名和指定的邮箱。此命令对回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区没有影响。
    EXPAND (EXPN)
    此命令要求接收者确认参数指定了一个邮件发送列表,如果是一个邮件发送列表,就返回表中的成员。如果这是(已经知道的)用户名,返回用户的全名和指定的邮箱。此命令对回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区没有影响。
    HELP (HELP)
    此命令导致接收者向HELP命令的发送者发出帮助信息。此命令可以带参数,并返回特定的信息作为应答。此命令对回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区没有影响。
    NOOP (NOOP)
    此命令不影响任何参数和已经发出的命令。它只是说明没有任何操作而不是说明接收者发送了一个OK应答。此命令对回复路径缓冲区,转发路径缓冲区和邮件内容缓冲区没有影响。
    QUIT (QUIT)
    此命令指示接收方必须发送OK应答然后关闭传送信道。接收方在接到QUIT命令并做出响应之前不应该关闭通信信道。发送方在发送QUIT命令和接收到响应之前也不应该关闭信道。即使出错,也不应该关闭信道。如果连接被提前关闭,接收方应该象接收到RSET命令一样,取消所有等待的操作,但不恢复原先已经做过的操作。而发送方应该象接收到暂时错误(4XX)一样假定命令和操作仍在支持之中。
    TURN (TURN)
    此命令指定接收方要么发送OK应答并改变角色为发送SMTP,要么发送拒绝信息并保持自己的角色。如果程序A现在是发送SMTP,它发出TURN命令后接收到OK(250)应答,它就变成了接收SMTP。程序A就进入初始状态,好象通信信道刚打开一样,这时它发送220准备好服务信号。如果程序B现在是接收SMTP,它发出TURN命令后接收到OK(250)应答,它就变成了发送SMTP。程序A就进入初始状态,好象通信信道刚打开一样,这时它准备接收220准备好服务信号。
    若要拒绝改变角色,接收方可以发送502应答。
    对于这些命令的顺序有一定的限制。对话的第一个命令必须是HELLO命令,此命令在此后的会话中也可以使用。如果HELLO命令的参数不可接受,必须由返回一个501失败应答,同时接收到的SMTP必须保持在与刚才一致的状态下。 NOOP,HELP,EXPN和VRFY命令可以在会话的任何时候使用。MAIL,SEND,SOML或SAML命令开始一个邮件操作。一旦开始了以后就要发送RCPT和DATA命令。邮件操作可以由RSET命令终止。在一个会话中可以有一个或多个操作。
    如果在操作开始参数不可接受,必须返回501失败应答,同时接收到的SMTP必须保持在与刚才一致的状态下。如果操作中的命令顺序出错,必须返回503失败应答,同时接收到的SMTP必须保持在与刚才一致的状态下。
    会话的最后一个命令必须是QUIT命令。此命令在会话的其它时间不能使用。
    COMMAND语法格式
    命令是由命令码和其后的参数域组成的。命令码是四个字母组成的,不区别大小写。因为下面的命令的作用是相同的:
    MAIL Mail mail MaIl mAIl这对于引导任何参数值的标记也是适用的,如TO和to就是一样的。命令码和参数由一个或多个空格分开。然而在回复路径和转发路径中的参数是区别大小写的。特别是在一些主机上,”smith”和”Smith”就根本不是一个用户。
    参数域由不定长的字符串组成,它由\<CRLF\>结束,接收方在完全接收到此序列前不会采取任何行动。方括号代表可选的参数域。如果不选择的话,系统选择默认的设置。
    下面是SMTP命令:
    HELO <SP> <domain> <CRLF> MAIL <SP> FROM:<reverse-path> <CRLF>RCPT <SP> TO:<forward-path> <CRLF>DATA <CRLF>RSET <CRLF>SEND <SP> FROM:<reverse-path> <CRLF>SOML <SP> FROM:<reverse-path> <CRLF>SAML <SP> FROM:<reverse-path> <CRLF>VRFY <SP> <string> <CRLF>EXPN <SP> <string> <CRLF>HELP [<SP> <string>] <CRLF>NOOP <CRLF>QUIT <CRLF>TURN <CRLF>3.2.2 SMTP的应答码对SMTP命令的响应是多样的,它确定了在邮件传输过程中请求和处理的同步,也保证了发送SMTP知道接收SMTP的状态。每个命令必须有且只有一个响应。
    SMTP响应由三位数字组成,其后跟一些文本。数字帮助决定下一个应该进入的状态,而文本对人是有意义的。三位的响应已经包括了足够的信息,不用再阅读文本,文本可以直接抛弃或者传递给用户。特别的是,文本是与接收和环境相关的,所以每次接收到的文本可能不同。在附录E中可以看到全部的响应码。正规的情况下,响应由下面序列构成:三位的数字,\<SP\>,一行文本和一个\<CRLF\>,或者也可以是一个多行响应。只有EXPN和HELP命令可以导致多行应答,然而,对所有命令,多行响应都是允许的。
    500 格式错误,命令不可识别(此错误也包括命令行过长)501 参数格式错误502 命令不可实现503 错误的命令序列504 命令参数不可实现211 系统状态或系统帮助响应214 帮助信息220 <domain> 服务就绪221 <domain> 服务关闭传输信道 421 <domain> 服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应)250 要求的邮件操作完成251 用户非本地,将转发向<forward-path>450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙)550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问)451 放弃要求的操作;处理过程中出错551 用户非本地,请尝试<forward-path>452 系统存储不足,要求的操作未执行552 过量的存储分配,要求的操作未执行553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误)354 开始邮件输入,以<CRLF>.<CRLF>结束554 操作失败4 RFC822说道发送和接受邮件,我们就必须不得不提RFC822了。RFC822的全称是“ARPA因特网文本信件格式的标准”(Standard for theFormat of ARPA Internet Text Messages)。该标准提供了邮件内容的格式和相关语义。
    4.1 RFC822简单介绍RFC822规定的电子邮件内容全部由ASCII字符组成,就是通常所说的文本文件,因而标准将它称为Internet文本信件(Internet Text Messages)。
    从直观上看,信件非常简单,就是一系列由ASCII字符组成的文本行,每一行以回车换行符(“CRLF“,就是ASCII码的13和10)结束。
    从组织上看,信件内容结构分为两大部分,中间用一个空白行(只有CRLF符的行)来分隔。第一部分称为信件的头部(the header of the message),包括有关发送方、接收方、发送日期等信息。第二部分称为信件的体部(Body of the message),包括信件内容的正文文本。信头是必需的,信体是可选的,即信体可有可无。如果不存在信体,用作分隔的空白行也就不需要。在信体中,也可以有用作分隔的空白行。这样设计的信件便于进行语法分析,提取信件的基本信息。
    在RFC822中规定,信件体就是一系列的向收信人表达信息的文本行,比较简单,可以包含任意文本,并没有附加的结构。信件头则具有比较复杂的结构,在下一小节中详述。
    4.2 信件的头部4.2.1 信头的一般格式信头的结构比较复杂,信头由若干信头字段(header field)组成,这些字段为用户和程序提供了关于信件的信息。要了解信头的结构就要弄清楚各种信头字段。
    所有的信头字段都具有相同的语法结构,从逻辑上说,包括四部分,字段名(field name),紧跟冒号”:” (colon),后跟字段体(field body),最后以回车换行符(CRLF)终止。即
    信头字段=字段名:字段体 CRLF字段名必须由除了冒号和空格以外的可打印US-ASCII字符(其值在33和126之间)组成,大多数字段的字段名称由一系列字母,数字组成,中间经常插入横线符。字段名告诉电子邮件软件如何翻译该行中剩下的内容。
    字段体可以包括除了CR和LF之外的任何ASCII字符。但是其中的空格,加括号的注释,引号和多行字段都比较复杂,另外,字段体的语法和语义依赖于字段名,每个类型的字段有特定的格式。
    RFC822为信件定义了一些标准字段,并提供了用户自行定义非标准字段的方法。
    4.2.2 结构化字段和非结构化字段每个字段所包含的信息不同,字段大体可以分为结构化字段和非结构化字段。
    结构化字段有特定的格式,由语法分析程序检测。Sender 字段就是一个很好的例子,它的字段内容是信箱,有一个离散的结构。
    非结构化的字段含有任意的数据,没有固定格式。例如,Subject字段可以含有任意的文字,并且没有固定格式。非结构化的字段数量较少,只有Subject、 Comments、扩展字段,非标准字段、IN-Reply和References等。所有其它字段都是结构化的。
    4.2.3 信头字段的元素尽管Email信件的总体结构非常简单,但一些信头字段的结构是很复杂的。下面介绍一些大多数字段共有的元素。
    1.空白符
    像其它文本文件一样,空白符包括空格符(ASCII码32)和制表符Tab(ASCII码19)。此外,行末的回车换行符CRLF也应算是空白符。使用空白符可以对字段进行格式化,增加它的可读性。例如,每个字段间用CRLF来分离,在字段内用空格来分隔字段名和字段内容。在Subject后面的冒号和内容之间插入空格字符,会使字段结构更加清晰。在Email中,空白符的使用并没有固定的规则,但应当正确地使用,仅在需要时才使用空白符,以便接收软件进行语法分析。
    2.注解
    注解是由括号括起来的一系列字符,例如,(这份礼物)。注解一般用在非结构化的信头字段中,没有语法语义,仅为人提供了一些附加的信息。如果在加引号的字符串中有包括在括号中的字符,那是字符串的一部分,不是注解。在解释信件的时候,会将注解忽略,可以用一个空格字符代替它们,这样就什么也不会破坏。
    3.字段折叠
    每个信头字段从逻辑上说应当是一个由字段名、冒号、字段体和CRLF组成的单一的行,但为了书写与显示的方便,增加可读性,也为了符合1000/80的行字符数的限制,可以将超过80个字符的信头字段分为多行,即对于比较长的字段,可以分割成几行,形成折叠。在结构化和非结构化字段中都允许折叠。通过在字段中某些点插入CRLF符和至少一个或多个空白字符来实现字段的折叠,第一行后面的行称为信头字段的续行。续行都以一个空白符开始,这种方法称为折叠(folding),例如标题字段Subject: This is a test可以表示为:
    Subject: This is a test反之,将一个被折叠成多行的信头字段恢复到它的单行表示的过程叫做去折叠,只要简单地移除后面跟着空格的CRLF,将折叠空白符CRLF转换成空格字符,就可以完成去折叠(unfolding)。在分析被折叠的字段的语法时,要把一个多行的折叠字段展开为一行,根据它的非折叠的形式来分析它的语法与语义。
    4.字段大小写
    字段名称是不区分大小写的,所以Subject、subject或SUBJECT都一样。不过字段名称大小写有习惯的常用形式,如主题字段的大小写形式通常为Subject。字段体的大小写稍微复杂点,要视情况而定。比如Subject后面的字段体,其中的大写可能就是缩写的专用名词,不能改动。
    4.2.4 标准的信头字段下面介绍RFC822中定义的常用的标准信头字段。



    与发信方有关的信头字段





    格式:From:mailbox 举例:From:wang@163.com
    写信人字段。说明信件的原始创建者,给出他的电子信箱地址。创建者对信件的原始内容负责。


    格式:Sender:mailbox 举例:From:wang@163.com Sender:li@sina.com
    发送者字段。说明实际提交发送这个信件的人,给出他的电子信箱地址。当发信人与写信人不一样时使用。比如,秘书替经理发信。发送者对发送负责。


    格式:Reply-TO:mailbox 举例:From:wang@163.com From:zhao@soho.com
    回复字段。指定应当把回信发到哪里。如果有此字段,回信将会发给它指定的邮箱,而不会发给From字段指定的邮箱。比如,发送的是经理的信,但回信应交办公室处理。


    与收信方有关的信头字段



    格式:TO:mailbox list 举例:TO:zhang@263.com
    收信人字段。指定主要收信人的邮箱地址,可以是多个邮箱地址的列表,地址中间用逗号隔开。


    格式:Cc:mailbox list 举例:Cc:zhang@863.com
    抄送字段。指定此信件要同时发给哪些人,也称为抄送。也可以使用邮箱地址列表,抄送给多个人。


    格式:Bcc:mailbox list
    密抄字段。指定此信件要同时秘密发给哪些人,也称为密件抄送。也可以使用邮箱地址列表,密抄给多个人。


    其它的信头字段



    格式:Date:date-time 举例:Date:Tue,04 Dec 2004 16:18:08 +800
    日期字段:Date字段含有电子邮件创建的日期和时间。


    格式:Subject:*text 举例:Subject:Hello! Subject:Re:Hello!
    信件主题字段。描述信件的主题。当回复信件时,通常在主题前面增加“Re:”前缀,标记为该信件为回复信件:当信件被转发时,通常在主题文字前面加上“Fw:”,“Fwd:”这样的前缀。


    格式:Received: [“from” domain] ;发送主机 [“by” domain] ;接收主机 [“via” atom] ;物理路径 [“id” msg-id] ;接收者msg id
    接受字段。是投递信件的特定邮件服务器所作的记录。处理邮件投递的每个服务器必须给它处理的每个信头的前面加一个Received字段,用以描述信件到达目的地所经过的路径以及相关信息。当跟踪各个电子邮件问题时,这个信息很有帮助。


    举例:Received:from wang[195.0.0.1] by li[129.5.0.4] Tue dec 2003 12:18:02 +800



    格式:Comments:*text
    注释字段。用于把一个注解添加到信件中。


    格式:Resent-* 举例:Resent-From Resent-Sender Resent-date Resent-Reply-To
    重发字段。当需要把收到的信件重发给另一组收信人的时候,可以保持整个原始信件不变,并简单地产生重发信件所要求的新信头字段。为避免与以前的字段相混。新添加的信头字段都加上Resent-前缀字符串,它们的语法与未加前缀的同名字段相同。


    格式:Message-ID:msg-id
    信件标识字段。用于表示一个信件唯一标识,该字段通常有Smtp服务器生成,这个值通常是唯一的。形式根据使用的软件而定。通常左边是标识符,右边指定电脑名



    表中的关键字表明了电子邮件借用了办公室备忘录中的概念和术语:电子邮件的头部能够包含一行说明应当接收到该备忘录的接收方。象传统的办公室备忘录一样,电子邮件使用关键字Cc指明一个复写副本(carboncopy).电子邮件软件必须向Cc:后面的电子邮件地址表中的每个地址发送一份消息的副本。
    传统的办公室过程要求备忘录的发送方通知接收方副本是否传给其它人。有时发送方希望将备忘录的一个副本给别人而不显示出有一个副本被发送出去。一些电子邮件系统提供这样的选项,遵循传统的办公室术语,用盲复写副本(blind carbon copy)来表示。创建消息的用户
    在关键字Bcc后给出一个电子邮件地址表,指定一个或多个盲复写副本。虽然Bcc在发送方出现,但当信息发送时,邮件系统将它从消息中除去。每个接收方必须检查头部的To和Cc行以决定信息是直接发送还是作为盲副本发送的(有些邮件系统在正文部分附加信息来告诉接收者它是一个盲副本)。其它接收者不知道有哪些用户接收到盲副本。
    电子邮件使用与传统的办公室备忘录相同的格式和术语:头部包括与消息有关的信息,正文包括消息文本。电子邮件头部的行说明发送方、接收方、日期、主题、应当收到副本的人的列表。
    扩展字段
    如果想在信头中加入RFC822中没有规定的字段,就需要创建非标准字段。方法非常简单,只要在自定义的信头字段名的前面使用X-前缀。RFC822将这种方法称为扩展字段。 事实上已经有许多扩展字段被广泛应用,但没有标准定义。例如:

    X-LOOP字段
    X-LOOP字段用来防止邮件的循环传送。过滤或邮件列表处理程序,可以给它处理的每个信件增加一个X-LOOP字段,以后就可以根据这个字段中含有的特别值,判断一个信件是否被循环传送。如果确认邮件发生了循环,过滤或邮件列表处理程序就可以用不同的方式处理该信件。
    X-Mailer字段
    X-Mailer字段用于指示什么样的程序产生了这个信件,它是使用最广泛的扩展字段。产生邮件的软件可以为所有发送的信件增加合适的X-Mailer字段,该字段不仅含有软件的名称,还包含软件的版本号。例如软件名为Littlefox Mailer,版本为V1.0, 可以将“X-Mailer:Littlefox Mailer V1.0”加到邮件信头中去。

    下面列出了一些在因特网电子邮件中可以找到的普通关键字,以及使用它们的目的。



    关键字
    含义




    From
    发送方地址


    To
    接收方地址


    Cc
    复制副本地址


    Date
    信息创建日期


    Subject
    信息主题


    Reply-To
    回复地址


    X-Charset
    使用的字符集(通常为ASCII)


    X-Mailer
    发送信息所使用的软件


    X-Sender
    发送方地址的副本


    X-Face
    经编码的发送方面孔的图象



    整个系统的核心是收发信件的操作,因此为了方便维护,以后的升级,故将这两个最主要的操作写成类库(.dll)的形式,以组件的形式加载到主程序中,而且其它的功能如果需要的话,也可以通过这样的组件的形式增加到主程序中。这也体现了C#这一新的微软主推语言的方便和高效,而且这样做也方便程序的顺利结合。
    5 命名控件MailSend由于在C#语言中,都是以命名控件来组织程序的。而所有的类都归属于一个特定的命名空间下。需要的命名空间系统本身自带了一部分,而且如果系统没有你需要的命名空间的话,就可以自己编写,本节中的这个命名空间就是由于需要而编写的。而调用某一个类中的某个变量成员的方法就是通过命名空间名.类名.变量成员 来访问的,当然在C#中如果在程序开始通过Using 命名空间名,就可以直接的象C++那样来访问成员变量,可以说相当的方便,这些都会在程序中体现出来,再次不再做过多的叙述。
    5.1 发送邮件类SmtpMail5.1.1 主要成员变量说明1.网络连接类及实例TcpClient tc
    为 TCP 网络服务提供客户端连接类TcpClient实例对象tc。TcpClient 类提供了一些简单的方法,用于在同步阻塞模式下通过网络来连接、发送和接收流数据。而实例化的过程也是连接SMTP服务器的过程。它的重载方法之一的两个参数一个为服务器名称字符串,另一个为服务器的埠。
    2.提供用于网络访问的基础数据流及其实例 NetworkStream ns
    此类提供访问网络的基础数据流的方法。其中最基本也是最重要的两个方法就是Write()和Read()方法,至于参数不再次赘述。
    3.一维字符串数组变量FilePath
    此字符串数组主要用来存放用户选择的附件的绝对路径名,并在发送带附件的邮件时用到。
    4.发送邮件所需的基本参数
    比如用于ESMTP等录检验用的用户名、密码,发送邮件需要的收信人,发信人地址以及主题等等在此不再赘述。
    5.1.2 主要成员函数说明1.重载的构造函数 SmtpMail()
    此函数主要用于在初始化过程中,把用户选择的附件的路径以参数的形式传给FilePath。
    2.添加附件的函数AddAttachment
    传给FilePath的路径,通过这样一个函数就可以循环的动态的添加到IList接口的一个对象中了,方便以后在具体的实现的过程中的使用。
    3.得到上传的附件的文件流GetStream
    由于在网络中的操作都是以网络流的形式来实现的,因此先将上传的附件转换成文件流,然后再用Write的方法把这些附件的文件流写入到网络中,来完成发送附件的操作。具体实现代码如下所示:
    private string GetStream(string FilePath) { //建立文件流对象 System.IO.FileStream FileStr=new System.IO.FileStream(FilePath,System.IO.FileMode.Open); byte[] by=new byte[System.Convert.ToInt32(FileStr.Length)]; FileStr.Read(by,0,by.Length); FileStr.Close(); return(System.Convert.ToBase64String(by)); }
    4.将字符串编码为Base64字符串的函数Base64Encode
    由于ESMTP的LOGIN认证机制是采用Base64编码,当用户发出AUTHLOGIN的命令后,服务器返回334的应答码等待用户输入。如果身份确认后服务器返回235的应答码,否则返回失败信息。所以要将用户名和密码转换成Base64编码然后再发给服务器。此函数的作用就是把给定的字符串转换成相应的Base64编码的字符串。
    5.发送SMTP命令的函数SendCommand
    这个函数的作用是把SMTP命令的字符串转换成对应的字节型值(C#中规定的Write方法只能写入字节型的数据)然后写入网络中,如果操作成功就返回一个标志为真的布尔型变量,如果操作失败或者发生异常就返回标志为假的布尔型变量。具体代码如下所示:
    private bool SendCommand(string str) { //定义一个数组 byte[] WriteBuffer; //设定一个布尔类型的变量 bool state=false; WriteBuffer = Encoding.Default.GetBytes(str); //加入防错机制,可以有效提高程序运行的效率和捕获出错信息 try { //向网络中写入数据 ns.Write(WriteBuffer,0,WriteBuffer.Length); state=true; } catch(Exception ex) { //返回出错信息 MessageBox.Show (ex.ToString ()); state=false; } //返回标志位 return state; }
    6.接受服务器应答的函数RecvResponse
    它的作用就是从网络流中读取服务器返回的字节型的信息,将其转换成字符串型的变量,然后将其返回,可以通过其返回值来判断操作是否成功。具体实现代码如下所示:
    private string RecvResponse() { int StreamSize=0; string ReturnValue =""; //定义一个字节型的数组 byte[] ReadBuffer = new byte[1024] ; try { //从网络流中读取数据,并返回读取的个数 StreamSize=ns.Read(ReadBuffer,0,ReadBuffer.Length); } catch (Exception ex) { //返回异常信息 MessageBox.Show(ex.ToString ()); } if (StreamSize!=0) { //将当前读取的信息转换成字符串型然后返回ReturnValue= Encoding.Default.GetString(ReadBuffer).Substring(0,StreamSize); } return ReturnValue; }
    7.重载的函数Dialog
    它们的作用是与服务器交互,发送命令并接收回应。不同的是参数是字符串类型的那个函数,每次发送一条命令,并接受服务器的响应,根据响应的信息来判断交互的结果是否成功。而参数是字符串数组的函数每次发送的是一组命令,用于和服务器的交互,这个函数主要是用于ESMTP服务器的验证的功能,因为验证的过程是一个等待然后又输入的过程,因此将他们放在一个数组中有利于理解和操作。而他们的实现主要是通过调用上面的发送SMTP命令函数SendCommand以及接受SMTP服务器响应的函数RecvResponse来实现的。具体的代码如下所示:
    private bool Dialog(string str,string errstr) { bool flag=false; if(str==null||str.Trim()=="") { flag=true; } if(SendCommand(str)) { string RR=RecvResponse(); //从返回的数据中截取前三位 string RRCode=RR.Substring(0,3); //然后用这前三位与哈希表中正确的回应码比较 if(RightCodeHT[RRCode]!=null) { flag=true; } else { flag=false; } } else { flag=false; } return flag; }
    发送一组命令主要用于服务器验证的重载函数为:
    private bool Dialog(string[] str,string errstr) { for(int i=0;i<str.Length;i++) { //循环调用单个的与服务器的交互过程 if(!Dialog(str[i],"")) { return false; } } return true; }
    8.邮件发送程序SendMail
    这是整个程序的核心部分。具体的实现SMTP协议的程序正是通过它一步一步实现并最终实现发送简单邮件甚至带附件的邮件的功能。而它的实现是调用以上给出的各个函数的结果。以下就简单的通过几个SMTP命令的格式来实现:
    private bool SendEmail() { //连接网络 try { //建立一个TCP连接 tc=new TcpClient(mailserver,mailserverport); } catch { MessageBox.Show ("连接失败","请确认"); return false; } //获取当前流的资料 ns = tc.GetStream(); SMTPCodeAdd(); //验证网络连接是否正确 if(RightCodeHT[RecvResponse().Substring(0,3)]==null) { return false; } string[] SendBuffer; string SendBufferstr; //进行SMTP验证 //具体的SMTP命令与代码的结合 if(ESmtp) { SendBuffer=new String[4]; SendBuffer[0]="EHLO " + mailserver + enter; SendBuffer[1]="AUTH LOGIN" + enter; SendBuffer[2]=Base64Encode(username) + enter; SendBuffer[3]=Base64Encode(password) + enter; if(!Dialog(SendBuffer,"SMTP服务器验证失败,请核对用户名和密码。")) return false; } else { SendBufferstr="HELO " + mailserver + enter; if(!Dialog(SendBufferstr,"")) return false; } SendBufferstr="MAIL FROM:<" + From + ">" + enter; if(!Dialog(SendBufferstr,"发件人地址错误,或不能为空")) return false; //把传过来的收件人的地址分割然后提交给服务器 string split=";"; string []address=Regex.Split (Recipient,split); SendBuffer=new string [address.Length]; for(int i=0;i<SendBuffer.Length;i++) { SendBuffer[i]="RCPT TO:<" +address[i]+">" + enter; } if(!Dialog(SendBuffer,"收件人地址有误")) return false; SendBufferstr="DATA" + enter; if(!Dialog(SendBufferstr,"")) return false; SendBufferstr="From:" + FromName + "<" + From +">" +enter;SendBufferstr += enter + "." + enter; if(!Dialog(SendBufferstr,"错误信件信息")) return false; SendBufferstr="QUIT" + enter; if(!Dialog(SendBufferstr,"断开连接时错误")) return false; //关闭流对象 ns.Close(); //关闭连接 tc.Close(); FilePath=null; return true; }
    以上即为发送不带附件的邮件SMTP命令用代码实现的过程。
    5.2 AddExtra类这个附加的小类只是提供一些返回当前系统时间,获取主机名,主机IP,有关帮助等小的功能,在此仅对帮助信息中的“关于”操作函数稍加说明。因为它说明了在C#中调用 Windows API 函数所需如下几个步骤:
    5.2.1 调用Windows API 所需的命名空间using System.Runtime.InteropServices;
    而调用显示关于对话框的函数ShellAbout还需要用到两个命名空间如下所示:
    using System.Reflection;using System.Diagnostics;
    5.2.2 在程序中声明所需的API函数[DllImport("shell32.dll")]static extern int ShellAbout(IntPtr hWnd, string szApp, string szOtherStuff,IntPtr hIcon);
    5.2.3 在程序中具体的使用Assembly ass=Assembly.GetExecutingAssembly();FileVersionInfo myVersion=FileVersionInfo.GetVersionInfo(ass.Location );ShellAbout(this.Handle ,"邮件收发系统#","版本"+myVersion.FileMajorPart +"."+myVersion.FileMinorPart+"." +myVersion.CompanyName ,this.Icon .Handle );
    至此就完成了在C#中调用 Windows API 函数的过程。
    6 软件运行时的界面6.1 新建邮件帐号用户打开软件之后,需要新建一个邮件帐号,在这个信件帐号的过程中,需要指定SMTP服务器,SMTP的端口,以及用于ESMTP验证的用户名和密码。指定这些发邮件的必须参数之后,再回到系统的主界面如下所示:

    6.2 发送邮件界面6.2.1 发送不带附件的邮件在新建帐号的过程中已经指定了邮件地址,和帐号名称,所以默认的以这些参数来发送邮件。通过调用参数的不同程序会自动的调用相对应的代码来执行不同的操作。发送简单的邮件运行界面如下。

    6.2.2 发送带附件的邮件和简单的邮件不同之处在于多了发送附件的功能,软件模拟FoxMail里面发送邮件时,在程序的下面自动显示增添的附件的名称,以及图标等信息。并且邮件支持添加,删除,排列图标等功能。运行界面如下所示:

    6.3 验证邮件发送是否成功邮件发送出去之后,用FoxMail跟踪接收之后,证明邮件和附件都可以正常接收,具体的FoxMail的接收界面如下所示:

    7 系统测试我个人做的是这个软件收发系统的一个最基本也是最主要的功能之一:发送邮件。
    所以主要的测试也是围绕发送邮件展开的,具体的可以分为以下几个方面。
    7.1 同一SMTP服务器发送邮件的测试这个方面的测试测的是,用户登录一个服务器(测试中用的是163的SMTP服务器)来发送一封邮件的测试。而这个测试又可以分为以下两个方面:
    7.1.1 同一服务器,发送一封纯文本邮件的测试1.发送一封文本邮件给一个收信人
    测试中用163的邮箱分别往163的邮箱以及新浪的邮箱发送邮件均可以用FoxMail正常的接收到发送的普通的纯文本文件。
    2.发送一封文本邮件给多个收件人
    测试中仍然用163的邮箱同时发往不同的邮箱,通过FoxMail都可以正常的接收到。从而很好的验证了,我们的邮件发送系统支持群发的功能。
    7.1.2 同一服务器,发送一封带附件的邮件的测试。1.发送一封带附件(可以是多附件)的邮件给一个收件人
    测试中用163的邮箱分别往163的邮箱以及新浪的邮箱发送之外,又添加了不同的邮件类型(个数分别为等于1,大于1即验证是否支持多附件的发送),用FoxMail接收之后,所有发送的纯文本信息,以及附件信息都正常无误。经过这些验证可以证明本软件支持对一个收件人发送多附件。由于带有多附件的信件,所以写入速度明显慢于纯文本邮件的速度。
    2.发送一封带附件(可以是多附件)的邮件给多个收件人
    测试中用163的邮箱分别往163的邮箱以及新浪的邮箱发送之外,又添加了不同的邮件类型(个数分别为等于1,大于1即验证是否支持多附件的发送),用FoxMail接收之后,所有发送的纯文本信息,以及附件信息都正常无误。经过这些验证可以证明本软件支持对多个收件人发送多附件。
    7.2 利用不同的SMTP服务器发送邮件的测试这个方面的测试是指利用不同的邮箱来发送邮件,至于测试的分类雷同于利用同一服务器发送邮件的测试,所以不再此赘述。
    总之,通过以上的各方面的测试,使我改正了代码中的许多不合理以及错误之处,最终也证明了,我们的软件系统是支持多种服务器,支持多附件发送的群发软件。
    8 结论这次编写的邮件客户端系统,我负责的是邮件的发送的功能。在熟悉了专门用于发送邮件的SMTP协议以及RFC规定的邮件的格式的基础上,运用了微软新推出的C#这一新型的面向对象语言的便利性和灵活性,从SMTP协议规定的底层命令做起,一步步的与服务器进行交互操作,最终实现发送多附件多接收人的功能。其中,具体的和服务器的交互操作,都封装了在SmtpMail.dll这个动态链接库里面了。而为了方便最终的调用和整合,所有的有关后台操作发送邮件的类以及其他的附加功能的类,全部都归属于MailSend这个命名空间了。在力求达到FoxMail功能的同时,又加了一点个人的思想并把它体现到了这一软件上。最主要的体现就是新建帐号的提前检测这一特色上,这一功能类似于很多Web页面的“检测新帐号”的功能,这样就免去了用户一直到确定注册完成时,才因为帐户因为已经被使用而注册失败的麻烦。总之,通过这次的编程,使我对网络编程有了一个很好的认识和锻炼,也使我对C#这一语言的掌握程度又上了一个新台阶,虽然编出来的软件不能和功能强大的FoxMail相提并论,但是相信它简单,易操作性,和FoxMail的很多强大但却“鸡肋”似的功能比较起来,更多了几分实用性。以后的日子,随着我技术的提高和思想的成熟,我一定会把它做的更好,更趋近于完美。
    参考文献[1] Simon Robinson, K.Scott Allen等.C#高级编程. 北京:清华大学出版社, 2002,3
    [2] Tom Archer. C#技术内幕. 北京:清华大学出版社, 2002,1
    [3] 沉舟.Microsoft.NET编程语言C#. 北京:希望电子出版社. 2001,3
    [4] 罗军舟,黎波涛,杨明等.TCP/IP 协议及网络编程技术. 北京: 清华大学出版. 2004,10
    [5] Tim Parker .TCP/IP 协议及网络编程技术. 北京: 机械工业出版社, 2000,7
    [6] 周存杰. Visual C#.NET网络核心编程. 北京:清华大学出版社, 2002,11
    [7] 电脑编程技巧与维护杂志社.C#编程技巧典型案例解析. 北京:中国电力出版社, 2005,8
    [8] 云颠工作室. Visual C#中文版全面剖析. 北京:中国水利水电出版社, 2003,5
    [9] 叶树华《电子协议与编程》,《电子邮件格式》,《电子邮件接收》,《mime 编码解码与发送附件》
    [10] MSDN中文网站网络广播 C#设计模式纵谈http://www.microsoft.com/china/msdn/events/webcasts/shared/Webcast/MSDNWebCast.aspx
    [11] 滁州,马金虎,朱力勇. 编写基于SMTP网络应用程序. 电脑爱好者,2003,5:92~94
    [12] 滁州,马金虎,朱力勇. 编写基于POP3网络应用程序. 电脑爱好者,2003,6:92~94
    [13] 潘泰国. 新一代电子邮件系统. 电子技术应用. 1992,11
    [14] 代继红. SMTP认证机制模块化设计及实现. 中南民族大学学报(自然科学版), 2005,4
    [15] 胡安廷. 简单实现中文邮件. 中国计算机报, 2004,11
    1 评论 21 下载 2018-10-01 22:31:48 下载需要12点积分
  • 基于C#和SQL SERVER的校园知识问答论坛网站的设计与实现

    摘 要本文使用Asp.Net Core 和MsSqlServer技术,详细说明开发校园知识论坛系统的开发。校园知识论坛系统是基本B/S模式的一种交互性极强的电子信息服务系统。它为使用者提供一个交流的平台,每一个用户都可以在上面问答知识,获取信息,发布观点和评论,极大地促进了信息的共享。
    关键词:Asp.Net Core;MsSqlServer;知识论坛;B/S
    AbtractThis article uses Asp.Net Core and MsSqlServer technology to explain in detail the development of the campus knowledge forum system. The campus knowledge forum system is a highly interactive electronic information service system based on the basic B/S model. It provides users with a platform for communication. Each user can ask questions, obtain information, publish opinions and comments, and greatly promote the sharing of information.
    Keywords:Asp.Net Core; MsSqlServer; Knowledge Forum; B/S
    1 绪论1.1 开发背景及意义论坛又名网络论坛BBS,它是一种电子信息服务系统,为使用者提供一个平台,用户可以在上面撰写、发布信息或者是提出意见看法[3]。它是一种交互极强,知识高度共享的电子信息服务系统,使用者可以在平台上获取各种信息服务,问题、聊天等。
    论坛随着网络的发展,迅速发展壮大。现在的论坛涵盖我们生活的各个方面,而校园知识论坛是面向在校学生的信息共享和交流的平台,可以促进在校学生的学术交流、增强互动性。它可以为在校学生提供一个解决方法的途径,一个共享知识的平台,一个展示才华的舞台。
    在本文中,主要介绍使用Asp.Net Core技术来实现校园知识论坛建设。通过基本Internet和Web数据库技术,解决用户间的数据共享,用户可以通过浏览器来获取想要的数据,同时可以向服务器存储自己的数据。服务端把用户授权的信息展示出来,构建出一个小型的数据中心,方便用户查阅信息,获取解决方案,兴趣交流。
    目前,可以通过校园知识论坛系统,向广大的同学提问自己的疑问,同时可以回答别人提出的疑问,阅读分享的知识。而志同道合的人可以关注同一个话题进行交流、阐述观点。
    1.2 课题目标本课题主旨在于阐述如何使用Asp.Net Core为基础开发校园知识论坛系统。根据软件的开发流程,在开发前需要对需求和功能进行分析和深入的了解,然后根据需求和功能模拟出流程,最后利用Asp.Net Core平台构建项目架构和功能的开发。在开发的过程中,将会使用到很多技术,本课题会阐述开发的技巧以及功能实现的思路。
    2 开发技术介绍2.1 服务器由于开发使用的C#语言,基于Asp.Net Core2.0.5运行环境,在服务器上选择Windows Service 2016数据中心版的IIS10(Internet Information Services)进行部署和托管。基于IIS的托管及其方便维护和调试。
    2.2 数据库Microsoft SQL Server是Microsoft 公司推出的关系型数据库管理系统,具有使用方便可伸缩性好与相关软件集成程度高等优点,被企业广泛使用[4]。Microsoft SQL Server 数据库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能,可以构建和管理用于业务的高可用和高性能的数据应用程序[4]。对Asp.Net Core2.0有着良好的支持,可以高效处理大量数据和并发操作,事务机制可以保证数据的一致性,同时可以构建集群用于大数据的处理,所以采用Microsoft SQL Server作为数据服务。
    Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库[5]。对网站中重复读取的数据,如果采用Redis进行缓存可以减轻数据库服务器的负担,同时能够提高访问速度[5]。
    2.3 Asp.Net Core.Net是Microsoft针对Windows开发而编写的集成框架。.Net 允许开发者使用C#,VB,F#语言开发能够在Windows设备上运行的程序。Asp.Net Core是.Net跨平台的产品,它允许程序能够在不同的操作系统上运行。Asp.Net Core基于.Net Core环境,无托管模式,MVC结构使得代码结构清晰,同时包含Css,JavaScript自压缩功能,使得开发的东西更健壮。
    3 系统分析3.1 系统功能分析用例图是指项目的蓝图,通过用例图明确项目的功能[6]。以下是用户和管理员的用例图。
    在此校园知识论坛系统内,用户将可以写文章、提问、评论、管理个人信息和关注他人的社交。如下图3-1所示:

    管理员拥拥有着用户管理、运维管理、数据统计、内容管理和权限管理等功能。如下图3-2所示:

    3.2 系统数据分析在知识论坛中的问答模块,用户可以提问多个问题,我们通过标签来区分每个问题涉及的领域,所以,1个问题可以对应n个标签。
    每一的问题可以由多个人进行回答,因此,问题好和回答之间是1:n的关系。专栏是是给用户写文章的地方,同样,一篇专栏对应多个标签,一个用户可以发表多篇专栏文章,专栏和标签的关系时1:n,用户和专栏之间的也是1:n。具体如下图3-3所示:

    3.3 系统流程分析用户在没有登录的状态下,只能查看问题、文章和、评论。用户需要登录后提问、评论、写文章。如果用户没有登录账户则需要进行注册。一般流程是用户进行登录,没有账户进行注册,注册成功返回登录,登录成功后提问、回答问题、写文章。
    登录模块。具体操作流程如下图3-4所示:

    问答模块。其具体的流程图如下图3-5所示:

    文章发布流程图如图3-6所示:

    3.4 功能模块设计在此次研究中,使用Asp.NetCore 开发高可用的站点是本次研究的重点。知识论坛由于其言论的自由性,我们需要对网站的内容进行严格的管理,因此,知识论坛有多个模块组成,由管理后台对全站的内容进行管理。各个模块相应结构图如下图3-7所示:

    Web站点是对用户提供的可视化交流平台,后台管理系统是对网站运维提供的操作平台。
    Web站点主要包含用户的注册、登录、找回密码、完善个人信息、提问、回答、评论、写文章、申请话题、关注其他用户等。Web站点是用户的操作平台,为用户提供良好的用户体验和高效的问题解决途径,同时为用户提供良好的沟通平台。她作为重要的一个大模块连接的是用户。
    后台管理系统是管理人员对网站的运行提供支持,净化网络环境,为用户提供良好的上网环境,也是服务于用户。它主要包含内容管理(包含提问、回答、写专栏、用户资料的管理)、基础数据管理(包含敏感词库、网站运行参数等的管理)、角色和权限管理(管理人员之间的角色和权限,能够访问到的和可操作的权限管理)。
    3.5 系统架构设计在系统的架构方面上,我采用微软提供的“简介架构”。采用仓储-服务模型。在项目的基础设施中包含仓库服务,其他公共的类库,项目核心中包含实体类,服务,接口,主要用于处理业务核心。Web站点和后台都使用的一套核心代码,这样处理能后减轻代码量,同时能够让项目更容易管理和扩展。具体的系统架构如图3-8所示:

    4 数据库分析与设计数据库在整个系统中起着重要的所有,它存储着整站的基本数据。再设计数据库的时候要哦根据业务需求,把数据拆分为多个表,通过各表之间的逻辑关系,构建表与表之间的关系模型。同时,数据库在网站的并发中是重要的一环,在保证数据库能够支持高并发的同时,数据的安全性、稳定性、一致性都是需要进行考虑。
    数据库设计包括数据库系统的需求分析、概念设计、逻辑设计、物理设计阶段[1]。
    4.1 数据项说明
    用户信息表:Id、UId、用户名、邮箱、密码、图像、电话、QQ、微信、邮箱是否被验证、注册时间 问题表:Id、标题、问题描述、提问者Id、是否匿名、提问时间回答表:Id、回答内容、是否采用、问题Id,回答者Id、回答时间标签表:Id、标签名、标签描述、父级Id、是否可用、创建时间专栏表:Id、标题、内容、是否匿名、创建时间问题和标签中间表:问题Id、标签Id专栏和标签中间表:专栏Id、标签Id管理员信息表:Id、管理员用户名、密码、手机号、创建时间
    4.2 数据表说明校园知识论坛共包含8张表,系统数据表如下所示:



    表名
    说明
    功能




    UserInfo
    用户信息表
    存储用户个人信息


    Question
    问题表
    存储用户提问的问题


    Answer
    回答表
    存放问题的回答


    Tag
    标签表
    存放标签信息


    Blog
    专栏表
    存放用户撰写的文章


    QuestionTags
    问题和标签中间表
    存放问题和标签的关系


    BlogTags
    专栏和标签的中间表
    存放专栏和标签的关系


    Admin
    管理员信息表
    存放管理员信息表



    4.3 表结构设计根据系统的需求,创建不同的表用来存储用户的信息,根据不同表之间的关系,构建一对一、一对多、多对多的关系。用户产生的数据大多都是文本数据,很少涉及图片,视频等数据资料,MsSQLServer这种关系型数据库完全满足系统的使用。具体的表信息如下所示:
    4.3.1 用户信息表此表用于记录所有用户的个人资料,包括Id、UId、用户名、邮箱、密码、图像、电话、QQ、微信、邮箱是否被验证、注册时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    Int
    -

    主键


    UId
    用户唯一Id
    Nvarchar
    32

    -


    UserName
    用户名
    Nvarchar
    32

    -


    Email
    邮箱
    Nvarchar
    128

    -


    Password
    密码
    Nvarchar
    32

    -


    Icon
    用户图标 Url
    Nvarchar
    128

    -


    TelNumber
    图手机号
    Nvarchar
    11

    -


    QQ
    QQ
    Nvarchar
    18

    -


    WeChat
    微信号
    Nvarchar
    18

    -


    IsEmailValidate
    邮箱被验证
    Bit
    -

    -


    SubTime
    创建时间
    datetime2(7)
    -

    -



    4.3.2 问题表此表用于记录问题信息,包括Id、标题、问题描述、自定义标签、所属话题、提问者Id、是否匿名、提问时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    int
    -

    主键


    Title
    标题
    nvarchar
    64

    -


    Description
    问题描述
    nvarchar
    Max

    -


    Tag
    自定义标签
    nvarchar
    32

    -


    UserInfoId
    提问者Id
    int
    -

    外键


    Anonymous
    是否匿名
    bit
    -

    -


    SubTime
    提问时间
    datetime2(7)
    -

    -



    4.3.3 回答表此表用于用户的回答,其包含Id、回答内容、是否匿名、是否采用、问题Id,回答者Id、回答时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    int
    -

    主键


    Content
    回答内容
    nvarchar
    Max




    Adoption
    是否采用
    bit
    -




    QuestionId
    问题Id
    int
    -

    外键


    UserInfoId
    回答者Id
    int
    -

    外键


    SubTime
    回答时间
    datetime2(7)
    -





    4.3.4 话题表此表用于记录话题信息,包括Id、话题、话题描述、父级Id、是否可用、创建时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    int
    -

    主键


    TagName
    话题名称
    nvarchar
    32

    -


    Description
    描述
    nvarchar
    Max

    -


    PerId
    父级Id
    int
    -

    -


    Enable
    是否可用
    bit
    -

    -


    SubTime
    创建时间
    datetime2(7)
    -

    -



    4.3.5 专栏表此表用于记录系统用户撰写的文章信息,包括Id、标题、内容、所属话题、是否匿名、创建时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    int
    -

    主键


    Title
    话题名称
    nvarchar
    1238

    -


    Content
    描述
    nvarchar
    Max

    -


    Anonymous
    是否可用
    bit
    -

    -


    SubTime
    创建时间
    datetime2(7)
    -

    -



    4.3.6 问题和标签中间表记录问题和标签之间的关系。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    TagId
    标签Id
    int
    -

    外键


    QuestionId
    问题Id
    int
    -

    外键



    4.3.7 专栏和标签中间表 记录专栏个标签之间的关系。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    TagId
    标签Id
    int
    -

    外键


    BlogId
    专栏Id
    int
    -

    外键



    4.3.8 管理员信息表此表用于记录系统中网站管理员的信息,包括Id、用户名、密码、手机号、创建时间。



    字段名
    说明
    类型
    长度
    是否为空
    主键或外键




    Id
    自动编号
    int
    -

    主键


    UserName
    话题名称
    nvarchar
    32

    -


    Password
    描述
    nvarchar
    32

    -


    TelNumber
    所属话题
    nvarchar
    11

    -


    SubTime
    创建时间
    datetime2(7)
    -

    -



    5 详细设计每一个站点,用户看到的是界面,而界面的配色,排版会给用户很深的印象。因此网站的界面需要设计的简洁,美观,以此来提高用户的体验度。
    5.1 问题列表页主页主要展示用户的提问和回答详情。

    5.2 提问界面
    5.3 回答界面
    5.4 标签界面
    5.5 专栏页面
    5.6 个人中心
    6 系统运行与测试6.1 测试目的软件测试是对全部或部分的功能、模块的计算机程序在正式使用前的检测,以确保该程序能够按照预定的方式运行。在软件投入生产性运行之前,尽可能多地发现软件中的错误。测试是对软件规格说明、设计和编码的最后复审,所以软件测试贯穿在整个软件开发期的全过程[2]。
    6.2 测试环境
    WindowsMsSQLServer.Net Core2.0.5集成环境
    6.3 测试方法6.3.1 单元测试单元测试时在开发的过程中,对最小单元代码的测试。通过单元测试能够及时发现系统存在的语法、逻辑等问题。在开发的过程中,我们会对每一部分的功能代码进行单元测试,以期达到预期目标。
    6.3.2 集成测试集成测试是在单元测试的基础上,把所有的模块进行组装,整体测试,以发现模块与模块之间是否存在问题。在集成测试中,通过走业务流程,逐步查看程序是否达到预期目标
    结束语本次毕设耗时近两个月的时间,数据库迁移8次,SVN代码提交52次。在这次的制作中,因为使用Asp.Net Core 2.0.5,在国内并没有大规模应用,解决方法不够,遇到很多的问题,同时也踩了很多坑,但是学习到很多东西。在实践中学会了Entity Freamword Core First一对多,多对多的关系模型设计,使用Asp.NetCore,了解并应用了IOC这种优雅的设计模式。这促使着我不断的学习新的知识,技术在不断的更新我们需要不断的学习来充实自己。
    参考文献[1] 段有志. 浅谈关系数据库的系统设计[J].中国科技纵横,2011,(09)
    [2] 刘欣怡, 周跃东, 田秀丽. 软件工程[M].清华大学出版社,北京交通大学出版社,2007
    [3] 赵晓军.基于ASP.NET的在线教学论坛设计[D].河北大学,硕士论文,2016
    [4] 朱丹.关于教务管理系统的设计与实现[J].卷宗,2015,(10)
    [5] 李会娟.缓存技术研究及其在电子政务平台中的应用[D]. 北京工业大学, 硕士论文,2009
    [6] 钟珞 袁景凌 魏志华汤练兵.软件工程[M]. 清华大学出版社,2005
    [7] SteveSmith.Architecting Modern Web Applications with Asp.Net Core and MicrosoftAzure[M], DevDiv, .NET and Visual Studio product teams,2017
    [8] 严文涛,张明.MSSQL SERVER数据库服务器的安全问题和安全配置[J].山东电子,2002
    [9] 超宇,李金香.Redis在高速缓存系统中的应用[J].微型机与应用,2013,(06)
    [10] 徐鹏民.用MSSQL Serve 建立Internet上的全文检索系统[J].计算机世界,2000,(08)
    [11] 叶红卫.基于ASP.NET MVC框架的Web设计[J].河北北方学院学报(自然科学版),2009,(12)
    [12] Nagel.C,李铭(译),黄静(审).C#高级编程[M],清华大学出版社,2017(10)
    1 评论 14 下载 2018-10-01 22:13:12 下载需要10点积分
  • QT实现的基于A*算法的电力线路规划系统

    摘 要电网建设中输电线路的选择过程比较复杂,需要综合考虑地理,地形,交通,施工和维护等因素对线路的影响。随着电力行业信息化的不断发展,传统的电力选线方法已不再适应信息化时代电网建设的需要,近年来利用数字化手段规划电力线路的研究已经取得了显著的成绩。
    电力线路规划本质上属于航迹规划问题,在航迹规划算法中由于A*算法搜索过程简单,算法效率高而得到广泛的应用。本文主要讨论了基于A*算法的电力线路规划演示软件的设计思路;首先简要介绍了课题背景和国内外研究现状;详细论述了系统的设计原则;并粗略探讨了A*算法的工作原理;然后基于电力线路规划约束条件,利用稀疏A*算法原理设计并实现了一种适用于电力选线的路径规划算法;最后通过测试验证了算法的正确性。
    本系统借鉴一般管理软件和航迹规划算法的设计原则,人机交互界面采用QT图形界面实现,搜索算法采用C/C++语言实现,开发环境采用VS2010,从而使得系统可以稳定的运行在Windows平台。
    关键字:电力选线,航迹规划,A*算法,路径代价
    AbstractSelection of transmission lines in power grid construction process is complicated, need comprehensive consideration of geography, topography, traffic, construction and maintenance and so on the influence of factors on the line. With the continuous development of electric industry information the traditional electric power line selection method is no longer meet the needs of the power grid construction of information age, using digital means planning electric lines of research in recent years has made significant achievements.
    Electric line planning in essence belongs to the route planning problem,in the route planning algorithm due to the A* algorithm search process is simple, high efficiency and widely used. This article mainly discussed the design idea of electric power line planning presentation software based on A* algorithm.; first briefly introduced the topic background and research status at home and abroad; the design principle of the system are discussed in detail; and discuss the working principle of A* algorithm; then based on power line planning constraints, a route planning algorithm for power line selection is designed and implemented by using the principle of sparse A* algorithm; finally the correctness of the algorithm is verified by tests.
    This system from general management software and the design principle of route planning algorithm, using QT graphical interface to realize the human-computer interaction interface, search algorithm is implemented using C/C+ + language, development environment using VS2010, thus making the operation of the system can be stable in the Windows platform.
    Keywords: power line selection, route planning, A* algorithm, route cost
    1 绪 论1.1 课题背景及意义1.1.1 课题背景1882年中国第一台发电机组落户上海,中国电力工业从零开始起步。虽然此后国内战乱不断,但电力工业却逐渐成长起来,到1949年新中国成立时全国发电装机总容量为185万千瓦,年发电量为43亿千瓦时,在当时排世界第25位[1]。到1996年底时全国发电装机总容量为2.5亿千瓦,发电量为1.13万亿千瓦时,居世界第二位。到2014年底,全国发电装机容量为13.6亿千瓦,全国总发电量5.55万亿千瓦时。经过近130多年的发展,我国电力工业从无到有,从若到强,终于迎来了持续健康发展的时期。
    由于电力工业属于国民经济的基础产业,随着我国经济的不断发展,工业化和现代化进程的加快将对电力工业发展提出更高的要求 [2]。电力系统是由电力生产,电力变换,电力传输,电力分配,电力消耗等子系统构成,当我国经济不断向前发展时,势必增加对电量的需求,这就使得电力系统越来越复杂,管理越来越困难。伴随着飞速发展的计算机信息技术,电力行业信息化为电力系统的现代化生产和管理带来了解决方案。电力行业信息化起源于上世纪60年代,即将IT技术运用在电力规划设计,基建,发电,输变电,供电,用电,以及企业管理经营和对外服务的各个领域[3]。近年来,随着信息技术的突飞猛进,我国电力行业信息化建设已经取得了显著的成绩,国内各大电力企业已全部实现了生产和管理的信息化。
    输电线路是电力系统的重要组成部分,它将发电厂,变电站,配电设备及电力用户连接成一个有机的整体[4]。在电网建设过程中输电线路的选择极为复杂,传统线路选择是将收集到的相关地理信息制作成一定比例的地图,事先在地图上选择多条线路,然后根据实地考察对这些线路进行修改并找到最合理的一条路径。传统线路选择效率低下,误差较大,所以计算机辅助选线便应运而生。
    1.1.2 课题意义在电网建设中合理的线路选择能够降低成本,缩短工期,降低施工难度和维护难度,并且能够提高电力传输效率和安全性。输电线路的选择通常还需要综合考虑地形,地貌,地质,交通,环境以及各地部门政策法规等多方面的因素[5]。
    上节讨论到传统线路选择分为图上选线和野外选线两步,线路选择不能只考虑地形地貌。线路相邻塔杆间的间距要合理,不能过长也不能过短;相邻塔杆间的转弯角度必须在施工和维护可以接受的范围内;线路应该尽量绕过湖泊,房屋,建筑,居民区;线路不能经过军事禁区,易燃易爆区;线路不能和铁路,公路,高速重叠,但可以交叉等等。这些因素都给线路选择人员增加了难度,不但效率低而且还极易出错,一条线路往往要实地徒步查看多次才能确定,这就需要线路选择人员具有扎实的专业功底和从业经验。
    本系统针对图上选线的低效性,复杂性和易错性,把人工智能搜索算法应用到电力线路规划中,将启发式搜索和稀疏搜索结合,改进了传统搜索算法的效率,为图上选线提高了工作效率,降低了选线的复杂性,提高了线路的准确性。
    1.2 国内外研究现状设计一条合理的电力线路需要对电力线通过区域内的地形,环境非常了解,传统的地理勘察方法要获取比较精确的地理信息比较困难,随着现代卫星遥感技术的飞速发展,使得地理勘测人员可以获得非常精确的地理信息。遥感图像是地表的真实拷贝,具有覆盖面大,数据现势和信息直观的特点其光谱信息可以反映地质特征,在电力线路的规划中可以起到很好的辅助作用[6]。根据获取的DEM(数字高程模型)数据和相应的地理约束条件就可以利用人工智能算法规划一条或者多条合理的路径,从而可以让电力线路选择人员轻松的获取多个线路方案。
    计算机电力线路规划的基础是卫星地理信息的获取而核心则是路径规划算法,一个优秀的路径规划算法规划出的路径可以为企业节省大量的人力和资金。文献[5]讨论了基于分层模型的电力线路规划方法;文献[7]使用卫星数字地图规划出一条最优路径;文献[8]讨论了城市街区电力线路的选择方法;文献[9-11]讨论了利用栅格数据规划电力线路;文献[12]讨论了如何消除公共参与对电力线路规划的影响;文献[13]研究了人工引导选线的方法。
    电力线路规划算法属于航迹规划算法中的一种,常用的航迹规划算法有A*算法[14],Dijkstra算法[15],遗传算法[16],蚁群算法[17],粒子群算法[18]等。由于A*算法可以找到最优路径并且易于实现,故而应用范围非常广泛,比如在无人机航迹规划,机器人航迹规划,以及游戏中都有大量的应用。A*算法属于启发搜索算法,算法依据搜索过程中结点的代价选择最优的结点从而得到最优的路径。本文针对传统A*算法搜索过程中搜索结点过得多的缺点,采用稀疏A*算法减少了搜索中间结点,优化了算法规划效率。
    1.3 课题主要研究内容和目的1.3.1 主要研究内容课题主要通过深入研究国内外路径规划算法,根据DEM数据和规划约束条件,对电力线路规划算法进行建模,用C/C++语言在Windows平台下实现算法并测试其正确性。具体研究内容有:

    DEM数据的转换。由于原始DEM地形数据是文本文件,不方便规划算法处理也不方便显示路径规划结果,所以需将DEM数据读取到程序的地图数据结构中保存,并将DEM地图数据转换为一张可视化的灰度图。规划算法的设计。由于地图可能比较大,所以如果使用传统的A*规划算法就会非常耗时,所以需要对地图数据进行采样即稀疏地图,然后在稀疏地图上规划一条最优的路径。A*搜索算法代价函数的设计。上节提到A*规划算法的代价函数很重要,这直接决定了算法的优劣,所以需根据电力线路规划的各个约束条件对代价函数进行设计。
    1.3.2 研究目的由于传统电力线路规划的种种复杂性,课题主要的研究目的是实现电力线路规划的数字化,信息化,选线的简单化和准确化,并根据国内外研究现状重点研究A*算法在电力线路规划中的优化策略和代价函数设计方法。
    具体来说有一下几个研究目的:

    研究原始DEM数据的转换为系统内部数据的方法,完成地图的可视化工作。研究基本A*算法的工作原理和搜索过程中路径节点的数据结构设计。研究大地图下稀疏搜索的方法,并将其与A*算法有机高效的结合在一起。研究大地图下在有限RAM中地图和约束区域的数据结构设计和存储方法。
    1.4 论文组织结构本文共有六章其结构如下:

    第一章,绪论。本章对课题的主要研究内容进行了概括性的描述。第二章,系统需求分析。本章讨论了电力线路规划演示软件的各种功能性和非功能性需求。第三章,A*算法概述。本章详细讨论的A*算法的基础原理,并用伪代码描述了算法流程,最后通过一个例子说明的算法的执行过程。第四章,系统概要设计。本章详细论述了电力线路规划演示软件的总体架构以及各个子模块的设计原则。第五章,系统详细设计。本章详细介绍了系统各个模块的具体实现方法,并深入讨论了线路规划算法的设计。第六章,实验结果演示。本章对电力规划演示软件的各个功能模块进行了演示,并测试了在不同约束区域中算法的正确性。第七章,总结与展望。本章回顾了论文探讨的主要问题,并总结了系统的不足之处。
    2 系统需求分析2.1 系统概述本系统在用户输入的DEM地形数据上按照地理环境和各种选线约束规划出一条最优路径并验证算法的正确性,为方便规划将各种规划约束条件整合到一张组合地图中,系统需要完成的主要任务有:

    规划环境地形数据的预处理。将用户输入的数据格式转换为程序的内部各式并可视化为一张地图,建立规划威胁区和规划禁止区,并将这些信息写到规划用的组合地图中。电力线路可以通过但是代价较大的区域为威胁区,电力线路绝对不能通过的区域为禁止区。规划约束区域的指定。为方便验证算法的正确性,规划约束区域人为指定。约定威胁区为圆形,禁止区域为矩形,实际情况中约束区域大多为不规则的形状,但是可以将这些形状简化为不规则的多变形。实现规划算法。根据电力线路规划的特殊性和特殊要求在现有国内外路径规划研究的基础上确定一种适合电力线路规划的算法并改进算法,使算法在满足指定线路开始点,结束点和规划约束的条件下,以尽量少的时间规划出一条最优路径。坐标转换。由于DEM数据给出的坐标是地球经纬度,不适合直接在系统中使用,所以必须将其转换系统方便处理的计算机屏幕坐标。演示规划结果。当规划算法规划结束后将规划结果输出到文件保存,系统再从文件中将规划结果读取并显示在地图上以便选线人员分析。
    2.2 路径规划限制电力线路规划需要考虑很多因素,这些因素都对最终的规划结果有影响,在设计系统时必须充分考虑。主要因素有:

    电力线路禁止进入沼泽地,水草地,盐碱地,炸药厂,鞭炮厂,采石场,采矿区,飞机场,军事禁区以及江河湖海等地区。电力线路应该尽量避免进入村庄,规划区,零星房屋,成片森林等地区。电力线路不能和道路,规划道路,高速公路,铁路,油气管线,输电线路重叠,但是可以交叉。线路在高度上不能超过指定的爬升角度和下滑角度。线路的转弯角度不能超过指定的转弯角度。线路各个塔杆之间有最小距离与最大距离的约束。塔杆之间的距离不得小于最小距离也不得大于最大距离。
    综上所述我们可以将规划限制总结为两类:地理限制和线路选择限制。地理限制是指某些地区不能通过,视为禁止区,某些地区应该避免通过但是可以通过,视为威胁区。线路选择限制是指对线路施工的要求,比如某个点的转弯角度不能过大,线路的爬升角度和下滑角度不能过大否则施工和维护难度将会增加或者根本不能施工。
    2.3 功能性需求本系统根据电力线路规划的特点和实际情况,以及规划环境,路径规划条件的限制,在数字高程地图的基础上对地图数据进行预处理,生成系统可以直接使用的地形海拔数据和组合地图数据,然后自动进行规划并对规划结果进行演示和观察。整个系统的结构流程如图2.1所示:

    本系统各个模块按照如图2.1所示的流程分工合作最终规划出一条合理的路径。工程管理模块负责打开原始数字高程图并将其可视化;约束条件模块负责指定规划的起止点,禁止区和威胁区;预处理模块负责将禁止区和威胁区的信息写到一张组合地图中并生成规划任务文件;线路规划模块负责根据任务文件和组合地图规划出一条合理路径并输出规划结果;结果演示模块将规划结果显示在可视化地图上。
    各个模块的具体功能如下所述:
    2.3.1 工程管理模块当系统完成启动后必须新建或者打开一个原有的工程文件才能开始一次新的规划,本模块是后续所有模块的基础模块,负责将用户输入的DEM数据转换为系统可以统一处理的格式化文件,然后将DEM数据可视化。本模块由打开工程,新建工程,关闭工程等三个子模块构成。

    新建工程文件。读取原始DEM地图数据并将其转换为系统方便处理的格式化文件(后续简称为高程图)。
    打开工程文件。读取新建工程文件时生成的高程图文件,将其转换为可视化的灰度图(BMP图)显示在系统主界面上。
    关闭工程文件。关闭整个工程,将系统恢复到刚刚启动时的状态。

    2.3.2 约束条件模块约束条件模块主要是为后续规划算法指定规划起止点,规划区域中的禁止区域,威胁区域。为了验证线路规划算法的正确性,稳定性,在算法的设计、实现、测试过程中必须考虑到各种可能出现的问题。如果使用真实的地理约束区域有以下两点劣势:

    测试用的数据收集比较繁琐,收集到的数据量有限不能代表大部分的实际情况。
    收集到的数据是固定的,不能测试数据极限的情况,更不能产生复杂多变的约束区域。

    综上两点本系统决定采用人工指定约束区域的方式产生测试数据测试算法的正确性。人工指定约束区域有以下几点优势:

    产生测试数据简单,而且可以随机产生大量的测试数据。
    可以模拟很多复杂的约束区域以验证线路规划算法是否能在这些复杂的环境中规划一条正确的路径。

    2.3.3 预处理模块预处理模块负责生成最终规划用的组合地图,分为初始化组合图和生成组合图两个部分。初始化组合图模块从高程图中读取地图的有关信息,然后将地图中的所有点初始化为未限制区域。生成组合图模块将系统记录的各个规划约束区域的信息写入到组合图中,并生成任务文件。任务文件只包含规划的起止点坐标和海拔信息。
    2.3.4 线路规划模块线路规划模块是整个系统的核心模块,由于规划区域可能会特别大,所以本模块采取稀疏A*算法行规划。算法根据任务文件,高程图,组合地图规划一条最优路径,规划完成后将规划路径坐标信息输出到文件保存。
    2.3.5 结果演示模块规划结果是一系列的点的集合,各个点表示线路的塔杆位置。结果演示模块负责将线路规划模块的规划结果显示在可视化地图上,可方便观察规划结果是否合理。
    2.3.6 参数输入模块由于在实际电力线路的选择过程中存在诸多约束,比如两个相邻塔杆之间的距离选择,两个相邻塔杆之间的坡度选择,相邻塔杆之间转弯角度的选择都会影响线路走向,所以系统在规划电力线路时必须将这些因素考虑在内。系统应在规划之前设置相邻塔杆之间的这些约束参数,这些参数将会影响算法的搜索行为和搜索效率并影响最终的规划线路。
    2.4 数据需求本系统数据采取自定义文件的方式统一管理。原始DEM数据,高程图,组合图,任务文件,规划结果文件都放在指定的文件夹中。DEM数据放在dataorg目录下,高程图放在datamap目录下,组合图放在datazht目录下,任务文件放在Task目录下,规划结果放在LocalPlan目录下。高程图和组合图是二进制文件,任务文件和规划结果是文本文件。系统数据流程图如图2.2所示:

    系统的新建工程模块以DEM地形数据为输入以高程图数据为输出;打开工程模块以高程图为输入以可视化的灰度图为输出;当用户指定了起止点与约束区域后,预处理模块以高程图和约束区域坐标信息为输入以组合图和任务文件为输出;线路规划模块以高程图、组合图和任务文件为输入以规划结果为输出。
    2.5 非功能性需求系统各种非功能性需求如表2.1所示:



    设备项
    最低要求
    推荐值




    CPU
    2.0GHz
    2.5GHz以上


    内存
    2.0GB
    4.0GB以上


    硬盘
    50.0GB
    100.0GB以上


    显卡
    显存512.0MB
    显存1.0GB以上


    操作系统
    WinXP
    Win7


    开发环境
    VS2010
    VS2010+QT4



    3 A*算法概述3.1 A*算法基础3.1.1 A*算法简介A*算法属于人工智能算法,人工智能算法主要是以问题特定的知识为基础求解其最优解的算法,其搜索策略有盲目式搜索和启发式搜索两种。盲目式搜索并不利用实际问题提供的信息计算搜索结点的状态,而是根据当前结点不断的产生后继结点,直到后继结点是目标点为止。盲目式搜索并没有“实际问题实际分析”的能力,算法只能根据事先确定的步骤寻找目标点,这样算法的效率就只能单纯的依赖问题的规模。启发式搜索可以克服盲目搜索的不足之处,利用问题特定的知识制定搜索策略,待求解的问题不同相应的搜索策略不同。启发式搜索在每次搜索过程中都会计算搜索结点的状态,利用状态信息求解问题的最优解,并能有效减小问题的求解规模,提高算法的效率。
    A*算法通过综合源点经过搜索路径到当前点的实际代价,以及从该点到目标点的预估代价来计算点的状态信息。A*算法在搜索过程中会将所有搜到的中间结点保存起来,所以算法效率仍然和源点到目标点的深度密切相关。本章将会分析A*算法的各个特性,并讲述A*算法的搜索过程,最后通过一个例子演示了其搜索状态的变化。
    3.1.2 图论简介图的定义
    图G=(V, E)是一个二元组,其中V表示图中顶点的有穷集合,E表示图中边的有穷集合。集合V中的元素称为顶点或结点,集合E中的元素称为边。图中每个顶点都有一个邻接点集合,例如对于顶点u的邻接点集合可表示为Adj[u],它是所有与顶点u有边相连的顶点集合。如下图所示,顶点集合V={A, B, C, D, E },边集合E={a, b, c, d, e, f, g },为了叙述的准确性通常不将边表示为a,b…,例如边a可以表示为边(A, B),b可以表示为边(B, C),顶点A的邻接点集合为 Adj[A]={B,E},顶点B的邻接点集合为Adj[B]={A, E, D, C}。

    图通常包含有向图和无向图两种,由于本课题讨论的A*算法只使用无向图所以有向图不再赘述。无向图的性质有:

    边集合E是由顶点的无序对构成。例如边(A, B)和边(B, A)是指同一条边a,即边的顶点是无序的。图中不允许存在自环。每条边的两个顶点必须是不同的顶点,例如边(A, A)是不存在的。
    权重图
    权重图是指图中的每条边都有一个权重。边的权重通常是一个边集到实数集的函数ω:E->R。例如图G=(V, E)是一个权重图,设其权重函数为ω,则边(u, v)属于E的权重可以表示为ω(u, v)。在不混淆的情况下通常可以将边的权重称为边的代价。
    路径及其权重
    路径是图中的一个顶点序列。设图G=(V, E)中顶点u到u’的路径为p(u, u’)=\<v0, v1, v2, ..., vk\>,其中 u=v0,u’=vk,且有(vi-1, vi)属于E,i=1, 2, …, k。
    路径权重是指构成该路径的所有边的权重之和。在图G=(V, E)中结点u到结点u’的路径p的权重为:

    由上述定义可以得出该路径中包含了顶点v0, v1, v2, …, vk以及边(v0, v1), (v1, v2), …, (vk-1, vk),路径总长度为k即路径中边的数量。如果从顶点u到u’有一条路径p(u, u’),则称顶点u到u’是可达的。
    前面探讨了路径及其权重,下面引入最短路径及其权重。如果图G=(V, E)中结点u到结点u’可达,则其最短路径的权重(u, u’)为:

    故而从结点u到结点u’的最短路径为:任何从结点u到结点u’的路径中路权重ω(p)=(u, u’)的路径。从此处可以看出最短路径并非一条。
    网格地图
    本系统搜索算法使用的是网格地图,网格地图是一个矩阵。地图中的单元格可以抽象为一个质点,每个质点只具有坐标属性,即每个质点由平面坐标和高度三个量唯一表示一个质点。
    3.1.3 A*算法原理估价函数模型
    前面讨论过A*算法是启发式搜索算法,就是运用与需求解问题相关的启发性知识,在搜索过程中对搜索到的点进行搜索代价评估,得到一个最优的位置,然后从这个最优位置继续向下搜索,直到搜到目标点为止。在启发式搜索中位置估价函数非常重要,不同的估价函数算法的工作效率不同。在A*算法中位置估价函数为:

    f(n)表示顶点n的总估价代价,g(n)表示从源点s到顶点n的实际路径代价,启发函数h(n)表示从顶点n到目标点t的启发代价。 文献[21-23]讲述了估价函数模型并分析了启发函数的特性。
    估价函数性质
    由于A*算法是可采纳的,可设源点s到目标点t是可达的,且

    路径p(s, t)为源点s到目标点t的一条最短路径。若有函数h(n)满足h(ni)-h(nj)<=c(ni, nj)且h(n)<=h’(n),c(ni, nj)为从顶点ni到nj的实际代价,h’(n)为顶点n到目标点的实际代价,则有f(ni)<=f(nj),此时称启发函数h(n)是相容的。
    证明:

    综上所述,如果从源顶点s到目标顶点t是可达的且启发函数h(n)相容,则估价函数f(n)单调递增。则当A*函数在扩展结点时估价函数的值不减少,那么最先找到的路径的代价一定是最小的(路径是最优的),而且从理论上说已访问过的结点以后都不需要再访问。如果搜索算法一定能找到最优路径则称启发函数h(n)是可采纳的。
    估价函数设计
    估价函数的设计并不是一成不变的,也没有固定的规律可循,通常是实际问题实际分析。并不是所有的启发函数都是相容的,但启发函数一定是可采纳的,否则算法就不能找到最优路径。
    设顶点n的坐标为(xn, yn),目标点t的坐标为(xt, yt),搜索地图是网格地图而不是数学意义上的图,一般来说启发式函数有如下几种:
    曼哈顿距离。平面直角坐标系中线段在两个坐标轴上投影长度的总和,即线段在X轴的分量加上线段在Y轴的分量之和。

    欧几里得距离。平面直角坐标系中两点间的直线距离。

    契比雪夫距离。平面直角坐标系中线段在两个坐标轴上投影长度最大者。

    上述启发函数都是固定的简单的距离计算,不利于算法动态调整搜索方向,可以给启发函数h(n)乘以一个动态权值来改善算法的效率和搜索方向。设动态权值函数为K(n)则改进后位置估价函数为:

    K(n)的值可以通过分析待解决的问题加上实验确定,大多数情况下K(n)是一个常量值,K(n)用来改善f(n)中g(n)与h(n)所占的比重,从而改善算法的搜索效率。
    3.2 A*算法详解3.2.1 A*算法基本操作在介绍A*算法前需要说明A*算法使用的松弛操作,松弛操作即对某点的扩展。对图G=(V, E)中的每个顶点u都有三个与其相关的属性u.f,u.g,u.h以及u.p,分别为点u的估计代价,源点s到u的实际代价,u到目标点t的启发代价以及顶点u的前驱结点。可以使用下面的算法对图中的点进行初始化:

    在经过上述初始化操作后源点s的路径估计代价为源点的启发代价且小于无穷大,其他点的路径估计代价和实际代价为无穷大,用启发式函数h(n)算出所有点的启发代价值,所有点的前驱结点都为空。
    对一条边(u, v)的松弛操作包含如下步骤:

    测试源点s到点v的实际代价是否可以减小。将u.g加上边(u,v)的权重ω(u,v),然后与v.g比较,如果前者比后者小则转到下一步。修改v.g与v.f的值。将v.g修改为u.g加上边(u,v)的权重ω(u,v),v.f修改为v.g加上v.h,将点v的前驱结点v.p修改为顶点u。转下一步。修改集合Q。如果顶点v不在Q中则将顶点v加入到Q中。
    松弛造成伪代码如下所示:

    当算法成功找到一条最优路径时需要将这条路径打印出来,伪代码PRINT-PATH可以打印从源点s到t的路径中的所有结点。算法在搜索过程中会保存其前驱结点的信息,当成功找到目标点后,可以根据其前驱结点的信息回溯到源点,从而打印其路径。对PRINT-PATH伪代码稍作修改便可将源点到目标点的路径信息保存到一个向量中。

    3.2.2 A*算法基本流程A*算法通过启发函数评估搜索过程中点的代价,通过比较选择最优的点进行扩展,直到搜索到目标点为止。A*算法在运行过程中维护两个顶点集合CLOSE表和OPEN表。OPEN表存放其估计代价已经计算但是尚未被扩展的点,CLOSE表存放已经被扩展的点。算法重复从OPEN表中选择路径估计代价最小的顶点u,并将点u存入CLOSE表中,然后对与顶点u相连的边进行松弛操作。在下面的算法伪代码中OPEN表使用最小优先队列实现,其关键值为点的路径估计代价f,CLOSE表使用一般性容器即可。算法伪代码如下所示:

    上述伪代码中过程POP-MIN是从OPEN表中弹出路径估计代价f最小的点u。上述伪代码第1行对图中所有点进行初始化,第2行将OPEN表初始化为源点s,第3行将CLOSE表初始化为空。第5~6行弹出OPEN表中路径估计代价最优的结点u,并将其并入CLOSE表。第7~9行判断点u是否是目标点t,如果是则打印源点s到目标点t的路径并退出循环。第10~11行对所有与u相邻的点v进行松弛操作。第4行中判断OPEN表是否为空,如果OPEN表为空还未找到合理的路径说明源点s到目标点t不可达。
    为了更清晰的说明算法的执行流程,图3.2所示的例子详细展示了算法执行时的中间状态。本例中采用网格地图,图中深蓝色的方块表示障碍区域,障碍区域不能通过,算法将从方块s寻找到一条到t的最优路径。图中每个方格中有三个数字一个箭头。方格中左上角的数字表示路径估计值即f值,左下角的数字表示实际代价值即g值,右下角的数字表示启发值即h值,右上角的箭头表示寻路方向,箭头末端指向其前驱结点,黑色方块表示已经放入CLOSE表的点,浅色方块表示OPEN表中的点。

    (a)中表示的是算法第一次从OPEN表中取得源点s并扩展后的场景。(b)~(e)为每次成功执行while循环后的场景,由于中间场景比较多只取了其中的几个具有代表性的场景。(f)为最后一次寻找到目标点t时的场景。当寻找到目标点t后根据图中的箭头反向搜索就可以找到这条最短路径。
    需要说明的是,为了简单明了的展示算法执行过程中各个变量的变化情况,图中每向相邻方块移动一步其代价为1,只能水平或者垂直移动。图中启发函数h(n)使用的是曼哈顿距离。从图中(d)可以看出某一时刻路径估计代价最优的点有多个,这时最优点的选择并没有特定的法则,可以选择为最近找到的最优点或者选择最先找到的最优点也可,当最优点有多个选择时选择任何一个都不会影响最终的最优路径,算法能在所有最优路径中选择其中的一条,从图中(f)也可以看出只是选择了多条合理路径中的一条。

    4 系统概要设计4.1 概述本课题的任务是完成电力线路规划演示软件的设计和实现工作,同时在现有航迹规划算法的基础上研究并改进一种适用于电力线路选择的航迹规划算法。本系统的主要设计目的是综合电力线路选择的各种要求,结合计算机技术为传统电力线路选择提供服务。由于电力线路的选择分为纸上选择和实际勘测两步,本系统主要是用信息化手段替代传统纸上选线,为实际勘测提供精确的数据资料。系统设计的主要任务是:

    界面简洁,操作简单。电力线路规划系统在人机交互方面应当直观,灵活,便捷,易用,符合用户使用习惯。系统各个模块间高内聚,低耦合,功能完善。系统各个模块高效完成其对应的功能并与其它模块合作在用户的操作下规划出一条合理的路径,各个模块间互不影响。
    4.2 系统总体设计4.2.1 系统模块结构本系统能根据电力线路规划的目标点,起始点,路径规划限制,规划环境约束等要求自动规划出路径结果,同时能对规划结果进行显示并将规划结果按照指定文件格式输出。系统按照工程的方式组织每次规划中的文件,即一次规划任务产生不同的规划相关文件。系统的总体模块结构如图4.1所示:

    本系统皆以菜单的方式实现各个功能模块,这些功能模块是:

    工程管理。本模块包含的子模块有新建工程文件,打开工程文件,关闭工程,关闭系统。约束条件。本模块包含画起始点,画终止点,画威胁区,画禁止区四个子模块。线路规划。本模块包含参数设置和线路规划两个子模块。预处理。本模块包含初始化组合图,生成组合图两个子模块。效果演示。本模块没有子模块。
    4.2.2 系统使用流程本系统各个模块分工合作,其使用流程如下图所示:

    如图4.2所示,当第一次启动系统时,工程管理模块中新建工程文件子模块将读取DEM文件并生成高程图;打开工程文件子模块读取高程图文件以便转将其转换为灰度图;约束条件模块在系统的可视化界面上指定起止点和约束区域;生成组合图模块读取高程图的信息并综合约束信息生成一张组合图以及任务文件;线路规划模块则读取任务规划文件并依据高程图和组合图中的信息进行规划并输出规划结果;效果演示模块则读取规划结果并将其显示在可视化地图上。系统的各个模块相互独立,又分工协作,任何一个模块的任务未完成都不能进入到下一个模块。
    4.2.3 系统总体界面为了实现友好的人机交互操作,提高可视化效果,本系统参照一般软件的设计思路将系统主体管理界面设计为下拉菜单的方式。界面的构成部件有:系统标题区,主功能菜单区,功能快捷按键区,地图浏览区,消息显示区。界面设计如下图所示:

    系统采用下拉菜单方式管理各个模块,启动时整个界面充满屏幕。系统标题区显示系统的名称:电力线路规划演示软件;主功能菜单区的每个菜单将各个模块综合在一起,分为:工程管理菜单,约束条件菜单,线路规划菜单,效果演示菜单,预处理菜单。快捷按键区是各个菜单中子菜单的工具栏按钮;地图浏览区是用来显示可视化地图,用户指定的起止点,约束区以及最终规划结果的区域;消息显示区并未包含在主界面之内,消息显示区是一个单独的模态对话框,只有当系统有消息显示时才会出现,消息显示区有两种,第一种为常规信息显示对话框,第二种是时间计数对话框,下小节将会对这两种对话框详细描述。
    4.3n系统模块设计4.3.1 工程管理菜单一般系统的工程管理菜单都会生成工程管理文件,这个文件记录了相应工程的各种信息。当关闭工程后再次打开工程时,工程管理菜单会从工程管理文件中读取记录的信息,然后自动加载用户的设置,不需要用户管理工程用到的各种文件,从而改善用户体验。由于本系统文件不多且流程简单,所以本系统工程管理菜单并不具备真正的工程管理能力,本模块相当于一个数据处理模块,负责把DEM数据转换为高程图数据,然后将高程图数据转换为可视化灰度图。本菜单由新建工程文件,打开工程文件,关闭工程,退出系统等子菜单构成。工程管理菜单界面设计如下所示:

    工程管理菜单结构图如下所示:

    工程管理菜单数据流程图如下图所示:

    如图4.6,工程管理菜单中各个子菜单的输入输出如下所述:

    新建工程文件子菜单输入为DEM地形数据,输出为高程图数据。打开工程文件子菜单输入为高程图数据,输出为可视化地图数据(灰度图)。关闭工程子菜单无确切输入输出数据,关闭当前打开的工程文件,释放当前工程占用的资源。关闭系统菜单关闭整个系统,清理系在运行期间所申请的任何资源。
    4.3.2 约束条件菜单约束条件菜单用于指定线路规划算法的起止点,规划约束区域。为了方便后续组合图的生成将禁止区设计为矩形,威胁区设计为圆形。本菜单有画起始点,画终止点,画威胁区,画禁止区四个子菜单组成。
    约束条件菜单界面设计如下:

    约束条件菜单结构如下图所示:

    约束条件菜单数据流程图如下图示所示:

    当用户选中约束条件中某个子菜单后,可在地图上单击一个点,系统记录下这个点的坐标,并依据这个坐标生成相应的约束区域坐标,具体入下:

    当用户选择画起止点或是终止点时,用户单击点的坐标就为相应的起止点坐标。当用户选择画威胁区时,用户单击点的坐标为圆心,系统随机产生圆的半径。当用户选择画禁止区时,用户单击点的坐标为矩形左上角坐标,系统随机产生矩形的长宽。
    4.3.3 预处理菜单预处理菜单用于生成规划时使用的组合图数据,所谓组合图就是标明规划地图内每个点是否在威胁区、禁止区内或者点不在任何规划约束区内。当线路规划算法要判断某个点是否可以扩展时就需要用到组合图中的信息。预处理菜单包含初始化组合图和生成组合图两个子菜单。
    预处理菜单界面设计如下:

    预处理菜单结构如下所示:

    预处理菜单数据流程图如下所示:

    如图4.12,预处理菜单中各个子菜单的输入与输出描述如下:

    初始化组合图。输入为一个组合图文件名和高程图数据,输出为根据高程图数据初始化的组合图。
    生成组合图。输入为初始化的组合图和约束区域坐标,输出为包含约束区域信息的组合图以及规划任务文件。

    4.3.4 线路规划菜单 线路规划菜单根据指定的起止点,约束区域及其他规划要求规划出一条合理的线路。线路规划菜单包含输入参数和A*线路规划两个子菜单。
    线路规划菜单界面设计如下所示:

    输入参数界面设计如下:

    线路规划菜单结构如下所示:

    线路规划菜单数据流程图如下所示:

    如图4.16,线路规划菜单中各个子菜单输入与输出描述如下:

    输入参数子菜单输入的参数包含相邻塔杆间的距离、爬升和下滑坡度以及转弯角度,当选择输入参数子菜单时会弹出一个参数输入对话框,用户输入的这些参数会被保存在系统相应的变量中,作为A*线路规划算法的参数被算法使用。A*线路规划子菜单输入为任务文件,高程图和组合图,输出为规划结果,规划结果是一系列点的集合,这些点构成了一条电力线路。
    4.3.5 效果演示菜单线路规划完成后会把规划的线路保存成固定格式的文本文件,效果演示就是对规划结果的二维动态演示,即把结果中的各个点连线显示在可视化地图上。效果演示菜单只有效果演示一个子菜单。
    效果演示菜单界面设计如下:

    效果演示菜单结构图如下所示:

    效果演示数据流程图如下所示:

    如图4.19,效果演示输入文件为线路规划结果,输出为可视化的规划结果,规划结果与起止点,约束区域一起显示在可视化地图上。
    4.3.6 消息对话框信息显示对话框在每个模块中都有可能出现,主要用于提示系统出现的错误,向用户显示某项操作是否成功,显示某个操作所消耗的时间或是在执行某种操作前取得用户的选择信息。两种消息显示对话框如下图所示:

    如图4.20,常规信息显示对话框的取消和确定按钮是可选的,二者中可以只出现一个,时间对话框的右上角设计有取消按钮。常规信息显示对话框是用于向用户显示一般的消息,比如某个模块是否执行成功或者询问用户是否执行某项操作。时间对话框用于显示某项操作的执行进度,比如线路规划模块非常耗时于是向用户显示当前规划已经用了多少时间,每个时间对话框都关联了一个耗时的操作,用户可以单击取消按钮取消这项耗时的操作。
    5 系统详细设计5.1 工程管理设计5.1.1 模块说明本模块是系统的基础模块,后续各项操作均在本模块基础之上。本模块执行过程中主要是以对文件的读、写、保存为主要操作,读取原始DEM地形数据进行转换,并将转换后的数据保存在高程图文件中。本模块由新建工程文件,打开工程文件,关闭工程等模块构成。
    5.2.2 新建工程文件新建工程文件子模块负责完成DEM地形数据到高程图数据的转换,因为原始的DEM地形文件并不能满足系统规划的需要,所以为了便于规划系统使用了统一的内部文件格式(*.map)。
    DEM地形图数据格式
    DEM地形图数据文件是文本文件,分为两部分:文件头,文件数据。

    文件头。本部分只有一个整数n,这个整数表示整个地图有n个点。文件数据。本部分由n行组成,每行由4个浮点数组成,前两个数据为点的经纬度,第三个数据为点的高度,第四个数据为高度的误差。
    高程图文件格式
    转换的高程图(*.map)的格式也包含文件头信息,文件数据两部分。

    文件头信息。本部分包含了地图左上角的经纬度坐标,数据的行数,数据的列数,最低海拔,最高海拔,精度以及无效数据。文件数据。本部分包含的是地形的海拔数据,文件被设计为一个网格模型,每个单元格存储地形中一个点的海拔,数据长度为16位。每个点的坐标加上文件头信息中左上角的经纬度坐标就是点的原始经纬度坐标。
    程序算法

    程序通过弹出一个文件选择对话框获取需要转换的DEM地形文件路径字符串,然后判断获取的字符串是否为空,如果为空则退出。弹出一个文件选择法对话框获取转换后的高程图文件路径字符串,然后判断获取的字符串是否为空,如果为空则退出。将上述两个文件路径字符串传递给数据转换线程。数据转换线程首先打开DEM文件读取数据,然后按照高程图文件的格式将数据写入到高程图文件中。
    程序逻辑流程如下所示:

    5.2.3 打开工程文件打开工程文件子模块对系统自定义的高程图文件实现打开浏览功能,即可视化。输入为高程图文件,输出为RGB图或灰度图。
    灰度图格式
    灰度图是一个像素矩阵,其行数和列数与高程图中的行列数相等,矩阵中的每个像素对应高程图中的每个点的海拔。海拔数据到RGB数据的转换公式为:
    RGB=((elevation-minElevation)÷(maxElevation-minElevation))×255从公式(5.1)中可知每个像素点RGB值的三个分量都为同一个值,即点的海拔和地形中最小海拔的差值与地形中最小海拔和最大海拔的差值的比例乘以255。RGB值为0代表此点的海拔与最低海拔相同,255代表此点的海拔与最高海拔相同,在灰度图中显示出来则为越白的区域海拔越高,越黑的区域海拔越低。
    程序算法

    打开需要浏览的高程图文件。读取高程图文件的文件头和文件数据。申请用于保存灰度图数据的存储空间。按照公式(5.1)和灰度图的格式将高程图数据转换为灰度图存储在申请的空间中。将转换后的数据交由界面窗口部件显示。
    程序逻辑流程如下所示:

    5.2.4 关闭工程关闭工程子模块关闭当前正在使用的工程,设置系统界面部件的状态为初始状态,释放工程打开期间所申请的资源,关闭工程打开期间打卡的文件。程序逻辑流程如下:

    5.2 约束条件设计5.2.1 模块说明为了方便系统处理和模拟真实环境中的障碍区域,约束区域有两种:威胁区,禁止区。威胁区电力线路可以通过但是每个威胁区都有威胁等级,不同的等级线路通过的代价不一样。禁止区则禁止线路通过。本模块由画起止点,画威胁区,画禁飞区组成等子模块组成。
    为了区分威胁区和禁止区,将禁止区设置为长度和宽度在10至50像素的矩形,威胁区设置为半径在20到50像素的圆形。由于用户随机设置约束区域所以每个约束区域有可能和前一个区域重合,如果威胁区与威胁区或者禁止区域与禁止区域重合则重合部分的约束性质不变。如果威胁区和禁止区重合则重合区域的约束性质以禁止区为主,即线路不能通过重合区域。约束区形状域如下图所示:

    如图5.4所示,(a)为禁止区,(x,y)为左上角坐标,l为长,w为宽;(b)为威胁区,(x,y)为圆心坐标,r为圆的半径。
    5.2.2 程序算法
    用户约束条件菜单中的任意子菜单,程序保存用户的选择。
    当用户在系统主界面上单击鼠标时:
    若用户选择的是画起始点或者画终止点,系统在主界面上以用户点击点为中心画一个等边三角形,并保存单击点的坐标。
    若用户选择的是画威胁区,系统在主界面上以用户单击点为圆心,以一个大于10小于40的随机数为半径画一个圆,并将这个圆的坐标保存到威胁区链表中。
    用户选择的是画禁禁止区,系统在主界面上以用户单击点为左上角,以两个大于10小于50的随机数为长和宽画一个矩形,并将这个矩形的坐标保存到禁止区链表中。

    5.2.3 模块流程本模块的流程图如下所示:

    5.3 预处理设计5.3.1 模块说明在规划过程中需要不断的从高程图中读取数据,不断的判断某个点是否在威胁区或者禁止区中。例如当算法扩展某个点时需要判断这个点的高度是否合法,需要判断这个点是否在某个约束区域中。通常我们可以用一个循环判断某个点是否在禁止区或者威胁区中,算法的时间复杂度,n为约束区域的个数。当威胁区和禁止区的数目非常大,而判断的点非常多时将严重影响算法的性能。故而设计组合图将某个点是否在约束区内的详细信息予以记录,当要判断某个点时只需根据点的坐标便可在组合图中获得点的详细信息。组合图以文件的形式保存,并不常驻内存,当需要取得组合图中的信息时,系统用Windows内存映射的方法取得组合图中的相应数据。
    5.3.2 组合图编码组合图用32位编码高程图中每个点的约束信息。低16位用于表示该点是自由规划区,禁止区,威胁区。高16位用于表示某个点在威胁区中的编号。当某点在自由规划区或者禁止区中时高16位没有意义。当产生威胁区时系统按顺序将每个威胁区坐标存入一个向量中,高16位编号即代表某个点所在威胁区在向量中的下标。当低16位为1时表示自由规划区,为2时表示禁止区,此时高16位无意义,当低16位未4时表示威胁区,高16位为相应编号。每种区域的编码如下图所示:

    如图5.6,(a)为自由规划区的编码,前两个字节为0,后两个字节为1。(b)为禁止区编码,前两个字节的值不确定,当威胁区与禁止区重合时x为威胁区编号num,否则为0。(c)为威胁区编码,前两个字节为威胁区的编号num,后两个字节为4代表是威胁区。
    5.3.3 程序算法本模块分为两步:初始化组合图,生成组合图。

    初始化组合图时将所有点的信息初始化为自由规划区。
    生成组合图时分为将威胁区写入组合图和禁止区写入组合图两步。
    写入威胁区信息时先求出每个圆的外接矩形,然后依次判断外接矩形中的每个点是否在圆中,如果在圆中则将相应点设置为威胁区,并将这个点所在威胁区的编号写入组合图。
    写入禁止区信息时循环将每个禁止区中的点设置为禁止区即可。

    5.3.4 模块流程本模块处理逻辑流程如下所示:

    5.4 线路规划设计5.4.1 模块说明本模块是系统的核心模块,在前面的各种处理基础上规划出最终的路径。线路规划算法的设计优劣直接影响系统的性能和用户体验。算法在用户指定的起止点,威胁区信息和禁止区信息上规划出一条满足电力线路规划约束的航迹。通过研究现有航迹规划算法,以及综合电力线路规划约束条件,为保证算法的效率本模块采用稀疏A*算法进行搜索。文献[24]研究了综合各种路径代价影响下的线路规划方法,文献[25-27]讨论了改进数据结构对算法进行优化,文献[28-31]讨论了通过改进搜索策略对算法进行优化。
    5.4.2 程序算法计算搜索区域
    稀疏A*算法在搜索过程中将结点分为三类:已扩展的结点存放在CLOSE表中,待扩展的结点存放在OPEN,尚未扩展的结点。传统算法扩展点时会扩展与所有与待扩展点相邻的点,稀疏A\算法将塔杆间最小距离Lmin,最大距离Lmax,最大转弯角度Alpha考虑在内先进行平面规划,当平面规划符合条件后进行高度规划,即扩展点需满足最大爬升角度Beta。将上述规划约束综合后形成一个以Lmax为半径,圆心角为2xAlpha的扇形有限规划区域,为缩小搜索规模再次将搜索区域等分S份,整个搜索区域如图5.8所示。

    扩展结点
    如图5.9所示,算法在搜索过程中扩展点时不需要遍历该区域内的每个结点,只需考虑区域内的具有代表性的点即可。假设把搜索区域均匀划分为S个扇区,每个扇区中只取与当前结点距离l满足Lmin <= l <= Lmax的点,算法并不扩展每个扇区中所有满足条件的点,算法逆时针遍历每个扇形中第一条边上满足条件的点,并计算这些点的代价。根据稀疏A*算法每个扇区只保存代价最小的点,则每个结点产生的扩展节点共有S个。

    扩展结点合法性判断
    算法根据当前待扩展点n找到一个尚未扩展的点m时需要判断结点m是否在威胁区或禁止区中,如果结点m不在禁止区中,则算法根据结点m的坐标从组合图中取得m的高程值,然后判断从当前待扩展点n到结点m的爬升或下滑角是否满足要求,通过两点间的高度与距离可以算出爬升下滑角,其计算公式如下所示:

    如果有

    则结点n满足约束条件,此时判断点n与m是否可以建立航迹,即结点n与结点m连线之间是否所有点都不在禁止区中,如果结点n与结点m连线之间有任意某个点在禁止区中则结点m都必须舍弃,算法流程如下图所示:

    计算扩展方向与扩展角
    算法根据当前待扩展点s与目标点t确定扩展方向,以结点s与结点t的连线为角平分线,等分一个角度为2xAlpha的角,点s到t的射线的方向就为扩展方向。扩展方向射线的倾斜角为:

    扩展区域角度的下限为:

    扩展区域角度的上限为:

    扩展区域角度上下限满足关系:

    扩展方向与扩展角度的关系如下图所示:

    扩展点代价计算
    代价函数是A*算法的核心,A*算法本身并不是特别复杂,它的复杂度主要体现在代价函数的设计,代价函数主要是用于控制算法的搜索方向和搜索效率,使算法“带有方向”的搜索。代价函数的设计需要综合考虑线路规划的各种约束条件,这样搜索形成的路径才具有实用性。根据公式(3.3)可知代价函数分为两部分:实际代价g(n),启发代价 。
    当前结点n到其前驱结点p的已知代价包括点n到点p的距离代价dpn,点n和点p的高度代价hpn,点n到点p连线经过威胁区的代价thrpn。故而结点n的实际代价为已知代价与其前驱结点p的实际代价p.g的和。如下所示:

    距离代价为dpn为欧几里得距离,高度代价为两点间的高度差。设源点s到结点n的路径为p(s, n)=\<s, n1, n2, ..., n\>,其长度为k,则n.g还可表示为:

    其中di表示p(s, n)中第i条边(ni-1, ni)的距离代价,hi表示第i条边的高度代价,thri表示第i条边的威胁代价。
    启发代价包含当前点n到目标点t的距离代价 ,点n和目标点t的高度代价 ,以及当前点n和目标点t连线之间经过威胁区的代价fbdnt和禁止区的代价thrnt 。

    其中fbdnt为当前点n与目标点t的连线经过禁止区的长度和禁止因子的积。
    如果路径p经过了某个威胁区i则其威胁代价计算公式为:

    其中u为威胁因子,ri威胁区i的半径,di为路径p到威胁区圆心的距离,所以如果路径离圆心越近威胁代价越大。如图5.2所示为威胁区与规划路径相交的场景,图中路径p(n1, n2)经过圆1的圆心,与圆2和圆4相交,与圆3不相交。根据公式(5.10)则线路p(n1, n2)的威胁代价为:


    当计算待扩展点n的启发代价时需计算点n与目标点t经过的禁止区代价,设点n与目标点t的连线进过了禁止区i则其禁止代价计算公式为:

    其中6为禁止因子,di为点n到目标点t连线进过禁止区i的长度。如图5.13所示为点n和目标点t连线与禁止区相交的场景,则其禁止区代价为:

    5.4.3 数据结构设计图的存储
    网格地图在平面坐标系下按照x坐标和y坐标将平面划分为无数个网格,在特定境下可以将每个网格抽象为一个只具有x与y坐标以及一个属性z的质点,整个地图由这些质点按照其坐标排列成一个矩阵。本模块将地图设计为一个二维数组的形式,map[n][m]表示整个地图有n列m行,map[x][y]表示坐标(x,y)处点的高程值。由于整个地图可能非常大,程序使用Windows内存映射加载地图数据。
    结点数据结构
    算法在搜索过程中会产生很多中间结点,根据第3章的知识每个结点包含的信息有:点的x和y坐标,点的高程值z,从源点到当前点的实际代价值g,从当前点到目标点的代价预估值h,g值和h值的总和f,当前点的前驱结点p。本模块将结点信息设计为一个C结构体PointInfo(),当搜索到一个新的点时就申请一块PointInfo大小的内存存放点的信息。
    OPEN表设计
    OPEN表用于存储算法在搜索过程中搜索到的点,在本模块中将OPEN表设计为一个链表,链表中的点按照f值从小到大排序,f值最小的为表头。OPEN表中存放结点信息结构体的首地址,OPEN表的设计为std::list\<PointInfo \*\>OpenList。当有新的结点加入OPEN表时,首先查看表中是否存在某点的坐标与其相同,如果存在且其f值比新的f值大则删除原来的点,并将新的点插入OPEN表中,如果不存在则直接按照f的大小插入OPEN表中。
    CLOSE表设计
    CLOSE表存放算法已经扩展过了的点,与OPEN表相似,CLOSE也保存点信息结构体的首地址。CLOSE表的设计为std::list\<PointInfo \*\>CloseList,当从OPEN表中取得一个最优点后可以直接放入CLOSE表中。
    5.4.4 算法流程前面几各小节主要讲述了算法如何缩小搜索区域,如何在搜索区域中扩展一个点,如何计算待扩展点的估计代价。由于第3章已经详细的描述了A*算法的执行流程,所以本节将不在赘述,本节将把上节讨论的子步骤综合起来简明扼要的概述算法的主要流程。算法的主要流程如下所述:
    1.清空OPEN表和CLOSE表,将源点s的各个代价设置为0放入OPEN表。
    2.当OPEN表不为空时循环从OPEN表中取出代价值最小的点n,并将点n加入CLOSE表中。
    3.判断目标点t是否在点n的搜索区域内

    如果目标点t在点n的搜索区域内则判断点n与点t是否可以直接连接(n与t的连线不经过威胁区和禁止区)。
    如果目标点可以连接则找到一条路径,搜索结束。
    如果目标点不在n的搜索区域内或者不可连接则继续。

    4.将点n扩展。

    计算点n的搜索扇形区域。
    将点n的扇形搜索区域均分为S个扇区,然后计算每个扇区中可以扩展的点m。
    如果点m不在OPEN表中则将点m加入OPEN表,回到第(2)步继续搜索。


    5.5 效果演示设计线路规划模块规划线路结束后会生成一个规划结果文件,结果文件包含一系列的点,每个点包含有经纬度和高度值。这个结果文件可视性很差不利于分析系统规划的结果是否符合要求,于是需要把这个规划结果在地图上演示。本模块首先读取文件中每个点的经纬度,然后将每个点的经纬度转换为灰度图中的坐标即屏幕坐标,最后将这些点以折线的方式画在灰度图上显示给用户。
    6 实验结果演示6.1 系统主界面系统启动时的主界面将会充满整个屏幕,如图6.1所示:

    6.2 工程管理功能6.2.1 新建工程文件新建工程文件菜单完成原始DEM地形数据到高程图数据的转换。如图6.2和6.3所示,本模块会让用户输入原始DEM的文件名以及转换后高程图的文件名。



    打开原始DEM文件
    新建高程图文件









    新建工程模块在得到原始DEM地形数据的文件路径,以及新建高程图数据文件名后,将把原始地形数据转换为规则的高程图数据以便后续处理,由于数据量大转换时转换工作在后台进行,主界面会提示转换进度。转换进度及成功提示如图6.4和6.5所示。

    6.2.2 打开工程文件打开工程文件菜单读取高程图数据并将其转换为可视化的灰度图显示在主界面上,图6.6为打选择高程图文件名对话框,图6.7为高程图转换为灰度图后的系统主界面。

    6.2.3 关闭工程关闭工程子模块会清理工程打开期间所申请的资源并将系统主界面设置为图6.1所示的启动时的状态,这里就不在赘述。
    6.3 约束条件功能如图6.8所示,图中三角形为起止点,圆形为威胁区,矩形为禁止区。

    6.4 预处理功能预处理模块负责将约束条件模块中的约束区域写到组合图中,生成工作在后台完成,系统主界面显示进度。如图6.9和图6.10所示为生成组合图时的进度条以及成功提示。

    6.5 线路规划功能6.5.1 输入参数输入参数子模块将设置线路规划过程中用到的各种约束参数。输入参数界面如图6.11所示。

    6.5.2 线路规划本模块首先读取规划任务文件,然后在后台进行规划,系统主界面显示规划操作所耗时间。如图6.12和图6.13所示为读取任务文件名对话框,以及规划任务进度条。

    6.6 规划结果演示为了直观的判断规划结果是否正确,效果演示模块将规划结果显示在系统主界面的灰度图上。为了说明算法的正确性,将线路规划模块在不同的约束区域中运行,并演示其规划结果,实验使用的数据大小为1500*1500的DEM地形数据,即整个地图中有2250000个点。前面讨论到威胁区线路可以通过但是有一定的代价,所以算法规划的路径应该尽量避开威胁区,即离威胁区中心尽量远。如图6.14所示,线路避开了威胁区。

    图6.15演示了算法在禁止区中寻找到的一条最优路径,线路不能穿过禁止区。如图所示,算法所规划的路径有效的避开了其中的禁止区域。

    如图6.16演示了在综合约束区域中,算法规划的路径。从图中可以看出算法规划的路径依然有效的避开了禁止区域,在无法避免穿过威胁区时,线路采取了尽量远离威胁区中心的措施。

    7 总结与展望7.1 论文主要内容总结电力线路规划是电力系统的重要组成部分,由于人工选线的局限性使得利用信息化手段规划电力线路成为研究重点。电力线路规划综合了规划任务,规划地理环境以及电力架线与维修的要求等约束条件,从而规划出一条从起始点到目标点的最优可行路径。本系统根据DEM地形数据,在Windows平台下用C/C++语言与QT图形界面完成了电力线路规划演示软件的设计与实现。重点讨论了A*算法的的理论基础与启发函数的原理,在电力线路规划约束条件下改进并实现了可用于电力线路规划的A*算法,同时验证了算法的正确性。本文主要探讨了以下内容:

    探讨了电力线路规划的国内外研究现状,介绍了使用A*算法完成线路规划的优缺点。完成了电力线路规划演示软件的设计,程序实现及测试工作,对程序中的关键数据结构进行了分析与设计。讨论了A*算法的基本原理,改进并实现了适用于电力线路规划约束条件的A*搜索算法。设计并实现了线路规划演示软件的人机交互界面。
    7.2 不足与展望由于作者能力和时间有限,对本课题所涉及的内容探讨还不够全面和深入,探讨方法也有不甚合理之处,还有待进一步改善:

    软件人机界面设计还不够合理,软件很多设计没用考虑用户的使用习惯,还需改进。规划算法的效率还有带提高,由于组合图的设计不够合理使得组合图比高程图还大,所以当数据比较大时,组合图不能全部装入内存,对文件的读写严重影响了算法的效率,可以考虑去掉组合图改用算法判断点是否在约束区域中。算法规划的线路平滑度不够,这是由于本系统算法弱化了结点转弯控制造成的,对线路的平滑处理还需进一步研究。在线路规划过程中高度规划的设计不够合理,在高度规划过程中由于只是判断了相邻两点的爬升下滑角是否合理,这只能局部优化线路,若将最终的规划结果综合来看有可能出现高度参差不齐的情况。
    参考文献[1] 杨华壮,刘权锋.中国电力工业发展概述[J].中国科技博览,2011(32):376.
    [2]孙庆海.浅谈中国电力事业的发展与规划[J] .房地产导刊,2014(35):189.
    [3]辜体仁.电力行业信息化发展及现状介绍[A] .OA’2011办公自动化国际学术研讨会论文集(C),北京.2011:1-6.
    [4]王艳丽.输变电工程项目送电线路最优路径选择研究[D].保定:华北电力大学,2012.
    [5]康健民,袁敬中,肖少辉,赵俊生,刘震.基于分层模型的输电线路选线算法设计[J].电力建设, 2012,33(4):6-10.
    [6]杜全维,王全心.基于卫星遥感技术的电力选线系统设计与实现[J].科技资讯,2009(28):22-23.
    [7] Miguel V,Hector G,Sarmiento.Image processingapplication maps optimal transmission routes[J].IEEE Computer Applications in Power,1996,3 (1 ) : 33 -39.
    [8]West N A,Dwolatzky B,Meyer A S.Terrain-based routing ofdistribution cables[J]. IEEE Computer Applications in Power,1997,4(1):42-47.
    [9]Salman A,Hamid E,Valadan Z M.A new method for pathfinding of power transmission lines in geospatial information system usingraster networks and minimum of mean algorithm [J].World Applied Sciences Journal,2008,3(2):269-277.
    [10]Volkan Y,Recep N.Developing a geospatialmodel for power transmission line routing [C] //Proceedings of the 3rd AfriconConference.Ezulwini Valley,Swaziland:IEEE,1992:59-64.
    [11]Schmidt A J.Implementing a GISmethodology for siting high voltage electric transmission lines[J].Papers in ResourceAnalysis,2009,11(2):58-63.
    [12]Ward J,Fellow I,Ted G,etal.A New method for publicinvolvement in electric transmission-line routing[J].IEEE Trans On PowerDelivery,2009,24(4):33-38.
    [13]刘春霞,王家海.基于空间分析的送电线路选线方法J].测绘工程,2008,17(3):28-30.
    [14]Yao J F, Lin C, Xie X B, et al. Path planning for virtual human motion usingimproved A* star algorithm[C] ∥Proc. Of the 7thInternational Conference on Information Technology: New Generations,2010:1154-1158.
    [15]Kang H, Lee B, Kim K. Path planning algorithm using the particle swarmoptimization and the improved dijkstra algorithm[C] ∥Proc. Ofthe Pacific-Asia Workshop on Computational Intelligence and IndustrialApplication, 2008: 1002-1004.
    [16]Ju M Y, Cheng C W. Smooth path planning using genetic algorithms[C] ∥Proc. Ofthe 9thWorld Congress on Intelligent Control and Automation, 2011:1103-1107.
    [17]Zhao S G, Li M. Path planning of inspection robot based on ant colonyoptimization algorithm [C] ∥ Proc. of the InternationalConference on Electrical and Control Engineering, 2010:1474-1477.
    [18]Shakiba R, Najafipour M, Salehi M E. An improved PSO-based path planningalgorithm for humanoid soccer playing robots[C] ∥Proc. Of the 3rd Joint Conference of AI & Roboticsand the 5th RoboCup Iran Open International Symposium, 2013:1-6.
    [19]Thomas H.Cormen,CharlesE.Leiserson,Ronald L.Rivest,CliffordStein著.算法导论(第2版)[M].潘金贵,顾铁成,李成发,叶懋译.北京:机械工业出版社,2006.9:667.
    [20] 李枭扬,周德云,冯琦.基于分级规划策略的A*算法多航迹规划[J].系统工程与电子技术,2015(2):318-322.
    [21]钟敏.A*算法估价函数的特性分析[J].武汉工程职业技术学院学报[J],2006,18(2):31-33.
    [22] 王德春等.基于A*算法的舰船最佳航线选择[J].青岛大学学报,2005,18(4):10-13.
    [23] 杨素琼等.基于A*算法的地图路径搜索的实现[J].铁路计算机应用,2001,9(4):8-11.
    [24]李季,孙秀霞.基于改进A-Star算法的无人机航迹规划算法研究[J].兵工学报,
    2008,29(7):788-792.
    [25]乐阳,龚健雅.Dijkstra最短路径算法的一种高效率实现[J].武汉测绘科技大学学报,1999(03):209-212.
    [26]王开义,赵春江,胥桂仙.GIS领域最短路径搜索问题的一种高效实现[J].中国图象图形学报.A 2003(08):951-956.
    [27]陆锋,卢冬梅,崔伟宏.交通网络限制搜索区域时间最短路径算法[J].中国图象图形学报.A 1999(10):849-855.
    [28] 程林,王美玲,张毅.一种基于SuperMaP GIS的改进Dijkstra算法[J].地球信息科学学报,2010(5):649-653.
    [29] 杨琰,廖伟志,李文敬,杨文,李杰.基于Petri网的顾及转向延误的最优路径算法[J].计算机工程与设计,2013(10):3643-3647.
    [30] 杜立智,张晓龙.一个高效的3SAT到Hamilton环转化方法[J].南京理工大学学报(自然科学版),2013(4):506-510.
    [31] 许岩峰,王巍.浅谈突发事件应急物资调度[J].科技广场,2012(6):210-213.
    1 评论 6 下载 2018-09-30 23:48:17 下载需要10点积分
  • 基于百度云和NFC技术的会展导游系统APP设计与实现

    摘 要本文详细介绍了基于百度云和NFC技术的会展导游系统的实现。
    由于展会中存在大量未经筛选或提炼的信息,它们过分模糊了用户的焦点,导致用户的视线被噪音所遮掩,无法快捷地获取感兴趣的信息;并且传统的宣传媒介已无法满足用户获取更大的信息量、更多种类信息的需求。针对这种情况,本系统采用了手机移动客户端的方式,解决了用户浏览过程中关于信息获取的问题。
    会展导游系统采用传统的C/S架构,使用了百度提供的各种云能力。在客户端上,通过富媒体展示,解决用户获取商品详尽信息、获取商品优惠券的需求;基于百度LBS云能力,解决用户获取周边商铺信息的需求;基于百度PCS云存储,解决用户对信息的持久化存储的需求;接入了流行的社交渠道,增加了用户浏览过程中的趣味性。
    本文首先将对系统的产生背景做了介绍,并对现有媒介能力不足的现状做了分析;对百度提供的各种云能力,也做了详尽的说明。而后将会讲解系统的需求,与详尽的系统设计。最后,会预估系统对用户和对展商的收益,说明该系统的使用,不仅能为用户带来更友好的浏览体验、也能为展商带去更多的收益。
    关键词:百度云能力;NFC(近场通讯技术);展会;需求;系统设计;移动互联网
    AbstractIn this page, an android mobileapplication system called ExGuide, which is based on Baidu Application Engineand near field communication technology is introduced here. The page will notonly show the idea of the system, but also the design and implementation ofwhich.
    The idea comes from scene of exhibition. When you attend anexhibition, such as GuangZhou Auto show, a motley variety of new cars or sexual Models will be displayed there You maybe distracted by them, and can’t focus your attention .You may not get what you wants immediately and fully. Adirect mail can’t satisfy your requirement. So this android mobile applicationis design to deal with the situation mentioned above.
    Client / Server architecture is applied on this system ,the Serveris deployed on Baidu ApplicationEngine .With the cloud ability of Baidu ApplicationEngine, the Client will provides rich media of exhibition and production for users, Baidu Location BasedService Cloud Service search restaurant near by venue for users, Baidu PersonalCloud Storage Service can save the rich media attachment on internet .On the otherside, visitors should enjoy their trip by publishing what they see in exhibition through Sina Weibo with ExGuideClient .
    This page will introduce the background of the idea of ExGuide first,then analyze the current situation of advertising system , point out whose weakness. In addition , Baidu Application Engine will be introduced too. Finally , it will estimate its value that bring for exhibitor .
    Keyword: Baidu Application Engine, Near Filed Communication ,Exhibiton,Requirement, System Design, Mobile Internet
    第一章 绪论1.1 会展经济1.1.1 需求背景会展是一个前景广阔的朝阳产业和绿色产业,正逐步成长为第三产业中的一颗耀眼明星。
    近年来,中国内地已成为全球发展最快的展览市场。数据显示,从2006年至2011年,中国展览场地面积增长了48%,而美国和德国则分别增长5%和2%。此外,中国的展览场地数量和面积在整个亚洲也分别占了57%和69%,居亚洲第一位。目前,我国的会展业取得了良好的经济效益,并已成为第三产业中最具发展潜力的行业之一,对经济的拉动作用非常明显。
    有一组数据可以佐证,据商务部统计数据显示,2011年全国共举办展览6830场,展出面积8120万平方米,提供社会就业岗位1980万人次,直接产值3016亿元,比上年同期增长17.7%,拉动效应2.7 万亿元,占全国GDP的0.64%,占全国第三产业的13%。
    从地方的上经济会展经济数据图表,也可看到会展行业的潜力之大。



    地方
    场次
    备注




    沈阳
    举办展会295场
    交易总额1901亿元


    济南
    举办展会155场
    相关行业增收165亿元,交易额1221亿元


    成都
    举办展会268场
    促成各类商品成交3500亿


    郑州
    举办展会158场
    实现经济效益150亿元


    南京
    举办展会2182场(包含会议)
    相关产业拉动420亿


    重庆
    举办展会521场
    直接收入54.3亿元,拉动消费435亿元



    1.1.2 面临的问题面对如此蓬勃发展的会展行业,传统营销渠道已经无法满足展商的宣传需求,它未能与高速发展的容量相配套,滞后的营销技术导致展商耗费了大量的成本,却收获不到相应的效果;另一方面,传统的营销方式,也为众多的用户带去了困扰,比如说,满天的传单和花哨的宣传让消费者苦不堪言、静态的展示无法让消费者体会到实体运动时的动感、传统方式承载的信息不足等。
    传统的营销渠道有:报纸杂志、广播电视、电话、网络等。它们都有不同程度的缺点,事实上营销应该能做到消息到达及时、快速,刺激需求和市场化等几点要求。从数据上来看,到达率和转化率是一个重要的考量指标;而留存等数据,则更多地反应了产品本身的效果。
    下表是对几个营销渠道的一个分析,说明传统渠道的到达率、转化率堪忧,已经无法承载高速发展的会展行业。



    营销渠道
    缺点




    报纸杂志宣传
    信息更新周期长,到达速度慢,受众少,成本高,转化率低


    广播电视宣传
    成本巨大,信息一旦形成,很难修改


    电话营销
    目标不准确,受众少,形式有限,不能刺激需求


    网络营销
    到达率低,操作难度大



    所以市场需要一款产品,在解决参展商宣传需求的同时,也能为用户带去便捷、有趣的浏览体验。
    1.2 移动互联网1.2.1 移动互联网概述移动互联网是指互联网的技术、平台、商业模式和应用与移动通信技术结合并实践的活动的总称。,随着宽带无线接入技术和移动终端技术的飞速发展,人们迫切希望能够随时随地乃至在移动过程中都能方便地从互联网获取信息和服务,移动互联网应运而生并迅猛发展。
    移动互联网的核心是互联网,因此一般认为移动互联网是桌面互联网的补充和延伸,应用和内容仍是移动互联网的根本。虽然移动互联网与桌面互联网共享着互联网的核心理念和价值观,但移动互联网有实时性、隐私性、便携性、准确性、可定位的特点,日益丰富智能的移动装置是移动互联网的重要特征之一。
    从用户需求来看,移动互联网以移动场景为主,使用上有时间碎片化、场景移动化等特点。
    1.2.2 将会展驶入移动互联网快车道移动互联网,是时下最热门的话题。数据上也反应出,网民从传统互联网向移动互联网迁徙的趋势非常明显。从2012年11月的数据反应出,移动互联网的流量已经占到了互联网总流量的13%;而2012年Q4的数据显示,用户的移动互联网在线时长,已经超过了传统的PC互联网。说明用户的习惯正发生改变。

    而移动互联网的基础之一,就是移动设备,比如说智能手机。现在智能手机的发展,已经让手机不仅仅是个通信工具那么简单,随着无线网络接入技术的不断升级,智能手机与移动互联网的结合,使得移动生活越来越丰富多彩。从内容上,智能手机是一个更好的载体,能承载更多的富媒体内容。
    事实上,在移动化的大潮下,会展行业也走到了十字路口。传统营销方式表现出的内容短板,在移动化的大潮下,都能在终端解决,智能终端承载的不仅仅是富媒体内容,还包括社交等娱乐因素。
    所以应借力于移动互联网,将会展的营销需求也驶入移动化的快车道。
    1.3 项目意义本系统的意义,在于解决会展场景下参展商的宣传需求,为参展商节省了资源,使其更专注于展会本身的产品。移动端的应用,使用长连接,解决了传统方式到达率低的问题;应用展示的富媒体内容,帮助用户决策,也将大大提高转化率。另外,系统接入了社交元素,增加了用户在会展中的互动,提升了趣味性。
    1.4 系统概要介绍系统采用了传统的C/S架构,将服务端置于百度云应用引擎上(BAE)。使用了百度的云的托管服务、个人云存储服务(PCS)、地理位置信息服务(LBS)等等,基础服务依赖于百度云,解决了系统的基础技术支持,让系统更专注于逻辑上的功能。
    客户端承载着用户接口的功能,向用户传递展会及相关展品的富媒体信息,帮助用户决策;同时提供了基于展会场景的小功能,满足用户的城需求。
    第二章 基础技术介绍2.1 百度云开发环境2012年百度世界大会上,百度移动云事业部总经理李明远,向众多开发者公布百度的云开发环境,致力解决开发者基础服务问题,为开发者节省开支。
    其中包括帮助开发者节省服务器成本的个人云存储(PCS)、帮助开发者整合地理位置服务的LBS云、解决终端适配问题的百度移动云测试中心(MTC)、安全稳定的运行环境百度应用引擎(BAE),还包括多屏幕Screen X技术、百度移动云应用生成服务(Site App)和百度移动浏览内核。
    2.1.1 百度应用引擎(BAE)百度应用引擎(BAE)是百度推出的网络应用开发平台。基于BAE架构,使开发者不需要维护任何服务器,只需要简单的上传应用程序,就可以为用户提供服务。BAE有能力将原本单机的LAMP架构,变成分布式架构。开发者可以基于BAE平台进行PHP、Java、Python应用的开发、编译、发布、调试。
    同时BAE平台也提供了大量的云服务给开发者,包括fetch URL、task queue、SQL、memcache,后续会提供更多服务。在性能方面,如果开发者希望增加服务能力,可以通过申请更多执行单元的方式进行灵活的调整。
    BAE服务可以让开发者在开发网络应用程序的过程中摆脱繁琐的环境、服务问题,把精力专注于业务逻辑。
    本系统将服务端置于百度应用引擎上,借助于引擎的弹性计算和带宽,解决系统的网络连接问题。
    2.1.2 百度LBS云百度LBS云是服务百度地图与BAE针对LBS开发者一起推出的平台级服务。专门解决LBS开发者针对存储和高并发检索的难题。
    百度LBS云服务提供海量数据的存储、检索、展示三大功能,可以解决:

    基于位置数据的空间数据库管理和维护问题,尤其是移动开发者对于位置数据爆发式增长的海量空间数据存储压力;基于位置的空间检索问题,不再依赖数据库实现周边检索这一类基于空间的位置数据计算,而是开放检索引擎彻底解决LBS应用的高并发检索瓶颈问题;海量位置数据在地图上展现的问题,不再受制于Web端JS的标注能力的影响,将大数据在服务端渲染为矢量数据图层,开放图层渲染引擎,从“云”直接下发图层和矢量数据到“端”。
    百度LBS云服务的到来,开创了新式的应用开发模式。传统模式,开发者需要解决基础服务和业务逻辑的问题,而在新的模式下,开发者只需要关注自身的业务逻辑,大量的地理位置存储与计算,都将通过调用百度LBS云服务来实现。

    弊端:

    开发者需要自己实现位置数据存储(空间数据库)开发者需要自己实现空间计算(周边检索等)开发者需要自己处理检索时效性和并发瓶颈


    百度提供位置数据存储百度提供空间计算能力开发者关注应用本身的业务数据和业务逻辑


    百度提供位置数据存储百度提供空间计算能力百度提供自定义扩展字段存储,让业务数据参与检索规则配置(筛选排序)进一步方便开发者聚焦LBS应用本身的业务逻辑
    本系统将使用百度LBS云能力,为系统提供定位功能,以及相关的POI(point of information)信息,解决用户查找展馆附近的餐厅需求。
    2.1.3 百度个人云存储(PCS)百度个人云存储PCS (Personal Cloud Storage)是百度2012年推出的针对个人数据的云存储服务。开发者可以利用PCS的开放接口存储用户个人数据,进而解决数据碎片化、终端多样性的问题。通过使用PCS服务,开发者无需考虑设计复杂的海量存储系统,而更专注于解决系统的业务逻辑问题。
    本系统将使用百度个人云存储,解决用户需要持久化保存电子资料的需求。
    2.2 安卓(Android)系统简介Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。Android操作系统最初由Andy Rubin开发,主要支持手机。2005年8月由Google收购注资。2007年11月,Google与84家硬件制造商、软件开发商及电信劳动商组建开放手机聪明共同研发改良Android系统。随后Google以Apache开源许可证的授权方式,发布了Android的源。第一部Android智能手机发布于2008年10月。
    Android的系统架构和其操作系统一样,采用了分层的架构。从架构图看,Android分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和Linux内核层。
    2.2.1 应用程序Android会同一系列核心应用程序包一起发布,这些应用程序包包括但不限于客户端、SMS短消息程序、日历、地图、浏览器、联系人管理程序等。因为Android是开源系统,不同厂商会基于自己的设计,对Android进行二次开发,这时核心的应用程序也将因不同的厂商而不同。而由Google发布的系统,称为原生Android系统。用户还可以在应用商店,下载Android应用。所有的应用开发都是使用JAVA语言编写的。
    2.2.2 应用程序框架因为Android的开源特性,程序开发人员均可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用,使得用户可以方便的替换程序组件。
    每个应用程序都可以使用一系列的服务和系统来实现自己的逻辑业务,这些服务和系统包括;

    丰富而又可扩展的视图(Views),可以用来构建应用程序,是用户接口。内容提供器(Content Providers)使得应用程序可以访问另一个应用程序的数据(如联系人数据库), 或者共享它们自己的数据。资源管理器(Resource Manager)提供非代码资源的访问,如本地字符串,图形,和布局文件(Layoutfiles )。通知管理器 (Notification Manager) 使得应用程序可以在状态栏中显示自定义的提示信息。活动管理器( Activity Manager) 用来管理应用程序生命周期并提供常用的导航回退功能。
    2.2.3 系统运行库Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。以下是一些核心库:

    系统 C库,它是一个从BSD继承来的标准C系统函数库 ,它是专门为基于Embedded linux的设备定制的。媒体库 - 基于PacketVideo OpenCORE;该库支持多种常用的音频、视频格式回放和录制,同时支持静态图像文件。编码格式包括MPEG4, H.264, MP3, AAC, AMR, JPG, PNG 。Surface Manager - 对显示子系统的管理,并且为多个应用程序提供了2D和3D图层的无缝融合。LibWebCore - 一个最新的web浏览器引擎用,支持Android浏览器和一个可嵌入的web视图。
    2.3 近场通讯技术(NFC)NFC是Near FieldCommunication缩写,即近距离无线通讯技术。由飞利浦公司和索尼公司共同开发的NFC是一种非接触式识别和互联技术,可以在移动设备、消费类电子产品、PC 和智能控件工具间进行近距离无线通信。NFC 提供了一种简单、触控式的解决方案,可以让消费者简单直观地交换信息、访问内容与服务。
    它使得消费者更方便和简单地处理事务、交换数字信息和连接电子设备。NFC是一种无线电传输标准,使得无线信号可以在极短距离中传播,这个距离可能是10毫米。NFC是一种基于RFID技术的标准,它包括了通讯协议和数据传输格式。NFC设备包括RFID标签无线智能卡片。NFC技术运行在13.56MHZ的频率下,但是数据传输速率较慢,最快只能达到0.42Mb 每秒。我们需要搭载android 2.3以上版本的手机才支持NFC芯片。
    近场通讯技术应用前景广泛,现在以及将会,可能在如下领域广泛应用:

    门禁系统 :用户通过带有NFC芯片的手机,可以代替传统的智能卡片识别系统。消费者电子:用户为不同的无线智能卡片写上不同的标记,当带有NFC芯片的手机接触这些卡片时,会自动改变手机的状态。医疗:用户带着感应器,则可以通过NFC传输感应器的信号。信息收集和交换:用户可以用NFC手机相换交换用户信息。支付:用户能使用NFC手机支付,不需要过多烦琐的操作,代替了现有的刷卡支 付等。运输:物流行业运用NFC技术清点货物,实现智能仓储系统。
    2.4 会展导游系统架构概述和运行原理系统将服务端迁移至百度云应用引擎(BAE),其弹性的运行环境和安全架构,保证了系统运行的稳定性与安全性;使用百度云提供的各种云能力:使用百度云LBS云为客户端提供定位服务和周边商户搜索服务、使用百度个人云存储为用户保存持久化数据保存提供了可能,客户端可以通过无线局域网或手机的数据网络与云端服务器通讯、交换数据。可以说百度云能力帮助系统解决了很多基础功能问题,而系统本身专注于解决业务逻辑问题。
    用户手持安装了本系统的Android终端(集成NFC芯片),打开本系统后,系统将会向服务器请求位置信息,在获取位置信息后,向用户展示所在地理位置的展会列表,用户可以查看到关于展会的富媒体信息。当用户将Android终端(集成NFC芯片)靠近芯片时,系统将为识别芯片携带的识别码,并向服务器请求识别码对应的展示的富媒体信息。

    2.5 小结百度云服务是百度为众多移动互联网开发者准备的基础服务,它不仅包含了应用运行的服务器环境,还将百度近年来积累的云存储、LBS(地理位置信息服务)技术等技术以云服务的形式提供给开发者,目的是帮助开发者节省了基础服务的成本,使开发者专注于业务逻辑的设计与实现,是百度构建移动互联网生态圈的重要组成一步。
    而本系统因开发需要,也接入了这些云服务。服务器端将部署在百度BAE,而客户端也将使用百度提供的PCS、LBS等云服务,这些服务的应用在相关的小节已有说明。可以说,百度云服务是本系统的根基。
    第三章 需求分析3.1 用户需求分析
    本节主要介绍用户需求。从用户观展前、观展中、观展后几个场景,梳理分析用户的需求点。
    3.1.1 用例1
    用户希望获得所在城市的展会信息。
    当用户有参展的兴趣时,希望通过定位功能查找当地展馆举办的展会信息,并希望系统提供展会的相关内容作为决策的依据。比如说车展,用户可以看到最新或者是热门的车型,又或者是车模的照片,吸引用户决策去参加这个展会。
    3.1.2 用例2
    用户希望在参展的过程中,能获得更多的展品的介绍,必要时能获取展品优惠券。
    目前参展商的宣传方式仍以传单和现场讲解为主,大屏滚动播出宣传视频也作为一种宣传手段使用。可是这些方式存在天生的缺陷,传单所承载的内容不够详尽,而现场的讲解或宣传视频,让晚到的用户无法知晓到全部内容。
    所以,用户希望可以获得更多的展品介绍,无论是文字、图片、视频或者是其他形式的附件。当用户看到某些展品的介绍后,如果产生了购买的兴趣,希望能够获得展品的优惠券,以便用户在购买时享受到优惠。
    3.1.3 用例3
    用户希望将感兴趣的展品的电子介绍保存至百度云。
    展品的详情介绍,包含了文字介绍,但都是概要式的说明,详情中还包含了更详尽内容的富文本附件,用户对展品感兴趣时,可以将附件保存至百度云,长久的保存起来,不易丢失。
    这种保存附件的形式,解放了用户。用户不需要再担心在展会看中某一心仪的展品后,回家后就忘记其名称;或者是不再需要在逛展会的时候,拿着五花八门的传单介绍。
    3.1.4 用例4
    用户在观展其间,希望能在饭点在展馆周边找到吃饭的地方。
    展馆建筑面积比较大,一般坐落在城市的近郊,四周较为荒凉,而且因为展馆更注重自身的建设,所以周边的餐饮等配套设施,有所不足,比如说餐厅的地点距展台较远,或者较偏僻,位于地下层或者是在附近某栋建筑较多。而对于异地游客,陌生的环境对于他们而言,更难找到目的地。
    所以,用户希望能够在饭点,在展馆周边找到餐厅。餐厅应能在几个维度作分类查找,比如说距离、品味、价格等。
    3.1.5 用例5
    用户在观展其间,会认识不同的人。比如说销售代表会认识客户,用户在对某件展品产生购买意向时,也会认识某些销售代表;摄影师对在展会其间认识了一些影友;用户无意见也可能搭讪某位车模。当他们需要相互留联系方式时,希望能够快捷方便的交换联系方式信息,因为他们可能在赶车时才想起留联系方式,要求交换的方便更快捷。
    3.1.6 用例6
    用户在看到某件有趣的产品或者是遇见某件趣事时,希望能通过微博与朋友们分享。
    3.2 系统需求分析 本节主要介绍系统需求,涉及第三方服务授权和系统性能等需求。
    3.2.1 系统界面运行流畅系统向用户展示展会、展品的概况,需加载大量的图片,系统使用资源较多,应使用缓存、多线程技术绘制用户界面,保证界面的流畅性。
    3.2.2 第三方授权系统引入的第三方服务,均需获得用户授权能可使用,引入的第三方服务包括百度云个人云存储(PCS)、新浪微博开放服务。百度云LBS云服务,需要应用申请权限用户无需授权。
    3.3 小结系统的需求分析,充分基于用户在参展的前、中、后的活动进行,是一次合理的分析。它包括参展前的决策,系统需提供富媒体信息供用户参考,帮助决策是否参展;参展中,使用近场识别技术方便用户识别展品、使用百度个人云存储服务持久化保存展品介绍。此外,还挖掘出用户在参展中有查找附近餐饮的需求和交换联系人信息的需求。
    第四章 系统概要设计本章主要介绍会展导游系统的详细设计,包括了前端的交互与界面原型,后端的模块设计、数据库设计、多线程时序、第三方服务的调用和与服务器的交换过程。
    4.1 系统前端设计4.1.1 交互与原型设计
    该组交互包含了系统运行的首级页面、首级页面隐藏菜单的交互。
    在首页中,会向用户展示系统定位的城市,并快速展示所在城市的展会举办情况。采用列表形式展示用户所在地的展会举办情况,而对于第一屏展示的展会概况信息,相信用户关注的焦点在于:展会是否已开幕、展会的名称是什么、展会的地点是哪里等。而列表可上下滑动,方便用户浏览更多信息。
    交互上,采用android4.0以上版本标准交互组件。Action Bar隐藏的菜单将授权放在一级菜单中,原因是使用户更快速的进行授权。

    该组交互包括首级页面和二级展会详情页面的跳转交互。
    当用户在单击任一展会项,系统将会跳转至二级展会详情页面。详情页面采用可滑动视图,将展会概况、展会图片和热门产品放入滑动视图。
    该组交互采用android 4.0标准交互方案,在二级页面,单击返回键,即可返回首页,系统将销毁二级页面。

    该组交互包含展会详情二级页面和该页面内的操作交互。
    二级页面下采用滑动视图,将展会概况、展会图片、热门产品放入滑动视图中。展会概况为默认视图,向左滑动至展会图片,展会图片向左滑动至热门产品、向右滑动至展会概况,热门产品向右滑动至展会图片。
    展会概况简要介绍了与展会相关的信息,包含logo、展会名称、展馆地址、举办时间、简介,扩展信息包含了招展单位和展馆电话。
    该组交互采用android4.0标准交互组件,左上角返回键可返回一级视图,左右滑动是系统定义的标准手势。

    该组交互包含二级展会详情页滑动视图下,展会图片的操作交互。
    图片的展示方式采用时下流行的瀑布流展示,特点是交互友好,单张图片显示得更大,便于用户仔细查看,缺点是一屏显示的图片数量有限。向下拉动至底后,系统将会自动加载更多图片。关于瀑布流交互,将在稍后小节详细介绍。
    该组交互采用android4.0标准交互组件,左上角返回键可返回一级视图,上下滑动是系统定义的标准手势。

    该组交互包含展会详情二级页面与展品详情三级页面的跳转交互。
    在二级页面的滑动视图中,在热门产品视图下,通过单击任一展品项,可跳转至展品详情三级页面。三级页面上方采用滑动视图展示展品的海报图片,下方区域包含展品的文字介绍。页面下端平均摆放三个按钮,左起分别为“赞”(对展品评分时使用)、分享给好友、更多。在更多的下级列表中,提供观看宣传视频的入口。
    展品详情的三级页面,还可以通过系统自带的近场通讯芯片,识别展品后,激活该页面。
    该组交互采用android4.0标准交互组件,左上角返回键可返回一级视图,上下滑动是系统定义的标准手势。
    4.1.2 瀑布流布局瀑布流,又称瀑布流式布局。是目前比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。
    瀑布流有非常多的优点:

    布局简单,没有特别的难点。不必明确知道数据块高度,当数据块中有图片时,就不需要指定图片高度。
    但缺点也非常明显:

    列数固定,扩展不易,当浏览器窗口大小变化时,只能固定的x列,如果要添加一列,很难调整数据块的排列。滚动加载更多数据时,还要指定插入到第几列中,不够方便。数据块排列是从上到下排列到一定高度后,再把剩余元素依次添加到下一列,这个本质上就不一样了。
    4.2 系统后端设计4.2.1 系统模块设计
    本系统采用MVC架构,将实体、控制与视图相剥离。这样子做的好处是,系统架构清晰,模块划分细化,各模块耦合度大大降低,便于系统的开发与维护。
    下面将概述MVC架构在系统中的使用。
    视图层,是用户接口,是用户与系统交互的重要层。它包括三个模块,活动(activity)组件、视图(view)组件和适配器(adapter)等。

    活动(activity)组件模块:是视图的容器,是捕捉用户与系统交互的模块。视图(view)组件模块:是数据展示的容器,负责将后台数据展示给用户,是活动组件的内容。适配器(adapter)模块:是数据的容器,负责将后台数据整理,活动组件所展示的数据,由适配器提供。
    控件层,是逻辑模块,负责与用户的交互逻辑和系统与服务器的交互。它包括三个模块的内容,包括多线程管理、数据库管理、接口等。

    多线程模块:系统实现需要大量的资源消耗,为了保证用户界面流畅和系统执行效率,资源的加载与渲染采用了异步加载的方式。该模块负责与后台交互,获取相关数据并封装。数据库模块:该模块的设计,是为了持久化保存系统数据,包括保存了第三方授权的acess_token信息、联系人信息等。服务模块:系统使用了第三方提供的服务,包括百度云服务和新浪服务开放平台等,该模块是在第三方提供的服务基础上再进行的逻辑封装。实体层,是数据的容器模块。系统运行时维护了大量的业务数据,为更有效的管理业务数据而设计的层。业务数据:它包括数据库存储的第三方授权的access_token、联系人、实体类等。
    4.3 系统视图层设计4.3.1 展会列表界面刷新流程
    系统运行初,将首先定位用户的位置,这过程将使用百度LBS云服务。云服务器将返回位置信息,之后系统以位置为参数,向后台查询当地展会列表。如果列表这为空,则提示用户当地无展会,如果有则向用户显示展会列表。
    4.3.2 展会概要界面刷新流程
    当系统从一级页面向二级跳转时,系统将首先获得用户所选择的展会ID,之后与服务器交互,以ID为参数查询展会的概要。服务器返回的信息由逻辑层封装之后,再返回视图层将数据展示。
    当用户不断下拉瀑布流时,展会图片也会不断加载,这个过程需要系统再次向服务器请求图片数据。
    4.3.3 展品详情界面刷新流程
    系统首先识别展品的ID ,再以ID为参数向服务器查询展品详情信息,并向用户展示。
    4.4 小结系统的前端设计采用基于用户体验的交互设计原则,交互组件使用Android4.0以上版本标准组件。后端采用MVC架构,将逻辑与视图相剥离,降低了系统的耦合度。系统模块细分,除掉系统的粒度,便于系统的开发。
    第五章 系统详细设计与实现本章节主要说明各系统模块的详细设计,包括相关类的依赖关系及消息传递的时序等。
    5.1 视图层详细设计与实现本节将会分小节说明视图层中各类的职责,并简述其设计,最后将说明该类与逻辑层交互的时序逻辑。
    5.1.1 展会列表首页的详细设计与实现展会列表首页(一级页面),指用户打开系统后看到的第一屏。
    其主要职责示向用户展示所在城市的展会列表。其展示的内容与交互参照第四章相关小节。
    类名:HomeActivity

    展会列表首页(一级页面)刷新交互时序如上图所示:

    用户打开系统系统调用RequestLocation方法,同步向百度LBS云服务器请求用户所在地理位置信息百度云服务器返回信息并封装成Message返回给系统通过服务器返回值,系统启动多线程,向服务器同步请求所在地展会列表服务器返回展会列表信息,逻辑层将信息封成ArrayList<Exhibition>实体类列表,再调用Update,更新首页的UI(user interface)
    5.1.2 展会详情页的详细设计与实现展会详情页指的是用户在展会列表页,选中某一展会后,查看到的对应展会的详情页。
    其主要职能是向用户展示展会详情信息,包括文字介绍、图片介绍、热门产品介绍等。相应的交互与视觉设计,参照第四章相关小节。
    类名:ExhActivity

    展会详情页(二级页面)刷新交互时序如上图所示:

    用户在展会列表页选中某一展会,系统跳转至展会详情页系统调用GetID,获取用户选中的展会ID系统启用多线程,向服务器同步请求返回展会详情服务器返回详情信息,逻辑层将信息封装为Exhibition实体类,调用Update刷新界面系统启用多线程,异步向服务器请求展会热门展品信息服务器返回热门展品信息,逻辑层将信息封装成ArrayList<Product>实体类列表,并调用Update刷新界面
    5.1.3 展品详情页的详细设计与实现展品详情页指的是用户展会热门产品视图下,选中某一展品后,查看到的对应展品的详情页,或者是用户将集成有NFC芯片的手机(安装了本系统)靠近展会中摆放的识别卡片后,系统识别了卡片携带的展品识别信息后,打开的展品详情页。
    其主要职能是向用户展示展品详情信息,包括文字介绍、图片介绍、视频介绍等。相应的交互与视觉设计,参照第四章相关小节
    类名:ProductActivity

    展品详情页(三级页面)刷新交互时序如上图所示:

    系统调用GetID,获得展品的ID信息系统启用多线程,向服务器同步请求展品详情信息,逻辑层将信息封装成Product实体类,并调用Update刷新页面用户打开视频,系统打开自带视频播放器向服务器请求资源播放
    5.2 逻辑层详细设计与实现本节将会简要说明逻辑层各类的职责,并简述其设计。
    5.2.1 联系人模块详细设计与实现本小节主要说明联系人模块的详细设计与实现,包括数据库表的设计,与联系人模块使用流程。



    字段名
    类型
    举例




    Id
    Int
    1


    Name
    Varchar
    叶XXX


    Phone
    Int
    18500067965


    Home_num
    Int
    010-56799446


    Company_add
    Varchar
    北京市海淀区西二旗上地东路鹏寰国际大厦


    Company_nam
    Varcahr
    百度在线网络技术(北京)有限公司


    QQ
    int
    307971172


    Image
    Varchar
    /storage/exguid/image/xxx.jpg


    Rec_Tag
    Varchar
    百度产品经理



    类名:ContactManager
    该类的职责是,联系人管理。功能包括管理用户本人的信息和使用终端交换的联系人信息,这些信息支持插、删、改、查的操作。系统交换联系人逻辑模块也由其负责。
    下图说明的是用户使用系统录入联系人信息流程:

    系统将在用户进入联系人管理界面后,检查系统是否存在默认联系人,如果不存在将提示用户输入。
    下图说明用户使用系统交换联系人信息流程

    5.3 关键问题及解决方案本节将主要介绍系统的设计过程中发现的问题,及其解决方案。包括瀑布流实现与资源加载任务的设计等。
    5.3.1 瀑布流图片的设计与实现瀑布流是近几年流行起来的图片展示方式,相关介绍在第四章相关小节有所提及。但在移动终端上实现瀑布流存在几个缺陷,主要是移动终端设备的原因导致的。因为移动终端本身处理能力有限,内存小,移动浏览器解析能力弱,更残酷的是,因为移动终端显示屏小的原因,导致瀑布流在移动端不仅实现难度加大,交互的效果也会大打折扣。
    但得益于Android系统架构的优良设计,在实现瀑布流效果上,系统可以不需要使用HTML语言实现,而使用Android系统本身提供的应用框架即可解决。
    瀑布流的布局实现,采用动态加载Layout(布局)的方式解决。而图片资源和其他耗时资源的加载带来的巨大的系统开销将在下一个小节中提及解决方案。本小节着重解决瀑布流的布局问题
    瀑布流布局分为三个部份,分别是:外层容器、内层容器和内部元素。

    外层容器(outercontainer):指最外层布局,它承载着整个瀑布流布局,通过向外层容器增加内层容器,可以增加瀑布流的展示列数;从实现上来讲,即是一个layout文件中里唯一的LinearLayout; 内层容器(innercontainer):指内层布局,它是外层容器内的元素,承载着瀑布流中的某一列瀑布;从实现上来讲,它是向outercontainer添加的LinearLayout,有多少列瀑布就应该有多少个innercontainer.,即是向外层布局添加多个LinearLayout;内部元素:指图片布局,它是内层容器的元素,一张图片对应一个内部元素的对象。从实现上来讲,可以是一个ImageView,也可以是包含ImageView的复杂布局。需要向innercontainer添加内部元素,才能在用户界面展示。内部元素是动态向内层容量添加的。
    将瀑布流布局这样划分的好处是:

    易于管理,通过容器对象即方便的向界面中添加元素;易于扩展,通过全局常量,修改内部容器数量,从而增加或者是减少瀑布流的列数;易于实现,该实现使用的是Android提供的标准组件,实现的难度大大降低。
    下图表达的是三者的依赖关系与系统的实现;


    红色线框内即是外层容器,即是Layout中的LinearLayout;蓝色线框内即是内层容器,即是向外层容器中添加的LinearLayout;绿色线框内即是内部元素,即是向内层容器中添加的ImageView。
    下图说明的是瀑布流图片的加载流程

    5.3.2 资源加载任务的设计与实现因为系统将使用多种类型的资源,比如说文字、图片等。它们的加载将为系统带来巨大的开销,而使用异步线程解决耗时资源的加载问题,是一个必然的选择。但如果为不同类型的任务设计不同的异步线程会使得系统结构看起来复杂,所以设计中使用了任务(Task)的概念。设计任务接口,将逻辑操作与UI更新操作封装起来,不同的资源加载将实现任务接口,封装成不同任务。
    异步线程将维持任务的队列,线程取出任务后,将任务逐出队列并执行任务。任务执行结束后调用相应接口,完成任务。


    TaskHandler接口,实现该接口必须实现下列接口:getPath()函数,获得任务与后台交互的URL;Update(object…param)函数,更新用户界面;getActivity()函数,获得创建任务的父活动。
    TaskForWaterFall、TaskForLogo、TaskForProductPics是基于TaskHandler接口,处理不同耗时资源加载任务的实体类。
    5.3.3 异步加载线程的设计与实现本系统的设计中,为了满足用户获取更多展会或者是展品信息的需求,系统将向用户展示大量的富媒体信息,其中包括了展示大量的图片。图片的加载是非常消耗资源的,单线程阻塞式加载对于用户体验的损伤是非常大的。
    解决方案是使用多线程异步加载,但是考虑到瀑布流的实现需要更多的资源,所以将采用两个异步图片加载线程的方式,对于临界资源采用加锁的方式,同步两个线程。

    需要异步加载的图片,系统会将其封装成Task,将Task放入临界资源。LoaderImageTask的两个实例,会不断轮询将临界资源取出,并执行,之后被执行的任务会从临界资源中移走。系统中存在两个LoaderImageTask实例,所以可能使系统处于不安全状态。
    为了避免使系统处于不安全状态,引用临界区调度原则,如下:

    如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。
    根据临界区调度原则,设计中采用了加锁的方法,避免系统处于不安全状态。
    当线程进入临界区时,打开锁,使得其他线程无法进入临界区。线程在临界区执行获取Task和从临界资源RemoveTask的工作。在线程退出临界区前,关闭锁,使得其他线程可以进入临界区。
    这样的设计不仅有效地避免的不安全状态的出现,得益于这种设计,线程的实例可以根据系统的负荷添加,使得执行效率大大提高。
    5.3 小结为保证系统运行流畅,设计中多处使用了多线程异步处理的办法。多线程的运用虽然解决了系统的运行效率问题,但仍然可能带入不安全状态。所以在设计过程中,对多线程问题作了严谨的设计,包括其线程运行时序问题。
    图片的展示使用了当下流行的瀑布流形式,但是加载图片资源会带来的巨大的资源开销并影响系统的运行效率,针对这种情况,也采用了多线程和任务的方式解决。可这种方式会带来死锁问题,为了避免死锁的产生,系统也采用了相应的设计解决。这些都在本章的各个小节有所讲解。
    第六章 总结与展望6.1 总结本文在观察国家宏观经济运行的基础上,抓住了经济转型的关键节点上,最有生命力的经济增长点之一 ―― 会展经济,以此为切入点,设计并实现了这一系统。
    会展行业是朝阳产业、绿色产业,在相当多的一线二线城市,已经是支柱产业。但通过研究当前会展行业的现状,发现它在新媒体宣传方面存在严重滞后的问题。论文结合时下IT(InternetTechnology)界最热门的两大话题,云计算和移动互联网,运用相关的知识理论和先进经验,以图解决这些问题。
    本文在认真分析了用户需求和参展会的商业需求的基础上,对在系统的功能设计、交互设计做了大量的工作。包括使用时下流行的交互方式,和精力设计的系统模块,保证了系统高效的运行。依托百度提供的强大云能力,和借力移动互联网的高速发展,成功实现了该系统。
    6.2 展望本系统的实现,虽然为解决展会宣传能力滞后于展会发展的问题提供了一种可能性。但就目前本系统的实现和设计,仍存在着较大的不足,也意味着系统还有很大的提长空间。总的概括起来,包含以下几个方面:

    用户需求和商业需求覆盖不足,只着重解决了用户的问题,但无法体会系统对于会展行业发展的商业价值。对于系统本身而言,如果能获得大量用户是有益的,但更大的意义在于转化这些用户消费,这是系统在解决了基础问题后,急需思考的问题。系统的交互上,虽然尽力做到以用户的体验为出发点,但限于个人的能力,设计得不尽完善,应在用户体验上下更大的功能,以期获得更多用户的肯定。系统的虽然使用了百度的云能力,但事实上,百度能提供的云能力还有很多。比如说Channel(管道,长连接),这些能力的使用能更大幅度的提高系统的可用性,无论是从用户出发还是商业目的出发,这都是有价值的,应该更多地使用。系统架构和实现,限于个人水平,仍有相当大的提升空间,希望以后可以就系统运行效率、资源消耗等问题上着手,提升系统的质量。
    参考文献[1]Craing Larman著 李洋等译. UML和模式应用[M]. 原书第3版. 北京:机械工业出版, 2006.
    [2]李刚. 疯狂Android讲义[M]. 第1版. 北京:电子工业出版社, 2011.
    [3]杨丰盛.Android应用开发揭秘[M]. 第1版. 北京:2010, 机械工业出版社.
    [4]Jesse James Garrett 著. 用户体验要素[M]. 原书第2版. 北京:机械工业出版社, 2011.
    [5]张波. O2O:移动互联网时代的商业革命[M]. 第1版. 北京:机械工业出版社, 2013.
    [6]Ferraro R, Murat Aktihanoglu 著, 李丽译. LBS应用开发[M]. 第1版. 北京:人民邮电出版社, 2012.
    [7]Juhani Lehtimaki 著. Smashing Android UI [M]. 第2版. John Wiley & Sons Inc, 2012.
    [8]Jason Ostrander 著. Android UI Fundamentals: Develop & Design[M]. 第2版. Peachpit Press Publications, 2012.
    [11]百度. 百度个人云存储(PCS)[EB/OL]. [2013-5-14].http://developer.baidu.com/wiki/index.php?title=docs/pcs.
    [12]百度. 百度应用引擎Baidu App Engine[EB/OL]. [2013-5-14].http://developer.baidu.com/bae.
    [13]百度. 百度地图API[EB/OL]. [2013-5-15]. http://developer.baidu.com/map/.
    [14]马丽米克尔. 互联网趋势报告[R]. 斯坦福大学:KPCB, 2012-12-3.
    [15]邵维忠 杨芙清 著.面向对象的系统分析[M]. 第2版. 北京:清华大学出版社, 2006.
    [16]邵维忠 杨芙清等.面向对象的系统设计[M]. 第2版. 北京:清华大学出版社, 2006.
    [17]Clifford A. Shaffer 著. 数据结构与算法分析[M]. 第2版. 北京:电子工业出版社, 2009.
    致 谢本论文工作是在老师的悉心指导下完成的,非常感谢老师给予我这么大的自由,允许我在自己的设想下自由发挥。这种自由的学习与创作,使我保持了研究热情和攻克难关的信心,是我日日夜夜努力的源泉和动力。完全自主的选题,充分发挥了我的主观能动性,使我的能力进一步提高。再次感谢老师对我的信任和支持。
    同时也非常感谢与我合作的同学,和你一起开展项目是一段很愉快的经历。不仅让我在技术上有所提升,也让我认识到相互合作的意义和过程。另外,你在工作上表现出来的专业态度和坚忍不拔的精神更深深地影响了我,你身上所表现的优秀品质让我学习到了很多东西,
    此外,对所有指导、帮助过我的老师和同学们都表示一致的感谢和敬意,你们专业的技术能力和助人为乐的做人态度都将令我永生难忘。
    特别要感谢的,是在我求学生涯当中,一直坚定不移站在我身后,给予我支持和鼓励的父母,正因为你们的无私付出和无微不至的关怀,使得我能排除干扰专心学习、能不断地追求自己的梦想,正因为你们的爱,才有我今天的成绩。感恩父母,你们对我的痛爱将是我一生的财富。
    最后,感谢所有帮助过我和关心过我的人,谢谢你们!
    1 评论 2 下载 2018-09-30 00:35:42 下载需要13点积分
  • 对JavaScript和node.js研究并实现WEB聊天系统

    摘要计算机硬件技术的发展带来了极其繁多的显示终端,数据的爆炸式增长也使得展现给用户的界面越来越复杂,交互也变得更加多样化,在这些变化的冲击之下,web随之更新换代,变化速度之快令人咋舌。面对以上问题,web开发技术时刻保持更新,而且开发技术和手段越来越丰富。
    基于此,本文将对web领域的一部分新技术进行研究并加以实践。
    首先是对JavaScript部分新特性和JavaScript开发模式做以详细研究和阐述。由于JavaScript对ECMAScrtpt 6规范的实现使得JavaScript拥有了众多新的特性,这些特性极大的丰富了开发方式,使得开发人员能以更少的代码完成更多的开发任务。Web系统越来越庞大,传统的JavaScript开发方式已不能满足需求,在这里也将阐述一下JavaScript模块化开发。
    然后是对node.js进行详细的研究与阐述。Node.js的出现将JavaScript语言带向了服务器,打破了浏览器的束缚,使其拥有了更广阔的适用领域。
    最后是web聊天系统的设计与实现。综合以上技术,糅合websocket和mongoDB数据库等技术,将这些技术综合运用并加以实践,以展望未来web技术的发展趋势。
    关键词:JavaScript;模块化开发;node.js
    ABSTRACTDevelopmentof computer hardware technology has brought great variety of display terminals,the explosive growth of data is presented to the user interface makes theincreasingly complex interaction has become more diverse, under the impact ofthese changes, web attendant replacement fast pace of change is astounding.Faced with these problems, web development technology always kept up to date,and the development of technology and are becoming more abundant.
    Basedon this, this article will be part of the new technologies in the field of webconduct and practice.
    Thefirst is the JavaScript part of the new features and JavaScript developmentmodel to make a detailed study and elaboration. Because JavaScript toECMAScrtpt 6 specification implementations allow JavaScript has many newfeatures that greatly enriched the development mode, so that developers can domore with less code development tasks. Web system increasingly large,traditional JavaScript development methods cannot meet the demand, there willbe elaborate JavaScript modular development.
    Thennode.js detailed study and elaboration. Node.js JavaScript language with theemergence of a server to break the shackles of the browser, it has a broaderfield of application.
    Finally,the design and implementation of web chat system. Based on the abovetechnology, which combines websocket and mongoDB database technology, theintegrated use of these technologies to practice in the future developmenttrend of web technology.
    KEYWORDS: JavaScript;modular development;node.js
    第一章 绪论第一节 研究背景计算机技术的不断更新发展,使得我们的社会已然进入了互联网时代,在这个时代里,生活已经离不开互联网,这种大需求刺激着互联网市场的急速发展,各种各样的设备充斥着我们的生活,大量的数据围绕着我们,所以在在各种各样的设备上将数据和用户连接起来成为一个极具挑战性的工作。
    首先我们来看看各种终端设备。几年前,最主要的设备就是pc了。那时候web刚刚起步,互联网时代也刚刚起步,能在pc能浏览一下东西就很不错了,网页也极其简单,交互方式也非常简陋。慢慢的出现了网页动画,提交表单等,使得web技术有了一点新的活力,JavaScript也开始慢慢流行起来,但是也只是做动画,提交以下表单而已,称之为玩具语言,作用相较其他语言显得十分单薄,只能在浏览器上做一些简单开发。后端技术是java和jsp的天下,没有之一,对产品的要求也是非常低的,保证运行流畅即可。回头看一眼当前,pc、手机、平板等,显示终端五花八门。交互方面,指间稍微一动,就能触发好几种不同的操作,再加上pc和移动端的操作方式不同,要处理的问题就更多了。页面的复杂度已经不能同日而语了,此情此景,开发技术只有不断的更新迭代才能创造出更好的产品,也才能满足当下的需求。这样导致技术的更新速度之快令人咋舌,尤其是web前端技术,在这种情况下我们只有不断地学习新的技术来运用到实践中创造出更好的产品。
    接下来我们再看看数据方面。爆炸式增长的数据,给技术人员带来的压力显而易见。数据量和交互决定了页面的复杂度,前面我们说了交互,这里我们探讨一下数据量。近几年大数据已经不是什么新鲜事了,大量的数据需要展示给用户时,页面的复杂度也就直线上升了,服务端压力陡增,前端压力也不容小觑。开发代价变高,复用性也就慢慢的提了出来,这是一个用来减小开发代价的方法,过去的开发方式也就不适用于现在了,模块化,组件化开发慢慢的流行了起来,在这种情况下又刺激着新的技术发展,总之在技术的更新换代面前,从事此类工作的人员只能是不断的学习。
    第二节 研究内容Web是一个极大的领域,在这里我只是将web开发中近几年出现的新技术做一些研究,这些新的技术覆盖了前端、后端以及服务端。对这些技术进行深入的研究之后,为了将其应用到实际开发中,在研究之后我也将使用一个web聊天系统对这些新的技术加以实践,使用最新的技术来开发完成这个小项目。下面我们就来一一简单阐述一下本次研究涉及的新的技术点。
    首先我们来看看前端方面涉及的新技术点。随着移动端越来越火,html5、css3开发也越来越备受瞩目,现在已经应用在实际的开发当中。页面复杂度的提升也刺激产生了更多的开发工具,模块化开发、组件化开发等等,模板引擎也不再是以前单一的后端模板引擎,前端也出了JavaScript模板引擎,用来更好的适应开发需求。面对这样的局面,JavaScript语言自身也积极发展,添加了更多的语言特性来保证自己不会在这发展的潮流中不会落伍,虽然这些新的特性还没有完全应用来实际开发当中,但是已经指日可待了。在这些新的技术点中每一个项单提出来都足以进行深入的研究,这里我将重点选取一部分内容进行深入研究,在前端方面我主要重点研究了JavaScript语言的部分常用的新特性以及前端模块化开发。Html5、css3、JavaScript模板引擎等在聊天系统开发过程中也将用到,在这里只是做一个简单介绍,不是重点。
    接下来我们看看后端的新技术。我相信只要从事web开发的人在最近一段时间里就不可能没听过node.js,为什么node.js会这么火呢?简单来说就是它让JavaScript脱离了浏览器的束缚,让它跑在服务器上,JavaScript也可以进行服务器开发,这绝对是一个震撼人心的消息。从前端到后端通用JavaScript开发,这减少的学习成本不言而喻,想想都是一件极其有意思的事情。所以在后端新技术方面我将重点研究一下node.js开发,为大家讲解一下JavaScript是如何在node.js平台上进行服务器开发的。
    凭借上面的知识点搭建系统架构已经不会有太大问题,但是开发过程中还会使用到其他的新的知识点,如mongoDB、websocket等,但是这些不是重点,在web聊天系统的设计与实现章节中做一个简单的讲解,不再为它单分一章出来。
    综合以上内容,本文将对前端的JavaScript新特性、模块化开发、node.js开发做深入的讲解,其他提到的各个新的技术点将做简单介绍,不会进行深入的讲解。最后综合应用这些新的技术点来实现一个基于web的聊天系统,对此系统的实现过程做一个详细的介绍。
    第三节 论文结构及内容安排丰富多彩的web开发技术使得其极具研究价值,尤其在面对其技术点不断更新时更是吸引极多的工作人员对其做深入的研究。本文将选web技术中的几点新技术做深入研究,并将其付诸实践。以下为本论文的结构和内容安排。

    第一章为绪论,主要介绍了课题的研究背景,论文的研究内容以及论文的结构和内容安排。
    第二章为JavaScript部分新特性及模块化开发。此章将重点讲解JavaScript部分新特性为开发带来的好处与便利,与之前版本作对比。在模块化开发方面将带来新的模块化开发方式,将目前使用的模块化开发方式和未来即将使用的进行比说明,再将模块化开发方式和较早的传统开发方式做一对比,体现模块化开发方式的优点。
    第三章是对node.js平台的详细介绍,对其特点和实用性进行阐述。
    第四章主要内容为以web聊天系统作为实践对象,讲解说明在此项目中我是如何将这些新的技术融合在一起进行实践开发的,对项目中的关键技术点和关键代码进行详细介绍。
    第五章是对全文的总结和展望,此章将对前面讲解的内容进行一个总结,对未来的技术做一个展望。

    第二章 JavaScript脚本及模块化开发随着web的飞速发展,JavaScript语言也跟着向前推进,web与JavaScript互相成就,最近一段时间里JavaScript又实现了ECMAScript 6 规范的特性,使其功能更加强大与完善。与此同时web更加趋于复杂化,简单原始的开发模式已经不能满足开发者,模块化开发使得开发人员在做web开发的时候更加得心应手。在这一章里,我将阐述两点,首先是JavaScript的部分我在项目中使用到的很闪亮的新特性,以class特性作为示例讲解一下,然后将阐述一下模块化开发。
    第一节 JavaScript中的class概念1、ECMAScript]规范是什么简单说明一下,ECMAScript是一个标准化的脚本程序设计语言,而JavaScript是对其标准(规范)进行实现与扩展,所以ECMAScript是一个标准或者说规范,而JavaScript对这个标准进行了语言实现。ECMAScript最新的且被JavaScript实现了的版本是ECMAScript 6标准。ECMAScript 7正在制定当中。
    2、JavaScriptclass概念类概念是所有面向对象编程语言中最重要的特性,也是衡量一个编程语言是否具有面向对象特性的最直接的判断准则。随着web复杂度变高,为了适应开发需求,ECMAScript规范也提出了class概念,JavaScript也对其进行了实现,目前因为浏览器的支持原因导致这部分新的特性无法在实践项目中直接使用。但是我们可以使用babel或者traceur这种翻译工具将使用ECMAScript 6规范写的代码翻译成符合ECMAScript 5规范的代码,这样就可以在开发中使用新的特性。接下来我们看看class概念在JavaScript中是什么样的,将其书写方式与class概念没有出现之前的书写方式进行对比说明一下。
    首先我们来看一下class概念没有出现之前我们是如何实现类似class特性的。
    (1) function Box (width, height) {(2) this.name = “box”;(3) this.width = width; this.height = height; }(4) Box.prototype = {(5) constructor: Box,(6) init: function() { },(7) input: function() { }}(8) var box = new Box(20, 30);(9) console.log(box.name); //”box”(10) console.log(box.width); //20(11) box.init();
    上面这一段代码是在ECMAScript 6规范没有出来之前JavaScript语言对类概念的实现。 (1)是一个函数,函数和对象会有什么关系呢?我们再来往下看看(8)的代码,正是因为(8)使用了new关键字来调用这个函数,使得此函数执行完之后生成一个对象,而this指向的就是生成的此对象。再来看(4)处,每一个函数都有一个自带的属性prototype,此属性为一个对象。使用new关键字生成的每一个Box实例对象都能访问到此函数的属性prototype中的方法或者属性,如(9)(10)(11)所示。(5)处涉及JavaScript语言的一些原理问题,涉及内容较多,这里就不详细阐述了。要实现继承就更加麻烦,下面我们来简单看一下如何实现继承。
    function Person () { }Person.prototype = {constructor: Person,eat: function() { }}Function Worker () { }(1) Worker.prototype = new Person(); //将Person的方法和属性集成到Worker(2) Worker.prototype.checkSalary = function() { }; //给Worker添加自己的方法var worker = new Worker();(3) worker.eat(); //调用继承自Person的方法eat(4) worker.checkSalary(); //调用自己的方法checkSalary
    上述实现继承的方式只是JavaScript语言中众多实现继承的方式的一种,这种实现方式相对简单容易理解,但是弊端也多,我们简单来说一下这种方式的实现原理。在(1)处我们将Worker函数的prototype属性设置为Person对象的一个实例对象这样我们就可以访问Person对象中的属性和方法了,在(2)处我们继续添加属于Worker对象自己的方法,在(3)(4)处访问继承来的方法和自己本身的方法。
    纵观以上的实现方式,都是开发者利用JavaScript语法特性自己想办法实现class概念,在JavaScript语言中尚不存在class概念,在这种情况下实现class概念的方式非常多样化,导致代码规范难以统一,而且众多的实现方式都有自己的局限。终于,在万众期待之下,ECMAScript 6制订了class概念,JavaScript并对其class概念进行了实现,接下来我们就看看利用JavaScript新特性class概念来书写上面的代码是什么样的。
    class Person {(1) constructor (name, age) {this.name = name;this.age = age;}getName () {return this.name;}}class Worker extends Person {constructor (name, age, salary) {(2) super (name, age); //调用父亲的构造方法this.salary = salary;}getSalary () {return this.salary;}}var worker = new Worker(“张三”, 12, 50);worker.getName(); //”张三”worker.getSalary(); //50
    上述代码是使用了其新特性class概念之后关于类的书写方式,简单明了,相信每一个有一点编程经验的人都能看懂。不过可惜浏览器暂时不支持这样的新特性,不过这只是时间问题。(1)处是一个名为constructor的方法,这是Person类的构造方法,每一个类都必须有一个constructor方法作为其构造方法。再来看看(2)处调用super方法,super指的是父类,直接调用super方法即执行父类的构造方法,通过super.XXXX即调用父类的某一个方法。除了这两点之外,剩下的代码都是简单易懂的。
    Class概念的出现,使得功能块的封装开发变得更加简单,而且统一了代码风格,新的开发方式明显减少了代码量,相比程序员自己想办法实现class简单了许多。
    3、JavaScript新特性总结在上面我只是以class特性作为一个例子进行简单介绍一下这些新的JavaScript特性为开发带来的益处,长时间以来页面的复杂度越来越高,使得开发人员不得不去想办法实现各种各样的开发方式,而这种开发方式并不是一种已定的规范,只是开发人员利用JavaScript语言特性去实现自己的想法,导致这样的实现方式多种多样,虽然最后也能满足开发需求,但是导致代码风格迥异,后期维护非常麻烦。在这时ECMAScript 6规范的制定和JavaScript对其的实现使得开发人员有现成的规范去遵从,代码风格一下实现了统一,而且减少了大量的辅助代码,在之前为了实现某一特性必须添加非常多的辅助代码,JavaScript对这些新特性已经实现,那么开发人员只需要遵从其语言规范进行开发即可。类似这样的新特性还有很多,异步操作控制、对数组的扩展、二进制数组、module等等,篇幅有限,这里仅拿class特性作为示例进行讲解,想要了解更多的话,可以直接去google搜索ECMAScript 6即可找到非常多的资料,可以进行查阅。
    第二节 模块化开发首先来用一张图来直观的看一下模块化开发的概念,如下:

    如上图,page1.js是page1页面的入口文件,下面则是page1.js文件的组成部分,最底层则是无法抽离的最小模块,整个page1.js文件则由多个模块文件组成。这就是模块化开发的直观概念,下面将详细阐述模块化开发阿德各个细节。
    1、模块化是什么模块化在较早之前就已经出现了,页面复杂度提高,如果不对其进行模块化,将导致页面不可控,代码后期维护将变得异常困难,合作开发、代码复用也会变得非常困难。为了方便开发,便于维护管理代码,将页面划分功能区域,将能抽离出来的一些功用方法抽离出来,比如header、footer、弹窗、对话框等这样的页面结构,访问cookie、正则匹配等这样的公用方法,这些都是可以抽离出来的,这样抽离出来之后对于代码复用、维护优化非常有帮助。所以模块化开发是势在必行的,而且模块化开发已经使用了很久了。
    2、如何实施模块化开发在进行模块化开发之前,我们首先得考虑如何开展模块化。
    从整个网站来说,所有的页面中必然会有相同的功能区,比如头、尾、翻页、小工具等,这些都是可以抽离出来的。再从单独一个页面来说,一个页面大多情况下可以划分成多个互相独立的功能区域,这些独立的功能区域也可以对其进行模块化,这样可以使得代码更加整洁,简单,方便后期的优化与维护,一个上千行代码的文件显然要比一个几百行代码的文件难阅读的多,模块化之后可以将其拆分成多个几百行代码的文件,这样阅读和维护就变得简单易懂了。从公用功能来看,cookie管理,正则匹配,对字符串的一些特殊处理等,这样的功能是完全可以抽离出来的。总结以上,在进行模块化之前必须要详细规划出有哪些内容可以抽离出来进行模块化,盲目的模块化很容易导致做很多重复的工作。
    当我们清楚的划分出了要模块化的内容之后就可以进行模块化开发了,开发过程不存在太大问题,使用相应的语言完成相应的功能即可。这里我们还有一个问题,那就是模块化之后如何加载使用,在ECMAScript 6规范出来之前,JavaScript语言自身是没有直接加载文件功能的,也就是说模块化出来的文件如何加载这是一个大问题。比如某一个文件中要用到cookie相关的方法,而cookie相关方法我们已经抽离出来开发完成了,那么怎么引入到当前文件去使用呢?接下来我们一起来看看。
    3、目前的模块化开发技术模块化主要有三个部分,抽离、开发、加载使用,抽离和开发不存在技术上的难点,详细理解功能需求,分析抽离,再将抽离出来的功能用JavaScript去实现就好。主要的难点在加载使用上,因为目前浏览器支持的JavaScript语言特性中不存在加载文件不这样的功能,也就是说抽离出来开发完成之后的文件无法加载使用,这将是一个毁灭性问题,但是最终开发人员还是经过一段时间的研究解决了这个问题,目前的模块化开发使用的就是这种方法。
    为了解决这个JavaScript文件加载问题,开发人员开发出了几种加载工具,目前项目中经常使用且很出名的就是require.js和sea.js,sea.js是由阿里巴巴的大牛玉伯开发的,require.js则是国外的,无论怎样这两个工具都是为了解决模块化开发的最后一个问题,即文件加载。接下来我们以require.js为例来看看在项目中如何进行模块文件的加载。
    首先在项目中先引入require.js,下面的代码将演示如何使用require.js。
    //定义一个模块,此模块只有一个toString方法,存在于module路径下的toString文件中define(function () { var toString = function (arr) { return arr.join(‘,’);}return toString;});//在一个文件中使用上面的模块(1) require([‘module/toString’], function(toString) { var arr = [‘h’, ‘o’, ‘m’, ‘e’]; var str = toString(arr); console.log(str); //home});
    首先根据require.js接口定义了一个模块toString,然后在另一个文件使用它,(1)处第一个参数为一个数组,数组中是要加载的文件的文件名,第二个参数为一个函数,函数的形参和数组中的文件名一一对应,即为数组中的文件取一个在当前文件中使用的名字。正是因为有了类似require.js这样的工具使得模块化开发成为了现实,目前几乎所有大型web项目都在使用这样的模块化开发。
    即便这样问题解决了,但是大家从来都没有停止追求如果JavaScript语言自身有这样的功能那该多好啊,经过努力,JavaScript实现了ECMAScript 6规范,使得类似上面这样的文件加载不需要再使用工具了,JavaScript语言自身就可以解决,不过可惜浏览器暂时还不支持,所以实际项目中依旧使用的是工具,不过更新换代的速度是很快的,用不了多久这些工具都会被淘汰的,下面我们看看马上就要被使用的JavaScript自身的文件加载方式。
    4、未来的模块化开发技术这算不上什么未来的技术,JavaScript自身的模块加载功能已经是实现了,只是我们暂时没有办法将它使用到实际项目开发中。我相信用不了多久,这种新的技术就会进入实际开发当中。无论怎么变,抽离和开发是不会变的,变化的是加载,在众多加载工具流行的现在,JavaScript也不甘寂寞,module特性的出现,一举解决了加载问题,使得开发过程中完全可以抛弃这些加载工具,而直接使用JavaScript就够了,接下来我们看看如果纯粹使用JavaScript进行模块化开发是什么样。
    //定义一个模块,此模块只有一个Person类,存在于module路径下的Person文件中class Person { constructor () { } eat () { }}(1) export default Person//在一个文件中使用上面的模块(2) import Person from ‘module/Person’; //引入上面的Person文件var jack = new Person();jack.eat();
    看了上面的使用工具进行模块化开发,现在看到这个感觉一下简单太多了,在(1)处我们将开发完成的Person模块导出一下,在(2)处将其引入到一个文件当中,取名为Person,然后就可以正常使用了。简单易懂。
    5.模块化开发总结从最早的一个页面一段JavaScript代码,到使用工具进行模块化开发,再到马上就要进行的使用JavaScript自身进行模块化开发,短短的几年就达到了这样一个水平,web的发展速度之快,令人瞠目结舌。但这些都只是新技术的一部分而已。在未来页面势必比现在的更加复杂,这就要求模块化做的更加彻底,否则后期维护与优化将成为重灾区。
    第三节 本章小结本章首先用JavaScript一个新的特性class概念作为实例讲述了一下自ECMAScript 6规范出现之后,JavaScript语言特性的一些变化以及这些变化对开发带来的好处,然后对JavaScript模块化开发有进行了阐述,讲述了从早期到现在模块化开发经历的变化过程,其中也附带了JavaScript新的特新module。
    综上两方面我们讲解的都是JavaScript技术,JavaScript作为前端开发最重要的技术点,这几年来不断的更新发展,而在最近这一年多里更是让人眼前一亮,随着对ECMAScript 6规范的实现,其能力越来越强,而作为web开发人员,我们有一种对新技术追求的天性,面对web的快速发展,我们也必须跟上时代的变化来适应越来越复杂的需求。阐述完前端的JavaScript技术之后,在下一章我们将来研究一下一门后端技术node.js,这也是近年来火的不行的一门技术,我们将用接下来一章的内容对其进行阐述。
    第三章 node.js平台在上一章我们阐述了前端开发技术JavaScript语言的一些新的特性,以及模块化开发接下来咱们把目标转向后端技术。一说后端技术,我们首先想到的是java、php等,既然论文的题目是基于web新技术的研究,那么我们肯定不会说这些了。我们这里的研究对象是node.js。在近几年里,node.js慢慢的给自己开辟了一片新天地,虽然它是在2009年被开发出来的,但是真正火起来是在近几年,同时也是在近几年里被越来越多的开发人员使用在实际的项目开发中。
    第一节 node.js的诞生在2009年node的创始人Ryan Dahl将其面向世人做了介绍,从此node问世。在在它面世之后即带来了巨大的反响,在github上受关注项目排行榜上位列第二,最重要的是它已经被使用在实际的项目当中了,说明它能够接受挑战。
    有关Node.js的技术报道越来越多,Node.js的写法也是五花八门,有写成NodeJS的,有写成Nodejs的,到底哪一种写法最标准呢,我们不妨遵循官方的说法。在Node.js的官方网站上,一直将其项目称之为“Node”或者“Node.js”,没有发现其他的说法,在这里我将使用后者,将其称为node.js。
    第二节 node.js是什么在其官网上(http://www.nodejs.org)给node.js下的定义是:“一个搭载在chrome JavaScript运行引擎上的平台,用于构建高速、可伸缩的网络程序。Node采用事件驱动、非阻塞I/O模型,使它既轻量又高效并成为构建运行在分布式设备上的数据密集型实时程序的完美选择”。在这句话中包含了非常多的概念,我们来分开理解一下。
    “一个搭载在chrome JavaScript运行引擎上的平台,用于构建高速、可伸缩的网络程序”,即node.js是一个平台,搭载在JavaScript运行引擎上,那么在node.js平台上做开发使用的是JavaScript语言,其实此引擎就是google的V8引擎,一个速度非常快的JavaScript引擎,因为它去掉了中间环节,执行的不是字节码,用的也不是解释器,而是直接编译成了本地机器码。虽然在node.js平台上开发使用的是JavaScript语言,但是node.js本身是用C++开发出来的。后半句说它可以用来构建网络程序,即可以在node.js平台上进行web后端开发,是的,你没有看错,总结上面两点,即在node.js平台上可以使用JavaScript语言进行web后端开发。
    “Node采用事件驱动、非阻塞I/O模型”,首先来看事件驱动,比如A是事件发出者,B是事件执行者,A对B说我有个事情a你去做,B说好的,B当前没事做,所以直接上手开始做a事情,此时A又给B说这里有个事情b你去做,B说好的,B当前正在做a事情,那么b事情就等着,B完成了a事情,告诉A说a事情做完了,a事情的结果给你,然后B开始做b事情,就这样一直做下去。其中还会出现这样的问题,B做a事情需要一个斧子,结果在做a事情的途中斧子坏了,导致a事情无法正常进行而中断,虽然a事情还没有做完,但是B此时无事可做,所以B丢下a事情去做b事情,等到斧子好了并且B没有其他事情可做的时候,B就回头继续去做a事情。这个例子的前面做事情的部分是事件驱动模型,而斧子坏了是非阻塞I/O模型。如果是阻塞I/O模型的话,B必须等到斧子好了完成a事情之后才能开始b事情,如果斧子长时间不好,那么B将一直处于无事可做的状态而且不能去做其他的事,这就是阻塞I/O模型。
    “它成为构建数据密集型实时程序的完美选择”,这里数据密集型很重要,这里的数据密集型可以理解为并发量非常高,比如一个网站用户访问量非常高,可能同时会出现成千上万的用户同时访问,那么就说这个网站的并发量非常高,事件驱动加上非阻塞I/O模型是非常适合这种情况的,相反的node.js不适合构建计算密集型程序。
    最后一点node.js是一个异步平台,它把JavaScript带到服务端的方式和浏览器吧JavaScript带到客户端的方式几乎一模一样,也就是说在node平台上,JavaScript也是异步单线程执行的。
    第三节 node.js带给了我们什么在以往,提到后端语言,在我们眼里,就是java、php等,但是node.js给人眼前一亮的是它将一个大家原本认为只能在浏览器里跑一跑的语言JavaScript带到了后端,这无疑是一个让人很振奋的消息,试想前端用JavaScript,后端用JavaScript,目前也有适用于JavaScript的数据库,这样一来,从前端到后端只通过一种语言JavaScript就可以做到全部的开发,这无疑极大的降低了前端开发者的学习成本,使得前端开发者可以做更多的事情了,而且不用学习新的语言。
    虽然在node.js平台上JavaScript可以做几乎其他后端语言可以做的一切事情,但是我们还是极少仅仅使用一种方式去做后端开发,首先是有太多已经完成的项目使用的就是java或者php,迁移是一件非常麻烦的事情,再者每一种语言都有自己的局限,我们完全可以使用多种方式配合来完成后端开发。目前的选择方式一般是将后端和UI层相关的数据处理交给node.js去开发,将服务化的数据处理交给java或者php,这样就可以使得后端开发人员将注意力全部集中在数据服务化上,和UI相关的数据处理就不需要自己去做了,可以交给前端的开发者使用node.js去处理这一部分数据。
    综合上面,node.js带给我们的是对后端的拓展,而不是颠覆,它给我们提供了一种新的数据处理方式以及开发方式,java、node、php等它们各自扮演着自己的角色,各自发挥着自己的作用,让它们发挥各自的优点,这样才能使得开发更加的容易便捷。
    第四节 node.js的特点Node.js在设计上也是比较大胆,它以单进程、单线程模式运行,事件驱动机制是Node.js通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,Node.js凭借事件驱动搞定一切,习惯了传统语言的网络服务开发人员可能对多线程并发和协作非常熟悉,但是面对Node.js,我们需要接受和理解它的特点。
    Node.js的异步性使得它可以处理高并发量的事务,虽然高并发,但是由于JavaScript是单线程执行的,所以并不会消耗太多CPU,单核CPU使用起来也是没有任何问题的。非阻塞I/O模型使得大量的并发活动不会因为一个事务的阻塞导致其他事务无法正常处理,即使一个事务处理出现了问题,其他的事务也不会受其影响而正常执行,除非有数据上的耦合。
    有了上述的优点之后,不足之处也就很明显了,首先对高并发处理得心应手,那么高计算量事务呢,一个长时间占用CPU的高计算量事务会导致其他事务无法开始执行,因为JavaScript是单线程的,很明显它并不适合高计算量事务的处理,这也就意味着它无法合理的使用多核CPU,会导致CPU性能的浪费。虽然有上述这些缺陷,但是我们依旧有手段来解决这些问题,在node.js上JavaScript是单线程的,但是node.js平台的底层是多线程的,我们同时开多个node.js服务,也就是开多个进程,每个进程跑一个node.js服务,每个node.js服务监控一个端口,再通过nginx服务器做反向代理来处理这些不同的监听端口,就可以实现类似多线程的效果。但是不足依旧是不足,通过这些手段来弥补也只是大家努力想出的办法,对于node.js本身来说,这些是它承认的不足之处,也从来没想过往这方面发展,node.js一直注重于做好单线程、异步性来处理高并发的事务,对自己的发展点一直确立的非常清楚。
    第五节 本章小结这一章内容我们详细讲解了node.js平台,从它的诞生,一直到现在它带给我们的变化。Node.js作为一个新面孔,这里从它是什么,有什么用,以及它的优缺点这几方面做了一个深入的讲解。究其变化,node.js带来了一种新的后端开发方式,将JavaScript从浏览器带到了服务器,使得后端开发分的得更加细致,将服务化和UI数据层分离开发。对于热衷于JavaScript语言的开发者来说,这无疑又拓宽了其开发领域。
    综合前面两章内容,我们讲解了JavaScript一些新的特性和模块化开发,这一章讲解了node.js开发,接下来一章将以实践出发,以基于web的聊天室立项,将前面提到的多种新的技术应用到此实践项目当中,实现此聊天室功能。接下来我将讲解一下开发此web聊天室的大致过程以及部分核心技术,对一些疑难点的解决方式加以讲解。
    第四章 web的聊天室的设计与实现前面几章讨论了前端和后端的部分新技术,在这一章里,我讲以前面提到的技术作为基础,再结合mongoDB、websocket等关键技术来实现web聊天室。在章节最后部分是重点功能实现的代码讲解。
    第一节 功能说明聊天室系统在网络生活中最为常见,微信、QQ等,这里将其最重要的好友管理和聊天功能进行了实现。此系统流程图如下:

    如图首先是具体化一个用户,具体化用户的过程由注册账号和登录这两个步骤,完成这两步之后,正式成为此系统用户之后就可以使用其他功能,未注册登录之前其他功能均不可见。
    登录之后来到个人信息功能模块,在这里可以看到自己的个人信息,包括用户名、头像等等。个人信息模块还包含一个非常大的功能模块,好友管理,好友管理包括查看当前自己的好友及好友个人信息、删除好友、发送好友添加申请、管理其他用户发送给自己的好友申请。
    然后来到聊天模块,在这里可以看到自己已经添加的好友,可以选择一个与其通过发送文字进行聊天,在聊天过程中可以查看历史消息,历史消息分批加载。
    最后就是两个简单模块,关于我们和退出登录,关于我们介绍了毕设的大致内容,点击退出登录之后退出当前账号的登录模式。
    以上即是这个web聊天室的全部功能模块,接下来我们看看它的技术架构。
    第二节 系统技术架构首先综合来看一下系统结束架构,如下图为此系统技术架构,用以展现从UI层到数据库层所使用到的技术。

    结合上图可以一目了然的看到在各个层所使用的技术,以及此技术点所起到的作用,为了能更清楚的说明此系统技术架构,接下来将分别阐述各个层的技术框架。
    1、前端技术框架首先我将此系统定位在单页面纯异步应用,即所有的内容都在一个页面中进行,页面只同步加载一次。纯异步即所有的数据都是通过异步请求得到的,页面中不存在后端直接添加的数据。纯异步通过ajax实现就可以了,没有太多问题。主要在与单页面,要想实现单页面就得按需加载且保证页面不会刷新,URL改变,但是保证页面不会刷新,只有一个办法,那就是改变URL中的hash部分,如http://www.cuc.com#computer ,在这个URL中#computer就是hash,这部分的改变不会引起页面刷新,在html5中有一个事件是hashchange,这个事件检测的就是URL中hash的改变,这样一来就可以实现页面不刷新且能按需加载,根据hash值的不同加载不同的页面内容。
    基于web就免不了和页面打交道,原始的JavaScript操作页面比较麻烦,此时我们需要一个简单DOM操作的工具,这里选择jQuery。将异步获取的数据渲染到页面上我们还需要一个JavaScript模板引擎,我们选择art-template,这是腾讯的一位开发者写的。有了这些我们就可以进行开发了,但是等等,前面有一章内容讲解了JavaScript新特性,这些新的特性在浏览器里暂时是不支持,怎么使用它们?不用担心,google给我们提供了一个翻译工具叫traceur,通过这个工具可以将浏览器不认识的符合ECMAScript 6规范的代码翻译成浏览器认识的符合ECMAScript5规范的代码,这样我们就可以在项目中放心的使用最新的JavaScript特性了。
    综上,在前端部分,使用hashchang实现单页面应用,ajax实现异步化,art-template做JavaScript模板引擎,jQuery简化DOM操作,traceur翻译ECMAScript 6。前端技术就是这些,这里有新有旧,hashchange、ECMAScript 6这是新的技术点,而jQuery、ajax、JavaScript模板引擎这些则是旧的技术。
    说完前端的技术架构,接下来讲讲后端的技术架构。
    2、后端技术框架第三章讲解了后端技术node开发,相应的这个系统的后端技术我们就选择在node平台上进行开发,关于node是什么前面已经讲解的很清楚了,这是一个全新的技术,所以整个后端技术架构以node展开,而且全部是新技术。
    首先我们选择node平台上的Express框架,Express是用JavaScript开发的用于node平台上进行后端开发的框架,就像java的一些框架一样。后端模板引擎选择ejs,ejs就是freemarker一样,同样的概念。这样就有了主体框架,遵循Express框架规范进行开发就行。
    聊天系统,那么消息通道就是非常重要的一块。在以往我们可以使用轮询和“长连接”,为什么“长连接”有引号呢?因为它并非真正的长连接,只是大家这么叫了而已。“长连接”指前端发一个请求,后端接受请求,等到后端有数据时返回请求,前端接受了返回数据之后立马发送一个新的请求建立另一个新连接,就这样周而复始,这样一看,其实它就是不间断的发送请求而已,并非真正的长连接。所谓长连接指发送一个请求,这个请求不会断掉,一直在这个请求中来回传输数据。这样一看我们前面说的都不是长连接。这里我将引入另一个新技术,websocket,这是html5中出的一种新的web协议,类似http协议,通过websocket可以建立真正的长连接。Socket.io在websocket基础上开发出了直接建立长连接的方式,不需要再进行底层的包装,这里我们使用socket.io来建立长连接。
    最后重申一点,node平台上开发用的是JavaScript语言,以上说的所有使用的都是JavaScript进行开发的。这样后端开发的必备技术框架就准备全了。接下来我们看看服务端,也就是数据库。
    3、服务端技术框架前面两节,我们讨论了前端和后端的技术架构,现在只剩下服务端技术架构,即数据库,接下来我们看看数据库模型。
    从之前的内容来看,前端的开发语言必然是JavaScript,后端使用的是node.js平台,那么开发语言必然是JavaScript,这样一来数据库方面如果可以选择一个符合JavaScript数据结构的,那么开发起来会和谐很多。虽然在node平台上使用JavaScript开发也可以访问SQL数据库,但是SQL数据库的数据结构不符合JavaScript数据结构,不是很方便,这里我们再来推出一种新的数据库模型,mongodb数据库。
    Mongodb数据库对于此项目的好处在于其是使用JavaScript数据结构来存储数据的,即JSON格式,这样一来使用JavaScript去访问数据库得到的数据更加便于处理,不需要繁琐的数据处理过程。
    综合以上三部分,前端、后端、服务端的技术架构和选型,可以看到,在这个系统中开发语言只有一个,那就是JavaScript,这是一件非常棒的事情,想想我们习惯的web开发方式是至少两种语言,前端一种语言,后端一种语言,也就是说必须在学习至少两种语言以上才能正常的进行web开发,而现在一种语言就可以完成以上任务。
    第三节 系统实现1、系统功能模块设计详细的功能模块划分可以使得开发过程更加清晰快捷,开发效率也能有大幅度的提高,下图即是此系统的功能模块设计图:

    从此图可以清晰的看到在此系统中我的好友模块和聊天模块最为繁琐复杂,其他部分均是每一个系统必备的部分。
    2、系统文件结构设计一个成熟且需要长期维护开发的系统,文件结构的设计直接影响其是否能进行长期维护开发,同时也决定了其维护成本的大小,是否便于交接等问题。麻雀虽小,五脏俱全,此聊天系统也是尽最大努力使其便于维护开发且便于理解。如下图为此系统的文件设计图:

    这里我们直接从每个文件夹的功能说起,文件结构在设计图中非常清楚,不需要再累赘的讲解。node_modules文件夹包含着本系统开发过程中使用到的npm程序包,如上图有ejs模板、数据库连接包mongodb等,这些npm包当使用到的时候直接引入文件进行使用即可;Router即为后端文件所在的位置;Upload是上传的图片的暂时存储位置,等到上传完毕之后移动到相应的最终位置;Static中包含了所有的静态文件,css、js、图片等;Views文件夹中包含了所有的页面文件,即html文件。
    综上,文件结构设计就结束,为了更好的进行开发和维护,文件结构非常重要,无论系统大小如何,这都是必须好好做的一个步骤。
    3、系统关键功能实现在此系统中最先想到的关键点莫过于两点,单页面应用和消息通道,虽然其他关键点还有很多,篇幅限制,我将以单页面应用和消息通道作为示例来简单讲解一下。
    首先来看单页面应用,前面几章我们提到了一个html5事件hashchange,它监听着URL中hash的变化。
    bindEvent: function() {(1) $(window).on(‘hashchange’, $.proxy(this.hashChanged, this)); //绑定hashchange事件}hashChanged: function() { let self = this; let params = { hash: location.hash }; //获取当前页面的hash值(2) $.get(this.url, params, function(res) { //将hash值发送给后端(3) self.jQmainSection.html(res);});}
    上述代码便是实现单页面的关键代码,(1)处将hashchang事件绑定在window上,这样hash有变化时将触发给其绑定的事件hashChanged,在hashChanged事件中(2)处将此hash值发送到后端服务,后端服务根据hash值判断需要加载的内容,(3)处将返回的内容展示在页面的主体部分。
    再来看看消息通道的建立。
    //客户端代码init (from, callback) {(1) this.io = io.connect();(2) this.io.emit('new user', from); //新用户进来触发后端new user事件(3) this.io.on('to'+from, function (data) { //定义收到消息事件 callback(data); }); } sendMessage (from, to, message) {(4) this.io.emit('private message', from, to, message) //触发后端私信发送方法}//后端服务代码ioListen: function() { let self = this;(5) this.io.on('connection', function(socket) { //建立socket.io服务(6) socket.on('new user',function(data){ //定义new user方法(7) if(self.users[data]){ //判断接收消息通道是否存在 }else{ let nickname = data;(8) self.users[nickname]= socket; //保存此用户建立的消息通道 } });(9) socket.on('private message', function (from, to, msg) { //定义私信发送方法 storeMessage.store(from, to, msg); //存储聊天信息 if(self.users[to]) {(10) self.users[to].emit('to'+to, msg); //触发前端接收消息事件 } }); }); }
    Socket.io服务的建立和通信是需要客户端和后端共同完成的,以事件发布订阅模型来实现。说明一下,在代码中io.on表示定义事件,io.emit表示触发事件。在客户端(1)处表示实例化一个socket.io通道,如果后端的也实例化了socket.io的话即表示此通道建立,此时将触发后端代码中(5)处的connection事件,将其执行。然后客户端(2)处触发执行new user事件,此事件定义在后端(6)处,客户端触发此事件,在后端执行此事件。执行过程中判断此用户要使用的消息通道是否已经建立,在后端(7)处进行判断,如果不存在,则在(8)处为其创建一个并存储下来。在客户端(3)处以用户名来定义一个事件,在后端(10)处触发执行,在客户端(4)处触发进行一对一通信的事件private message事件,此事件定义在后端(9)处。
    综合以上,socket.io的使用需要客户端和后端服务协同进行,且以发布者订阅者模型进行开发。
    综上以hashchange和消息通道代码作为实例讲解了关键功能的实现,在系统中大大小小的功能点还有很多,但是篇幅有限,只能以此两点作为示例。
    第三节 本章小结这一章主要以基于web的聊天室作为实践项目,综合运用前面几章提到的技术点
    来实现此系统。从此系统的功能、技术架构、实现过程这三方面进行了讲解,在讲解过程中将在系统开发中使用到的但是在前面没有着重介绍的技术点进行了介绍,比如mongodb、socket.io等。麻雀虽小,五脏具全,虽然系统内容不太多,但是从技术和实现过程来说,开发需要考虑的点和常用的技术也都使用到了,在此过程中还使用到了许多暂时在实际项目中不能使用的技术点,这也是做此系统的实际意义所在,学习综合运用新的技术。
    下面一章将是对以上所有内容的总结,展望一下新技术的发展。
    第五章 总结与展望到这里,文章要讲解的内容就全部结束了,纵观文章,以技术点作为开始,以实践作为结束,技术结合实践。开始以前端开发最主要的技术JavaScript语言作为主要内容,展望其发展,研究其新特性,然后再以后端平台node.js作为内容对其进行深入研究,完成这两者之后,将其带入了实际项目。在实践的过程中又将涉及到的其他的新技术点进行了讲解。综合以上所有的内容,在众多的技术点中新技术点最多,也是最重要的,这也是此文章的重点所在,基于web新技术的研究与实践,前半部分阐述研究所得,后半部分将研究结合实践。相对于web新技术而言这些也只是沧海一粟而已。
    近年web的发展速度令人瞠目结舌,这些变化首先是从需求开始的,web需求导致市场的庞大,各大公司都想分一杯羹,这时势必要求开发人员顶住压力做出更好的更能满足需求的东西,这些新需求相较以往的需求往往更加复杂,规模也更加庞大,这就需要开发人员需要使用更好的开发方式来做开发,在如此推动之下技术也将势必被推着前进。Web技术的变化速度之快也源于此,在众多技术速度最快的莫过于前端,对开发人员的学习能里要求也非常高。
    前面讲解的使用到的有一部分因为浏览器或者其他平台的问题导致暂时还没有使用到实际的开发中来,但是以web的发展速度来看,用不了多久,目前的这些新技术将完全使用到实际开发中,不仅如果还会出现更多的新技术,不断的学习将是永久的。最后用一句话来结束总结,那就是拥抱变化。
    附 录 附录一:基于web聊天室页面截图
    1.功能选择区
    2.注册功能区
    3.登录功能区
    4.添加好友功能区
    5.好友申请列表
    6.好友资料
    7.删除好友
    8.选择好友聊天
    9.关于我们
    参考文献[1] Mike Cantelon,Marc Harter, T.J.Holowaychuk, Nathan Rajlich 著. Node.js实战. 吴海星,译. 人民邮电出版. 2014.5
    [2] Kristina Cbodorow著. MongoDB权威指南. 邓强,王海辉译. 人民邮电出版社. 2014.1
    [3] Nicholas C.Zakas 著. JavaScript高级程序设计. 李松峰 曹力 译. 人民邮电出版社. 2012.3
    [4] David Flanagan 著. JavaScript权威指南. 淘宝前端团队译. 机械工业出版社. 2012.4
    [5] Douglas Crockford 著. JavaScript语言精粹. 赵泽欣,鄢学鹍译. 电子工业出版社. 2009.4
    [6] 高云 著. jQuery技术内幕. 机械工业出版社.2014.1
    [7] Eric A. Meyer 著. CSS权威指南. 尹志忠,侯妍 译. 中国电力出版社.2007.10
    [8] 阮一峰 著. ECMAScript 6入门. http://es6.ruanyifeng.com/
    [9] art-template. http://aui.github.io/artTemplate/
    [10] node.js. https://nodejs.org/en/
    [11] express.js. http://www.expressjs.com.cn/4x/api.html
    [12] socket.io. http://socket.io/
    [13] traceur.js. https://github.com/google/traceur-compiler/wiki/Getting-Started
    [14] MongoDB. https://docs.mongodb.com/
    [15] MongoDB. https://github.com/mongodb/node-mongodb-native
    [16] jQuery. https://jquery.com/
    [17] body-parser. https://github.com/expressjs/body-parser
    [18] formidable. https://github.com/felixge/node-formidable
    [19] ejs. https://github.com/tj/ejs
    [20] FormData. https://developer.mozilla.org/zh-CN/docs/Web/Guide/Using_FormData_Objects
    [21] CSS. http://w3help.org/zh-cn/kb/
    [22] Mozilla网络开发者. https://developer.mozilla.org/cn/
    致 谢经过长达几个月的研究和学习,毕业设计的论文接近了尾声。完成论文的过程很辛苦,但是看到成果却无比的欣慰。在整个论文的研究及完成过程中,我学到了非常多的知识。仅凭我一己之力,不会有这篇论文的诞生。我需要感谢很多人在我完成论文过程中给予的关怀和帮助。
    首先要感谢我的论文导师。在一开始从选题的决定到最后论文的提纲,老师都给予我莫大的帮助,而且其热心程度令我非常感动,只要我有问题随时都能找到老师,随时都能对我不理解的地方做出指导。最感动的一次莫过于老师提前出会议室来帮我解决问题,而且当时我并不着急,有时间可以等,非常感谢。在这几个月的时间里,老师能分出一部分精力为我做指导,我感到无比的感谢和庆幸,再次感谢老师。
    其次要感谢大学四年来计算机学院所有的领导、老师对我的关怀和照顾。计科的四年让我学习到专业知识的同时更懂得了许多做人的道理,计科人踏实务实的精神在论文创作的过程当中一直激励着我。
    最后要谢谢实习过程中碰到各种各样的工作人员,这里面有领导,有开发人员,还有其他形形色色的人,在学习路上,他们给我了莫大的帮助和激励,也是他们让我知道了学习之路原来这么广阔,每一个都足以成为我的导师。
    再次向所有曾经帮助支持我的老师、同学和朋友们表达深深的谢意!
    由于学术水平有限,本篇论文存在的不足之处还请老师,同学们指正。今后我也将不断努力,争取在web开发领域有更深入的研究。
    1 评论 6 下载 2018-09-28 23:19:30 下载需要13点积分
  • 基于JavaScript和MySQL的文化平台网站的设计与实现

    摘要中国文化源远流长,自古就有文人雅士作诗赋词,舞文弄墨,尽显风雅。现今则有歌手作家思想成文,心绪为曲,亦现儒雅。文化是传承的,是流传不息的,也是众多人所追求的。从各种各样的文化中,我们提升自己,丰富自己。而我现在所要做的就是给这类用户提供一个关于文化的平台,通过这个平台,用户可以查看各类诗词散文,各类优秀用户的奇思妙想,同时也可探索一些世界奥秘,从而提升自己的文学素养,文化底蕴以及文化知识。现在的社会趋向于数字化,信息化,越来越多的人通过互联网来获取信息,丰富自己。而这个平台也就迎合了社会的发展,符合了现在人的日常习惯。
    就技术方面说,现在社会发展迅速,农村人也几乎家家户户通上了网线,纷纷加入互联网大军。提到互联网,几乎每个人先接触到的事物应该是浏览器,而浏览器就是个网站显示的平台,有了它,用户才能访问不同的,各式各样的网站。自从h5现世,网站的发展更是一发不可收拾。页面元素从此不再是单一的文字,慢慢的也有了炫酷的动画效果和美好的色感,这也促使了更多的用户来访问网站。由于浏览器的良好兼容性,更是促进了它的发展。因此,我也选择了做网站来做我的毕业项目,目的是使更多人接触到我的网站,然后通过我这个平台提高文化素养。
    关键字:文化; 思想; 网站; 浏览器
    AbstractChinese culture has a long history, Since ancient times,Literati make rhyme, showoff literary skill, full of elegance. Todaythere are singer and writer make idea to article and Elegant, full of elegant. Cultureis inherited and endless, Is also the pursuit of many people. Froma variety of cultures, We raise and rich ourselves. All I haveto do now is to provide a platform for this kind of culture, Throughthis platform, users can view all kinds of prose poetry, allkinds of excellent user ideas, explore the mysteries of the world. Soas to enhance their literary quality, cultural heritage and cultural knowledge. Thepresent society tends to be digitized and Informatization, Moreand more people have access to information through the Internet to raise themselves. Andthis platform will cater to the development of society, in line with thepeople’s daily habits.
    Technical aspects said, Nowthe rapid development of society, almost everyhousehold in rural areas on the net, join the internetarmy. Mention Internet, almosteveryone should first be exposed to the browser, thebrowser is a web site display platform, userscan access different, various websites by this. Sincethe H5, the development of the website is more rapid.Pageelement is no longer a single text, slowly also has acool and bright color animation, this also promptedmore users to visit the site. Due to the good compatibility of thebrowser, but also to promote its development..So, Ialso chose to do the site to do my graduation project, thegoal is to get more people into my website, andthrough this platform to improve cultural literacy.
    Key words: Culture, Idea, Website, Browser
    1 引言1.1 课题背景人的一生是一个不断学习的过程,从呱呱坠地时期,我们就已经开始了学习,第一门课就是语言,从最开始的拼音字母到日常交流在到书写诗词,我们身心不断成长,自身素质不断提高,文化素养也不断增强,逐渐成长为一个有文化素养的人。同时使得我们在谈吐之间就显风雅。中国是一个有着文化底蕴的国度,而作为这个国家的子民,学习一些诗词遍成为了我们的基础课程。不同学期的课本也都有所体现,小学接触简单诗,如《山村咏怀》,从中还学会了数学知识。慢慢长大,又接触到诸如李白,李商隐,陶渊明等著名诗人,从其诗词中仿佛看到那个时代的风景与时事。现在的人对它在创造,便有了自己风格的短片散文,如徐志摩的《再别康桥》,戴望舒的《雨巷》,艾青的《我爱这土地》等等,这些文章都写出了自己的风格,也与时俱进的符合现在大众的胃口。还有各类新闻看报,其中内容也带有极强的文化感,文字标点之间散发文化之气。而对于咱们平凡人来说,也有不少人喜欢写写文章,陶冶情操。逐渐积累文化的慢慢的写出自己风格的书籍,再辅以装饰,最终出版,让世人领略自己的文化风格。而这就称之为创作,创作就少不了灵感,这些灵感从任何而来,如何保证能启发灵感?就需要一个平台,一个分享自己文学作品的平台,从中查看,获取灵感。所以我要做的就是为用户提供这个平台,这个平台采用各类优秀文章,在用简单明了的页面加以呈现,供用户查看,欣赏。
    现在是个互联网的时代,网络的出现和迅速发展,冲击了传统信息的传播方式,冲破了人们获取新的时间和空间的限制,获取信息的途径不再限制于报纸、书籍,人们更多接触的是电脑,手机,越来越多的人习惯从网络上获取自己想要的信息,这种方式也符合现在快节奏的生活方式,人们获取信息追寻的是更好,更快。而对于互联网来说,人们接触的更多是网站,点开即看,关闭即走,简单便捷。我的课题也因此确定实现个文章信息类的网站,供用户查看。本网站秉承专而精的精神,服务用户,提供用户优质服务,构建良好平台,功能全面,希望能被用户接纳和喜欢。
    1.2 课题意义该课题适应现代社会人们的快节奏的生活,现在社会人们不再希望用书籍那种接受信息慢的方式去接受文化,而会去追求更快更好。希望在短时间内丰富自己的文化素养。我这网站,用户看到后,能简单明了的找寻自己需要的东西,各类文章随点随看。只需通过简单的点击,遍可以查看各个文章,没有复杂操作。页面整洁大观,给人以清爽之感,在寻找文章和阅读文章过程中也不因界面而反感。有了这样界面做基础,在辅以优美的文章,遍会激励用户去学习,去探索,即扩大了网站的用户量,也给用户提供了优质的服务。
    在这纷纷扰扰的现在社会中,总有些人想找块属于自己的静土,而这个平台也就迎合了用户的需要,为用户提供了一处心灵的温煦地。在这里用户可以学习,可以探索,可以互相讨论,甚至可以绘画。在这块地域中,用户获得更多的是正能量,这里的思想模块,慢慢正能量,为迷茫的用户指明方向,送去阳光。用户还可以对一些自己深有体会的文章下面说出自己的故事,是一种宣泄,也是一种激励。也可以让更多用户来了解你的故事。画板页面,用户可以随意编织自己的世界,让你的心灵为画,并可下载。总之,这个网站是一个好用,好看,好玩的网站。
    2 相关技术简介2.1 html技术它是一种超级文本标记语言,是一种规范,也是一种标准。它通过用各式各样的标签来组成网页的各个部分。它本身也是一种文件,类似txt文件。它控制的是网页的结构,或称之为“骨骼”,当浏览器读取它的时候根据它内容标签的不同,而呈现出不同的结构形式。当然,对于不同的浏览器内核而言,它的解析形式也不同,而现在它也更新到了5版本,添加了许多的新特性,而有些浏览器对这些新的特性解析也不同,甚至不能解析。所以用低版本的浏览器需要注意这点。它的语言特点,简易性,可扩展性,平台兼容性,通用性等等,这些好的特性也使得它用的更加全面和发展迅速。
    2.2 css技术CSS全称Cascading Style Sheets(层叠样式表),如果说html是骨骼的话,那么他就是皮肤,它点缀html,使网页有了色彩和质感。它提供了丰富的样式外观,以及设置文本和图片属性的能力,可以调整各个标签的大小以及间距,允许调节文本的大小和装饰。同时它又易于修改,简简单单的几行代码,就能为网页增色不少。通过各类选择器(id选择器,标签选择器,类选择器等等),把想要的样式加到确定的标签上,为此标签添加属性和样式。另外它的层叠属性也不能忽略,层叠意味着后面的代码会把前面的覆盖掉,这也就实现在基础的属性上添加新的属性。为了手机端,它也新增了媒体查询功能,使得开发手机端也变的简单和快捷。
    2.3 JavaScriptJavaScript,它应该称之为网页的血液。是一种直译型语言,是一种弱类型的语言。它可以直接嵌入html页面中,为标签添加交互行为,也可放到单独的js文件中,这样利于结构和行为的分离,更明确交互的行为。它也有良好的跨平台性,支持绝大多数浏览器,更甚至在可以在windows和mac中使用同一套,便可有相同的呈现。当然它不仅仅有交互本领,它不断发展,可以控制cookies和sessions,保存用户的信息,把数据送到服务器等各种本领。另外它也和其他语言一样,是基于对象的,是动态性的,它也有大众编程语言的api,相同的状态下又有自己的独特性,在浏览器方面独树一帜。
    2.4 JQueryjQuery是JavaScript封装好的一个库,有良好的兼容性,一方面又解决了ie的兼容问题。它封装了多个用于实际开发的api,简化和方便了开发,比如常用的和后台进行交互的ajax,操作简单,轻松获取后台数据,进而渲染界面。它还提高了html中dom和js的交互性,使得一些DOM操作简单方便。同时他入门简单,也方便了后台人员来学习,进而明白前台工作的运行原理。
    2.5 VueVue是一套构建用户界面的渐进式框架,它是轻量级的框架,入门简单,且运行方便,采用自底向上增量开发的设计。采用mvvm模式,方便管理和开发。而且它是轻量级框架,适合刚接触前端开发的人来学习,对于练习小项目,有利而无害。
    2.6 MySQL技术MySQL是一个关系型数据库管理系统,它也由库,表等特性。体积小,速度快,总体成本低,适合中小型网站的数据库。它可以和Apache搭配成良好的开发环境,我的可以已使用Apache搭建的MySQL库。一个网页,便可进行相关的操作。它支持支持 AIX、FreeBSD、HP-UX、Linux、Mac OS、NovellNetware、OpenBSD、OS/2 Wrap、Solaris、Windows等多种操作系统。为多种编程语言提供api,支持多线程,充分利用cpu的资源,优化sql查询算法,更快的查询速度,同时它又是开源的,使用它,完全不用担心额外的费用。
    2.7 node.js技术Node.js其实是js的一种环境,这里单拿出他来说,是因为它可以称为前端发展的里程碑,它自己的架构等等方面,为前端的发展注入了动力。举个例子,它采用了一个“事件循环”的架构,使得编写可扩展性的服务器变得即容易又安全,提高了服务器的性能和形式。而且它又有不同的模块,各个模块可以随意导入,简单操作就能使得自己项目拥有强大的本领。这几年他更是发展迅速,慢慢的开始挤掉php王者的地位,好多公司也开始从php向node转变。有了它,你甚至可以开发一个桌面应用。Node.js,看名称,它是由js写成的,也就使得前端开发者能更容易的掌握它,也就促使了前端开发者向全栈发展。
    3 需求分析3.1 系统功能该项目主要是为用户提供一个平台,用于向其展示文章。

    用户注册登录功能
    首页模块,简单呈现网站所有模块
    梦感觉模块,展示各类文章
    梦探索模块,展示各类离奇事件
    梦思想模块,以标签的方式呈现给用户优质思想
    梦画板模块,提供绘画功能

    3.2 系统开发环境
    开发工具 : webstorm,photoshop
    前台开发语言 : html,css,JavaScript
    后台开发语言 : node 6.7.0
    数据库 : Apache环境下MySQL
    项目演示 : chrome浏览器

    3.3 功能性需求分析3.3.1 用户系统用户的注册登录功能,注册设置限制条件,如用户名和密码长度
    3.3.2 文章系统录入各类文章,不同文章不同处理,梦感觉界面文章保存其文章名称、图片、作者以及内容,梦思想界面保存用户名称和用户思想短句。
    3.3.3 评论系统根据用户的id和文章的id保存评论,再在页面加以显示
    4 系统设计4.1 用户登录注册注册功能,用户名不能为空,不能少于3位,长度不能超过12位,密码不能为空,只能由数字和字母组成,长度不能少于8位,不能超过15位。确认密码必须和密码 一致。每条判断均有对应的错误提示。
    登录功能,用户名不能为空,数据库应有相应的用户,密码不能为空,不能填写错误。登录成功后页面自动显示用户登录状态。
    具体流程如图4-1所示。

    4.2 用户评论评论方面,初次登录页面显示其他用户的评论。对该文章评论判断评论内容是否为空,用户是否登录,为否则不能进行评论。
    具体流程如图4-2所示:

    4.3 数据库设计用户表存放用户注册信息,梦思想,梦探索,梦感觉主要存放关于文章的相关信息,评论表依赖用户表和梦感觉表,并存有用户评论。
    用户表如图4-3所示,Id为主键,用户名(name)设置成varchar类型,方便用户起随意名字,密码(pwd)也为varchar类型,使得密码的设定也更随意,同时也提高了密码的破译程度。

    梦思想表如图4-4所示,Id为主键,内容(content)考虑到文章的长度,设置成text类型,方便录入一定长度的文章,作者(author),长度和用户名一样,所以设置成varchar类型。

    梦探索表如图4-5所示,Id为主键,标题(title)和日期(date)都设置成varchar类型,满足要求也方便日后填写,内容(content)因长度原因设置成text类型。

    梦感觉表(文章)如图4-6所示,Id为主键,标题(title)、图片(image)和作者(author)都设置成varchar类型方便录入,内容(content)考虑到长度原因设置成text类型。

    梦感觉表(评论)如图4-7所示,评论表,依赖用户表和梦感觉文章表,对应的是uid和aid,这两个字段使用int类型,时间(date)设置varchar类型,方便录入,内容(content)设置成text,防止数据溢出。

    5 系统实现5.1 首页如图5-1所示,主要展示网站的所有模块,一眼便可看尽网站功能,使得用户更能了解该网站,从而去用该网站。登录和注册也在该页面,可以方便用户更简单的去注册和登录,少去跳转页面的时间和流量。顶部栏颜色采用深蓝色,常看也不产生眼部疲劳,也有自己设计的网站logo,顶部栏也是整个网站的核心,有着去任何一个页面的连接,每个页面有这个顶部栏,方便用户查看各类文章。整体网站颜色采用白色背景,更能凸显网站结构和内容,也方便了用户的查看,同时布局是常用的左右布局,符合常理。页面初始不再采用每个网站都有的轮播图,而是采用用户点击的方式渐入渐隐图片,看似平凡却深藏奥妙。
    所有呈现的图片均有划入变淡效果,使得网站整体简约而不失风雅。各个模块分块明确,用户可以轻松看出每个模块的位置,并用点击进入的入口。如图5-2和5-3所示,具体功能有,分别是登录和注册。注册时会进行常规的验证,我这里采用失去焦点,就进行验证,方便用户某条信息不规范及时改正。常规验证主要有,用户名和密码不能为空,用户名和密码长度限制等等,验证成功,后台在做检测,用户是否已经注册,该用户名是否已经存在等操作,双重验证,从而保重网站坚固性。注册成功后会直接登录。登录时同样进行常规验证。

    5.2 梦感觉如图5-4所示,现,侧栏点明网站的名称,主体栏存放文章。主体栏中以表格方式呈现,单个表格可以看出文章的图片、作者、以及时间等信息。每页可呈现16篇文章,多余文章在其他页面显示,底部有分页功能,可以跳转上下页以及具体页面,方便用户查看。如图5-5所示,文章详细内容,已登录用户可以进行评论。当呈现文章详情时显示以前用户对该篇文章的评论。文章阅读到最后,有切换上下篇按钮,用户可以通过简单点击查看上一篇或下一篇文章。

    5.3 梦探索如图5-6所示,边栏的方式呈现,主体栏显示所有文章,多余文章分页显示,侧边栏显示优秀的文章,文章可点击查看详情。

    5.4 梦思想如图5-7所示,使用黑板背景图,“黑板”上贴有各类用户思想短句便签,用户可以根据自己的喜好移动便签,并可以通过点击移除便签,以及换一换功能。

    5.5 梦画板如图5-8所示,页面主要是一个画板,画板工具有,普通笔、毛笔、画矩形,画圆形,写文字,橡皮擦。顶部栏左侧提供设置笔触大小和颜色,以及填充色功能,右侧提供一键清屏和点击下载功能。

    6 结论本项目主要是以前端为主项目,后台功能并不多。项目以HTML,CSS完成前台页面,以JAVASCRIPT,JQUERY,VUE完成前台逻辑,用NODEJS完成后台逻辑,用MYSQL搭建数据库。项目功能主要是向用户展示优美文章,促进用户文化素质的提高,也方便大众用户查看优秀散文和各类神奇事件。散文是培养人文素养的文化载体,能够使学生得到精神上的愉悦和身体上的放松,能培养健康审美方向和高尚情绪。自小学习,语文就是一门主课程,并伴随我们的学习生涯。在学习中,我们阅读诗词歌赋,并会主动模仿去学习,去书写。而慢慢长大学文章看文章的时间逐渐减少,减少的原因不想而知,我们看书的时间越来越少,更多的时间是在互联网上,而我因时而生,这个平台,意在帮助更多人拾起中国文化,提升素养。
    7 致谢经过漫长的努力,终归完成了毕业设计,感觉毕业设计是大学四年的结晶,和大学各位老师细心的教导是离不开的。在大学,因为老师的鼓励和教导,自身不断成熟,从出生牛犊变成了能独挡一面的人。使得我现在进入社会,有了成熟的心智和优质的能力,独自一人能在大城市里闯荡。
    现在算算,在公司上班也有半年了,经常深刻感受到,大学给我的能量,好多人都说大学教的都是理论,都是些没用的东西,但真正上班了,你会逐渐发现工作是需要这些理论去支持的,没有了这些理论寸步难行。
    感谢在最后学期老师以及各位同学对我开发项目的指导和意见,在此表示衷心的感谢您!
    参考文献[1] W3C与IDPF. w3school在线教程. [2016-05-01]. http://www.w3school.com.cn
    [2] John Resig. jQuery. [2016-05-01]. http://jquery.com
    [3] node.js 官网(中文网) http://nodejs.cn/
    [4] vue.js 官网(中文网)http://cn.vuejs.org/
    [5] Mysql 360百科 http://baike.so.com/doc/2303745-2436931.html
    1 评论 18 下载 2018-09-28 22:42:58 下载需要13点积分
  • 基于Haar特征的AdaBoost人头检测算法和基于Hog特征的SVM人头检测算法实现视频监控系统

    摘要传统的门禁视频监控系统一般是无法对人员进出门禁时进行人头检测,只能通过简单的硬件设施将监控画面传到后端,由监控人员人工进行监视判断。这样会加大监控成本也增加了监控人员的压力,同时也对监控人员的身体健康产生较大影响。所以就有必要设计一款新型门禁监控视频系统来实现人头检测功能,本文设计出的一款基于WPF的门禁监控视频系统,将基于Haar特征的AdaBoost人头检测算法和基于Hog特征的SVM人头检测算法进行比较,在人员进出门禁系统时进行人头检测并将检测结果以图像方式进行保存。
    基于WPF的门禁视频监控系统,采用C#与EmguCV的技术,是实现对人员出入门禁系统时进行人头检测。论文主要分析了两种算法的特征值的提取方法以及利用这两种算法进行人头检测,设计出一个可以检测人头的门禁监控视频。
    关键词:视频监控; 人头检测; C#; EmguCV; AdaBoost; SVM
    AbstractTraditional access control videosurveillance systems are generally unable to detect the head of personnelentering and leaving access only through a simple hardware facilities willspread to the rear end of the monitor screen, the monitoring personnel tomonitor artificial judgment. This will increase monitoring costs are increasingpressure monitoring personnel, it will be have a greater impact on healthmonitoring personnel of the body. Therefore it is necessary to design a newaccess control system to achieve video head detection, designed out of aWPF-based access control and video systems, will head detection algorithm basedon AdaBoost and Haar features characteristic of SVM-based Hog head detectionalgorithm comparing head detection and test results performed at the personnelaccess to the access control system to save the image mode..
    WPF-based access control videosurveillance systems, using C # and EmguCV technology is to achieve when peoplehead out access system testing. Thesis analyzes the eigenvalues extraction andthe use of these two algorithms two algorithms detect head, designed to detecta surveillance video access control head.
    Key words: Video Monitor, Head Detection,C#, EmguCV, AdaBoost, SVM
    第1章 绪 论1.1 选题背景和意义随着视频监控技术的迅速发展与设备成本的不断下降,视频监控已经遍布于城市的各个角落成为了一种重要的视频信息来源。目前,视频监控系统在银行的安全防范、道路的交通管理、公安刑事侦查等特殊领域已经发挥了及其重要的重用。由于上述领域对于实用的监控视频采集、处理与分析技术和应用表现了十分迫切的需求,各类监控视频信息处理相关的基础理论研究与应用系统的设计开发也越来越受到政府、科研院以及公司的重视。
    目前使用的监控系统系统大多只具有录像功能,因此只能提供事后查询,并不能对当时情况作出及时判断并提出警告,例如门禁监控,传统的门禁系统往往将门禁前的视频图像发到视频墙上,由人工监视什么时间人员可以进出,进出是否刷卡,是否有异常情况发生。所以有很多不足产生:

    工作强度大,持续时间长,容易对负责浏览视频的工作人员的视觉会造成很大的影响,危害身体健康。监控成本上,人力成本的持续投入使得成本不断增加。长时间高强度、高压力的工作状态下,负责视频浏览的工作人员注意力难以集中,容易出现一些重要信息的遗漏和误判现象。门禁系统刷卡与视频无关联,在突发情况下,监控人员很难判断出发生的原因。
    为了解决现阶段门禁的不足。本文设计出基于门禁的视频监控软件系统,在本系统中实现了门禁硬件与视频监控的交互。减轻监控人员的负担,记录异常情况的发生和截图。
    1.2 视频监控研究现状目前美国以及欧洲地区等西方先进国家仍然掌握着大部分智能视频监控系统的核心算法技术,在国际市场上的智能视频监控系统中占据很大的优势。美国国防部高级研究项目署(Defense advanced research projects agency , DARPA)于1997年建立了视觉监控重大项目VSAM(Visualsurveillance and monitoring),该项目对用于民用场景和战场中视频监控的自动理解技术进行了重点研究。马里兰大学研发成功的实时视频监控系统W4主要任务是是被监控场景中的行人,将形状分析和目标跟踪技术相结合,对人的外表进行模型构建。高级研究计划VACE(Video Analysis and Content Extraction)于2003年由美国ARDA机构开始主持。在对车辆和行人的跟踪及其交互作用识别的相关研究方面上,英国雷丁大学(University of Reading)取得了一系列的研究成果。IBM 公司于 2006 年 11月 7 日宣布已开发并销售一款用于分析视频实时监控系统的安全软件—智能监控系统(S3:Smart Surveillance System),它能够将视频摄像头捕捉到的信息通过计算机网络传递到整个系统软件上,发现监控环境中的潜在安全隐患并自动报警以色列也在智能视频监控领域取得了很大的研究成果,他们的产品主要用在边界不含以及商业调查分析领域[7]-[8]。
    在对智能视觉监控领域的研究方面,国内起步比较晚,目前,中国科学院自动化研究所模式识别国家重点实验室在国内的研究机构中处于领先地位,取得了一系列研究成果。除此之外,国内还有一些高校也在这一领域投入了相当的研究精力,如上海交通大学、清华大学、华中科技大学、北京航空航天大学、北京理工大学等[3]。中国科学院自动化研究所模式识别国家重点实验室已经成立了智能视频监控研究小组,正在开展智能视频监控方面的研究,研究内容包括:快速准确的运动检测,实时性、鲁棒性的基于三维模型的车辆与行人的定位、识别和跟踪,基于移动摄像机的视觉监控技术,多摄像机的协作监控,事件的机器学习方法,异常现象的检测、报警与目标的行为预测,对目标运动情况给出语义上的解释的方法以及远距离的身份识别等等。目前视觉监控研究组针对目前交通管制中大量人力和物力资源的浪费,提出了一种基于三维模型的交通监控系统,采用了基于三维线框模型的车辆定位算法和基于改进的扩展卡尔曼滤波的车辆跟踪算法;研究组在自主研发的交通场景模拟平台上最终实现了交通监控原型系统等。
    1.3 本文研究内容和章节安排本文将监控视频作为研究对象,基于监控视频本身的特点,对监控视频摘要技术进行了研究。在门禁视频监控中,分析人进出门禁的情况,并进行统计某一时刻进出人员数量。
    基于以上工作,将本文安排为四章,具体如下:
    1.绪论
    简单介绍视频监控系统的研究背景,意义以及视频监控系统的现状和发展趋势,总结前人的基础上,结合项目实际应用阐述本论文的主要工作。
    2.视频监控中的关键技术
    本章主要介绍了视频监控系统中应用到的一些关键技术,例如EmguCV图像处理、人头检测技术中的AdaBoost和SVM技术、C#编程其中的WPF编程技术、目标跟踪[9]-[10]等。
    3.人头检测算法的实现
    本章主要介绍了人头检测中所用到的两个算法,即基于Haar特征的AdaBoost人头检测算法和基于Hog特征的SVM人头检测算法,以及如何生成特征数据和利用特征数据进行检测。
    4.视频监控系统软件设计与实现
    本章主要从系统的需求分析开始。分析系统的需求,提出需要的功能,然后进行概要设计,划分出功能模块,与各个功能模块之间的接口,从而进行系统各模块的详细设计,在进行类图的设计最终完成各个功能实现本系统的开发
    5.实验结果对比分析
    对系统中所使用的AdaBoost算法和SVM人头检测算法进行分析比较,得出各个算法的优势和劣势,从而为后期的系统优化做好准备。
    第2章 视频监控系统中的关键技术2.1 图像处理技术图像处理(Image Processing),用计算机对图像进行分析,已达到所需结果的技术。又称影像处理。图像处理一般指数字图像处理。数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组,该数组的元素称为像素,其值称为灰度值。图像处理技术一般包括图像压缩,增强和复原,匹配、描述和识别3个部分。
    OpenCV[4]( Open Source Computer Vision Library)是一个基于(开源)发行的跨平台计算机视觉库,由一系列C函数和少年C++类构成,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
    由于门禁视频监控系统在.NET平台下实现,不能直接运行C/C++类库,所以需要对OpenCV进行封装,所以在本论文中使用了EmguCV。
    EmguCV是.NET平台下对OpenCV图像处理库的封装。也就是OpenCV的.NET版。它运行在.NET兼容的编程语言下调用OpenCV的函数,如C#等。
    2.2 WPFWPF(Windows Presentation Foundation)是微软推出的基于Windows Vista的永华界面框架,属于.NETFramework3.0的一部分。它提供了统一的编程模型(MVVM模型)、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面。
    WPF具有强大的数据绑定功能,存储和访问数据由Microsoft SQL Server和ADO.NET之类的技术提供,涉及两个步骤:

    将数据从托管对象复制到控件中,在控件上可以显示和编辑数据。
    确保将使用控件对数据进行的更改复制回托管对象。

    为了简化应用程序开发,WPF 提供了一个数据绑定引擎以自动执行这些步骤。数据绑定引擎的核心单元是Binding 类,它的任务是将控件(绑定目标)绑定到数据对象(绑定源),关系如图2-1所示:

    WPF 主要编程模型是通过托管代码公开的。在 WPF 的早期设计阶段,曾有过大量关于如何界定系统的托管组件和非托管组件的争论。CLR 提供一系列的功能,可以令开发效率更高并且更加可靠(包括内存管理、错误处理和常规类型系统等),但这是需要付出代价的。图2-2说明了WPF 的主要组件.关系图的红色部分(PresentationFramework、PresentationCore 和 milcore)是 WPF 的主要代码部分。 在这些组件中,只有一个是非托管组件–milcore。
    milcore是以非托管代码编写的,目的是实现与DirectX 的紧密集成。WPF 中的所有显示都是通过DirectX 引擎完成的,因此硬件和软件呈现都很高效,WPF 还要求对内存和执行进行精细控制,milcore 中的组合引擎受性能影响关系大,需要放弃 CLR 的许多优点来提高性能。

    2.3 人头检测技术人头检测,就是把视频监控图像中出现的人物的人头目标从视频中检测出来,目标提取,就是当人头检测算法检测到了视频里面有人头目标时,就把这个目标从视频中分割出来,为我们下一步进行目标统计提供数据支持。
    基于人体检测的统计方法可以实现人与人间距较大,并且没有遮挡情况下的统计目的,但是在实际中,由于人员的无序性,以上的情况基本上不会出现,很难做到对每个人员的计数,所以就要减少由这些问题引起的误差,我们就需要选取合适的目标作为计数对象,而不论人员的多少,人的头都是不会被遮挡住的,所以人头作为我们的统计目标,能够最大化的降低误差。
    人头检测时现在具有较高的理论研究课题,同样具有较高的实际应用价值,随着研究人员的不断研究,新的检测方法也在不断的出现。从总体来看主要还是分两大类,即基于知识的方法和基于统计学习的方法。
    基于知识的学习放大的人头检测时通过一定的先验知识(如肤色、颜色、形状等)描述人头区域,并且根据这些规则去判断一个区域是否包含有人头区域。模板匹配则是典型的基于知识理论的检测方法,该方法通过利用模板对是否是人头区域进行描述,以及包括颜色、形状以及其他的组合。Viecente等人则是用形状作为模板[14],首先是假定人头全部是由俯视得到的图喜爱那个并且大致为圆形,然后把人头轮廓通过Hough变换以后再与标准圆形模板进行匹配,最后时限了人头检测和定位。而Jin等人则是采用边缘检测和椭圆形状匹配相结合,就是把人头作为类似椭圆的目标,首先要对视频中移动目标进行运动检测,提取运动物体边缘轮廓,并在轮廓图像上根据一定的宽高比进行椭圆匹配[11],获得人头区域。
    当前,人头检测主要采用统计模式识别的方法,分别为基于统计的特征抽取和基于特征统计的分类。AdaBoost算法作为统计学习方法中的一种,由Freund等人与1996年提出,算法的最初是想是基于弱学习模型,该模型假设存在一种弱学习算法经过训练获得若干个弱分类器,通过对弱分类器进行组合可以获得任意高精度的分类器。AdaBoost训练需要事先确定待选特征库,训练过程就是从待选特征库中挑选最具备分类能力的特征。目前常用的特征提取方法主要有Haar特征,并结合积分图进行求取。支持向量机(Support Vector Machine,SVM)是建立在统计学习理论的VC维理论和结构风险最小原理基础上的,根据有限的样本在模型的复杂性(即对特定训练样本的学习精度,Accuracy)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折中,以获取最好的推广能力。
    具体实现过程请见下一章节。
    2.4 门禁视频监控系统工作流程门禁视频监控系统监控过程中,人头检测可以采用AdaBoost检测算法,也可采用SVM检测算法,虽然算法不同,但检测结果几乎接近。系统的工作包括以下几步:
    1.预处理
    主要包括视频图像的加载、解析。
    2.加载样本数据
    加载人头的样本数据,为检测做好准备
    3.进行人头模板检测
    与样本数据进行模板匹配,获取视频图像中的人头
    4.人头校验
    对检测到的人头进行校验,去除误检。
    5.进行跟踪
    对检测到的人头进行跟踪。
    图2.3给出了门禁监控视频的大致流程

    2.5 本章小结门禁视频监控的实现是建立在许多技术基础之上,本章主要介绍一些关键技术,例如使用Emgucv/OpenCV图像处理库,可以更方便的进行图像处理;如何生成样本数据以及使用模板匹配进行人头检测,可以快速检测出人头;使用WPF进行界面设计使代码与界面分离,减少耦合度;使用面向对象的编程技术,可以快速开发、减轻开发人员的压力。WPF其实不仅仅是图形引擎而已,它将给Windows应用程序的开发带来一次革命,因为新的架构提供了一种全新的开发模式。对于开发人员而言,界面显示和代码将更好的得到分离,这与从前的桌面应用程序开发有很多不同(界面设置和代码是融合在一起的),这是比较具有革命性的改变之一。
    第3章 人头检测算法实现3.1 分类器设计原理机器学习的目的是把数据转换成信息。在学习了一系列数据之后,我们需要机器能够回答和对这些数据进行分析,最终获取我们所需要的数据。及其学习会从很多样本数据中获取特征,并且作为数据库,根据分类规则对这些特征进行学习,而最后生成的模型就是我们所要获得分类器。
    3.2 基于Haar特征点的AdaBoost人头检测算法3.2.1 AdaBoostAdaBoost是一种基于级联分类模型的分类器。级联分类模型可以用下图表示:

    级联分类器:级联分类器就是将多个强分类器连接在一起进行操作。每一个强分类器都由若干个弱分类器加权组成。
    3.2.2 算法描述人头检测可视为一个两类分类问题,其中一类为人头(正样本),另一类为非人头(负样本),假设 AdaBoost 学习算法的输入为训练样本集

    其中,xi表示第i个训练样本,yi表示样本类别,且yi属于(+1, -1),+1表示正样本,−1表示负样本。因此,对于每个样本,在训练过程中都事先知道其样本类型,以便对算法做出的预测正确与否进行判断。
    在算法训练阶段,首先对每个样本赋予相同的初始权重W,之后进行K轮训练,并且在每轮训练结束后根据当前分类结果调整训练样本的权重,减小正确分类样本的权重,同时增加错误分类样本的权重,从而使每个弱分类器主要围绕难以正确分类的样本进行学习。经过K 轮训练得到一个由K 个弱分类器(每个弱分类器对应一个特征)组成的弱分类器序列

    其中,hi表示第i个弱分类器,ai为弱分类器的加权系数。最后对K个弱分类器进行组合,得到最终的强分类器H。虽然每个弱分类器hi的分类预测能力只比随机稍好,但通过对K个弱分类器进行组合,能够使强分类器H获得任意高的精度。
    AdaBoost 算法流程如图3-2所示。算法主要由离线训练和在线检测两部分组成,在训练阶段首先对正负样本进行训练,从特征库中选取最优特征并组合成强分类器,在检测阶段,用训练好的分类器对各个待检测窗口进行检测。

    3.3 基于Hog特征点的SVM人头检测算法3.3.1 SVM支持向量机SVM是从线性可分情况下的最优分类面提出的。所谓最优分类,就是要求分类线不但能够将两类无错误的分开,而且两类之间的分类间隔最大,前者是保证经验风险最小(为0),而通过后面的讨论我们看到,使分类间隔最大实际上就是使得推广性中的置信范围最小。推广到高维空间,最优分类线就成为最优分类面。
    支持向量机是利用分类间隔的思想进行训练的,它依赖于对数据的预处理,即,在更高维的空间表达原始模式。通过适当的到一个足够高维的非线性映射,分别属于两类的原始数据就能够被一个超平面来分隔。如下图3-3所示:

    空心点和实心点分别代表两个不同的类,H为将两类没有错误的区分开的分类面,同时,它也是一个最优的分类面。原因正如前面所述,当以H为分类面时,分类间隔最大,误差最小。而这里的H1·H2之间的距离margin就是两类之间的分类间隔。支持向量机将数据从原始空间映射到高维空间的目的就是找到一个最优的分类面从而使得分类间隔margin最大。而那些定义最优分类超平面的训练样本,也就是上图中过H1·H2的空心点和实心点,就是支持向量机理论中所说的支持向量。显然,所谓支持向量其实就是最难被分类的那些向量,然而,从另一个角度来看,它们同时也是对求解分类任务最有价值的模式。
    支持向量机的基本思想可以概括为:首先通过非线性变换将输入空间变换到一个高维空间,然后在这个新空间中求取最优线性分类面,而这种非线性变换是通过定义适当的内积函数来实现的。支持向量机求得的分类函数形式上类似于一个神经网络,其输出是若干中间层节点的线性组合,而每一个中间层节点对应于输入样本与一个支持向量的内积,因此也被叫做支持向量网络。如图3-4所示:

    由于最终的判别函数中实际只包含于支持向量的内积和求和,因此判别分类的计算复杂度取决于支持向量的个数。
    不难发现,支持向量机作为统计学习理论中的经典代表使用了与传统方法完全不同的思路,即不是像传统方法那样首先试图将原输入空间降维(即特征选择和特征变换),而是设法将输入空间升维,以求在高维空间中问题变得线性可分或接近线性可分。因为升维知识改变了内积运算,并没有使得算法的复杂性随着维数的增加而增加,而且在高维空间中的推广能力并不受到维数的影响。
    3.3.2 算法描述假设训练样本集为其中xi表示第i个样本,n为样本数,yi属于(-1, +1)为类别标志。对线性和非线性的情况如下:
    1.线性SVM
    如存在分类超平面方程:

    满足

    则认为训练样本集是可分的。其中,<W·Xi>为向量W与Xi的内积。
    SVM 算法就是在这种线性可分情况下寻找最优分类面。如果一个分类面不但能将两类样本无错误地分开,并且到两类中离该分类面最近样本点的距离最大,则这个分类面就是最优分类面,如图3-5 所示。

    从以上分析可知,线性判别函数的一般形式为g(X)=W·X+b,若W为权重向量,且假设最小函数间隔为1,即在正样本点X+和负样本点X-上使函数间隔为1,即对于离分类面最近且平行于最优分类面上的样本点满足:

    则分类间隔为:

    其中,为向量的几何范式。这样,在两类中所有样本都满足,则最优分类超平面满足:

    而求解最优分类超平面需要最大化分类间隔2/||W||2,则问题转化为:

    但是,当两类分类数据不能完全线性分开时,最大间隔将是负数。因此需要对式3-7的约束条件进行调整:

    其中,C为惩罚系数,C越大表示对错误分类的惩罚越大。根据拉格朗日乘子算法,有:

    其中,ai>=0、Bi>=0为拉格朗日乘子。
    对W,ξ ,b求偏导,并置0,有:

    将式3-10带入式3-9中,得到:

    式中,0<=ai<=C对应标准支持向量(Normal Support Vector,NSV),ai=C对应边界支持向量。由Kaeush-Kuhn-Tucker条件,在最优点,有:

    结合式(9)-(11)可计算出阀值参数b为

    假设共存在Nnsv个标准支持向量,则为了提高计算的准确性,对每个标准支持向量分别计算b,并取均值,从而得到最终阀值b为:

    2.非线性SVM
    非线性 SVM 与线性 SVM 的区别在于,非线性 SVM 是通过一个非线性函数将样本X映射到一个高维线性特征空间,并在这个空间中构造最优分类超平面,并得到分类器。因此在该情况下,分类超平面为

    判别函数为:

    最优分类超平面问题描述为

    得到的对偶最优化问题为:

    其中,K(Xi, Xj)为核函数,判别式为

    其中,阀值为

    3.4 视频图像及样本采集及训练流程1.样本采集
    进行人头训练与检测需要大量的视频序列和训练样本。本文视频序列部分源自实际监控录像,部分采用Kinect拍摄,人头样本由手工截取,选择不同背景下的不同行人,并截取2000余张,人头样本一千余张,负样本为不包含人头的背景图片随机产生。为了方便训练,所有样本均归一化到相同尺度,本文为20x24像素大小。人头正、负样本示例如图3-6和图3-7所示。

    2.训练流程
    本论文训练人头的流程如下:

    准备训练样本集合;包括正样本集和负样本集;根据机器学习的基础知识我们知道,要利用机器学习算法进行样本训练,从而得到一个性能优良的分类器,训练样本应该是无限多的,而且训练样本应该覆盖实际应用过程中可能发生的各种情况。实际应用过程中,训练样本不可能无限多,本次使用了一千个正样本,一千个负样本收集到足够的训练样本之后,需要手动裁剪样本。本人使用的方法为利用收集到门禁视频行人进出大门的的视频作为训练样本集合,写个了简单程序,只需要鼠标框选一下,就将框选区域保存下来。裁剪得到训练样本之后,将所有正样本放在一个文件夹中;将所有负样本放在另一个文件夹中;并将所有训练样本缩放到同样的尺寸大小。OpenCV自带的例子在训练时,就是将样本缩放为64*128进行训练的;提取所有正样本的Hog特征;(提取所有负样本的Hog特征;对所有正负样本赋予样本标签;例如,所有正样本标记为1,所有负样本标记为0;将正负样本的Hog特征,正负样本的标签,都输入到SVM中进行训练;采用线性SVM进行训练。SVM训练之后,将结果保存为文本文件。这样就可以利用训练样本训练出来的分类器进行人头检测了。
    其中在AdaBoost特征选取上,考虑到人头普遍位于样本的中心位置,因此特征在样本中的位置进行一定的限制,使特征的各个子窗口不落在样本的四个角上,如图3-8所示,在生成特征库时,限制矩阵梯度和自生长梯度特征各特征子窗口全部位于图中的椭圆内,采用该方法一面可以尽可能减少背景梯度对特征形成的干扰,另一方面可以减少特征库的规模,提高训练速度。

    由于SVM算法是针对有限样本进行训练,参与训练的样本数不亦过大,否则会使训练变慢甚至无法完成。因此,本文在AdaBoost人头样本中随机挑选了四分之一,作为SVM训练的正样本,负样本由背景图随机产生,进行聚类。
    第4章 视频监控系统设计与实现4.1 视频监控系统的需求分析门禁软件已经广泛使用在企事业单位、工厂、公司等办公场所,职员,工人佩带名牌进入办公地点,通过名牌对出入口通道进行管制的功能对单位的组织纪律,以及安防都非常有帮助。
    目前很多的门禁软件会加上对出入口通道视频监控来进一步进行监控,来对出入口通道进行更好的监控,传统的门禁系统一般是无法实现门禁和视频在软件层面联动的,只能通过简单的硬件连接看到现场的实时监控录像,而视频门禁系统则可以在人员进出门禁时,联动视频监控系统对现场的照片和录像进行抓拍进行人头检测并保存在门禁服务器中,同时还可以及时在软件界面上弹出对应的现场监控画面。这样不仅让照片、录像和对应的门点事件绑定在一起,可以随时通过查询将现场情况进行再现,还可以确保系统内的警情得到及时确认和处理。
    4.1.1 系统目标系统开发的主要目标是能够针对上述问题,利用图像处理,目标检测与跟踪,相机标定,透视投影变换等理论知识,设计出对视频图像进行处理的算法,来达到对视频中人员人头的检测并进行统计图片保存,同时兼顾先进性、可靠性、安全性、经济型、可扩充性、可维护性和规范性做到一切应从实际出发,使监控系统具有较高的实用效能。本系统操作简单便捷,监控人员不需要经过严格复杂的培训技能即可上手使用。
    4.1.2 功能需求为了使用者能够方便使用此系统,系统功能还将包括辅助系统和效果显示系统。系统用例图如图4-1所示。

    辅助系统主要包括选择视频源,启用/禁用视频检测,视频检测算法,显示视频图像。用例图如图4-2所示。

    在显示系统中,主要是显示未开启检测的视频图像和开启视频的视频图像,以及保存图像。图4-3显示了系统的基本用例。

    4.1.3 功能分配根据功能需求部分所描述出的系统用例图,画出该系统主要功能,如下表3-1所示。



    模块
    详细功能




    辅助系统模块
    辅助模块主要处理人头检测之前的操作。例如选择视频源,开启视频检测,添加/删除视频源等功能


    人头检测模块
    通过AdaBoost算法或者SVM算法,进行人头检测,将检测到的数据再次进行分析,筛选符合的人头


    显示模块
    将检测到符合的人头在图像上进行显示,提供保存图像等操作



    4.1.4 性能需求1.精度
    在显示检测图像中,人头检测错误率不超过2个。
    2.时间
    本门禁视频监控系统视频延迟时间在2秒内。
    3.系统灵活性
    本系统具有可扩充性,可维护性和规范性。
    4.2 系统开发环境介绍1.硬件环境
    本系统对计算机硬件要求不高,处理器基于X86或X64指令集均可,摄像头最低可为低解330线,拍摄画面清晰,没有用肉眼不可分辨的事物。
    2.软件平台
    此系统主要运行在Windows操作系统下,推荐Windows XP及其以上版本。因为Windows XP系统兼容性,对新技术。新产品都有良好支持,是设计开发人员常用的操作系统。另外,Windows XP系统集成了微软的防火墙技术,保障了用户计算机使用安全。
    3.开发环境
    Windows平台下选择Microsoft Visual Studio 2013开发工具,它是目前最流行windows平台应用程序开发环境[2]。Visual Studio 2013是一个基本完善的开发工具集它包括了整个软件生命周期中所需要的大部分工具,如UML工具、代码管控工具、集成开发环境(IDE)等等。所写的目标代码适用于微软支持的所有平台,包括Microsoft Windows、Windows Mobile、Windows CE、.NET Framework、.NET Compact Framework和Microsoft Silverlight及Windows Phone。
    Visual Studio是目前最流行的Windows平台应用程序的集成开发环境。Visual studio 2013具有功能全面,灵活性好,效率高,深入底层的优点。另外,VisualStudio 2013具有MFCAppWizard/Class Wizard等功能强大的编程向导工具,在很大程度上简化了应用程序的开发,也提高了编程效率。VisualStudio 2013自然是本项目开发的首选。
    4.3 系统概要设计4.3.1 总体设计系统大体分为3个模块,包括辅助模块、人头检测模块和显示视频图像模块,如图4-4所示

    4.3.2 辅助模块设计辅助系统模块其结果如图4-5所示:

    主要完成任务有:

    选择人头检测算法:在 WPF中下拉框中选取一种作为接下来的视频图像检测算法。可以选择的检测算法有:SVM 人头检测算法和AdaBoost检测算法。如果不选择,默认为AdaBoost检测算法选择视频播放源:在WPF中的TreeView控件中对视频进行选择,左键选中视频名称前的复选框,则会获取在后台数据库中存放的对应视频名的视频路径,从而打开该视频,并在视频信息显示框中显示视频名称以及视频路径。视频源的添加和删除:在WPF中TreeView控件中进行右键操作,会出现添加视频源和删除视频源的功能,在添加视频源功能中,使用者只需添加视频所位于的根节点以及视频名、视频路径即可。在删除视频源的功能中,使用者只需选择视频位于的根节点以及视频名即可。
    4.3.3 人头检测模块系统核心的匹配人头检测模板结构如图4-6所示

    此模块主要完成任务有:

    AdaBoost人头检测:加载人头信息的样本库,将图像进行均衡化,然后进行人头模板匹配。得到匹配后的人头位置。SVM人头检测:加载人头矩阵的信息样本,进行人头模板检测,得到匹配的人头位置。
    4.3.4 显示模块显示模板主要是(1)开启视频检测后显示人头检测后的视频图像,(2)未开启视频检测后视频图像。其主要结构如图4-7所示

    该模块只要实现图像的显示和保存操作。显示图像直接利用EmguCV中的ImageBox控件来实现,保存图像默认保存格式为png,用户只需要在C:data\pic下找到对应的图像文件即可,操作简单容易上手。
    4.3.5 接口设计1.外部接口设计
    系统外部接口主要实现系统输入射出设备,例如鼠标,键盘事件的响应。系统利用WPF(Windows Presentation Foundation),设计相应对待鼠标、键盘监听响应程序。
    2.内部接口设计
    内部接口设计主要实现各个子系统之间的数据流的处理
    系统首先由用户在辅助系统选择待播放视频和人头检测算法开始,根据显示的已选择视频文件的视频路径,进行播放视频,如果使用者开始视频检测算法,辅助模块会将选择的视频检测算法传递到检测模块,在这里,所有的视频了图像将获得该算法下最大程度的人头检测。待人头检测完成后,人头检测模块会将检测好的视频图像传递给显示模块显示图像和检测信息以及提示保存图片信息。其数据流向如图4-8所示。

    4.3.6 维护设计系统各模块相互独立,减轻了许多维护升级上的麻烦,只需要在制定模块中作出改动,而不会影响其他模块的正常使用。
    4.4 系统详细设计4.4.1 辅助功能模块详细设计辅助功能模块主要操作是选择待播放视频,添加视频,删除视频以及选择人头检测算法。
    添加播放视频功能中,视频格式为“AVI”或者“WMV”视频文件。主要方法及功能如表4-2所示:



    方法名
    功能




    void menuSelectAllChild_Click(object sender, RoutedEventArgs e)
    选中所有子项菜单事件


    void menuExpandAll_Click(object sender, RoutedEventArgs e)
    全部展开菜单事件


    void menuUnExpandAll_Click(object sender, RoutedEventArgs e)
    全部折叠菜单事件


    void menuSelectAll_Click(object sender, RoutedEventArgs e)
    全部选中事件


    void menuUnSelectAll_Click(object sender, RoutedEventArgs e)
    全部取消选中


    void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    鼠标右键事件


    void TreeViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    鼠标左键事件


    int ChildrenSelectNum(MonitorCameraTreeModel model)
    获取子项选中的数量


    void menuAddItem_Click(object sender, RoutedEventArgs e)
    点击添加视频文件


    void menuRemoveItem_Click(object sender, RoutedEventArgs e)
    点击删除视频文件



    4.4.2 人头检测模块详细设计AdaBoost模板检测主要利用人头样本进行检测,SVM模板检测主要利用人头样本矩阵数据进行人头检测。其主要方法和功能如下表4-3所示:



    方法
    功能




    MDeteInfo GetAdaBoostHead(Image\<Bgr, Byte\> image)
    进行AdaBoost算法人头检测


    float[] GetData()
    获取样本数据


    MDeteInfo GetSVMHead(Image\<Bgr, Byte\> image)
    进行SVM算法人头检测


    bool StroageImage(Image\<Bgr,Byte\> image)
    保存图片



    4.4.3 各部分类图1.程序基于面向对象实现设计,其类图为:

    详细描述如下:



    方法名
    功能




    void PlayVideoInvoke (MonitorCameraTreeModel model)
    播放视频委托


    dTimer_Tick(object sender, EventArgs e)
    定时器委托


    void CloseVideoInvoke(MonitorCameraTreeModel model)
    关闭视频委托


    void PlayVideo(MonitorCameraTreeModel model)
    播放视频


    void DisplayPic(string picPath)
    显示图片


    void CloseVideo(Video _video)
    关闭视频


    Window_Loaded(object sender, RoutedEventArgs e)
    加载窗体时激发


    InitImageBox()
    初始化ImageBox


    void InitBackgroundworker()
    初始化backgroundworker


    void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    报告进度


    backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    操作完成、取消、异常时执行的操作


    backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    调用RunWorkerAsync时发生


    TvTestDataBind()
    数据绑定


    Window_Closed(object sender, EventArgs e)
    窗口关闭


    void OpenCheckedClick(object sender, RoutedEventArgs e)
    点击开启检测


    DisplayMessage(string message)
    显示操作消息


    void ShowDeteInfo(string info)
    检测信息显示



    2.显示结果类图

    3.进行人头检测类图

    4.5 系统实现4.5.1 辅助功能模块实现辅助功能模块包括视频源的加载,选择视频后播放视频、视频添加/删除的功能, 在视频播放功能上,首先需要从视频数据源中获取,本系统中,使用xml文件来存储视频源数据,使用XmlDocument类即可实现对Xml数据的访问以及添加数据删除数据等等功能。其部分代码如下:
    1.遍历xml节点
    // 将节点转换为元素,便于得到节点的属性值XmlElement xe = (XmlElement)xnl;// 得到Book节点的所有子节点XmlNodeList xnl0 = xe.ChildNodes;camerModel.Name = xnl0.Item(0).InnerText;camerModel.IsChecked = Convert.ToBoolean(xnl0.Item(2).InnerText);camerModel.IsExpanded = Convert.ToBoolean(xnl0.Item(3).InnerText);
    2.添加一个视频(节点)
    // 建立一个节点XmlElement newTreeModel = doc.CreateElement("children");XmlElement chilId = doc.CreateElement("childId");chilId.InnerText = model.ChildId;XmlElement name = doc.CreateElement("childName");name.InnerText = model.ChildName;XmlElement videoPath = doc.CreateElement("videopath");videoPath.InnerText = model.Videopath;newTreeModel.AppendChild(chilId);newTreeModel.AppendChild(name);newTreeModel.AppendChild(videoPath);xnFnodel.AppendChild(newTreeModel);
    3.删除视频(删除节点)
    foreach (var xmlNode in xmlNodeList){ XmlElement xe1 = (XmlElement)xmlNode; XmlNodeList xnl3 = xe1.ChildNodes; if (xnl3.Item(1).InnerText == model.ChildName) { xe1.ParentNode.RemoveChild(xe1); }}
    4.5.2 人头检测实现在人头功能检测中,我们需要加载人头数据,而人头数据则是利用上面提及的AdaBoost算法和SVM级联器生成的人头样本数据,在这里我们需要加载这些人头样本数据。首先需要加载人头数据:
    1.AdaBoost样本数据加载
    在C盘data文件夹下的cascades包含了人头样本数据,使用har分类器加载数据,其代码如下:
    HaarCascade cascade = newHaarCascade(@"C:\data\cascades.xml");
    2.SVM样本数据加载
    同样,在C盘data目录文件夹下有个HogDetector.txt,里面包含目标检测的图像特征-HOG特征,使用HOGDescriptor提取特征并进行训练,其代码如下:
    if (hog == null){ Size winSize = new Size(64,64); Size blockSize = new Size(16, 16); Size blockStride = new Size(8, 8); Size winStride = new Size(8, 8); Size cellSize = new Size(8, 8); int nbins = 9; hog = new HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins, 1, 1, 0.2, true); float[] data = GetData(); hog.SetSVMDetector(data);}
    而GetData()方法实现则为加载人头样本数据,因为SVM级联生成的为txt数据,所以需要进行文件读取,C#可以利用StreamReader类进行文本的访问,使用该类中的EndOfStream属性判断是否读取结束,这样就可以访问txt数据了。获取数据代码如下:
    var line = reader.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);var arr = new float[line.Length];for (var i = 0; i < line.Length; i++){ arr[i] = (float)Convert.ToDouble(line[i]); //将数据转换为float类型 data.Add(arr[i]);}
    人头检测的实现由两种方式:
    (1) 使用AdaBoost算法进行人头检测
    在加载完AdaBoost所需的样本数据后,将图像进行灰度化和均衡化,并利用EmguCV中的DetectHaarCascade方法进行目标检测,其主要代码如下:
    double scale = 1.3;Image<Bgr, Byte> smallframe = image.Resize(1 / scale, Emgu.CV.CvEnum.INTER.CV_INTER_LINEAR); //重新设置图片大小Image<Gray, Byte> gray = smallframe.Convert<Gray, Byte>(); //获取灰度图像gray._EqualizeHist(); //均衡化// 开始进行检测MCvAvgComp[][] faceDetected = gray.DetectHaarCascade( cascade, 1.1, 2, Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(30, 30));int count = 0;// 检测到的目标显示到图像上foreach (var item in faceDetected[0]){ count++; image.Draw(item.rect, new Bgr(Color.Red), 3);}model.Frame = image;model.HeadCount = count;
    (2) 使用SVM算法进行人头检测。
    利用前面获取的样本数据,利用EmguCV中HOGDescriptor对象进行目标检测。其主要过程为(1)提取Hog特征,(2)调用其DetectMultiScale方法获取检测目标,其主要代码如下:
    Rectangle roiRect = new Rectangle(144, 206, 406, 360);Rectangle[] rectangle = Hog.DetectMultiScale(image);if (rectangle.Length > 0){ Rectangle temp = new Rectangle(); bool pushFlag; foreach (var item in rectangle) { image.Draw(item, new Bgr(Color.Red), 3); }}
    4.6 系统效果演示系统主窗体如图4-15所示:

    系统未开启视频检测时运行界面如图4-16所示:

    系统开启AdaBoost检测算法如图4-17所示:

    系统开启SVM检测算法如图4-18所示:

    系统添加视频源如图4-19所示:

    系统删除视频源如图4-20所示:

    4.7 本章小结本章从系统整体需求出发,得出系统所需的功能,进而设计出各个模块,然后再是各个类来实现各个模块,以及各个类模块的主要实现代码最终实现了满足需求门禁视频监控系统。在进行人头检测中,分别采用了AdaBoost人头检测算法和SVM人头检测算法,并对两个算法进行了比较,在下章会详细介绍两个算法之间的区别。在本章第6小结,还进行了系统效果演示,对门禁视频监控系统了更加深刻的认识。
    第5章 实验结果对比分析5.1 AdaBoost和SVM算法效果对比采用AdaBoost检测算法,针对第一组测试视频时,其测试结果如图5-1所示,采用SVM检测算法,针对第一组图像,其检测结果如图5-2所示,两幅图片一对比,可以很明显的看出,在监控图像不是很清晰时,由于周边环境不是很明亮时,AdaBoost检测算法存在很大的错误检测。使用SVM检测能够准确检测出人头。但在光线明亮的环境中,如图5-3所示,在光线明亮下AdaBoost检测结果,图5-4在光线明亮下SVM检测结果,很显然AdaBoost也存在很大的错误检测,而SVM虽然检测出了人头,但是也出现了检测数量不对,存在少检测情况。



    AdaBoost检测结果
    SVM检测结果








    AdaBoost检测结果
    SVM检测结果







    通过上面几幅图片可以看出,AdaBoost算法的检测正确结果没有SVM算法检测正确结果高原因在于:

    虽然本次使用了2000余张样本图像,但还是样本图像过少,使得建立出的样本数据过少,在后面进行特征提取时,不能全面获取所有数据,导致检测结果不准确,从而影响检测质量本次选取的特征样本正样本和负样本数量几乎一样多,导致加大了被误判样本的权值,在训练过程中,每次迭代都会对那些分类错误的样本进行加权,但多个这样的样本多次分类错误过后,他们的权重过大,进行左右误差的计算和分类器的挑选,使分类器的精度下降,出现典型的“退化问题”[12]。
    5.2 结论分析本系统采用了AdaBoost人头检测算法和SVM人头检测算法,显然基于SVM算法的人头检测比简单的AdaBoost人头检测算法精度要高,SVM算法略占优势。虽然在AdaBoost检测算法上出现很多不足,但为后期的算法优化作出铺垫。
    总 结论文是在分析了人头检测算法的基础上完成的,主要任务是利用监控摄像头获取监控视频,然后利用图像处理的相关算法,特别事人头检测算法的是实现,以及利用检测算法完成人头目标检测。通过阅读大量文献,研究与实验,选择了基于Haar特征点的AdaBoost算法和基于Hog特征点的SVM算法来实现人头的检测,同时对AdaBoost算法和SVM算法作了对比,具体有:

    论文详细论述了基于统计学的图像处理技术,尤其是基于Haar特征点的AdaBoost和基于Hog特征点的SVM算法的原理和实现。从理论上说明了AdaBoost和SVM人头检测算法的可行性和正确性,并在系统实现了部分,利用C#和EmguCV对着两种算法进行了实现。但根据随后的结果分析,可以看出SVM算法的精度是基本令人满意的,SVM人头检测算法比AdaBoost人头检测算法更加出色。采用模块化对系统进行设计,使得实现和维护变得更加轻松。另外,采用面向对象的实现方式,把数据和方法实现分离开来,是程序封装更加彻底,减少了开发中不必要的麻烦。增加了不同算法之间的对比。使得可以更加清晰明了地看出各个算法之间的差异以及优缺点。从不同算法的人头检测效果中也可以看出自己对此程序将来升级维护的方向。
    但是,这里仍然有许多需要完善和解决的问题:

    检测结果精确度问题,不管是AdaBoost人头检测还是SVM人头检测,图像中的人头检测结果都存在漏检,需要对特征点数据加强筛选。使用GPU加速提高算法效率:采用SVM检测算法时,算法占用大量CPU和内存,需要进行更精确的优化。界面的优化,界面仍存在许多很多需要优化的地方结合门禁卡,实现人员进出门禁时,自动比较刷卡次数与出入人员数量是否一致,是否有人员未刷卡,并及时作出相应的判断。系统在安全性方面还需要进一步的加强。为用户提供可靠的保障
    参考文献[1] 陈东伟,韩娜.嵌入式数据库在基于多核处理器的视频监控中的应用〔J〕.郑州大学2007(4):52-55.
    [2] 杨富国.Visua1C++程序开发案例解析[M].北京:清华大学出版社;北京交通大学出版社2006.
    [3] 王华. 智能视频监控系统设计与实现. 山东大学2013
    [4] 刘瑞祯.OpenCV教程基础篇[M].北京:北京航空航天大学出版社 2007
    [5] http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/template_matching/template_matching.html?highlight=matchtemplate
    [6] 黄佳.基于OPENCV的计算机视觉技术研究.上海.华中理工大学
    [7] HatemH, Beiji Z, Majeed R, et al. Head Pose Estimation Based On Detecting FacialFeatures[J]. 2015.
    [8] HaritaogluI,Harwood D,Davis L. UW4: real-time surveillance of people and theiractivities[J]H. IEEE Trans PaRem Analysis and Machine Intlligence,2000,22(8):809—830
    [9] 郑金荣.视频监控系统中运动目标跟踪算法的研究[D].硕士学位论文,江南大学,2009
    [10] 徐振兴.基于机器视觉的行人检测和跟踪技术研宄[D].硕士学位论文,浙江理工大学,2012
    [11] 牛胜石. 基于AdaBoost 和 SVM 的人头检测[D].硕士学位论文,中南民族大学,2010
    [12] 廖红文,周德龙.AdaBoost及其改进算法综述[J].浙江工业大学.2012(21)
    [13] https://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker.aspx
    [14] 胡铮.面向视频监控的目标检测和跟踪技术.[D].浙江大学.2011:10~12
    致 谢光阴似箭,四年的本科生学习生活很快就要结束了。在西南科技大学的这四年里,我周围发生了很大的变化,最重要的是我四年前制定的目标实现了,四年让我收获颇丰。
    本论文的工作是在我的导师的悉心指导下完成的。在本课题的研究以及论文的撰写的过程中,老师给与我精心的指导;在生活中,老师也给与我了无微不至的关怀。正是这些指导和关怀使得我在本科生的学习期间取得了很大的进步。老师严谨的治学态度、科学的工作方法以及朴实的生活作风对我有着深远的影响。在此,向老师表达衷心的谢意。
    前几届的师兄对于我的工作学习以及论文都提出了许多宝贵的意见,对于论文的内容以及存在的问题提供了许多指导以及帮助。同时感谢所有在我学习期间教导过我的老师,谢谢你们在我求学的过程中给与的关怀和帮助,你们的一言一行都将成为我人生未来的宝贵财富。
    在课题研究以及论文的撰写过程中,许多同学对我论文中的研究工作也提供了热情的帮助,在此向他们也表达我的感激之情。最后特别感谢我的父母,他们一直在我的身边默默地鼓励、关心、支持我,是他们的理解和支持使能够专注于研究和学习,顺利完成我的学业。
    1 评论 42 下载 2018-09-28 22:21:45 下载需要20点积分
  • 基于Arduino和Java实现的教室桌面控制系统

    摘要物理实验室类的专业教室目前使用状况十分混乱,为了改变这一现状,我们提出并设计了一个专业教室桌面控制系统。这个系统主要由客户端和服务端两大部分构成。客户端又可以分为门禁机、通信机、桌面机和读卡机四部分。门禁机负责控制门禁和分配座位;通信机负责中转服务器和桌面机之间的消息;桌面机负责控制桌面电源的通断;而读卡机则负责获取学生卡号并传递给服务器。服务端分为消息响应程序和后台管理软件两个部分。消息响应程序开启Socket在服务端进行消息监听,处理不同类型的消息并返回相应的结果。而后台管理软件则提供教室管理、课程管理、使用记录查询、临时开放和卡号登记五大功能。管理员可以通过后台管理软件对系统的数据进行查询和更新,让系统可以顺利运行。在这个专业教室桌面控制系统的帮助下,可以有效地改变目前专业教室混乱的使用现状。
    关键词:嵌入式开发;教室管理;读卡器;服务器;后台管理软件;
    AbstractUsage oflaboratories like physical laboratory is in chaos today. In order to change the condition, we desined a desktop control system for theselaboratories. This system con- sists of clients andserver two parts. Clients have four small systems, called entrance guard system, communication system, desktop system and card reading system.Entrance guard system is designed to control the door and assignseats to students. Communication system id designedto pass socket messages between server and desktop systems. Desktop system is designed to control the power of each seat. And card reading system isdesigned to get the card number which is need by server. Server also has twoparts. One called message deal promgram, whichlistens on port 4321 to handle different socket messages. Another called server management platform, providing classroom manage, lecturemanage, usage search, temp open and card binding five function, isdesigned for manager to search and update data for system.With this system, laboratories will no longer be in chaos.
    Keywords: Arduino;Laboratory Management; RFID; Socket; Management Platform;
    第1章 项目背景1.1 背景与意义专业课程不同于通识课程,往往需要配合实验学习一些特定的知识。这些课程就需要在专业教室中进行。在专业教室中,会提供一些诸如示波器之类的设备,用于学生进行实践操作。但由于资源的限制,设备的数量往往不足以让所有学生同时使用,通常只能按照课程分批次地进行使用。
    但是,并不是所有学生都会按照课程的时间到专业教室进行学习。由于各种各样的原因,有些学生会在没有课程的时候来到专业教室,占用教室中的资源。当然,还有许许多多类似的情况导致了目前专业教室混乱的使用状况。
    想象一下下面的情景:一个学生匆匆赶到教室准备上课,却发现已经没有空余的设备可以使用了,只能在一旁干看着;有的学生遇到这种情况则会去别的教室寻找空位,从而可能导致另外一个学生没有设备可用;有的学生下课后仍然不离开教室,继续占用本应属于别的学生的资源;有的学生人离开了,设备却仍然开启着;教师看着一台台损坏的设备,却无从知道该设备最近的使用情况……
    这种混乱的现状是我们所要改变的,我们希望专业教室能够变得有序并且是易于管理的。我们期望中的专业教室应该是这样的:上课的学生可以顺利地到指定的位置上上课,设备在学生离开后会自动断开电源,教师能够随时知道当前教室的使用情况,并且能够查看历史记录……
    为了实现这样一个有序的专业教室,我们需要一个专业教室桌面控制系统。
    1.2 与相似产品的对比在浙大紫金港的计算机机房里,目前已经有一个类似的系统在使用了。要前往机房上机的同学,只要在入口处刷卡即可获知自己可以上机的机位,然后就可以前往指定座位上机。学生上机结束后,在出口再次刷卡即可离开。
    我们所要实现的专业教室桌面控制系统与这一系统有点类似。两个系统都需要让学生刷卡获取上机位置,然后前往指定位置上机,但是又有一些差别。
    首先,我们所要实现的系统控制的是物理实验室类型的教室。计算机机房本身就铺设好了计算机和网线,可以直接对设备进行控制。而我们所要实现的桌面控制系统没有现成控制设备可以使用,需要自己另外铺设系统对桌面电源进行控制。因此采用嵌入式设备而不是计算机进行控制是较好的选择。
    学生前往机房上机,如果他们离开的时候不关机或者不刷卡就离开的话,系统会根据时间对他们的校园卡进行扣费操作。而我们的系统没有扣费的权限,因此,为了能够实现学生离开就及时断开电源的机制,我们需要一种不一样的机制。考虑到酒店房间的供电机制,我们决定采用类似的机制,学生只有把校园卡持续地放置在读卡器上才能让桌面持续供电。
    更多细节上的设计和实现方案,将会在第2章和第3章中详细叙述。
    1.3 全文概述接下来,在第2章中我会讲解一下整个系统是如何设计的,各个部分的功能分别是什么以及各个部分之间是如何互相作用从而实现整个系统的功能的。另外,我还会简单描述下自己负责完成的内容。在第3章中我则会详细介绍一下我负责的内容。包括数据库的怎样设计的,Socket消息是如何定义的,服务器的工作逻辑,后台管理软件具体功能的实现以及作为客户端的各个系统的工作逻辑。在第4章中,我会总结一下整个系统的设计及实现,并且对系统目前的不足之处提出一些后续改进建议。
    2.1 系统整体设计方案整个专业教室桌面控制系统可以分为客户端和服务器两大部分。客户端主要让学生使用,均为嵌入式设备,包含了门禁机、通信机、读卡机和桌面机。设备均采用了 Arduino芯片作为控制核心,操作相应的模块实现所需的功能。服务器则主要让管理员使用,由请求响应程序和后台管理软件两部份构成。
    服务器与门禁机、通信机、读卡机之间通过网线连线,服务端开启Socket进行监听,而这3个客户端则在启动时主动连接到服务器上。桌面机与其余客户端不同,不通过局域网与服务器相连,而是通过485串口与通信机相连,通过通信机与服务器进行消息的传递。

    门禁机需要安装在每一个专业教室的门口,与服务器直接通过局域网相连,负责座位的分配和控制门锁功能。学生可以通过在门禁机上刷卡获取到座位号或者不能进入的理由。

    桌面机安装在教室内的每一个座位上,控制座位电源的通断。学生在门禁机上分配到座位后,即可前往对应的座位,并把校园卡放置在座位对应的桌面机上,这样桌面机就会控制座位持续供电,直到所允许的时间用尽或者学生主动离开。同时,桌面机还提供延时断电机制,在校园卡离开读卡范围后继续供电5分钟,便于学生离开临时处理一些事情。

    不同于门禁机,桌面机不直接与服务器通过局域网相连。为了便于布线和提高传输效率,桌面机和服务器之间的消息通过通信机进行中转。通信机每个教室里都会架设一个,一方面与服务器通过局域网相连,一方面与教室内的桌面机通过485串口连接在一起。当通信机收到来自任何一边的消息时,立刻把消息转发给另一边。

    读卡机比较特殊,仅是为了读取学生卡号而设计。读卡器用于管理员使用,与后台管理软件的卡号登记功能配合使用,目的是为了获取到学生卡号,并在卡号和学号之间建立对应关系。
    服务器分为请求响应程序和后台管理软件两个部份。请求相应程序根据从客户端发来的请求类型返回对应的结果。而后台管理软件则是为了便于管理人员对系统进行管理而设计。管理员可以通过后台管理软件对系统内的教室、课程进行管理。同时管理员还可以根据所需的条件查询特定的使用记录,便于对教室的使用现状进行监管。另外,管理员还有临时开放的权限,可以根据当前教室的使用状态分配座位给没有课程的学生使用。最后,还有卡号登记功能,用于在学生卡号和学号之间建议对应关系。
    2.2 采用的技术2.2.1 ArduinoArduino,是一个开源的单芯片微电脑,能够从各种各样的传感器接收数据并控制各种外接设备做出反应。Arduino具有使用类似Java、 C语言的Processing/Wiring 开发环境。[1]
    2.2.2 射频识别模块系统所需的读卡器是一种通过射频识别技术,能够阅读校园卡数据的设备。开发所采用的射频识别模块是以MFRC522芯片为核心芯片的。[2]

    2.2.3 LCD模块系统的门禁机需要有一个LCD屏幕可以显示学生分配到的座位号或者不能进入的理由。Arduino可以通过SPI方式驱动128*64的液晶屏模块,从而完成所需功能。[3]
    2.2.4 继电器门禁机控制门的开关,桌面机控制电源的通断都需要通过控制继电器来完成。继电器模块只要控制其中一个引脚电平的高低,即可以实现对电磁铁的控制。[4]
    2.2.5 MVC设计模式服务端程序我则采用了MVC的设计模式[51。将模型、视图和控制器分离开来,模型负责数据的计算,视图负责内容的呈现,而控制器则将这两者联合在一起。另外服务端程序还用到了单例模式和大量的回调机制。
    2.2.6 Socket编程在我们的系统中,客户端和服务端需要进行双向通信。服务端开启ServerSocket 监听某个端口是否有连接请求,客户端向服务端发出连接请求后,服务端会向客户端发回接受消息。这样,一个连接就建立起来了。服务端和客户端都可以通过Send,Write等方法与对方通信。[6]
    2.2.7 Swing组件系统包含有一个后台管理软件,因此需要绘制一些UI组件。Java的Swing组件用户界面元素要比AWT更加丰富,而且对低层平台依赖比较少,我选择了Swing组件作为后台管理软件绘制的组件。[7]
    2.3 我的工作我负责整个系统程序的实现,包括采用C语言编写的门禁机、通信机、桌面机和读卡机,以及采用Java编写的服务端程序。其中,服务端程序又包括了消息响应程序和后台管理软件两个部分。
    门禁机、通信机、桌面机和读卡机虽然各自功能不同,但在实现上还是有很多共同之处的。主要需要实现的内容包括读取校园卡、与服务器进行Socket通信、通过 485串口进行数据传输、控制屏幕显示特定的内容、控制电源通断以及各自独立的逻辑。
    服务端消息响应程序则需要根据接收到的不同类型的Socket消息,计算出对应的结果并且返回给请求的客户端。而后台管理软件则需要实现教室管理、课程管理、使用记录查询、临时开放和卡号登记这五大功能。
    第3章 在项目中负责的具体工作整个系统的设计和硬件部分的内容导师已经完成了,我所负责的是整个系统程序的实现。其中所实现的程序又可以分为两大部分。第一部分是用Java实现的服务端程序,用于响应客户端的请求以及进行后台管理。第二部分是用C实现的嵌入式程序,包括了以Arduino芯片为控制核心的门禁机、通信机、桌面机和读卡机。
    3.1 服务端服务端主要分为三个部分。第一个部分是请求响应程序,用于响应Arduino系统的请求。第二个部分是后台管理程序,便于管理员对教室、课程、学生、使用记录等信息进行查询管理。第三个部分则是数据库,用于储存所有服务端数据。
    请求响应程序总共会收到A、B、E三种类型的请求消息,A.B类型的请求会根据具体内容返回C或D类型的响应消息。而E类型的请求则会调用后台管理程序的卡号登记功能。
    后台管理程序会提供教室管理、课程管理、使用记录查询、临时开放和卡号登记5种功能,用于管理数据库中classroom、lecture. student、history、 tempOpen和card这6张表中的内容。

    3.1.1 请求响应程序请求响应程序运行后就开启一个ServerSocket,在4321端口开始监听Socket连接。当程序收到Socket连接请求后,会开启一个新的线程处理这个Socket连接。 Socket连接会来自三种途径―门禁机、通信机和读卡机。因为读卡机只有一个,而每个教室也只有一个门禁机和一个通信机,因此不会有大多的连接。基于这个考虑,再加上客户端消息在实际使用中会比较集中,所以我让Socket在连接建立后保持长连接,直到客户端主动断开连接。当然,这样还可以用于服务端主动推送消息到客户端,因为服务端无法主动连接到客户端上去(不过这个功能目前没有用到,可以便于后期扩展)。
    在Socket连接建立后,接下来就可以发送和接收消息了。不过在处理具体消息之前,我们需要先了解一下消息的格式。
    3.1.1.1 消息格式在我们的系统中,目前仅定义了A-E五种类型的消息(包含请求和响应消息), 而且每条消息所携带的信息也十分简单。这样自然没有必要让消息像HTTP请求那么复杂,只要设计一种简单实用的格式就可以了。
    首先,用一个$符号表示消息的起始符,在目前的设计中不是必要的,但便于后续开发的时候实现发送非请求类消息的需求。
    下一步,我们需要从一条消息中得出消息的来源。每一条Socket消息都有一个独立的币地址,根据这个ip地址我们可以判断出消息来自哪个教室(classroom表中有对应记录),但是却无法判断出消息来自教室内的哪个客户端。来自不同客户端的消息,即使类型相同,处理逻辑也是不一样的。因此,我们需要知道消息具体是来自哪个客户端的。为了表示足够多的来源,考虑分配3个字符的空间给来源属性。其中服务器用SRV表示,门禁机用MEN表示,读卡机用ADD表示,桌面机则用座位号对应的3位数字表示(如座位1则用001表示)。
    与消息来源标识符类似,我们同样需要一个消息目标的标识符,同样也分配3个字符,表示方法也和消息来源保持一致。
    接下来,我们还需要一个字符来表示消息的类型。因为消息的类型不多,一个字符就足够了。目前消息有5种类型。A类型表示请求进入或者通电的消息。B类型表示学生离开的消息。C类型表示服务器返回的允许进入或者通电的消息。D类型表示服务器返回的不允许进入或者通电的消息。E类型表示登记卡号的消息。
    最后,后面跟上所要发送的具体数据。当消息来自客户端的时候,数据都是学生的卡号信息。当消息来自服务器的时候,数据则可能是要显示在门禁机上的字符串,也可能是桌面机允许使用的分钟数。

    3.1.1.2 请求的总体处理逻辑客户端要向服务端发送请求,必须要先和服务端建立Socket连接。当服务端接收到客户端的连接请求时,会新建一个线程处理这个Socket连接。
    因为一个Socket连接的ip是固定的,而每个教室门禁机和通信机的币在 classroom表中又有记录,所以可以首先计算出这个连接来自哪个教室。接下来则不断尝试从这个连接的输入流中读取消息,直到客户端断开连接。
    当读取到一条具体的请求消息后,因为每条客户端请求带上的具体数据都是学生的卡号,所以可以通过查询card表计算出学生的学号。如果没有查询到学号,则直接返回D类型消息,带上提示学生前往管理员处登记卡号的信息。在获取到学生卡号的基础上,继续进行后续处理。
    接下来对消息的类型和来源进行判断,分别进入对应的处理逻辑。不同客户端不同来源消息的具体逻辑将在接下来的小节中详细叙述。下图为总体处理逻辑的流程图。

    3.1.1.3 门禁机请求处理逻辑门禁机只有在学生刷卡进入教室的时候会发出A类型的请求。前面已经提过,当服务器接收到Socket连接的时候,即可根据连接的ip地址计算出消息来自哪个教室。接着,服务器会首先根据获取到的卡号查询学生的学号,如果没有查询到学号,则直接返回D类型消息,以及让学生前往管理员处登记卡号的提示信息。
    获取到了学生的学号后,我们首先需要知道学生是否有权限进入此教室。学生要拥有进入教室的权限有两种方法,一是在当前时间有在此教室上课的课程,二是被管理员临时授予了权限。根据可能性的差别,我们优先对学生是否拥有课程进行查询,在没有课程的情况下,再去查询学生是否被临时授权。只要学生符合任何一种情况,即可允许学生进入教室。否则,则返回D类型消息。
    要知道学生在当前教室当前时间是否拥有课程,需要同时查询lecture表和 student表。从lecture表我们可以查询到此时此刻在此教室上课的课程,然后再根据获取到的lid到student表查询该学生是否在该课程的学生列表中。
    查看学生是否被临时授权就比较简单了,只要查询tempOpen表即可。
    在发现学生拥有进入权限后,接下来,我们需要分配一个位置给该学生。位置分配机制是这样的:首先查询history表判断是否有已经分配的位置,有则直接返回座位号。如果没有则重新进行分配。
    重新分配座位的时候,会从所有剩余座位里分配一个座位号最小的给该学生使用。在实现这一功能的时候,需要根据classroom表中教室的座位总数和history表中所有已经分配的位置列表进行计算。计算出结果后,返回C类型消息和分配的座位号。当然也有可能出现没有座位的情况,这种情况下也则需要返回D类型的消息,并带上位置已满的提示信息。

    3.1.1.4 通信机/桌面机请求处理逻辑通信机本身不会主动发出任何请求,所有发往服务器的消息均是转发自桌面机的。所以响应通信机的请求本质上就是响应桌面机的请求。
    当收到桌面机A类型的消息时,可以知道学生的学号和来自哪个座位。程序通过查询history表判断该位置是否已经由服务器分配给了该学生,如果是,则计算出可以上机的最大时间并且返回。如果不是,则返回D类型消息。
    当收到桌面机B类型的消息时,则更新history表中该学生本次使用记录的endTime属性,将离开时间从最后的时间点更新到当前时间点。

    3.1.1.5 读卡机请求处理逻辑读卡机只发送E类型的消息,并且读卡机的存在只是为了获取学生校园卡的卡号。当收到E类型消息的时候,服务器会弹出登记卡号的窗口,让管理员输入学生学号,从而完成卡号和学号的绑定。具体处理逻辑将在后台管理软件部分的卡号登记功能处说明。
    3.1.2 后台管理程序后台管理程序主要用于管理员对服务端数据进行查询和管理,程序和请求响应程序同时运行,各自处于一个线程中。后台管理程序提供了教室管理、课程管理、使用查询、临时开放和卡号登记五种功能。
    3.1.2.1 教室管理教室管理功能可以让管理员增加新教室,也可以让其对现有教室进行修改或者删除操作。每个教室都有4个属性用于操作,包括教室名、总座位数、门禁ip和通信机ip。教室名不用多说,自然是为了识别。总座位数跟座位分配机制息息相关,必须要和事实上部署在教室内的桌面机数目保持一致。门禁ip和通信机ip则是为了服务器在收到Socket消息的时候能够准确判断出消息来自哪个教室。因此,后面3个属性十分重要,需要管理员认真确认。
    因为程序采用了MVC的设计模式,所以功能在实现上分为三个部分,用于和数据库进行交互的Model部分,用于绘制界面的view部分,以及一个控制两者协同工作的Controller类。
    Model部分用于从数据库获取和管理界面所需的各种数据。教室管理功能的 Model自己内部维持了一个列表,储存所有教室的详细信息。对外则提供了一系列接口,比如返回所有教室名称用于视图绘制教室列表;增加一个教室的接口则对应到视图上的增加功能,同样还有删除和修改;还有一些检测的函数,用于Controller判断从视图所获取的数据是否符合格式。
    View部分负责绘制功能所需的界面。主要提供了一个展示教室名称的列表,点击后即可查看该教室的具体信息。另外提供了增加、删除和修改三个按钮,点击后界面从普通模式进入可编辑模式,可以对教室的4各属性进行编辑。修改完毕后界面回到正常模式,并传递本次修改的数据到Controller0Controller部分则负责在Model和View之间进行数据的传递。View上的每一个动作都会反馈给Controller, Controller会决定如何响应这个动作,并根据需求从Model获取相应的数据传入View。比如,当View上的修改按钮被点击时,Controller 会被View告知这一动作,而Controller并不会每次都让View针对这次事件作出变化。如果当前View已经处于编辑模式了,Controller会让View忽略本次点击动作。否则,则从Model中获取当前列表中所选教室的信息传递给View,并且让View进入可编辑模式。

    3.1.2.2 课程管理课程管理功能和教室管理功能十分类似,只不过属性有所区别。课程拥有5个属性,包括课程名称、上课教室、课程开始时间、课程结束时间和课程星期。
    课程还有一项区别于前5个属性的属性,即是上课的学生。每一门课程都会对应若干上课的学生,因此学生同样需要增加和删除功能。因为目前学生信息只包含了一个学号信息,所以没有提供修改功能。不过有一个导入功能用于更快地导入学生的信息。目前这一功能还比较简陋,只能够从一个txt文件中导入固定格式的学生列表。
    课程管理的MVC处理逻辑和教室管理的基本一致。Model部分内部维持一个列表,储存所有课程的信息,提供相应的接口用于Controller调用。而View所绘制的界面和教室管理的界面相比,多了一个列表用于查看课程的学生,同样还有增加、删除和导入三个按钮用于管理学生列表。增加学生按钮会弹出一个新的窗口,用于输入学生的信息。而导入按钮则会弹出一个文件选择框,用于选择包含学生信息的txt 文件。
    3.1.2.3 使用查询使用查询功能可以让管理员按需求查询特定的使用记录。管理员可以填入学号、教室、座位号、起始时间和结束时间查询所需的信息。除了时间和教室必须指定外,学号和座位号可以不做限定。确定条件后,即可点击查询按钮进行查询,结果会显示在一个新的窗口中。
    每一条记录包括学号、教室、座位号、开始时间、使用时长这5个属性,点击相应的属性名即可按该属性就结果进行排序。
    使用查询功能在实现上分成了两个不同的View,一个用于输入查询的条件,一个用于展示查询的结果。两个View之间不产生直接交流,同样通过Controller进行管理。Controller从查询窗口获取到限定条件后,即根据条件从Model获取结果,并把结果传递给查询结果窗口用于显示。查询结果窗口使用了JTable组件进行显示,为了更加清晰地显示数据,我给奇数行和偶数行设定了不同的颜色,并针对每个属性设定了不同的显示宽度。同时,每个属性都设置了属性头,拖拽属性头可以调整属性的顺序,而点击则可以实现将结果按该属性进行重排。
    3.1.2.4 临时开放临时开放功能实现了将空余的座位临时分配给学生使用的可能。没有课程的学生可以找管理员提出需求,管理员可以根据当前使用状况决定是否临时开放座位给该学生,开放多长时间,开放在哪个教室。
    3.1.2.5 卡号登记卡号登记功能的作用是将卡号与学号进行绑定。因为读卡器只能读取到校园卡的卡号,而在导入课程学生的时候,一般教师或管理员只可能有学生学号信息,所以需要将这两者进行关联,从而让整个专业教室桌面控制系统得以正常运行。
    卡号登记功能与之前几个功能又有所不同。之前的功能不需要客户端的合作,仅是对服务器数据库数据的查询与更新操作,而卡号登记功能需要读卡器的配合才行。卡号本身并不会书写在校园卡上,管理员无法自己从校园卡获取卡号,因此需要依靠读卡器从校园卡读取卡号,然后手动输入学生学号才能顺利完成卡号与学号绑定的操作。
    服务器在收到读卡器发出的E类型消息时,会首先检测卡号登记界面是否已经显示。如果没有显示,则首先显示该窗口。然后,界面上的卡号数据会更新为消息中所携带的卡号。这时候,管理员再手动输入校园卡上的学号即可完成卡号的登记。
    3.1.3 数据库的设计程序采用了mysql数据库[[8],针对所需功能定义了6张表,分别为classroom 表、lecture表、student表、history表、tempOpen表和card表。
    3.1.3.1 classroom表classroom表记录了每一个教室的详细信息。首先一个教室一般都有一个名字,方便学生识别,因此需要有一个name属性。然后,每一个教室内座位数量是必须要记录的,这样服务器程序才知道有多少座位可以用于分配,seats属性记录的就是座位数。接下来,guardlp和forwardlp分别表示的是教室门禁机和通信机的ip地址。通过这两个ip地址,服务器程序可以知道socket消息是从哪个教室来的。最后,虽然 name属性一般来说是独一无二的,但是为了减少教室作为外键时的空间损耗和提高查询效率,用一个mt类型的cid来作为唯一标识符还是有必要的。

    3.1.3.2 lecture表lecture表记录了每一门课程的详细信息。一门课程不外乎这样几个重要的信息:课程名称、上课教室、上课时间、上课学生。前三个属性和一门课程一般是一一对应的,而最后一个属性上课学生却使一对多的。所以我把前三个属性记录在了表中,而把上课学生分离出来另外建了一张student表。课程名称对应name,上课教室对应cid(关联classroom表中的cid),而上课时间则分解成了三个属性,表示课程开始时间的startTime,表示课程结束时间的endTime以及表示在一周中哪一天上课的 weekday。最后,自然少不了唯一识别符lid。

    3.1.3.3 student表student表记录了课程上课学生的信息,把每一个学生和一门课程关联起来。因此表的构成也就很简单了,一个sid表示学生的学号,一个lid表示课程号。

    3.1.3.4 history表history表记录了每一个位置的上机记录,每一条记录包含学生学号、教室号、座位号、开始时间和结束时间这几个信息。

    3.1.3.5 tempOpen表tempOpen表记录了临时开放位置的信息。除了正常有课的学生会来上机外,一些没有课程的学生可能也会来临时上机。只要教室座位有空余,他们就可以找管理员临时开放位置。这样,临时开放表就可以记录这些开放信息。临时开放信息类似于简化版的课程信息,因为作用是一次性的。因此只需要记录学生学号、教室号、开始时间和结束时间就可以了,分别对应了sid, cid, startTime和endTime。

    3.1.3.6 card表card表用于存储学生学号与卡号的对应关系。读卡器只能够读取到校园卡的卡号,而课程导入的学生信息都是学号。因此,我需要记录卡号和学号的对应关系,这样才能够根据得到的卡号去获取对应学生的其余信息。card表也十分简单,只需要两个属性,一个cardld表示卡号,一个sid表示学生学号。

    3.2 Arduino控制的系统Arduino控制的系统包括门禁机、通信机、桌面机和读卡机这4个部分。它们都是以一块Arduino nano的芯片作为控制核心,然后连接了一些所需要的设备。我所负责的就是在控制芯片上进行嵌入式编程,然后实现每一个小系统所需的功能。
    Arduino程序启动后,会首先调用setup()函数。这个函数只会在芯片启动或者重置的时候被调用一次,主要用来进行初始化变量、配置引脚和使用一些库等操作[9]。
    而loop()函数[10]则是像函数名一般被持续不断地调用,我们主要就是在这个函数里进行编程,实现所需逻辑。
    3.2.1门禁机门禁机会安装在每一个专业教室的门口。学生可以在门禁机上刷卡,然后可以在LCD屏幕上看到上机的座位号或者不能进入的理由。同时,如果可以进入,继电器会控制门锁短暂打开让学生进入,前往指定的座位上机。
    3.2.1.1硬件构成
    Arduino nano控制芯片LCD显示屏幕网卡读卡器红、绿LED灯以及蜂鸣器继电器
    3.2.1.2 程序逻辑门禁机启动后,除了必要的引脚配置逻辑,程序会在以下4个状态里不断循环。其中,起始状态为状态4。

    状态1:空闲状态。控制红绿LED灯交替闪烁,不断检查是否有校园卡进入读卡器的读卡范围。当读取到卡号的时候,控制蜂鸣器发出一声提示,表示已成功读取到卡号。然后把卡号通过Socket连接发送给服务器,并且进入状态2。状态2:等待服务器响应状态。等待服务器返回请求的结果,如果超过5秒钟没有收到响应,则跳转到状态4。在收到服务器返回的消息后,首先把返回的信息显示到LCD屏幕上,然后对消息类型进行判断。如果是C类消息,则控制继电器打开门锁,并进入状态3;如果是D类消息,则锁定门锁,控制蜂鸣器发出两声提示后进入状态1。状态3:锁定倒计时状态。控制绿色LED灯亮起,关闭红色LED灯。然后在5 秒倒计时结束后控制继电器锁定门锁,并且进入状态1。状态4:网络连接状态。控制蜂鸣器长鸣,红绿LED同时亮起。然后尝试连接或者重新连接到服务器。成功则关闭蜂鸣器,进入状态1。

    3.2.2 通信机每个教室中都会放置一个通信机。一方面,通信机通过局域网与服务器直接相连;另一方面,通信机与桌面机之间通过485网络互相连接。而通信机所做的事情也十分简单,即是在服务器和桌面机之间完成消息的传递。
    3.2.2.1 硬件构成
    Arduino nano控制芯片网卡RS485模块电源
    3.2.2.2 程序逻辑通信机的工作逻辑比较简单。在每一个循环中,程序都会检验是否有来自服务器或485网络的消息,如果有,则转发给另一方。
    3.2.3 桌面机桌面机会安装在教室内的每一个座位上。当有学生被分配到该位置后,只要把校园卡一直放置在读卡器上,座位就会持续供电。如果中途学生需要暂时离开,可以直接拿走校园卡,桌面机会提供5分钟的延时。在5分钟内,学生只要及时回来并把卡放置到读卡器上就可以恢复使用。当然,每一次上机都是有时限的,只要时限到了,桌面机会立即停止供电,不受延时机制限制。
    3.2.3.1 硬件构成
    Arduino nano控制芯片
    RS485模块
    读卡器
    红、绿LED灯以及蜂鸣器
    继电器电源

    3.2.3.2 程序逻辑桌面机启动后,程序会在以下5个状态里不断循环。其中,起始状态为状态1。

    状态1:空闲状态。控制红绿LED灯交替闪烁,不断检查是否有校园卡进入读卡器的读卡范围。当读取到卡号的时候,控制蜂鸣器发出一声提示,表示已成功读取到卡号。然后把卡号通过485串口发送给通信机,并且进入状态2。
    状态2:等待响应状态。等待通信机将服务器的响应结果返回,如果超过5秒钟没有收到响应,则跳转到状态5。在收到服务器返回的消息后,检测校园卡是否已经改变,如果校园卡已经改变则回到状态1。如果没有改变,则对返回结果进行判断。如果是C类型结果,则从消息中提取可以上机的总时间,然后通电,并进入状态3;如果是D类型结果,则断开电源,控制蜂鸣器提示后回到状态1。
    状态3:持续供电状态。控制绿色LED灯亮起,控制红色LED灯闪烁。在这个状态中,不断检测校园卡是否被拿走和上机时间是否要结束这两个事件的发生。当校园卡被拿走的时候,直接进入状态4。当时间临近结束的时候,控制蜂鸣器逐渐频繁地提示,直到正式结束。当上机时间结束后,断开电源,发送B类型消息给服务器并回到状态1。状态4:延时断电状态。关闭绿色LED灯,控制红色LED灯闪烁,进入5分钟断电倒计时。在计时结束前,如果原来的卡重新放回则状态3。计时结束后,断开电源,并发送B类型消息给服务器,然后回到状态1。状态5:出错状态。控制蜂鸣器长鸣,红绿LED同时亮起,提示管理人员此座位出现故障。

    3.2.4 读卡机读卡器的设计功能仅是获取卡号,然后传递给服务器。从而管理员可以通过输入学号实现和卡号的绑定。
    3.2.4.1 硬件构成
    Arduino nano控制芯片网卡读卡器红、绿LED灯以及蜂鸣器电源
    3.2.4.2 程序逻辑因为读卡器仅需要读到卡号并传递到服务器即可,所以逻辑也十分简单。与门禁机和桌面机的状态1类似,程序所做的就是不断检测是否有卡进入读卡器读卡范围。在读到卡后,发送E类型消息给服务器。如此不断循环即可。
    第4章 项目成果4.1 现有成果总结鉴于目前专业教室混乱的使用现状,我们有了设计一个专业教室桌面控制系统的需求。在借鉴了浙大紫金港机房系统的工作原理和酒店房间的供电模式后,我们设计了一个由服务器、门禁机、通信机、桌面机和读卡机构成的系统,并且在服务器上还设计了一个后台管理软件,便于对数据进行管理、查询和更新。
    4.1.1 Arduino设备成果整个系统中,门禁机、通信机、桌面机和读卡机均是由Arduino芯片作为控制核』臼的嵌入式设备。设备启动后,控制程序在几个状态之间不断循环,控制其余模块实现所需的功能。

    门禁机控制教室门禁,并负责分配教室的座位。桌面机负责控制座位的供电,保证学生离开后能够及时断电。通信机则负责在服务器和桌面机之间中转消息。而读卡机负责帮助管理人员获取学生卡号,用于绑定学生的卡号与学号。

    4.1.2 服务端程序成果服务器启动后就在4321端口对Socket消息进行监听,及时响应各种类型的请求,准确返回处理的结果。同时,服务器上的后台管理软件为管理员提供了教室管理、课程管理、使用记录查询、临时开放和卡号登记五大功能。管理员可以根据当前的情况及时调整后台数据,保证系统能够正常运行。当教室内设备出现故障时,也能够准确获取设备最近的使用情况,从而进行合理的调整。

    教室管理功能可以让管理员增加新教室,也可以让其对现有教室进行修改或者删除操作。每个教室都有4个属性用于操作,包括教室名、总座位数、门禁ip和通信机ip。

    课程管理功能除了可以对每门课程的课程名称、上课教室、课程开始时间、课程结束时间和课程星期几个属性进行管理外,还可以管理上课的学生。学生管理除了有增加和删除功能外,还有一个导入功能用于更快地导入学生的信息。

    使用查询功能可以让管理员按需求查询特定的使用记录。管理员可以填入学号、教室、座位号、起始时间和结束时间查询所需的信息。除了时间和教室必须指定外,学号和座位号可以不做限定。确定条件后,即可点击查询按钮进行查询,结果会显示在一个新的窗口中。每一条记录包括学号、教室、座位号、开始时间、使用时长这5个属性,点击相应的属性名即可按该属性就结果进行排序。

    临时开放功能实现了将空余的座位临时分配给学生使用的可能。没有课程的学生可以找管理员提出需求,管理员可以根据当前使用状况决定是否临时开放座位给该学生,开放多长时间,开放在哪个教室。

    卡号登记功能的作用是将卡号与学号进行绑定。因为读卡器只能读取到校园卡的卡号,而在导入课程学生的时候,一般教师或管理员只可能有学生学号信息,所以需要将这两者进行关联,从而让整个专业教室桌面控制系统得以正常运行。

    这样,整个系统的基本功能就完成了。在系统的帮助下,有课的学生能够顺利地前往教室上课,而没有课程的学生也可以在获得管理员授权后教室的使用权。同时,管理人员也不会再对教室的使用情况一头雾水。他们可以在任何时候根据需求查询指定的使用记录。
    4.2 后续改进方向虽然整个系统已经实现了所需的功能,但系统目前仍存在一些不足之处,也有较大的改进空间。
    目前每一门课程都只能设定一个上课时间,但是实际上一门课程可能会有好几个上课时间。这一问题可以通过把一门课程按上课时间分成多门课程来解决,但是这样一来操作起来就比较繁琐,管理人员需要多次导入课程学生,并且也不便于查看。因此,让一门课程可以拥有多个上课时间可以作为后续的一个改进方向。
    另外,系统的鲁棒性目前也不是很好。如果系统中的一个组成出现了故障,管理人员就必须立刻前往修复才能保证系统正常工作。在设计中,桌面机数量是最多的,因此它们在实际使用中也会存在较大故障的可能。假设有一个桌面机出现了故障,目前的系统是无法自动检测故障并停止分配该座位的。因此,后续改进中需要考虑增加故障处理功能,从而提高系统的鲁棒性。
    参考文献[1] Arduinointroduction. http://arduino.cc.
    [2] Mfrc522数据手册. http://www.nxp.com/documents/data_sheet/ MFRC522.pdf.
    [3] Lcd模块使用文档. http://www.dfrobot.com/wiki/index.php?title=3-wire_Serial_LCD_Module\_(Arduino\_Compatible)\ _(SKU:DFR0091).
    [4] 继电器模块说明. http://www.dfrobot.com/wiki/index.php/Relay_ Module_(Arduino_Compatible)_(SKU:_DFR0017).
    [5] Frank Buschmann, Regine Meunier, and Hans Rohnert. Pattern-Oriented Software Architecture. Wiley,1edition,1996.
    [6] Socket java api. https://developers.google.com/appengine/docs/java/sockets/.
    [7] Gary Cornell Cay S.Horstmann. JAVA核心技术. 机械工业出版社, 8 edition, 2008.
    [8] S.Sudarshan Abraham Silberschatz, Henry F. Korth. 数据库系统概念. 机械工业出版社, , 5 edition, 2006.
    [9] Arduino setup() function. http://arduino.cc/en/Reference/Setup.
    [10] Arduino loop() function. http://arduino.cc/en/Reference/Loop.
    致谢首先,诚挚地感谢导师在毕设期间对我的认真指导,让我可以在正确的方向上不断前行。每当我遇到困难的时候,总可以在导师处获取到关键的指点。另外,有了导师的监督,我才能按部就班地完成毕设的任务,不至于让拖延症耽误了进程。
    在完成毕设的过程中,实验室的研究生、博士生学长们也给了我很大的帮助。在他们的帮助下,我少走了很多弯路,能够更加快捷有效地获取所需的知识。
    感谢室友们在生活上对我的帮助,共同监督完成毕设论文,在遇到困难的时候互相鼓励,在解决难题的时候共享喜悦。
    最后,感谢女朋友在我做毕设期间的默默支持,感谢父母对我的默默关怀。
    1 评论 3 下载 2018-09-28 22:17:17 下载需要17点积分
  • 基于Android和Jsp的教务网服务端移动端的设计与实现

    摘 要本程序完成了从登录教务网(记住密码的功能),修改教务网密码,从教务网获取成绩信息,修改个人头像,从自己开发的服务器端拉取使用者的个人信息和查看通讯录与在数据库里存过信息的人实现聊天的功能。另外,自己开发的服务器端连接的数据库里的信息和教务网信息是不相匹配的,全部都是我制造的假数据。
    服务器端使用的开发工具有MyEclipse2014、Hibernate、Tomcat,服务器端的脚本语言使用的是JSP,连接的数据库是MySQL。
    客户端的使用的开发工具是Eclipse,实现UI界面的设计,和用户交互功能。
    关键词:教务网;移动端版;JSP开发的服务器端;MySql;聊天功能;
    AbstractCompleted the program from the login teaching network with persistent password, from educational network performance information, from their own development of server side pull the user’s personal information and the information saved in the database of people can realize the function of chat. People also can modify themselves passwords in educational networks and avatars. In addition, their development of the database server connection information and educational network information is not matching, all is I make false data.
    Server-side development tools used are MyEclipse2014, Hibernate, Tomcat, server-side scripting language is used JSP, connect the database is MySQL.
    The client’s use of development tools is the Eclipse, UI design, and user interaction function.
    Keywords:educational network; Mobile version; Server side of JSP development; MySql; Chat function;
    第一章 绪论第一节 引言由于我们学校一直都只有web端的教务网,我在使用的时候总会觉得有诸多
    不适。比如一页上的内容太过复杂,但是我在实际使用上有很多功能我根本就使用不到。另外由于手机屏幕的限制,出现在浏览器上打开的网页字太小,操作起来不方便的情况。在手机上浏览教务网网页还有一个问题就是会出现,就是想要回退的时候就非常不方便了。除非每一个页面上都有相互跳转的超链接,但是这样的界面看上去很乱,这又导致了第一个问题的出现。
    就目前来说,由于我们的社交环境、生活环境都相对局限,而像微信这样的线上聊天功能已经取代了短信成为了我们主要的保持社交和获取和交换信息的方式。但是在浏览器和微信之间相互切换也是一件相当恼人的事情。
    出于以上三个问题的考虑我设计了这个移动端版的教务网。简单地实现了聊天功能和我们常用到的查询成绩的功能。由于修改密码虽然不常用,但是我也作为一个必要的功能模块加入到软件当中,感觉这样会更加完整一些。同样地,查询个人信息(也就是“我”这一功能模块)和修改头像的功能都是出于同类软件(这里特指和微信比较)都具有的考虑上加入的功能模块,为了本款软件的完整性。
    最后想做一个移动端版的教务网其实是因为开学时老师在屏幕上演示了金黎明学长的作品,很羡慕,很想试试学长能做的我能不能做。
    第二节 课题描述本软件从首页进入到登入界面再进入到选择界面,选择界面会出现四个功能模块,分别是查看个人信息(指自己开发的数据库中)、修改个人在教务网上的密码、查询真实的成绩信息、和好友实现聊天的功能。其中查看个人信息的功能模块里面加上了修改头像的功能。现在进行简单的一一介绍。
    要实现登录、修改密码和查询成绩都需要先了解HTTP协议。我们浏览网页使用的就是HTTP协议,Http是一种通信协议,它允许将超文本标记语言文档从Web服务器传送到客户端的浏览器。
    当我们输入URL(UniformResource Location)后,浏览器会给服务器发送一个Request,当Web Server接到Request后会生成相应的Response,再发回给浏览器。浏览器解析Response(Body)中的HTML文档,就变成了我们看见的网页了。
    需要强调补充的是,HTTP协议是无状态的,所以同一个客户端的的这次请求和上次请求是没有对应关系的,也就是说,Web Server是不知道两次请求都来自同一个Client,因此Web程序引入了Session,Cookie来维护状态。当浏览器第一次访问某些服务器的时候,服务器会发现接收到的数据包没有携带SessionID,就会自动创建Session对象,并将创建一个保存有SessionID的cookie发送给浏览器。在这里SessionID其实就是Session对象的索引。浏览器下次再访问服务器时就会发送携带着这个SessionID的Cookie对象。
    这里我需要做的工作就是模仿浏览器的行为向Web Server发送请求,然后解析从Web Server收到的Response 中的Html文档,得到我需要的信息,再填充到我的android移动端的UI界面上。
    发送请求是我需要先知道浏览器向服务器发送的请求里面要包含哪些参数(一堆的key-value值),这就需要在浏览器发出请求时进行抓包,这里我使用的工具是WireShark,其实使用Chorme的F12开发者工具一样可以实现抓包。找到自己需要发送哪些参数。方便在后续的请求里面进行填写。
    为了方便我使用了在Android Studio中已经实现内嵌,但是在Eclipse中还需要导入包才能使用的Okhttp,来封装请求。解析从浏览器发送回来的HTML文档我使用了JSoup。
    另外由于教务网是需要登录的,所以在登陆之后的所有操作都是需要Cookie来维持状态的。Cookie时客户端技术,程序把每个用户的数据以Cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的Web资源时,就会带着各自的Cookie去,这里需要实现Cookie的存储和发送,Okhttp都提供了相应的函数。
    查看个人信息、修改头像、实时聊天、其实都是对数据库的增删改查的操作。在MyEclipse下的Web Project下,连接MySql开发的数据库,用Hibernate反向引擎生成和数据库表相对应的Java类,通过DAO类对象中的方法可以用Java语言直接对数据库进行增删改查。其结果都以字符串的形式显示在JSP脚本编写的页面上。Android端通过URL类,以及URL类的对象的openStream()方法,可以读取到网页上的信息,然后再通过Gson转化成对应的对象。这时候就可以实现对UI控件内容的填充。
    举例来说,在查看个人信息时,通过GET提交的参数取服务器端的数据库中进行数据查询。GET提交的数据放在URL之后,以‘?’分割,URL传输的参数间以‘&’相连。服务器端就是通过提取URL里的参数,作为WHERE语句里面的查询条件,进相连接的数据库中进行查询符合条件的列。同样地,修改头像就是对数据库进行一个先查询再修改的过程。
    第三节 国内外的研究国外对这方面的研究对这方面研究的资料比较少,因此不在我考虑的范围之内。国内网站上可以搜寻到的相关资料大多是关于方正教务网移动端登录功能的实现。有一定的参考价值。
    国内有一款APP叫做“超级课表”,可以获取到每个学校的课表信息,登录是也是和登录教务网差不多,需要输入学号和密码。同时可以在该平台上发布信息,加好友相互聊天。我写的这个程序与这款软件十分相似,“超级课表”获取每个学生的课程表应该也是找到学校对应教务网发送请求,实现聊天和发布信息是在自己开发的服务器端的数据库进行增删改的操作。
    第四节 论文组织结构本论文主要从以下方面安排及组织文章的结构:
    第一章绪论部分,首先对移动端版教务系统这一课题背景的行业背景及学术背景进行了基本的描述,在此基础上,对本课题系统的基本功能进行完整的表述。最后通过对当前国内外的应用研究场景及开发的意义上进行介绍。
    第二章系统研发相关理论及关键技术介绍部分,此部分介绍了移动端版的教务系统所使用到的各种理论与关键技术。
    第三章总体设计及架构部分,此部分通过对功能的详细分析与判断,把一套完整的系统进行划分,确定所要实现的多层环节和各环节的详细功能。
    第四章系统详细设计部分,此部分通过程序各部分的功能模块的功能介绍和详细说明以及开发工具的介绍还有实现这些功能需要的基础知识储备。从而实现移动端版的教务系统
    第五章系统实现及测试部分,此部分通过之前考察及分析登录、修改密码、修改头像、查看个人信息、查看成绩、实时聊天功能方面对实现这样一个系统进行设计与实现。
    第六章总结与展望部分,这部分内容总结了本次进行的android设计完成过程所学到的东西以及所获得的心得体会。
    第二章 关键技术及理论基础第一节 开发使用的关键技术1. Cookies的存储Okhttp提供了相应的CookieJar这个类来实现Cookies的自动存储与发
    送。当服务器端给客户端发送Cookie对象时调用saveFromResponse(),将Cookies存进我们自己创建的List对象当中。客户端向服务器发送请求时,会通过loadForRequest()的方法获取保存在list里面的Cookies,封装进请求头里发送给服务器端。
    由于在实现登录以后再访问服务器的其它页面时就没有必要重新获取Cookies了,所以我在第一次获取了Cookies以后就对其进行了持久化存储。
    在开发本系统一开始我就在Cookies上遇到了问题,查找bug好多天,所以在这里将Cookie放在第一。
    2. 异步任务的使用我所知道的在Android里想要使用异步有两种方法:一种是Handler,一种是Asynctask。在Android里,主线程也叫做UI线程,在其中主要实现的是对UI控件的一些操作。在Android开发中我们必须遵守这个单线程模型的规则。另外由于Android中的UI操作并不是线程安全的,并且这些操作都要在UI线程中执行。对服务器发送请求的功能需要在网络线程中进行。如果在UI线程中进行和网络相关的操作,网络操作属于耗时操作,UI线程超过5秒没有响应用于请求,就会引发Application Not RespondingException。Android4.0之后更是禁止了在UI线程中执行网络操作。因此在Android中实现异步操作就显得尤为重要。
    当然new Thread()重新去开辟线程也是可以的,但是由于我们无法完美保证多个线程之间的执行顺序,所以就更需要异步任务来支持。
    在使用Handler和Asynctask的过程中,我认为Handler在实现上是最为简单的,也是最容易理解的。就是让子线程给子线程发送消息,从子线程跳回到主线程,以便于进行UI操作。但是当进行多次连接网络的操作时,大量用Handler写出来的程序结构就会变得十分混乱了。因为要去找对应的Handler下的HandleMessage,使程序的可读性降低。
    而Asynctask类很好地把UI线程和子线程都封装在一起,写出来的程序结构清晰,并且在实现进度条上与Handler相比有着卓越的方便性。在doInBackground()实现较为耗时的网络操作。doInBackgroung()执行完以后执行onPostExecute(),所有的UI操作都可以放在这个函数里面执行。另外还有onPreExecute()是在doInBackground()之前被调用的,通常用于一些初始化操作,如:进度条的显示。publishProgress()和onProgressUpdate()配合使用实现进度条的更新。
    3. Hibernatehibernate是一个开源框架,它是对象关联关系映射的框架,它对JDBC做了轻量级的封装,而我们java程序员可以使用面向对象的思想来操纵数据库。hibernate核心接口;session:负责被持久化对象CRUD操作;sessionFactory:
    负责初始化hibernate,创建session对象;configuration:负责配置并启动;hibernate,创建SessionFactory;Transaction:负责事物相关的操作;Query和Criteria接口:负责执行各种数据库查询
    用myEclipse反向引擎工具来由数据库里的表生成实体类和hibernate文件。我们就可以通过DAO类中提供的函数对相连接的数据库进行增删改查的操作了。
    第二节 实现登陆教务系统和实时聊天的理论基础1. HTTP协议简介HTTP是一个属于应用层的面向对象的协议。HTTP允许传输任意类型的数据对象。是一个基于请求与响应模式的、无状态的、应用层的协议。
    支持C/S模式,当客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、POST。每种方法都规定了客户和服务器联系的类型。另外还具有无连接、无状态的特点。其中无连接是值:限制每次连接只处理一个请求。服务器处理完客户的请求,并受到客户的应答后,即断开连接,采用这种方式可以节省传输时间。无状态是指:HTTP协议是补状态协议,无状态是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,则它必须要进行重传,这样可能导致每次连接传送的数据量增大。
    2. 发送请求HTTP URL包含了用于查找某个资源的足够的信息。
    HTTP请求方法有多种。我只用到了GET和POST两种。其中GET是请求获取Request-URI所标识的资源。这里我用在了获取教务网默认首页和获取成绩以及获取修改密码的页面上。POST要求被请求的服务器接受附在请求后面的数据,常用于提交表单。
    3. 接收响应在接收和解释请求消息后,服务器会返回一个HTTP响应消息。 HTTP响应有三个部分组成:状态行、消息报头、响应正文。其中Cookies在消息报头,而传回来的HTML文档在响应正文里。
    接收到响应后浏览器再进行解析,就生成我们看到的网页页面了。
    第三章 总体设计及架构分析按照模式来划分可以将本系统分为两个部分。
    第一个部分是通过发送网络请求向教务网所在服务器发送请求,然后解析发回来的HTML文档。获取表格里面的数据。
    另一个部分就是实现实时聊天的功能,这个功能必须要自己开发服务器端,自己开发数据库,每发布一条聊天消息都相当于是往数据库里面存储了一条信息。聊天界面的刷新,就相当于从服务器里面不断地读取消息。
    本程序采用的是C/S架构的三层结构。客户端接收用户的请求,客户端向应用服务提出请求,应用服务从数据库服务中获得数据,应用服务将数据进行计算再将结果提交给客户端,客户端将结果呈现给用户。再三层结构当中,客户端并不参与运算,只是简单地接收用户的请求,显示最后的结果。由于三层结构中的客户端并不需要参与运算,所以对客户端的配置要求是比较低的。
    第四章 系统详细设计经过前一章系统架构分析后发现,由于本次设计的系统是一套不同设备间的多平台系统,所以系统详细设计部分依然按照模块化的功能设计来完成。本系统的功能模块划分如图4-1。

    第一节 服务器模块服务器模块是对从URL里获取到的参数进行处理。包装成HQL语句对数据库进行查询,然后回显到页面上。客户端获取到的数据又都是从服务器网页网页上直接拽取下来的字符串。本系统的主要数据处理都是在服务器端进行。服务器模块又主要包含两大功能,一个是对数据库进行增删改查的工作,另一个是数据展示。

    第二节 客户端模块客户端需要完成的功能就是按照使用者的需求向服务器发送请求,然后服务器返回筛选好或者反应操作是否成功的数据,客户端接收到以后对这数据进行解析,再显示到UI控件上,显示给使用者。

    第五章 系统实现及测试第一节 服务器端实现1. 查询信息在MyEclipse端连接数据库,通过MyEclipse的Hibernate反向引擎生成对应数据库表的实体类。一共有两种类型的类生成,一种是“表名.java”是与数据库表格的一一对应。另一种是“表名+DAO.java”数据访问对象类,是一个面向对象的数据库接口,通过这个类生成的对象可以间接地对数据库进行增删改查的工作。
    我另外按照每个实体类又写了相对应的服务类,旨在能将实现一种我需要的功能的语句封装在一起。为在写JSP脚本的时候调用方便。
    public Avatar[] findAllAvatar(){ AvatarDAO avaDao = new AvatarDAO(); List<Avatar> list = avaDao.findAll(); Avatar[] avatars=list.toArray(new Avatar[0]); HibernateSessionFactory.closeSession(); return avatars; }
    以下是根据具体条件查询数据的功能代码部分:
    Query query=chatlogDao.getSession() .createQuery("from Chatlogwhere ((senderID=:uid1 and receiverID=:uid2)or (senderID=:uid2 andreceiverID=:uid1))); query.setString("uid1", senderID); query.setString("uid2", receiverID);
    其中createQuery()当中的字符串是HQL语句。但是最后会被转换成SQL语句对数据库进行操作。另外这段代码使用了的是动态绑定参数的方法,query.setString(“uid1”,senderID)就是将senderID的值赋给uid1。
    2. 更新信息类似于要实现修改密码或是修改头像一类的功能时,就是对数据库内的信息进行更新。由于本程序中咋修改密码这一块直接是通过发请求要求学校教务网所在服务器进行修改,所以不涉及到数据库的操作。但是头像信息是存在我自己开发的数据库中的,这就需要涉及到更新操作了。

    3. 增添信息在本系统中要完成发布聊天信息这个功能模块时就需要用到往数据库里增添信息的功能。由于已经在登录模块完成过登录,所以可以通过Bundle可以直接将学号信息传入各个Fragment当中。聊天对象ID的获取则是通过点击list item,得到对应聊天者的姓名,再取数据库中查找出对应的ID。聊天内容从EditText当中获取,就可以组成一个URL向服务器发送请求了。

    4. 查询最新的聊天记录这个功能模块放在最后是因为为提高程序性能而做出的优化。前三项只是概括代表了我在本程序中用到了哪些数据库操作。
    这里的“最新”指的是当前登录下,最近一次获取过的聊天记录中的最后一条的下一条记录。微信里实现聊天时,能在界面灵活地收取到来自对方发来的消息。应该是一直在对数据库进行读取。这里我改成了当点击一次发送按钮的时候对数据库的信息进行一次读取。
    在这里刷新页面有两种实现方法,由于聊天界面是通过ListView来实现的,所以每一条聊天记录都是一个item.这些item中的数据又是来自一个List。第一种方法就是每次都清空列表然后,然后按照senderID和receiverID进行查询。再将查询结果全部塞进List。通过setAdapter()显示到聊天界面上。但是这样做的缺点显而易见了,随着聊天次数的增多,每次要拉取的信息也会越来越长,所以这种方法不可取。第二种方法就是拉取最近的聊天信息。也就是上次啦去过的聊天信息的最后一条的下一条信息。实现代码如下。

    第二节 客户端控制应用实现1. 向教务网所在服务器发送请求当我们需要登入系统时,需要先获取到教务网的系统登录页面,这时需要用到GET方法取请求页面。Okhttp已经提供了响应的接口函数。这里我将OkHttpClient对象封装封装进storeInfo当中,方便其它Activity中需要进行网络操作的类进行请求时调用。

    完成对默认页面的请求之后,从Response中的body中提取,我先在浏览器上完成登录操作,然后用WireShark进行抓包,查看POST方法提交的表单参数。然后再用JSoup去默认页面的表单中提取相应的参数,即图5-6中的__EVENTVALIDATION、__VIEWSTATE、ImageButton1.x、ImageButton1.y、txtPasswd、txtUserID,中的这六个参数,是需要我们封装进请求体里面的。成绩查询时则只需要用异步GET的方法了。

    2. 向自己开发的服务器发送请求当使用者需要使用更改头像和查询个人信息以及实现实时聊天的时候都要向我们自己开发的服务器进行默认GET请求。图5-7所示的是查询个人信息时所用到的核心代码。

    从图5-6可以看出,GET请求也是可以携带参数的,参数直接写在URL当中,参数与参数之间以&进行连接。在JSP写的脚本中会有一个函数进行提取URL中的参数值:request.getParameter(“stuID”)。
    但是GET请求的参数值都是暴露在URL里面的,POST请求提交的参数都是封装在请求体里面的,不易被人获取。因此POST提交参数比GET要更加地安全。
    修改头像和实时聊天的网路请求部分都和上述使用方法差不多,只不过是URL的不同,调用不同的资源。因为对数据库查增删改查的工作,都由服务器替我们完成了。
    3. 对ListView的优化之一:converViewListView这个UI组件在这个程序里被使用的次数非常多。和异步任务一样很必要写出来。ListView针对List中的每一个item,要求adapter个“给我一个视图(getView)”,然后一个新的视图被返回显示。但是如果我们有很多个item怎么办?一一为每个item创建一个新视图显然是不可能的。
    Android中有一个叫做Recycler的构建。图5-8是Recycler的工作原理。

    Android的ListView在初始化的时候,加入整个手机屏幕最多只能放下7个item,就只会创建7个视图。创建好的视图就是getView()中的convertView参数。如果我们继续向上滑动,item1消失,item8会出现,系统不会为item8再创建新视图,item1已经划出手机屏幕了,item1的convertView正好拿出来给item8使用。总结来说,converView的功能就是将之前加载好的布局进行缓存,方便之后可以进行重用。

    在getView()中进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,如果不为空则直接对converView进行重用。这样就大大提高了ListView的运行效率。可以在快速滚动的时候表现出更好的性能。
    4. 对ListView的优化之二:ViewHolder目前代码已经不会再去重复地加载布局了,但是每次再getView()还是会调用View的findViewById()获取一次控件的实例。于是借助一个ViewHolder来对这部分的性能进行优化。

    这个ViewHolder类的对象对控件的实例进行了缓存,当convertView为空时,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,把ViewHolder对象存储在View当中。当convertView不为空的时候则调用View的getTag()方法,将ViewHolder对象重新取出来。这样所有的控件实例都缓存好了。就没有必要每次都通过findViewById()来获取控件实例了。

    第六章 总结与展望这样一个系统做下来,感触颇多。因为遇到的错误实在太多了。清晰地记得在向自己开发的服务器发送请求时,怎么都连不上的痛苦。由于网络相关知识的欠缺,思考问题十分局限。幸好在老师创建的答疑群里有一位同学为我打开了思路。原来是连接服务器的URL中的主机名写错了。连的是无线局域网,就应该是用无线局域网下的动态IPV4,加入是插的网线,就应该写以太网下的动态IPV4。额外地,学习到了ping命令,顺带学会了怎么用笔记本发射无线信号。由于这个错误查找了许多博文,了解到多种可能发生这个错误的可能原因。可以说这一个错误带给我的收获颇丰。
    这次是我第一次完整地做出一个连数据库和服务器的系统。但是一路上遇到的问题越多,到最后反而愈能克服心里对复杂的代码结构的恐惧。我对从服务器的开发到客户端的开发已经已经有一个简单的认识。美中不足的是,Hibernate的的映射方式我还没有掌握,甚至可以说完全不懂。使用MyEclipse的Hibernate反向引擎固然十分方便快捷不易出错,但是由于不懂原理,我无法在数据库设计阶段对其完整性做出设计,因为反响引擎生成出来的实体类我看不懂。由映射文件带出来的还有注解。我觉得也有必要对其进行学习和理解。
    OkHttp的使用让我查阅了大量Http协议相关的资料。实现侧边栏效果找到的工具DrawerLayout的同时也让我附带知道了很多非常有创意的侧边栏设计。
    程序还有许许多多需要完善的地方,特别是我一开始完成异步时使用的是Handler,后来想尝试使用Asynctask,发现用Asynctask写出来的代码结构更加清晰而且不会很乱糟糟的。我没有修改Handler部分的代码。
    还有就是Activity和Activity之间,Activity和Fragment之间的参数传递,全局变量的滥用。这些都是需要再进行改进的。另外就是用户输入数据的限制,比如该输入学号的地方就不应输入英文字母。这类的安全性检查也是需要再进行改进的。
    缺陷实在太多。
    参考文献[1] 郭霖.第一行代码(第1版):人民邮电出版社,2014
    [2] eoe移动开发者社区组.Android开发入门与实战 (第二版) 人民邮电出版社,2013.
    致谢还记得第一节课老师展示学长的作品,老师的赞美之情溢于言表。觉得很羡慕,想试试学长能做的我能不能做,遂选了这个题目。老师讲课的方式,通俗易懂,追本溯源。并不是单纯地讲怎么使用,而是会讲出于什么样的问题,或是有什么样的需求导致的什么解决方法的出现,特别是会讲解一些android的内部机制,循循善诱,真的使我获益良多。
    感谢这一个学期以来老师的教导,总是积极回应同学们在学习上遇到的问题,不管多简单的问题都不会感到不耐烦,还总是分享一些技术难点。虽然已经说过一次了,但还是想再说一次,真的获益匪浅。谢谢老师,希望您身体健康,希望自己以后能一直保持对写码的热情。
    1 评论 5 下载 2018-09-28 22:04:38 下载需要14点积分
  • 基于FFMEPEG-MATLAB和C#-SQL SERVER构建的手机录音数据库及特征分析

    摘要当今科学技术发展迅猛,知识爆炸信息量的急剧增加不仅仅丰富了我们的现实生活,也对我们的信息处理技术提出了新的要求。音频信号在这信息洪流中占据着不可或缺的重要地位,诸如语音聊天,音频取证等在我们的生活中发挥着愈来愈重要的作用,故而对于音频的特征分析就显得极其重要。因此,本文将对大量的音频数据进行处理,在这样的前提下去对音频数据的特征进行分析处理,以期达到一个较为准确的成果。
    本次课题的主要任务就是对于收集到的音频进行特征分析和提取,之后再进行数据库的存储操作,保存其中重要的音频参数。具体的任务步骤如下:首先进行音频采样的工作,采用同型号的不同设备在不一样的场景下进行录音操作,记录下具体的场景信息,之后对收集到的音频进行FFMPEG格式转化,然后利用MATLAB软件进行频谱图的输出,记录下其中的特征参数进行分析,利用C#代码设计存储到数据库中。其中,音频的处理过程需要各个模块的相互协调,能够在同一软件平台上完成实现互联。
    本课题主要是以C#程序为基础,对音频数据资料进行处理,依托于MATLAB软件的图形功能,将音频的特征参数存储到数据库中。从软件的层面对录音文件进行研究,并设计相关数据库模型,对于手机录音技术具有一定的意义。
    关键字:特征参数,C#,MATLAB,数据库
    AbstractTherapid development of science and technology and the rapid increase in theamount of knowledge explosion have not only enriched our real life, but alsoput forward new requirements for our information processing technology. Audiosignal occupies an indispensable position in this information torrent, such asvoice chat, audio forensics and so on in our lives play an increasinglyimportant role, so the analysis of the characteristics of the audio isextremely important. Therefore, this article will deal with a large number ofaudio data, in this premise on the characteristics of audio data analysis andprocessing, in order to achieve a more accurate results.
    The main task of this topic is tocollect the characteristics of the audio analysis and extraction, and then thedatabase storage operation, save one of the important audio parameters. Thespecific task steps are as follows: First, the work of audio sampling, usingthe same type of different equipment in different scenes under the recordingoperation, record the specific scene information, after the collection of audioFFMPEG format conversion, and then use MATLAB Software to carry out thespectrum of the output, the use of C # code design stored in the database.Among them, audio processing process requires coordination of each module, canbe completed in the same platform to achieve interconnection.
    This topic is mainly based on theC # program, the audio data processing, relying on the MATLAB software graphicsfunction, the audio characteristics of the parameters stored in the database.design the relevant database model for mobile phone recording technology has acertain significance.
    Keywords: Feature Parameters; C #;MATLAB; database
    第一章 绪论1.1 引言在科学技术发展越来越迅猛的今天,对于音频信号的处理技术在当今社会上的方方面面都有着非常普遍的作用。特别是对于音频特征参数的提取部分,在此项技术中占据着不可或缺的作用。在各种事件场合中,包括信息的传输过程中,对于音频的处理将直接导致最后结果是否真实,在信息交流过程将直接导致声音在传输过程中是否保真,能否达到人们的预期。故而不难想象音频特征的处理对于我们的音频技术的发展有着毋庸置疑的作用。
    当然,不仅仅在信息传输过程中音频数据的作用不可替代,在一些日常生活中的案例中,也能体现出音频处理技术也在发挥着至关重要的地位。例如查询新闻报道,此类的相关报道层出不穷:2010年的杭州一起民间借贷案件,就是根据着几分录音数据,通过分析其中的来源以及相关处理得出其的可靠性,最终就是依据着这几份音频数据对这个案件进行了判决;还有一些录音门事件,以及还有一些航空安全事件等,其中关键的证据资料就只是一些音频资料,如果没有相关的过硬的音频处理技术,就很难得出准确详实的接近最后真相的并且让人信服的结果[1]。所以在这些事件中,音频处理技术就成了当之无愧的首选技术手段,从而能让当事人有个可以依赖的手段,也方便了相关部门的取证处理过程。
    对于音频资料的首要关注问题当然就是首当其冲的来源了,而音频的来源在如今是多种多样的,而手机录音作为当今最为方便常用的工具了,然而现如今手机品牌名目繁多,手机型号更是日新月异,更新换代的速度更是让人目不暇接,根据全球市场公司Gartner2014年的数据显示,全球手机市场销量已经达到18.38亿部,而主要有以下几个品牌:苹果、三星、华为、联想、LG、中兴、酷派、小米、魅族等[2];现如今国内异军突起的手机品牌还有OPPO、VIVO等品牌,相对应的各类手机硬件平台会有一定的差异,对于音频的分析会有不一样的影响,故而此次将就固定选择苹果品牌的手机进行录音分析,因为苹果手机的生态环境是比较封闭的,对于外界坏境变化所受到的影响较小,而且受品牌影响,苹果的受众在全球都占有相当大的比例,所以对苹果手机的定向研究也就更有代表性。
    在如今互联网发展如此迅猛的情况下,我们从中获取信息变得如此方便快捷,对音频的处理有了很多应用,例如很多视频处理软件都会应用到的变速处理,处理更加高级的应用还有类似许多变声软件如MorphVOXJunior等,其中对音频的处理涉及到相关参数的变化从而达到了预期的效果,而这只不过是音频处理的一部分,对于其他的参数也是我们分析和开发语音设备的重要技术,比如MFCC、傅里叶参数、功率谱参数等等[3],对于我们现在的录音设备和播放设备的开发有着极其重要的意义。
    1.2 课题背景和意义随着时代的发展与进步,人们越来越离不开手机的使用。手机在人们的日常生活中担当着至关重要的作用,借助于应用软件我们可以处理的信息越来越多,对于信息传播的要求也越来越高,与此同时人们对信息传播途中的安全性也愈发关注。而在信息传播途中,信息压缩、对音频的数字化处理是其中必不可少的一环,而且对于信息传播的安全防范有着不可或缺的作用。因此,为了保证信息的安全传递,保护信息传播途中的不被拦截,我们对音频的处理就显得极为必要。
    而本次毕业设计就是收集手机的特定录音文件,在不同电量和不同环境下进行录音操作,并设计音频管理数据库来存储录音文件,然后对其进行FFMPEG技术进行格式转换成wav格式,之后对其进行傅里叶变换,从而分析得出它的频谱变化,进而来详细了解音频文件在传输过程中的变化与其他的因素影响。
    在如今的手机行业中,苹果手机一直都是处于最重要的领军地位。所以选择这个苹果手机方向的研究就显得意义深远。而且苹果手机中的语音数据库较为完备,语音情感计算功能也较为先进,备受人们的推崇,故而对其中原理的深层次探究将对自己的知识技能有一个较大的提高与进步。而且这过程中所使用到的压缩技术,对数据的采集和处理,对数据库的代码认知,将让我对这方面的知识有一个整体的概念与把握,对今后的学习有很大裨益。
    1.3 音频特征参数分析现状近几年,数字信息媒体发展迅猛,信息呈爆炸式增长,音频数据在其中更是占据着非常重要的地位,国内外对录音音频特征分析和提取已经有了一定的研究。例如:Kraetzer等人已在2007年采用60多个音频的是与特征的统计对麦克风的分类问题进行了相关的研究,他们对所提取的音频数据资料进行统计分析,研究结果中所得的结论对于音频的来源采集判断达到了很高的符合度,这一研究让录音设备的相关技术开始得到了较为广泛的关注。Kraetzer等使用多达63个时频域统计特征对采用音频文件分析麦克风分类的问题进行了相关研究,其中利用提取的统计参数特征来进行分类的录音资源准确率达到了75.99%的高度[4]。而对于国内的研究现状也有很大的突破,2014年深圳大学王志强等人研究了关于粒子群算法优化音频参数的使用,来通过演唱者的演唱片段与系统实时评分模块来进行相关的比较计算,最终得出评分结果,从而有效地提高了视频点歌系统的准确率。而在华东理工大学贺前华等人的研究,就是在感知子空间分解模型的音频特征参数的基础上进行的提取方法,发现基于这种方法在梅尔倒谱特征以及在匹配追踪等方面有着很高的准确率,相较于传统的方法有着特别大的提升特性。
    而在一些较为专业的国内实验室,王志锋等人研究改进了音频无声段的功率归一化的参数设置,分别对GMM-UBM识别模型进行训练,在不同的录音设备中有着及其高的识别率。在重庆大学邮电大学计算机学院联合应用数学研究所郑继明等人在对音频特征参数的分析方法的研究,提取出来的Mel频率倒谱系数MFCC的同时,再基于SVM建立音频模板来对不同的音乐(背景音乐、纯音乐和混合音乐)进行识别,这种方法也具有很高的识别率。
    以上研究都是基于音频的特征参数进行的实验研究,对于音频处理技术都有着积极的促进作用,而且对于其中相关的参数特征都有着相对应功能作用。在本课题中选定对于苹果手机的录音文件进行特征分析,从而得出对应的特定参数特征。
    1.4论文主要任务及安排本课题论文的内容是在现有的需求分析中,根据实际情况设计存储数据库,再利用C#的窗口程序来调用FFMPEG实现对音频的格式转换,然后构建基于C#的数据库来对音频数据进行存储,并辅以MATLAB软件来对音频进行相应的频谱分析和图像处理输出。其中包括大量的音频数据收集以及分类处理,利用FFMPEG技术对音频进行格式处理,C#界面的设计和排版,MATLAB软件的调用以及SQL数据库的建立和使用。
    主要安排:

    第一章:介绍了本研究的背景和意义、国内外研究现状以及具体实施的步骤。
    第二章:对实现音频特征分析所需的数据库设计相关的知识点进行介绍。
    第三章:对FFMPEG技术的理论介绍。
    第四章:课题设计所需的软件知识介绍。
    第五章:相对应的音频特征系统的设计和调试。
    第六章:毕业设计总结和对未来前景的展望。

    第二章 基本理论2.1 引言要实现音频数据特征参数的提取,不仅仅是对于音频信息的提取,还包括了相对应的录音环境,以及录取的不同性别的声音,通过对音频数据的时域和频域波形的特征分析,以及它们的分帧信息,特别是在音频信息区分程度弱的情况下还要对相应数据进行预加重的处理,提取音频无声段[5],这对于音频数据的信息研究非常重要且易于考察。其中研究音频的特征内容还包括基音估计和共峰值估计。同时在录音过程中未知变化和环境的杂音也会给研究带来一定的影响,因此在实践的过程中存在着很多的干扰因素。
    2.2 语音信号处理基础2.2.1 语音信号分帧音频数据在经过采样后是无限长的,需要我们对其进行分帧处理,所谓分帧就是乘以一个无限长的窗函数,窗函数的表达式如下:
    x(m):语音信号,w(m):单位冲击响应,n:整数在音频信号的处理中通常会用到一下三种窗函数:
    矩形窗:
    w(n):窗函数,n:整数海宁窗:
    w(n):窗函数,n:整数
    汉明窗:
    w(n):窗函数,n:整数
    在读入语音信号之后,为了对其进行更好的处理,我们需要先对其进行分帧处理。音频信号的分帧公式如下:

    其中N表示存储的一段音频信号的长度,L表示对该音频进行处理的帧长,S表示后一帧对前一帧的位移量,通过这样的分帧处理之后,我们对音频信号的处理,不论是在时域还是频域中都能够进行相关的操作了。
    2.2.2 语音特征参数理论语音特征参数的估计其中主要有基音估计和共峰值估计。基音估计和共峰值估计的参数在语音特征参数的设计编码和识别功能中有着很重要的作用,以上这些参数通过语音的波形进行检测,它在语音的研究上占据着很不可替代的位置。
    基音是音频信号中的一个重要参数,在此模型中 ,基音也是激励源的重要参数,所以基音估计和提取在音频的处理过程中是需要高度重视的问题。因为基音的提取过程是比较复杂的,主要体现在这些方面:例如激励信号不是周期序列,清音与浊音在过渡段的区别是比较小的,如果去直接提取容易产生信号不稳定的问题,而且其中的频率会变化很大,而且对于外界环境的变化有着较为敏感的反馈,会导致结果容错性较差。
    而对于基音周期的提取,将用到自相关函数法和平均幅度差函数。进而我们考虑到语音信号的平稳性,我们还需要利用归一化自关函数,在音频信号经过了此类的处理后,往往还需要进行基音的平滑,基因倍频的处理等。基音变化率一般\<1%/ms,20ms变化\<20%。与这还有关联的处理方法还有SIFT法和AMDF法。变化的方法分为倒普法和循环直方图,其中倒普法的关键是在于计算倒普的后解卷,将其中的激励信息提取出来。
    2.2.3 MFCC特征参数的计算和提取MFCC又叫做Mel频率倒谱系数,这种频率是依赖于人耳所听到的声音,根据其中的特征将特性提取出来,并与Hz频率形成非线性的对应关系。我们通过采用这种关系,加以分析计算的到需要的Hz频谱特性,此项技术已经被语音识别领域广泛应用,对本课题的研究也具有很大的借鉴意义。其具体的处理过程如下:

    计算公式为:

    其中M:Mel滤波器的个数,j:MFCC的维数,在此处j取12维。MFCC参数特征提取的流程图如下所示:

    MFCC参数提取包括以下几个步骤 :

    预加重:通过使用一个一阶有限激励响应高通滤波器,使信号的频谱变得平坦,不易受到有限字长效应的影响。分帧:根据所知的短时平的语音特性,音频信号可以进行以帧为单位进行处理,将其分割成较短的信号,并且在前一帧和后一帧之间还会有混合部分,这样做的就是为了能够达到使语音更为平滑的预期效果。加窗:对一帧语音信号进行加窗处理,可以减小吉布斯效应的影响,减少误差,试验中采用哈明窗,这样能够提高框架左右端的连续性。快速傅里叶变换(FFT):将时域信号进行FFT转换,将其转换到频域上,并将得到的数据参数进行平方后得出结果。三角窗滤波:使用一组Mel频标上线性分布的三角窗滤波器(共24个三角窗滤波器),对信号的功率谱进行滤波,每一个三角窗滤波器所能达到的频率范围是近似于人耳的,所以利用这种特性可以用来模拟人耳的掩蔽效应。求对数:利用三角窗滤波器组的输出来求取对数,这样可以得到近似于同态变换的结果。倒谱均值减(CMS):利用CMS可以极大的减小语音信号输入信道对特征参数的影响。
    2.2.4改进的功率归一化倒谱参数在对音频信号进行处理,提取功率归一化倒谱参数时需有四个处理语音信号的步骤:一、前端处理:包含预加重、加窗、短时傅里叶变换、求功率谱、经过GAMMATONE滤波器组;二是基于长时帧的背景噪声设计,其中包括长时功率、非对称和临时掩蔽滤波器、权重平滑[6];三是归一化处理:其中包括时频域归一化,功率归一化;四是后端处理:其中包括了非线性幂函数、DCT变换、倒谱均值归一化。改进之后的功率归一化倒谱参数可以提供较为可靠的音频特征参数的提取,对于音频技术领域有很大帮助。
    在录音的过程中,无可避免地会有一些干扰因素,例如背景音、设备状态地改变、音源的改变等等。人耳的特性可以让我们较快的适应不同场合的不同声音,但是这个对于机器设备来说是暂时不大容易实现的,故而我们需要在处理过程中对其进行预加重处理,通过此系列的对应操作,将能够对此现象有很好的改观。
    综上所述,改进的功率归一化倒谱参数在提高低频分量的处理中非常重要,其中的倒谱系数的归一化处理会很好的保留录音设备的信息。
    2.3 语音信号的共振峰理论共振峰是指声音的频谱中能量相对集中的一些区域,这其中的决定因素不仅仅是音质,还能反映出声道(共振腔)的物理特征。声音在经过共振腔时,会由于腔体的滤波作用,使得频域中不同频率的能量重新分配。一方面时由于共振作用得到加强,另一方面则会受到衰减,其中会由于能量的分布不够均匀,得到加强的那部分会犹如山峰一样,所以称为共振峰,这决定着音频的音质。
    共振峰的提取方法比较多,为我们常见的有普包络法、倒普法、LPC内插法、LPC求根法、希尔伯特变换法等等,但是由于存在着虚假峰值等因素的影响,对音频的处理过程中会对连续语音的准确度存在较大的误差。
    所以我们应该对其的处理如下:首先要进行如前面MFCC介绍的预加重操作,而后进行希伯尔变换,经过自适应共振滤波器,通过清浊音的检测,最后通过移动平均值做决策,保留符合条件的估计值,不符合的将用平均值代替。该方法计算复杂,但是得出的结果准确度却相较于以前有很大提升。
    2.4 本章小结通过对相关理论知识进行了解和学习,我更加深刻认识到了研究本课题的意义和具体的操作流程,同时对自己所欠缺的知识有了个更为具体的认知,能够明白音频信号处理过程中所关系到的方方面面,类似于软件设计流程中的需求分析,对总体的框架有了更为详细的规划。接下来将要开始对该课题进行具体的方案流程设计,将各个模块进行相互联调,完成课题的实践部分。
    第三章 FFMPEG格式转换技术3.1 FFMPEG技术介绍FFMPEG是一种可以用来存储、转化数字音频、视频,并能将其转换成流的开源计算机程序。采用的是LGPL或GPL许可证。它提供了采集、转换以及将音/视频进行数字流化的完整的解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证可观的可移植性和编解码质量,libavcodec里很多code都是从最初开始研发的。
    FFMPEG在Linux环境下进行开发的,但它具有很高移植性同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起, Michael Niedermayer在2004年至2015年间对其进行了负责维护。许多FFMPEG的开发人员都来自MPlayer项目,而且当前FFMPEG也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的”FF”代表”Fast Forward”。
    3.2 FFMPEG项目组成 FFMPEG技术具有很强大的功能,与之相对应的,为了保证较高的可移植性和编解码的质量,libavcodec里很多codec都是从头研发的。
    FFMPEG项目主要有以下几个部分:

    libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;
    libavcodec:用于各种类型声音/图像编解码;
    libavutil:包含一些公共的工具函数;
    libswscale:用于视频场景比例缩放、色彩映射转换;
    libpostproc:用于后期效果处理;
    ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;
    ffsever:一个 HTTP 多媒体即时广播串流服务器;
    ffplay:是一个简单的播放器,使用ffmpeg库解析和解码,通过SDL显示。

    3.3 FFMPEG技术优势FFmpeg是一个开源的并且免费的在多个平台都可以使用的视频和音频流方案,属于自由软件。并且它还能提供录制、转换以及流化音视频的完整解决方案。作为一款拥有极其强大功能的软件,他在音频信号的的处理上有着较为完美的表现,不仅包含着领先的音/视频编码库libavcode等,还能够支持近三百多种编码解码器,甚至于能够对所有的常见的音视频格式都能够识别使用,并加以转码解码。
    FFMPEG的优势不仅仅体现在解码器的数量上,还在容器格式、过滤器、图像颜色空间等等的支持上都发挥着几位优秀的表现。
    3.4 本章小结本章主要介绍的是FFMPEG技术的具体含义和优势之处,ffmpeg技术的应用将有利于我们对解压缩方面知识的了解。而且对于格式转换有着较为完备的处理方案。对于格式转换,它在本课题的研究中占据着不可替代的作用,是方便我们后续操作的基石。
    第四章 软件部分简介在本次课题研究中,首次接触到的是VS2010(Visual Studio2010的简称),因为我们需要借助优秀的C#语言来搭建基础的框架界面,不仅仅如此,C#语言在编程方面和图形界面的操作技巧方面要远远优于C语言和C++语言,作为优秀的面向对象的编程语言,我们在windows系统下进行的编程操作当然就选择了它。随之而来的我们另一个需要掌握的软件则是SQL Server2008了。因为我们需要对处理的音频信号数据进行相关的存储以及调用,这时一个稳定的、功能强大的,并且使用起来方便快捷的数据库系统就显得必不可少了。有了这些前期的准备工作,后面的处理操作才会显得得心应手。我们对于后面的关键—-音频信号的处理采用的是MATLAB软件,这时一款极其强大且易用的产品,包含了许多非常实用的工具,对我们进行图形输出,数据对比等工作都是极其有利的。
    综上所述,本课题设计所采用的开发环境有:VS2010、SQL Server2008、Matlab2014。
    4.1 MATLAB优势4.1.1 发展背景MATLAB(矩阵实验室)是MATrix LABoratory的缩写,第一次是被Cleve Moler提出并对它进行系统开发的,由美国的The MathWorks公司出品的商用型数学软件,经过了30多年的发展壮大,现如今已经受到全世界各地的开发者和编程人员的喜爱和欢迎。
    4.1.2 浅显易懂的程序语言基于C++编程语言开发出来的MATLAB软件的语法规则和C++的语法是非常类似的,而且在其中的基础上进行了较大的改善,变得更加易于人们使用,最方便的莫过于其中功能全面的工具箱了,如信号处理工具箱、通信工具箱等,MATLAB的程序大部分是不需要进行注入编译、链接的过程的,我们需要做的只是直接输入相关程序,在对应窗口就会由结果显示,这一点完全不同于其他的编程工具,大大节约了时间和精力。
    现如今,MATALB这款软件还保持着很强的生命活力,开发人员结合使用状况以及用户的使用反馈情况,在与原先的基础上进行完善,增加了完整的联机查询功能,还完善了帮助系统,使得该软件更加的人性化,利于用户的使用和学习。而且还继承了C++语言面向对象的编程思维,具有很强的可拓展性和可移植性,便于新手的学习和专业人士的使用。
    4.1.3 强大的数据处理能力MATLAB的数据处理能力之强大早已是业内人尽皆知的了,并且已经被数学界和工程作业中所广泛应用。况且经过了长时间的磨练和完善,已经汇集了各地各界专家学者实验研究的成绩成果,对于各种数据处理都有着较为完备的解决方案,对于一些非专业人士,采用这个工具将会解决很大一部分的计算工作量。
    4.1.4 完备的图形处理能力MATLAB软件的图形处理能力也是首屈一指的,经过开发者的不断努力,如今MATLAB软件可以做到的操作处理能力已经非常完备,比如打印标注图形、可以处理色度、四维数据、图像光照等其他软件有所欠缺的功能,并且还对曲面和曲线的绘制和处理上进行了优化,同时MATLAB软件还包含了一些可视化功能的函数,完全可以满足各种用户的需求。
    4.1.5 工作界面介绍
    4.2 C#优势4.2.1 背景C#是微软公司研发的,主要是面向在那些需要在Microsof.Net Framework(也就是安装有Windows系统的PC端)环境下进行开发编程的研发人员,继承了C/C++的语法与优点,以及面向对象的思想。并且在其基础上有所突破和完善,特别是添加了图形界面的功能,对应用的研发编程起到了很大的促进作用。
    4.2.2 C#编程优势C#虽然是在C/C++的基础上发展而来,但是并不像前者一样晦涩难懂,涉及到很多关于计算机底层的操作,而且需要对整个系统有个较为完整的认识,对初学者不够友好。而C#就有了大大改观,C#的封装性较之前者有很大的提升,对很多的函数方法都进行了涵盖,所以它的编程过程较为简单,更容易上手。
    对于我们所需的界面编程,它的操作要求也不是很困难。C#提供的工具箱中有着大量我们所需的常见控件,我们需要做的就是添加相对应的响应事件。友好的操作界面和使用方法很利于我们的实验过程。
    4.2.3 VS2010软件简介Visual Studio是微软公司推出的编程软件,是目前最流行的基于Windows平台应用程序开发场景。Visual Studio 2010版本于2010年4月12日上市,其集成开发环境(IDE)的界面被重新设计和展现,使之变得更加简单明了。VisualStudio 2010同时带来了 NETFramework 4.0、MicrosoftVisual Studio 2010 CTP( Community Technology Preview—CTP),并且支持开发面向Windows 7的应用软件程序。除了Microsoft SQL Server之外,它还支持 IBM DB2和Oracle数据库。
    工作界面如下:

    4.3 SQL Server2008简介MS SQL是指微软的SQL Server数据库软件系列,它是一个数据库平台,提供数据库的从服务器到终端的一整套的解决方案,其中数据库服务器部分,是一个数据库管理系统,用于建立、使用和维护数据库。SQL Server一开始并不是微软自己研发的产品,而是当时为了要和IBM竞争时,与Sybase合作所产生的,其最早的发展者是Sybase,同时微软也和Sybase合作过 SQL Server 4.2版本的研发,微软亦将SQL Server 4.2移植到Windows NT(当时为3.1版),在与Sybase终止合作关系后,自力更生开发出SQL Server 6.0版,往后的SQL Server即均由微软自行研发
    SQLServer 2008 是一款常见的并且实用的数据库软件,发展时间较长,是在SQLServer的基础上完善发展而来的。我们可以通过可视化界面直接对数据进行操作,可以很方便的对原始数据进行增删改查,只需要了解部分SQL语句就可以轻松玩转这款软件,对于新手而言极其友好。
    主界面如下:

    4.4 本章小结本章的内容主要介绍了本研究课题所用到的相关软件,包括核心的开发环境MATLAB2014,必不可少的编程语言C#以及它的依托工具VS2010,还有起着幕后保障工作的SQL数据库软件。
    对于音频数据的分析,MATLAB软件提供着相当大的作用,依托该软件功能强大的工具箱,我们可以直接绘制出图形和对图形进行比较。当然基于C#的数据库系统的设计也对我们音频信号数据的存储起着非常大的作用。当然在软件的安装过程中不乏遇到了较多的困难,但是网上的前辈很多,总结的经验教训也不少,通过积极查询也都能够一一解决,是一个不错的自我学习的过程。
    第五章 系统构建及音频特征分析引言该课题研究主要包含了四个方面:首先是对收集到的音频的格式转换,接着是基于C#的界面设计,然后是通过界面操作将转化后的音频文件存储到数据库中,最后是将存储的音频文件通过MATLAB进行图形的输出,对其进行特征参数的提取。
    在这个循序渐进的过程中,C#编程语言在其中起着联系各个模块的作用,对整个系统进行联调。MATLAB方面则是要完成对音频的时频谱的输出分析,需要通过C#编程来实现对其进行调用。对于SQL数据库,主要实现的是音频信号数据的存入与输出,对信息进行管理的功能。
    5.1 前期工作在课题刚开始时,开题报告中所确定下的方案流程如下:

    所以首先要进行的前期准备工作就是大量的音频信号资料的收集任务,我也预留了足够的时间去做这方面的准备,要多进行各种情况下的录音准备工作。其中包括了多人多条进行录制、录制时设备电量在5%到50%之间、不同手机设备模式下进行录音、不同的录音环境下进行录音等等。同时,为了确保后期的分类整理存库方便,还要严格记录下录音文件当时所处环境的详细信息,再及时对不同的录音文件进行分类保存,做到较高的区分度。
    5.2 音频资料的格式转换因为收集到的音频资料最后要经过MATLAB软件进行处理和出图分析,所以我们需要对音频文件进行格式转换,将苹果手机存储的.m4a格式的音频转换成.WAV格式的音频,而且还不能够对文件有过压缩,否则MATLAB将不能进行运算操作。故而为了能够较为完美地转换音频信号资料,在指导老师地帮助下我选用了FFMPEG转换技术,这样能够很好地解决这个问题。
    在使用FFMPEG技术中,为了方便调用和系统的简易可读,通过C#的编程代码来调用该应用,核心代码如下:

    此处是直接以命令行的格式来进行调用:
    string c = @"ffmpeg\\ffmpeg.exe -i " + fileName + " " + targetFilName;

    通过代码编程实现线程的新建,通过以此来调用外部程序,从而对音频数据进行转码转换成.wav格式,并将转换后的音频存储在对应目录下。
    5.3 C#方面的相关设计对于C#代码编程,由于是刚刚接触,但是之前有学习过C/C++对现在所需要学习的新知识有点帮助,能够起到很好的提升作用。
    所以在刚刚开始时候就分别对各个模块进行了摸索实验,也算是对后续工作的完善系统做点提前准备。因为前面在指导老师的帮助下对音频信号资料进行了格式转换,所以接下来就是至关重要的存储操作了。这里我们需要实现VS2010与SQL Server数据库的联调。之后我们就需要通过MATLAB来绘制出图形了。
    5.3.1 用C#编程语言实现数据库链接首先我们需要启动数据库服务,如果没有启用数据库服务,SQL软件就会出现登陆不成功的问题,如下所示:

    这时我们需要启动命令窗口,启动数据库服务输入指令:
    net start mssqlserver
    然后就可以正常运行了。测试链接数据库代码如下:
    string constr = "Data Source = (local);Initial Catalog = db_test ; User Id = sa; Password = 123456";using (SqlConnection con = new SqlConnection(constr)){ string cmdStr = "select * from tb_user "; con.Open(); using (SqlCommand sqlcom = new SqlCommand(cmdStr, con)) { MessageBox.Show(sqlcom.ExecuteScalar().ToString()); }}
    这样我们就基本实现了数据库的调用了,将数据库中存储的用户信息显示出来。
    5.3.2 用C#编程实现音频操作在实现了数据库的连接之后,用户的信息登陆就算是基本实现了,主要代码如下:
    private void ok_button_Click(object sender, EventArgs e)//确认登陆{ string constr = @"Data Source = (local);Initial Catalog = test;User Id = sa;Password = 123456"; SqlConnection conn = new SqlConnection(constr); conn.Open(); string selectsql = "Select * from tb_test where username = '"+ textBox1.Text + "'and passwd = '"+textBox2.Text +"'" ; SqlCommand cmd = new SqlCommand("selectsql",conn); cmd.CommandType = CommandType.Text; SqlDataReader sdr; sdr = cmd.ExecuteReader(); if (sdr.Read()) { label1.Text = "登陆成功!"; } else { label1.Text = "登陆失败"; return; }}
    这样系统的登陆界面除了一些细枝末节的方面需要完善优化之外就算是基本创建好了,之后我们就需要对格式转化后的音频信号资料进行处理了,首先当然的我们需要对语音进行最基本的播放操作,基本的核心代码如下:
    String path = “D:/github/Demo/filename.wav”; //.wav音频文件的路径System.Media.SoundPlayer play = new System.Media.SoundPlayer(path);Play.Play();
    然后就是需要相对应的录入存储工作了。首先需要在数据库中建好表,然后通过相应的C#代码建立对应连接,之后通过按钮事件实现音频信号的录入。
    5.4 MATLAB方面的相关设计MATLAB软件在本课题中主要是起着对音频信号进行运算和绘制图形的作用,应用该软件的数学运算能力来对音频及逆行时域频域谱图的输出,通过该软件的调用我们能够更好的分析出音频信号的参数特征。在以前的学习生涯中,对该软件也有部分涉猎,所以对于图形输出是比较熟悉的,但是对于C#与MATLAB的调用方面还是存在着疑虑的地方,所以在这次的研究中将对其中的问题进行解决。
    5.4.1 MATLAB频谱图的生成​ 在对音频信号数据进行相关处理时刚开始有点不知所措,在MATLAB软件的调用上也存在着问题,但是在经过指导老师的帮助和同学的协助后,并且在网上查询了相关的教程及资料,最后得以成功解决了出现的问题,可以正确提取出所需的音频的时域频谱图。在这个过程中我也学习到了许多的方法,对软件的使用更加熟悉,对于音频信号的特征参数有了更为深刻的认识。
    以下就是基于MATLAB的音频时频域的提取:

    5.5 系统的工作界面的设计基于前面已经完成了基础模块的构建,所以这部分我要完成的时对系统关键模块的设计。这里要实现的是通过C#编程来调用MATLAB的时域频域图,同时将结果参数存储入数据库中。实现各个关键部分的连接功能。
    这个工作界面能够更为直观的向用户展现系统的功能,同时方便使用者的操作,也可以让结果更加简单明了。在这部分中,主要任务是将数据库中的音频信号显示出来,同时支持对它们的相关操作,实现诸如增删改查的功能。
    下面首先介绍一下C#调用MATLAB的时频域谱图,这部分首先要在MATLAB中生成m文件,然后通过C#调用MATLAB的m文件生成的dll生成波形和频谱。下面是生成dll文件的过程截图:

    接下来是调用中C#部分所需要的操作和设计,这部分主要是先设计一个窗体界面,这个而应该在操作C#的时候第一步就要做的,然后再添加相应的的引用,这里主要引用两个文件,一个是所生成的m文件,另一个是dll文件。以下是C#操作中的部分截图:

    接下来是C#与数据库的连接,这部分也是最终实现功能平台正常运转的核心部分,C#与数据库互联不仅是前面登录界面中账号注册部分所需要,而且也是音频特征数据存放所用到的核心部分。在C#与数据库连接这一块,通过图书馆借书查阅,看到三种方法,一种是直接通过VS2015平台的向导与数据库连接,这部分我个人觉得不太优越,另外两种方法比较像相似,主要不同之处就是一种通过网络IP地址连接,另一种是通过服务器名连接。这两种方法要比第一种方法优,因为在修改和阅读都很方便。下面是C#与数据库相连接的核心代码截图:

    最后就是工作的主界面的展示,以下是工作界面及实验结果的截图:

    5.6 系统总流程图
    5.7 本章小结本章主要介绍了课题研究过程的工作流程,详细介绍了相关的操作事项,诸如前期的录音资料的收集,并对其进行分类整理,还有对音频信号进行格式转换,利用C#编程对FFMPEG的调用,而后实现对数据库的连接操作,使得音频信号可以更好的保存。之后就是利用MATLAB来对信号进行绘制图得出特征参数的操作。最后就是基于C#的界面设计了,因为对用户来说,界面是最为直观的成果展示,而且还关系着整个系统的协调稳定,更是不可忽视的。
    在进行这部分工作时,遇到的主要时编程方面的问题,对C#语言的不熟悉会导致很多比较细枝末节的错误,但只要沉下心来好好寻找方法,通过询问知道老师和同学,以及上网寻求各位前辈的经验教训,基本上都是能够将这些问题解决的,在解决问题的过程中也学到很多的方法技巧,最关键的也是这种自主学习的方式方法,是需要在后面的学习生涯中一直保持的。
    第六章 毕业设计总结与期望6.1 论文总结主要是通过对录音文件进行处理得出特征参数并与数据库进行联调实现数据的存储入库。工作流程主要分成四个部分:首先是对收集到的音频的格式转换,接着是基于C#的界面设计,然后是通过界面操作将转化后的音频文件存储到数据库中,最后是将存储的音频文件通过MATLAB进行图形的输出,对其进行特征参数的提取。这几个模块的相互调用是这次课题研究的重点与难点,确实是在研究中遇到问题最多的地方。在解决问题的过程中,对该课题的研究也是日臻完善。
    以下就是本次课题研究的主要成果:

    对不同录音文件进行时域和频域波形的提取。
    基于C#实现与数据库的连接操作,实现基本的用户信息的增删改查。
    实现音频文件的数据库存储。
    基于C#实现与MATLAB软件的联调,实现依据音频绘制图形的功能设计。
    对音频的特征参数进行提取,并存储在数据表中。
    PC机上显示所有的数据结果。

    本毕业设计基本完成了开题报告中的所要求的研究内容,但是还有一些方面需要进行完善和优化,比如界面设计方面存在瑕疵,与数据库的连接建立存在漏洞,还有MATLAB的软件绘制图还能够实现更多,以及对于C#的编程逻辑尚显经验不足,在后期的优化过程需要得以解决。
    6.2 毕业设计中遇到的主要问题及解决办法1.如何将采集的音频还原成无损文件
    对于苹果手机的录音文件需要进行格式转换,因为手机录音文件大都是有损的压缩文件,而MATLAB只能对无损文件进行读取。在指导老师的帮助下,最终选择了FFMEPEG技术进行格式转换,保存为了WAV格式的文件。
    2.在C#编程连接数据库时出现问题
    刚开始使用数据库时,发现登陆不上特定的账户(sa默认账户),最后通过同学的帮助发现是系统的数据库服务是默认关闭的,需要手动启动,通过在命令行输入代码命令:net start mssqlserver 实现数据库的启用。
    3.C#中无法实现窗体和窗体之间的参数传递
    在写C#识别界面时,无法实现窗体和窗体之间的参数传递,通过和指导老师的交流,知道了需要定义一个公共类,通过公共变量来传递参数。
    4.如何实现C#和MATLAB混合编程
    C#识别界面初步确定,但是不知道怎么将MATLAB 2014b中参数提取和识别的算法和C#相连显示识别结果,方便用户操作。通过询问老师及上网查询资料,得知,这需要将MATLAB 2014b中的算法封装成动态链接库,然后C#调用这个动态链接库。调用这个动态链接库的方法有很多,其中比较方便简单的方法就是添加引用。
    6.3 对未来的展望本论文是对音频信号特征数据库系统的设计,在研究过程中实现了C#,SQL数据库以及MATLAB之间的调用,实现对音频信号波形和频谱的提取输出以及保存对应的特征参数。在目前的发展状况来看,信息的交流日趋繁多,重要地位不可取代,在语音方面更是重中之重,所以对于音频信号的处理就显得尤为重要了。对音频特征参数的研究有利于我们对音频信号的加工处理,利于音频处理技术的发展,并在相当大的程度上对我们的生活起着积极的反馈作用。
    当然,由于时间上的局限和作者专业知识的限制,在完成该课题时有很多的不足之处,对于音频信号的处理以及特征参数的认知还存在局限性,需要在以后的学习生涯中继续做出优化和完善。
    致谢惊风漂白日,光景西池流,当我开始着手写下这篇致谢时,才突然意识到我的大学生涯即将落下帷幕,思绪万千,感慨颇多,想念起刚刚走进小北门时的好奇面庞。满目山河空念远,不如惜取眼前时。虽然不舍,却更要珍惜。因为生命就是一段旅途,经历才是我们值得回首的时光,只为在途中遇到了那么多可爱可敬的人。我坚信学校给予我的一切都是我以后奋然前行道路上最宝贵的回忆和财富。
    引其流时思其源,成吾学时念吾师。在此论文就要写完的当下,我要衷心的感谢我的指导老师,感谢您对我的谆谆教诲和悉心关怀!在进行科研的过程中,遇到的问题产生的困惑都耐心的帮我解疑答惑,能够让我在遇到挫折时能够乐观勇敢的面对。您前沿而精髓的学术造诣,严谨勤奋的治学风格,宽以待人的崇高品质,都让我永志不忘必将深刻影响我日后的工作和学习。
    最后我还要感谢指导老师还有特别感谢我的班主任在我大学学习和生活上给予的帮助。感谢这些老师对我的不倦教诲。
    最后我还要感谢与我同组进行毕业设计和课题研究的同学的帮助。在这段毕设研究时间,我们相互帮助,互相配合,共同进退,和睦相处,最终圆满完成了毕设,给我留下了深刻的印象。
    最后,由衷感谢各位在百忙之中评阅我的毕业设计论文和答辩的老师!祝老师们工作顺利!
    0 评论 2 下载 2018-09-27 22:34:38 下载需要10点积分
  • 基于JavaFX的中文集句系统设计与实现

    摘 要本文在古诗词的内容上会有简单的描述,然后对于古诗集句软件进行详尽分析,确定功能需求以及非功能需求,对于软件运作流程给以流程图的展示。
    在需求分析基础上,进行软件的架构设计。进一步明确使用场景与功能点的关系。在查询模块主要任务是保证查询准确高效,而在集句生成部分,主要利用现有的可行手段,例如开源的中文分词工具对于待对偶诗句进行词性分析,在此基础上,给出相对较优的对偶句。最后,对于使用的开源工具运行机理进行介绍,并对系统,尤其是对偶生成算法的改进方向提出建议。
    系统最突出的是将词句匹配以及对偶句诗句生成融合,将客户端以及服务器启动程序结合,增强了程序的易用性以及使用场景的多样性。
    关键字:中文古诗词;诗词匹配;对偶句生成;中文分词使用
    ABSTRACTThis paper will make asimple description of the ancient poetry. Then, there is an analysis of thisancient poetry software system in detail. After the function requirements andnon-function requirements are curtained, there will take the flow chart about softwareoperation process.
    The software architecturewill based on the demand analysis before. Then, the paper will clarify therelationship between scenes and function points. On the query module, the taskis to ensure the query is accurate and efficient. And on the pair generationmodule, use existing open source project named HanLP, a segmentation tool, togenerate sentence pairs with readability according to the word’s property.
    At last, the paper willintroduce some algorithms used in the project HanLP, and show the direction ofsystem future improvement. System putthe client and server into one packet and put the function of pair generatingand poetry matching into the other packet which makes the system more reliabilityand easy-using.
    Keyword: Chinese poetry; poetry match; Generatepairs; the usage of segmentation
    第1章 绪论1.1 中文集句系统开发背景中文诗句作为传承中国古典文化的钥匙,以其凝练的表述,丰富的含义,优美的意境,在传承数千年中华文明的长河中熠熠生辉。优美的古诗文是全国各地语文教材的重要内容。我们每个人在接受语文教育的同时,都会接触到这精粹的文化。理解诗文内容是锻炼一个人思维的绝佳方式,对诗人的理解,对语境的体会体现了一个人的知识体系的完善程度,根据诗人所处境遇,相似的诗句可能的内涵截然不同。我们只有充分的阅读大量的诗文,才会有一定的能力,模仿创作。
    在计算机领域,在弱人工智能领域的机器翻译经过几十年的发展,在中文分词技术上,已经有令人可喜的成果。对于一篇人类的普通文章可以有较高理解程度,并将其转换为其他语言。这一技术基于的是对于语言、语法的理解与建模。
    作诗不同于翻译,已经属于强人工智能,模仿人类的作诗行为,是一个崭新的富有挑战的课题。
    模仿人的创作是一件很难的事情,尤其是高度凝练的诗句文字,需要使用合适的方式建模模拟这个过程。
    国内外的开源工具、开源方法层出不穷,为本软件的技术提供了重要的帮助。自然语言处理方面,HanLP自然语言处理有着良好的解决效率,并且开放了源代码,这让本地实现古诗句的匹配、生成更加具有可读性 (hankcs)。
    在数据集方面,网络中已经可以获取大量前人创作的诗文的电子数据,方便进行电子化的处理。
    在计算机方面,家用计算机的CPU已经可以胜任一些较为复杂的计算,性能的提高为实现对偶生成创造了可能,并且提高了匹配的速度。
    1.2 国内研究情况在自然语言的研究上,以百度翻译代表的国内中文处理与Google翻译代表的世界级互联网技术公司的文字处理利用RNN(RecurrentNeural Network)以及LSTM(LongShort-Term Memory)将中文自然语言处理通过大数据量文本推向了一个新的高度。机器人九歌与诗人陈昂联袂出演《机智过人》,现场作藏头诗引爆全场,其中背后的清华大学的自然语言处理与社会人文计算实验室与搜狗合作提供技术,代表了国内高校研究的最新水平。
    1.3 遇到的主要问题中文学习中,遇到平仄、对偶模糊查询一般需要使用搜索引擎,并且人工挑选符合需要的结果,然后通过信息进行二次查询,获得诗文、作者。本软件的精确查询与模糊查询可以进行诗文查询与保存,而且数据集成在程序内部,可以离线操作。软件集成服务器模式,根据启动命令不同,可以提供http访问服务,通过设定的request直接获得需要的结果。
    需求设计方面,要思考诗句查询可能出现的情况,制定详尽的需求分析表。一个好的需求分析直接决定了一个软件系统的上限,需求设计必须精确而有宏观眼光。
    设计方面,考虑到UI部分的简洁实用性,也要对程序的查询架构做好设计,避免占用过多资源。简洁的UI,令人愉快的交互,以及可能轻松愉悦的背景音乐都会让程序的使用体验变得更加富有人文情怀。
    在实现方面,首先,定义了较为全面的常用诗句查询规则,对于一般要求的诗句可以较清晰的描述。其次,在词性判断与对偶方面,采用开源的HanLP自然语言处理包中提供的算法,实现更加准确的词性匹配结果。最后,合理运用数据集,通过统计学的知识,让结果符合概率规则,让诗句的可读性更高。
    1.4 论文组织结构第一章绪论,介绍软件开发的背景,国内外研究现状以及软件解决的问题。第二章需求分析,对软件需求展开分析。第三章架构设计,根据软件需求,设计合理的软件架构,选择合适的软件,设计软件的技术架构与功能架构。第四章详细设计,查询过程建模,查询语句设计,对偶生成方法设计,网络通信设计,并简单介绍使用的中文分词方法。第五章实现与测试,使用单元测试检测各方法的执行效果,使用白盒测试整个软件查询流程。第六章结语,对软件进行总结,并提出改进方法以及措施。
    第2章 中文集句系统需求分析2.1 中文集句系统综述2.1.1 中文集句系统项目背景查询古诗词不便,模糊查询与精确查询需要连接网络并且反复查询,需要一款可以按照一定模式进行精确查找的软件,对于古诗的学习,查询有较好的效果。中文分词软件的准确性不断增加,开源项目增多而且成果显著。利用现有的手段以及个人计算机,可以尝试解决古诗文查询以及匹配生成的问题。
    2.1.2 中文集句系统项目介绍中文集句系统是为了更方便快捷的查询特定类型古诗词而开发的工具,通过查询语句对于古诗约束而针对性的查询出相关结果,并导出结果。对于古诗词的初学者,尤其是平仄韵律学习者,以及古诗词爱好者和有诗词查询需求的人,有较大的帮助作用。并且通过适当的启动命令可以担当小型查询服务器的任务,便于以后的功能拓展与业务拓展。在使用系统的时候,需要仔细阅读相关的程序说明,合理使用界面提供的功能。
    2.1.3 中文集句系统整体解决方案系统采用模块化系统组织,由用户交互界面模块、逻辑处理模块以及数据加载模块组成。其中,逻辑处理模块增加了处理网络请求的分模块。整个软件免安装,可以直接使用jar包启动,数据集内嵌程序中,不必考虑网络情况对于软件系统的影响。网络部分采用HTTP的Post与Get获取数据与返回结果,客户端demo代码也集成在jar包中。这种解决方案,虽然在计算能力与查询速度上带来的架构提升不大,但是十分适合在轻量级的电脑上运行。程序的内从占用与CPU使用不高,可以保证软件对于硬件的要求,同时可以为移植到移动端打下基础。
    下面是程序的结构图:

    2.2 中文集句系统工作目标以及解决的问题本系统目标:可以完整执行用户关于诗句的完整查询、结果预览、结果保存、对偶诗句生成、对偶诗句匹配等功能,形成可安装使用的完整程序。
    本系统解决的问题:

    诗句的具体约束查询,准确而快速
    对偶句生成有意义的诗句

    其他问题,JavaFX的UI界面使用,多线程操作,XML数据内存化技术,匹配优化等。
    2.3 中文集句系统需求描述2.3.1 中文集句系统功能性需求1.系统涉及的诗句查询匹配
    本系统匹配的诗句有几大约束部分。其中,主要使用的约束为:诗人约束、通配符、叠字约束、平仄约束、任选字符约束、声调约束(平仄约束)、拼音约束、或约束等。对于以上的约束,使用系统查询需要自定义一种表达方式来区分不同约束,各种约束见图2-2:

    根据查询的要求不同,将查询分为了两个大类。
    第一类,精确到逐字要求且字数有限制
    查询样例1,用户希望查询李白或者杜甫五言诗句中提到“紫”字,且该字出现在诗句的第1个字。对应的精确查询语句为:
    李白|杜甫&紫****第二类,查询的诗句中的字数不确定
    查询样例2,查询王维诗中提到“鸡黍”的诗句,查询关键字诗句为:
    王维&鸡黍通过规则对于诗句的描述,可以快速定位到相关诗句,而充分使用上述字符可以满足使用者对于诗句查询的要求。
    2.3.2 中文集句系统非功能性需求
    程序可以在Windows与Linux系统通用,该程序需要面对的使用者并不限制其操作系统的多样性。便于部署,提供网络接口方便拓展其他界面的功能,可以提供多样化的服务。查询的速度不可以超过90s,对于封闭数据集合,典型的7言诗句,所有的诗句全部吻合的时间为最高时间。在对偶生成期间,速度根据字数差异,典型为生成7言小于100s。对于异常数据有容错性,对于无属性的生僻字不能导致查询的中断。用户交互,确保全功能可以正常使用。生成的文件有一定可读性,需加入一定排版。
    第3章 中文集句系统架构设计3.1中文集句系统设计目标和原则中文集句系统的设计目标是满足需求分析的需求,并设计出可拓展的系统。
    中文集句系统的设计原则主要体现在模块化思想、多服务模式和系统可拓展三个方面。
    模块化思想,每个模块之间分清功能以及确定接口设计,模块内部实现全部逻辑,不能相互干涉使用模块内部方法。
    多服务模式,所有的业务逻辑封装在jar包中,根据启动命令不同,启动不同的服务模式。
    系统拓展方面,开启网络服务模式,只需要进行HTTP通信,就可以使用Client模式的绝大部分功能。所以,可以拓展到WEB页面的服务以及手机端。
    3.2 中文集句系统的技术架构设计3.2.1 中文集句系统技术架构采用JavaFX[4]进行界面UI设计交互,Java JDK1.8.0进行主程序编写,XML进行数据的组织以及标准化。使用jetty作为Web服务器的依赖包,提供Server服务。使用log4j作为系统日志生成组件。
    JavaFX,将界面展示与逻辑处理分开,摆脱了界面代码与逻辑代码混杂的Java界面设计模式,而且支持CSS等,让界面设计更加简洁而专一。
    XML文档有着强大的表述能力,规范的读写控制,作为轻量级的数据存储单位有着比数据库更加快速的优点。
    Jetty框架,提供轻量级的JavaWeb框架,比TomCat更加轻量级,且支持的jar包少,通过Maven引用简单快捷。
    Log4j是Apache开发的日志输出组件,对于监控运行的服务器端Java程序的运行状况有着十分明显的作用。
    具体详细结构见图3-1:

    3.3中文集句系统功能架构3.3.1 中文集句系统功能组成
    主要功能分为三个:
    1.诗句匹配
    对与诗句是运用规则进行精确匹配;使用关键字进行模糊匹配,匹配中运用部分精确匹配规则。精确匹配,是已知诗句个数,进行匹配时,首先排除数据集中不满足诗句长度的诗句,从而达到提高效率的目的。模糊匹配,是对应一首诗中不知道诗句长度,而需要查询所有诗句才能判断是否匹配成功,但是结果丰富,对于特定场景有很强的效果。
    2.对偶匹配
    对于输入的一句诗句(包括自己创作的),对于数据库中的诗句进行对偶匹配,返回符合对偶条件的诗句。对偶匹配的规则有很多,根据对于诗词格律概要[2]的研读,对偶对于平仄、词性有一定要求,在众多的对偶规则中,本文选取基本对偶规则进行对偶判断。
    3.对偶生成
    对于输入的诗句,通过一定算法返回合适的对偶诗句。对偶输入不仅要输入待匹配诗句,还需要输入生成个数,便于程序有限词的生成结果,程序本身不能评价生成诗句的好坏,所以需要人工对于生成结构进行整理分析。
    此外,服务器模式,实现HTTP通信使用上述三个功能并返回结果。对于网络客户端的代码,可以参考程序内的DEMO程序,在网页或者手机端可是同时调用接口,实现功能拓展。
    3.3.2 中文集句系统具体功能流程功能流程图见3-3,3-4:

    3-2匹配流程适用于精确匹配与关键字匹配,以及对偶匹配。匹配设计与第四章详细介绍。

    对偶句生成的分词,以及获得对偶词将由第四章详细展开。对偶句的关键逻辑在于对偶词的生成,以及中文分词的准确程度。
    第4章 中文集句系统详细设计经过需求分析和架构设计,逐步分析了中文集句系统涉及的需求和架构流程。本章在第三章的基础上,分析系统的模型结构和使用的算法以及诗句匹配的详细过程。详细设计分为静态设计、动态设计以及数据组织管理。
    4.1 中文集句系统静态程序设计4.1.1 中文集句系统的数据类基础文字类wordClass.java,负责存储诗的最基本元素单词。

    一个单词,属性有word字形、pinYin读音、pingZe平仄、yunbu在平水韵中所在对应韵部、kind平仄以及在全诗词中出现的次数。因为有三种主要功能所需要的wordClass需要承担不同的责任,所以我适应多态,不同的构造函数生成的word可以满足不同需要同时减少所有参数输入的问题,这部分将在具体情况下展示。
    与wordClass相似,诗句使用tangClass进行存储。

    tangClass中,author为作者,dynasty为朝代,context为诗文的String对象,包括换行与标点符号,title为题目,pairs为对偶生成结果存放位置,pairsWegiht为生成结果权重值(第一代对偶生成算法使用),关于对偶生成策略,在之后详细介绍。
    枚举类operation.java作用于服务器模式,http请求中的请求转化为业务请求。

    数据加载部分,由六种数据源组成,分别是诗句集合、平水韵表、作者集合、字典集、字分类集合以及诗句集字统计后形成的表。

    诗句集合,诗词查询的核心,诗句全部集中于该集合。平水韵表,根据诗词格律概要[2]中的描述,现存的最早诗韵是《广韵》,其前身为《唐韵》,而《唐韵》的前身为《切韵》,《广韵》共206韵,唐初许敬宗等奏议合并中临近韵。宋淳祐年间,江北平水人刘渊著《壬子新刊礼部韵略》,合并206韵至107韵。清代改称“平水韵”为“佩文诗韵”,合并为106韵,唐诗所用韵律实际使用平水韵。本程序主要解决唐诗及以后的研究。所以平水韵十分适合作为程序使用的韵律表。作者集合,是诗词集合的子集,主要作用是在查询过程中加快对于有诗人限制的匹配工作字典集,是拼音、字形的基本依据。收录了常用的汉字,并不含生僻字。字分类集合,这个集合感谢我的导师高晓程老师,他在繁忙的教学间隔中,根据自己的查询、以及自己的判断,整理了一个简易的字分类集合。本集合也是第一对偶生成算法的基础。基本思想是,用一个32位的数字表示汉字的属性,每一位表示一种属性,例如名词、动词、介词等。基本上分了26类,使用的时候,用两个汉字的对应数字相与如果相同属性位置均为1,则认为两个字的词性是匹配的。从数字角度上考虑,若两数字完全不匹配,则结果为0,数字越大,两汉字越匹配。这种生成对偶字的方法并不考虑词对于对偶的影响。同时,在对偶匹配中,也是用到了这个数据集。诗句集字统计表,是根据诗句集,将所有出现的汉字分类,并统计其出现次数的表格,因为第二种匹配算法使用到汉字出现概率,所以需要这项数据,这项数据对于固定集合也是固定的,所以固定成表加快运行速度。
    由于这六个数据集有较大的相似性,结构很相似,所以我以及字典集为例:wordSource.java。

    Logger对象为log4j包需要打印日志定位Class。ArrayList\<wordClass\>为数据集转换的java内存对象,也是程序搜索时取得数据对象。构造方法wordSourc(),将XML文件转化为ArrayList\<wordClass\>对象,getWordClassArrayList()返回生成的结果集。
    其他的数据集合类如:tangSource.java,kindSource.java均是如此构造可以将该类看为接口类来理解。
    4.1.2 中文集句系统的网络服务类网络部分,主要使用的类netServer.java。

    netServer负责启动服务器线程,hnadler处理每一个http请求,对于合法请求,经过调用主方法类,获得结果后返回。
    httpRequest结构。



    Header
    可输入项




    OPERATION
    isSearch,possibleSearch,pairMatch中任意一项


    INPUT
    实际查询语句,例如“李白|杜甫&桃花潭***”


    URL
    启动Server服务器的主机ip:port



    而客户端可以通过构造event对象实现对于客户端的response值的读取,代码如下:
    CloseableHttpResponse response = httpclient.execute(request); //解析response InputStream bis = response.getEntity().getContent(); ObjectInputStream ois = new ObjectInputStream(bis); event e = (event) ois.readObject(); logger.info(e.toString()); int status = response.getStatusLine().getStatusCode(); logger.info("返回 "+status); System.out.println(status); httpclient.close();
    event是一个构造的网络传输对象,并且继承了Serializable接口。
    public class event implements Serializable{ private static final long serialVersionUID = 3584042093326873203L; public ArrayList<tangClass> getResult() { return result; } public void setResult(ArrayList<tangClass> result) { this.result = result; } public String getInputString() { return inputString; } public void setInputString(String inputString) { this.inputString = inputString; } public operation getOp() { return op; } public void setOp(operation op) { this.op = op; } private ArrayList<tangClass> result; private String inputString = ""; public operation op; public event(operation op,String input){ this.setOp(op); this.setInputString(inputString); } public String toString(){ String out = ""; for (int i = 0; i < result.size(); i++) { out = out + "\n" + result.get(i).toString(); } return out; }}
    4.1.3 中文集句系统主方法类以及详细功能主方法类analyze.java。

    该工具类实现了查询的全部功能,主要入口函数为:Match(String),PMatch(String),pairMatch(String),getPair(String),对应精确匹配、关键字匹配、对偶匹配以及对偶生成。
    1.Match(String)
    首先判断输入合法,使用getAuthor()对有诗人限制的提取,并查询诗人集合,无结果直接返回,有结果进入下一步。
    调用isMatch(String,String),左侧为待匹配字符,右侧为诗句的Context属性。isMatch中,对于诗句Context中的每一句中的每一个汉字,都使用judge(String,String,String)判断是否与输入的匹配字符串吻合。judge中,第一个参数为匹配规则字符,第二个为来源于诗句Context中的待匹配汉字,第三个参数为上一个匹配的汉字。例如,juage的输入为””,”白”,“”。那么根据匹配字符规则,对应任意汉字,那么返回true。输入为“a”,“黑”,“白”。那么根据规则,希望查询叠字,但是本字为黑,上一个汉字为白,所以返回false。
    judge为最基本的规则判断方法,处理了基本所有的规则符号。具体代码参考附录中的analyze代码描述。
    2.PMatch(String)
    关键字查找的思路与精确查找一直一致,关键是两个单字之间空有复数个汉字时,使用第一种按序列匹配不能解决问题。
    首先也是对输入检查,调用possibleMatch(String,String),这个方法与isMatch()对应,在这个方法中,首先对于输入的查询规则进行建模,找到能影响规则的字符位置,这些字符有:汉字,[],调用getKeyIntArray(String),获得关键字数组。对于每一次的诗句循环,建立一个初始化全0的二维int数组isFound[][],存放key值与对应的其他字符的匹配情况。当key值字符在其他位置找到对应字时,在isFound的key值所在数组位置a,匹配到汉字的位置b标记为isFound[a][b] = 1。最后,遍历一句话的isFound数组,如果有一处为1,那么就可以判断这句话匹配成功,返回这首诗。这就完成了关键字搜索部分的关键代码段。
    3.pairMatch(String)
    这个对偶匹配方法使用的就是字分类集合,对输入的每一个字与诗句集中的相同大小的诗句进行一次对偶计算。由于分类集中汉字个数2177,远小于诗词集的8116。所以,对偶的匹配效果并不理想。这也是制约对偶诗句生成的主要问题。
    4.getPair(String)
    主要思路是首先获取输入字符串的中文分词,然后对于每一个输入的中文分词,生成对应的对偶词。对于诗词集合中的文字统计表,我们已经在上文中介绍并且实例化到了程序中。生成随机数R,对于诗词集的每一个字出现的频度A,总字数为M。我们在wordClass有记录count属性。所以,我们可以得到依据诗词汉字出现频率来随机生成的一个较为可信的随机汉字而不汉字,当

    且,

    认为该序列的汉字为对应生成的可信汉字。
    我们得到单个汉字之后,按照相同方法获得下一个汉字,直到达到分词结果的字数。然后对生成的汉字使用分词工具,判断词性,若与输入分词的对应词组词性相同,就直接填入结果中。
    例如:输入语句为“月涌大荒流”
    首先中文分词,分为[月/q,涌/v,大/a,荒/ng,流/v]

    然后对应生成“天如清寝尽”。
    4.2 中文集句系统动态程序设计系统动态设计是通过流程图、时序图展示的。中文集句软件主要是对于输入的查询、匹配。下面是对于系统的部分功能流程描述。

    在结果界面,可以选择保存文件,也可以点击单个诗句来进行保存操作,程序开始界面还有部分非必要功能,在这里不再赘述。
    4.3 中文集句系统数据模块部分设计数据模块的思想是将待查询数据直接载入内存,加快程序判断速度。在程序中,使用ArrayList作为基本类型wordClass与tangClass的组织对象,虽然六种数据源的数据不尽相同,但是最终都可以归纳到上述两个基本类的对象的属性之中。区别仅在于,数据加载程序需要根据不同的XML文件进行调整。
    以下是不同文件的实际内容截取:
    1.fullshici.xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><唐诗三百首 xmlns="http://tempuri.org/tangshi.xsd"> <唐诗> <题目>少年子</题目> <作者>李白</作者> <正文>青云年少子,挟弹章台左。鞍马四边开,突如流星过。金丸落飞鸟,夜入琼楼卧。夷齐是何人,独守西山饿。</正文> <注解 /> </唐诗></唐诗三百首>
    2.pingzezidian.xml
    <?xml version="1.0" encoding="UTF-8"?><dataroot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" generated="2017-03-10T10:08:22"><pingzezidian><字符>一</字符><读音>yi2 yi4 yi1</读音><平仄>2</平仄></pingzezidian></dataroot>
    3.authors.xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><NewDataSet><authors> <name>李清照</name> </authors> <authors> <name>李世民</name> </authors></NewDataSet>
    4.zifenlei.xml
    <?xml version="1.0" encoding="UTF-8"?><dataroot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="zifenlei.xsd" generated="2017-03-10T10:05:19"><zifenlei><zi>打</zi><fenlei>2</fenlei></zifenlei></dataroot>
    5.pingze.xml
    <?xml version="1.0" encoding="UTF-8" standalone="no"?><root><字符>东<上平去入>0</上平去入><韵部>1</韵部></字符></root>
    6.wordCountList.xml
    <?xml version="1.0" encoding="UTF-8" standalone="no"?><root><字符>川<出现次数>2332</出现次数></字符></root>
    在主分析类中,构造函数中自动加载上述六种数据源。下边是wordClass与tangClass的详细属性。
    wordClass



    成员
    类型
    说明




    word
    String
    汉字字符


    pinYin
    String[]
    拼音最后一位是读音,支持多音字


    pingZe
    int
    有0,1,2,3三种对应平,上去入(仄)


    yunbu
    int
    平水韵的韵部


    kind
    int
    对应32位的词性分类,每一位代表一个属性


    count
    int
    出现在诗句集中的次数



    tangClass



    成员
    类型
    说明




    author
    String
    作者


    dynasty
    String
    朝代


    context
    String
    诗文,包括标点、换行符等


    title
    String
    题目


    pairs
    String
    对偶匹配成功的诗句


    pairsWeight
    int
    匹配度,成功配对的诗句所有汉字权重累加



    4.4 中文集句系统UI设计由于使用Java作为开发语言,界面的开发是一个相对困难的问题。Java Swing是Java自带的界面开发框架,但是版本老旧,代码逻辑与界面代码混杂,不便于开发、维护工作。JavaFX是Oracle内置于Java7版本以及Java7的图形开发框架。经过简单的学习,就可以设计出外观简洁、代码简洁的UI部分,并且逻辑块与界面代码不会相互干扰,增加可读性。JavaFX可以通过网上的中文JavaFX网站[5]学习,上手方便快捷。
    4.4.1 JavaFX架构说明JavaFX是一个Orcale公司为了改进Java的图形界面而开发设计内置于Java8中的图形和多媒体处理工具包的集合,JavaFx可以让开发人员来设计、创建客户端的界面程序,并且和Java一样跨平台[6]。
    一般的JavaFX分为三个部分,fxml描述UI文件、JavaFX Application、JavaFX Controller。
    Fxml
    Fxml描述界面设计的文件,作为附加资源被Application启动时载入。下面是一个Fxml文件的部分内容。
    <?xml version="1.0" encoding="UTF-8"?><?import javafx.scene.control.*?><?import javafx.scene.layout.*?><?import javafx.scene.text.Font?><BorderPane fx:id="bigBorderPane" xmlns="http://javafx.com/javafx/8.0.101" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.Davi.poems.gui.Controller"><top></top></BorderPane>
    JavaFX Application
    Application是一个抽象类,规定了start()方法,用于创建最初窗口,一个实际使用的界面继承Application,创建Controller对象并且加载fxml文件。自定义方法welcome,加载Controller以及fxml文件。
    Public void welcome(){ try { Controller controller = replaceSceneContent("/welcome.fxml"); controller.setApp(this); } catch (Exception e) { e.printStackTrace(); }}");
    JavaFX Controller
    Controller是真正的逻辑处理部分。之中,fxml文件描述的组件的fx:id在Controller中使用关键字@FXML 引用。每一个方法必须在fxml中填写才可以生效。Controller中的一个方法。
    @FXML public void playOrStopBGM(){…}
    Fxml文件中的内容对应。
    <MenuItem mnemonicParsing="false" onAction="#playOrStopBGM" text="关闭&开启音乐" />
    4.4.2 中文集句系统UI设计根据需求分析部分,主要的查询界面有3个,分别负责精确&关键字查询、对偶生成、对偶匹配。
    所以采取的对应设计为一个主类Main.java继承Application。在之中设计连同欢迎界面在内的4个fxml文件。每一次切换Controller的场景上下文来实现切换页面的操作。这样可以将处理逻辑集中在一个Controller中,便于实现以及维护。
    第5章 中文集句系统实现与测试5.1 中文集句系统实现5.1.1 [软件界面功能介绍中文集句系统欢迎界面实现如下图5-1所示:

    点击开始集句之旅进入第一个查询界面,也可以点击文件,可选择三种不同的功能。附带诗句为本人拙作。
    5.1.2 中文集句系统的精确&关键字查询界面精确&关键字查询界面图5-2:

    输入框下方简单介绍了查询的匹配规则,并给出简单的示例。
    将文本输入文本输入框,点击精确查找,在右侧结果集返回结果图5-3:

    此时,点击右侧结果集图5-4:

    这是诗句的详细内容,并且附加了保存文本选项,可以选择保存目录并保存图5-5:

    还可以点击菜单栏编辑选项,保存结果,这是用于批量保存文件的方法。保存当前的结果集内容。
    5.1.3 中文集句系统的对偶匹配界面对偶匹配,点击文件-对偶匹配图5-6:

    对偶匹配的方法与精确匹配类似,可以对诗人进行限制,但是一般为了找到合适的对偶句,不做限制,进行查询5-7:

    查询速度较慢,因为使用的词性字典并不权威,所以结果不做置信的保证。
    5.1.4 中文集句系统的对偶生成界面对偶生成,点击文件-对偶生成,图5-8:

    软件集成4种中文分词算法,所以对偶生成的结果有不同,算法的介绍将会在5.2说明。
    输入待生成的对偶句上句,图5-9:

    HanLP的NormalSegment是作为默认调用的分析方法,结果如上图。
    其他方法直接截图,仅有使用分词算法不同。

    5.2 中文集句系统中文分词模块实现本软件使用的标准分词算法、NLP(自然语言处理)分词算法、N-最短路径分词算法以及Dijkstra最短路径算法分词将在本部分做介绍并分析算法异同。有关内容可以参考[1],网站对于HanLP的使用方法以及设计。
    5.2.1 标准分词算法生成词图,分词系统需要字典数据。词图,指句子中所有可能组成的词语的图。若有两个汉字A,B。其中,A是B的前驱,B是A的后继。那么A,B两个汉字间有一条词通路径(A,B)。一个词可以有N前驱或者后继。由A,B这样的字以及他们的前驱与后继构成的图,称为词图[7]。

    其中,建立点A,B的联系,找到后继前驱的算法有两种。
    1.使用 DynamicArray
    直观上,建立一个二维的表,从一个词最左侧汉字序列作为行,最右侧汉字序列作为列,就可以表达n个汉字之间的关系。但是会出现很多无效格,占用内存。DynamicArray的每个节点都包含词语的行编号(row)与行编号(col)也就是词的起止位置。
    上图中,列为n的词可以与行为n的词进行组合。例如,“实在”来源于(5,7)也就是从第五字符后到第七字符,而“实在”的col为7,那么与它一起计算平滑值的词就在row为7的“理”。连接词形成边使用上面的规则。在查询和便利的时候,需要逐个比对col与row,复杂度为O(n)。
    2.使用quick offset
    使用offset将相同的词写到同一行,建立一个一维数组,而数组内的每一个元素是一个单链表。
    1.始##始2.他3.说4.的/的确5.确/确实6.实/实在7.在/在理8.理末##末当查找下一个词的时候,以“实在”为例,实在位于6号位置,而实在的词语长度是2,所以下一个词的位置是2 + 6 = 8,8号位置。所以当查询和插入的时间均为O(1)。
    在生成词图后,就要进行词性标注工作。词性标注工作涉及面广,技术难度复杂,所以下文将引用成型文献进行解释。
    词性标注(Part-of-Speech tagging 或POS tagging),又称词类标注或者简称标注,是指为分词结果中的每个单词标注一个正确的词性的程序,也即确定每个词是名词、动词、形容词或其他词性的过程。在汉语中,词性标注比较简单,因为汉语词汇词性多变的情况比较少见,大多词语只有一个词性,或者出现频次最高的词性远远高于第二位的词性。据说,只需选取最高频词性,即可实现80%准确率的中文词性标注程序[8]。
    HanLP中使用了一阶隐马模型[9],在这个隐马尔可夫模型中,隐状态是词性,显状态是单词。HanLP训练语料采用了2014人民日报切分语料。
    汉语词的词性频数词典,分别统计所有单词的不同词性的出现频数,记录在词典的词语属性中,得到汉语词的词性频数词典(局部)如下:
    爱 v 3622 vn 598爱因斯坦 nrf 20爱国 a 178~统计每个标签的转移频率制作转移矩阵。
    利用求出的转移矩阵和核心词典词频数据可以计算出HMM(隐马尔可夫模型)中的初始概率、转移概率、发射概率,进而完成求解,关于维特比算法和实现请参考通用维特比算法的Java实现[11]。
    关于词性标注部分,请参考2014年人民日报切分语料。
    通过使用HMM模型,以及训练集的初步训练,得到了HanLP的标准分词算法,也是中文集句系统使用的基本算法之一。
    5.2.2 NLP分词算法NLP(自然语言处理)技术,目前取得了丰硕的成果,并且广泛应用于基于文档的信息检索、信息抽取与机器翻译等领域。目前的NLP在于数据检索上的应用可以分为两个层次,即词语层和上词语层。在词语层这个方面,NLP应用于自动分词识别内容词与功能词、识别复合短语与专有名词以及未收录名词。具体的应用为原形化或词干提取算法、机器可读字典开发、词语索引以及消除词语歧义和异意。而在上词语层,NLP应用到了概念表示、句法分析以及语义分析。
    NLP分词算法主要用到的是句法分析[13]。在自然语言中,语法规则是必需的存在,用于规范语言让语言有规律可循,一个句子中可能会出现主语、谓语、宾语。自然语言处理中的句法分析就显得比较重要。句法分析主要解决两个问题,第一是句法在计算机的存储和描述,第二是句法分析算法。所以,解决第一个问题,使用句法树来进行存储。
    假设,使用S作为句子,N,V,P代表名词、动词与介词,p代表短语(Np,Vp,Pp分别代表名词短语、动词短语、介词短语)。那么一句话就可以使用上述的描述作为计算机内的句法存储结构。
    例句:Jinan is located in Shandong Province S:Jinan is located in Shandong ProvinceN:Jinan Shandong ProvinceV:is locatedP:in(S (Np (N Jinan)) (Vp (V is) (Vp (V located) (Pp (P in) (Np (N Shandong)(N Province))))))在解决存储问题后,解决第二个问题是句法分析算法。
    采用CFG(Context-Free Grammar),上下文无关语法算法。为了简单的得到一颗语法树,定义语法如下:
    1)N表示一组非叶子节点的标注,例如{S、Np、Vp、N...}2)Σ表示一组叶子结点的标注,例如{Jinan、located...}3)R表示一组规则,每条规则可以表示为X->Y1Y2...Yn,X∈N,Yi∈(N∪Σ)4)S表示语法树开始的标注N = {S,Np,Vp,Pp,V,N,P,Vi,Vt,NN,IN}S = SΣ= {Jinan,is,located,in,Shandong,province }R={s->NP,VP;VP->Vi,Vt,Vp,Np,Pp;Np->Np,NN,Pp;Pp->IN,NP}由于句法分析的多重意义的性性质,所以在一个句子中可能会获得多个语法树,但是正确的语法树往往仅是一个,所以采用的一种常见的解决方法就是PCFG(Probabilistic Context-Free Grammar),概率分布的上下文无关语法。
    为了让语法树更加清晰,需要统计语法规则,对句子的语法规则增加置信程度,倾向于更有可信度的语法树,R规则内部,每一个句法规则都增加置信程度,例如:
    S->Np,Vp 1.0Vp->Vt 0.4Vp->Vi 0.4Vp->Vp 0.2Np->NN 0.3Np->Np,Pp 0.7Pp->P,Np 1.0而对于生成的语法树,对于每一个组成的规则的置信概率的乘积作为语法树置信程度。而同时出现多颗语法树时,有理由相信出现的可信度最大的语法树就是最有可能的的语法树结果。
    随后,利用足够大训练样本对于模型进行训练,PCFG中的参数经过训练调整到较符合训练集的程度。
    1)统计出语料库中所有的N与Σ;2)利用语料库中的所有规则作为集合R;3)针对每个规则A -> B,从语料库中估算置信程度p(x) = p(A -> B) / p(A);因为多语法树在解析上存在问题,所以在CFG的定义上,定义二叉语法的语法格式。要求每条规则只能是X -> Y1 Y2或者X -> Y的格式。实际上二叉语法格式保证产生的语法树总是二叉树语法树的格式,同时普通语法树可以转化成二叉语法格式。
    当训练完毕,拥有PCFG的模型完整参数,且语法转换为二叉树语法结构时,输入S=X1,X2,…,Xn计算句子对应语法树。
    由此基本的NLP的语句分析算法设计完毕,但是算法存在一些缺点:

    词法信息依赖外部的信息提供,训练集的丰富程度决定最终结果
    连续介词的处理在训练集中难以训练
    训练集对于使用环境的限制与要求

    5.2.3 N最短路径算法N最短路需要记录从哪一个点到哪一个点,花费是多少,采用的数据结构是PreNode节点。
    一个PreNode节点可能出现的内容有

    上一个N最短节点的节点名
    N最短路径的权重
    记录第N最短的index值

    N最短路径的基本思想是使用Dijkstra算法,以1-最短路径为例,求到下一个节点的最短路,沿最短路径前进至某个节点时,检查到下一个节点是否有其他路可以警告过,如果有的话,从发现的新的路径中选择一条路径,同时将所有最短路径加入PreNode而不是仅仅加入一条路径。而N最短就是第N短路径,维护N种PreNode,内容是第N短路径的上一个点的信息。
    所以,在拥有词语训练集的情况下,我们会拥有一个某词语到某词语的权重(概率分布),当输入一个字符串的时候,我们首先生成词图,并且去掉不存在的词语,然后将剩余词放入N最短路径算法模型中。
    例如:
    S = “商品和服务”可以分解为:“#开始”“商”,“商品”,“品”,“和”,“和服”,“服”,“服务”,“#结束”几个单词(“品和”未查询成功),且从0开始依次编号到8可能出现的情况0-1 “#开始”to“商”0-2 “#开始”to“商品”1-3 “商”to“品”2-4 “商品”to“和”3-4(不通)“品”to“和”2-5 “商品”to“和服”3-5 “品”to“和服”4-6 “和”to“服务”根据词语的训练集,我们可以获得每个字到下一字的出现次数(或者概率),所以根据N最短路径的算法,只需要在每一个边上加上权值(概率)就可以求出对应的分词情况。
    5.2.4 Dijkstra最短路径算法Dijkstra是一个十分经典的单源最短路径求解算法,在《算法导论》中作为经典算法进行讲解,本文作为回顾,简单介绍一下Dijkstra的核心思想。
    Dijkstra解决的是一个有向的带权图(非负)的最短路径求解的问题。对于分词的问题来说,只要通过训练集获得词语相关权重,查询输入字句的最短路径和5.2.3 N最短路径算法中N = 1的情况一致。
    Dijkstra算法涉及到松弛技术。对于每一个到原点S的顶点V,都维护一个d[v],含义是到原点S的估计最短距离,对于一个权值w(u,v)的边来讲,d[u]与d[v]的关系为:
    1. d[v] >= d[u] + w(u,v)2. d[v] < d[u] + w(u,v)对于第一种情况,说明v点的最短路径不通过(u,v);而第二种情况,需要松弛边w(u,v),给d[v]赋予新的权重。
    Dijkstra算法反复选取从顶点开始的所有与当前最短路径节点最近的节点(通过松弛当前节点的邻接节点选出最小的节点之一),最后得到一条最优解。
    在算法进行过过程中,只能得到一个关于当前输入语句的解,这是与1-最短路径的主要区别之一,但Dijkstra算法因为随机选择而使得计算量变小,计算速度加快,这是算法优点之一。
    5.3 中文集句系统的测试5.3.1 测试的方法论与主要技术软件测试,作为保证软件工程质量的关键性步骤,对于软件的水平起到了举足轻重的作用。测试的作用有两个,其一,是对质量做出判断;其二是发现存在的问题。根据软件工程的方法论,本系统的开发已经经历了需求分析、概要设计、详细设计、代码实现。接下来,本部分对系统采用3种不同的测试技术,用来测试软件的质量水平。
    测试主要使用方法有,功能测试、结构测试。
    功能测试又称黑盒测试,核心内容是给定输入获得正确输出,即程序正确。
    结构测试又称白盒测试,核心是已知程序结构,在输入数据的基础上,可以准确判断程序的运行情况以及定位出现的问题。
    5.3.2主要是功能测试的内容,给定输入值,获得结果,并验证结果正确性。对于没有答案的对偶生成部分,我们采用人工评分的方法,打出三组分数,取均值获得生成对偶句的分数,从而判断功能完善程度。
    5.3.2 测试项目与内容1.测试精确查询
    精确查询共支持8中字符以及汉字的操作,对于其中的字符可以分为两类。
    第一类是限制作者部分的字符,有‘&’与‘|’。
    第二类是对于诗句的限制字符,有‘*’,“[]”,“()”,“P”,“Z”,“a”。
    所以,测试用例应需要准备的数量为112。

    (“()”需要配合‘*’使用所以不测试全括号情况)
    但是,读取作者限制部分功能点代码位于一个方法,而诗句限制字符存在另一个方法。所以,实际准备样例测试主要使用情况,112中全部测试用例有很大一部分不符合实际使用情况,故不采用。例如:“PPPPP”,这样一个5言诗句全是平声字,不符合诗韵格律,虽然作为测试用例来讲没有任何问题,但是无效输入只会让搜索结果为0,不能检验方法功能正确性。所以”PZPPZ”这种输入更具有代表性,不仅符合格律而且可能有一批返回结果可供查验。



    测试用例1




    输入:杜甫&****汉


    输出集:20首









    测试用例2




    输入:李白\
    王维&***风*


    结果集:96首









    测试用例3




    输入:[水火]******


    结果集:899首









    测试用例4




    输入:*(hong)***(ping)*


    结果集:6首









    测试用例5




    输入:ppzzpzz


    结果集:386首









    测试用例6




    输入:白居易&**a**


    结果集:11首






    至此,精确匹配的主要使用场景测试完毕,分别测试了带诗人、带多诗人、多字任选、平仄限制、拼音限制以及叠字查询。
    2.测试关键字查询
    关键字查询,主要是测试在不提供词数的情况下,能否匹配多个不同长度的词语。



    测试用例7




    输入:百**苦


    结果集:4首









    测试用例8




    输入:[千百]*[雪盖]


    结果集:70首









    测试用例9




    输入:娉婷


    结果集:119首









    测试用例10




    输入:pzpp红


    结果集:354首









    测试用例11




    输入:王大伟&*****


    结果集:错误






    3.测试对偶匹配



    测试用例12




    输入:春眠不觉晓


    结果集:69









    测试用例13




    输入:不如御风行万里


    结果集:0首






    4.测试对偶生成



    测试用例14




    输入:不如御风行万里


    使用分词方法:标准分词


    结果集:指定5首





    生成诗句有一定可读性,但是具体诗意不明显。






    测试用例15




    输入:月涌大荒流


    使用分词方法:标准分词


    结果集:指定5首





    五言诗句明显可以有诗意,较七言表现更加。






    测试用例16




    输入:不如御风行万里


    使用分词方法:标准分词


    结果集:指定5首





    标准分词对仗并不工整。






    测试用例17




    输入:不如御风行万里


    使用分词方法:NLP分词


    结果集:指定5首





    NLP分词的对仗有明显进步。






    测试用例18




    输入:不如御风行万里


    使用分词方法:NShorPath分词


    结果集:指定5首





    发挥与标准分词类似,对仗并不工整。






    测试用例19




    输入:不如御风行万里


    使用分词方法:DijkstraShortPath分词


    结果集:指定5首





    表现与标准分词和NShortPath分词效果类似。



    综合表现,在输入句“不如御风行万里”,限制5首时,NLP分词表现最佳。



    测试用例20




    输入:春眠不觉晓


    使用分词方法:标准分词


    结果集:指定5首





    其中,柳绿未经瞻一句接近对仗诗句,表现尚佳。






    测试用例21




    输入:春眠不觉晓


    使用分词方法:NLP分词


    结果集:指定5首





    NLP分词表现好,5句对仗均有表现力。






    测试用例22




    输入:春眠不觉晓


    使用分词方法:NShortPath分词


    结果集:指定5首





    字形更为复杂,而含义却并不明确,表现不如前两种分词方法。






    测试用例23




    输入:春眠不觉晓


    使用分词方法:DijkstraShortPath分词


    结果集:指定5首





    表现效果良好,接近标准分词结果。



    结论,在输入诗句“春眠不觉晓”,指定生成5句时,NLP发挥依旧稳定,DijkstraShortPath与标准分词的结果也表现良好,只有NShortPath分词的对仗诗句表现最差。
    在生成诗句的时间方面,每一单句诗句的生成时间从最短的8ms到1799ms均有分布,但是平均在100ms左右,下附部分时间数据日志。
    2017-05-12 08:01:07,883INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 23ms2017-05-12 08:01:07,907INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 24ms2017-05-12 08:01:07,924INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 17ms2017-05-12 08:01:07,936INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 12ms2017-05-12 08:01:07,944INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 8ms2017-05-12 08:05:03,709INFO[com.Davi.poems.tools.analyze]-NLPSegment生成对偶句所用时间 Time 564ms2017-05-12 08:05:05,508INFO[com.Davi.poems.tools.analyze]-NLPSegment生成对偶句所用时间 Time 1799ms2017-05-12 08:05:06,162INFO[com.Davi.poems.tools.analyze]-NLPSegment生成对偶句所用时间 Time 654ms2017-05-12 08:05:06,349INFO[com.Davi.poems.tools.analyze]-NLPSegment生成对偶句所用时间 Time 187ms2017-05-12 08:05:06,460INFO[com.Davi.poems.tools.analyze]-NLPSegment生成对偶句所用时间 Time 111ms2017-05-12 08:06:14,461INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 43ms2017-05-12 08:06:14,496INFO[com.Davi.poems.tools.analyze]-normalSegment生成对偶句所用时间 Time 34ms2017-05-12 08:20:22,986INFO[com.Davi.poems.tools.analyze]-NshrotSegment生成对偶句所用时间 Time 163ms2017-05-12 08:20:23,114INFO[com.Davi.poems.tools.analyze]-NshrotSegment生成对偶句所用时间 Time 127ms2017-05-12 08:22:06,456INFO[com.Davi.poems.tools.analyze]-DjistraShrotSegment生成对偶句所用时间 Time 125ms2017-05-12 08:22:06,558INFO[com.Davi.poems.tools.analyze]-DjistraShrotSegment生成对偶句所用时间 Time 102ms2017-05-12 08:22:06,740INFO[com.Davi.poems.tools.analyze]-DjistraShrotSegment生成对偶句所用时间 Time 182ms2017-05-12 08:22:06,886INFO[com.Davi.poems.tools.analyze]-DjistraShrotSegment生成对偶句所用时间 Time 145ms2017-05-12 08:22:06,912INFO[com.Davi.poems.tools.analyze]-DjistraShrotSegment生成对偶句所用时间 Time 26ms此外,随着字数的增加以及诗句数量的增加,对偶生成的所需时间也迅速增加,且每一次的生成时间也不相同。生成时间与句子的对仗工整程度并没有直接关联。
    5.非功能需求的实现测试
    服务器模式启动:

    返回值:
    2017-05-12 09:14:03,120INFO[com.Davi.poems.net.myHttpClient]-这首诗的题目是赠汪伦(白游泾县桃花潭,村人汪伦常酝美酒以待白)这首诗的作者是李白这首诗的内容是: 李白乘舟将欲行,忽闻岸上踏歌声。桃花潭水深千尺,不及汪伦送我情。对偶句是:null权重是0这首诗的题目是山中问答这首诗的作者是李白这首诗的内容是: 问余何意栖碧山,笑而不答心自闲。桃花流水窅然去,别有天地非人间。对偶句是:null权重是0这首诗的题目是东鲁门泛舟二首这首诗的作者是李白这首诗的内容是: 日落沙明天倒开,波摇石动水萦回。轻舟泛月寻溪转,疑是山阴雪后来。水作青龙盘石堤,桃花夹岸鲁门西。若教月下乘舟去,何啻风流到剡溪。对偶句是:null权重是0这首诗的题目是曲江对酒这首诗的作者是杜甫这首诗的内容是: 苑外江头坐不归,水精春殿转霏微。桃花细逐杨花落,黄鸟时兼白鸟飞。纵饮久判人共弃,懒朝真与世相违。吏情更觉沧洲远,老大悲伤未拂衣。对偶句是:null权重是0这首诗的题目是江畔独步寻花七绝句这首诗的作者是杜甫这首诗的内容是: 江上被花恼不彻,无处告诉只颠狂。走觅南邻爱酒伴,经旬出饮独空床。稠花乱蕊畏江滨,行步欹危实怕春。诗酒尚堪驱使在,未须料理白头人。江深竹静两三家,多事红花映白花。报答春光知有处,应须美酒送生涯。东望少城花满烟,百花高楼更可怜。谁能载酒开金盏,唤取佳人舞绣筵。黄师塔前江水东,春光懒困倚微风。桃花一簇开无主,可爱深红爱浅红。黄四娘家花满蹊,千朵万朵压枝低。留连戏蝶时时舞,自在娇莺恰恰啼。不是爱花即肯死,只恐花尽老相催。繁枝容易纷纷落,嫩叶商量细细开。对偶句是:null权重是0这首诗的题目是昼梦这首诗的作者是杜甫这首诗的内容是: 二月饶睡昏昏然,不独夜短昼分眠。桃花气暖眼自醉,春渚日落梦相牵。故乡门巷荆棘底,中原君臣豺虎边。安得务农息战斗,普天无吏横索钱。对偶句是:null权重是02017-05-12 09:14:03,121INFO[com.Davi.poems.net.myHttpClient]-返回 200服务器端日志:
    2017-05-12 09:14:02,673INFO[com.Davi.poems.net.handler]-李白|杜甫&桃花*****2017-05-12 09:14:02,778INFO[com.Davi.poems.net.handler]-result size: 66.测试背景音乐
    可以完整播放,在查询时可以持续播放。
    7.程序查询时状态
    程序查询时有卡顿,期间不能进行其他操作,无法关停音乐。
    5.3.3 测试的结论首先,软件已经完成了需求说明中的大部分方法,但是在对偶生成的诗句的工整程度上,并没有达到原诗句等级。而诗句查询方面,可以较完整的实现功能,而且查询流畅度尚佳,但是查询时系统进入无法反应状态,必须等待结果出现之后界面才会刷新。对偶句匹配因为数据原因,无法达到对于所有汉字的精确对偶匹配从结果来看结尾可惜,但改进字典库是其中一个较为关键的方向。而在对偶生成部分,结合绪论中关于研究现状的表述,在机器自动生成诗句的方向下,仅仅是根据已有的训练集在加上简单的对偶模型并不能达到完美对仗的水平。而且,在服从概率分布的随机生成汉字的算法帮助下,最好的表现情况出现在NLP分词对仗较为工整,但也接近该算法模型的极限。对偶生成的改进方向,应该从现有框架中跳出,使用新的技术才可以有革命性的进步,更加近似于人的作诗风格。根据目前的研究成果,RNN(卷积神经网络)较为适合这种对于语言层面的学习,但限于本人水平有限、没有训练集群以及神经网络研究经验,不能再本片论文中继续增加RNN模型对于对偶生成的改进。
    第6章 结论中文集句系统,是以搜索、处理诗句之中的汉字为核心理念,提供诗句搜索,诗句匹配,关键字匹配、对偶句匹配、对偶句生成等功能的软件系统。经过较完善的软件工程模型—瀑布模型的流程监督,有完整的需求分析、概要设计、详细设计、软件实现、软件测试的流程。整体系统分为3个模块,用户交互的UI模块、数据查询的逻辑作用模块以及数据载入模块,使用Java作为程序开发语言,运用JavaFX解决用户交互界面设计,采用XML作为源数据的组织形式,有客户端模式以及服务器模式两种状态,分别提供界面的功能展示以及HTTP的网络请求返回功能。在检索部分,自定义了诗句的规则描述符号,使用符号组合进行诗句查询,在对偶生成方面,运用成熟的中文分词开源包,对诗句中的中文进行分词从而获取词性信息,再加上字典上的平仄从而生成对偶词,并且在生成的字词的概率参考了数据库中诗文的汉字出现概率,提高了结果的可读性。
    程序开发参考了指导教师高晓程老师的原型Demo程序,根据本科阶段的开发经验,使用Maven管理Java的依赖,使用Git管理程序版本,使用log4j打印程序日志,部分的功能模块,如音乐播放以及服务器模式采用了线程技术,努力的使程序的运行更加流畅且效率提高。
    本系统在帮助中文诗句爱好学习诗句、联系平仄等方面有积极的帮助作用,在闲暇时查询古诗不失为放松心情、排解压力的不二选择。软件系统在诗句的搜索、数据集的拓展、字典的完善以及对偶句生成算法上依然具有广阔的进步空间,是未来改进的主要方向。
    第7章 参考文献[1] hankcs HanLP自然语言处理包开源http://www.hankcs.com/nlp/hanlp.html 2015.03
    [2] 王力诗词格律概要 北京:北京出版社,1979
    [3] Li Zhongguo, Maosong Sun. Punctuation as implicit annotationsfor chinese word segmentation[J]. Computational Linguistics, 2009 35(4):505-512.
    [4] Orcale JavaSE 8 Document about JavaFX http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#JFXST784 2013.09
    [5] XMan JavaFX中文、OSGI、Eclipse开源资料 http://www.javafxchina.net/blog/docs 2017
    [6] Oracle JavaFX Document http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#BABEDDGH 2013.09
    [7] hankcs 词图的生成 http://www.hankcs.com/nlp/segment/the-word-graph-is-generated.html 2014.05
    [8] hankcs 词性标注 http://www.hankcs.com/nlp/part-of-speech-tagging.html#h2-8 2014.11
    [9] 刘河生 高小榕 杨福生. 隐马尔可夫模型的原理与实现[J]. 国际生物医学工程杂志,2002 25(6):253-259.
    [10] Bilmes J A. What HMMsCan Do[M]. Oxford University Press, 2006.
    [11] hankcs 通用维比特的Java实现 http://www.hankcs.com/nlp/general-java-implementation-of-the-viterbi-algorithm.html 2014.09
    [12] 刘向威 NLP技术在中文信息检索中的应用研究[D] 天津大学, 2005.
    [13] Allenxl NLP自然语法解析 http://blog.csdn.net/lanxu_yy/article/details/37700841 2014.07
    [14] hankcs N最短路径的Java应用与实现 http://www.hankcs.com/nlp/segment/n-shortest-path-to-the-java-implementation-and-application-segmentation.html ThomasH.Cormen Charles E.Leiserson Ronald L.Rivest Clifford Stein. 算法导论 北京:机械工业出版社
    第8章 致谢至此,本篇论文即将结束。在漫长的本科学习阶段,感谢我的导师,不仅在操作系统课程中为我答疑解惑,更是在最后的毕业设计为我提供了这个有趣而富有挑战性的课题,感谢我的导员在我的学习生活中无私的帮助,感谢我的任课教师,不仅教会了我的专业知识,更教会了我学习的方法。学海无涯,虽然大学的学习即将结束,但是在计算机领域,时时刻刻都在创新,在诞生新的方式方法,毕业是新的学习开始。在毕业设计的制作方面,我遇到过瓶颈,曾经彻夜DEBUG,测试程序方法的错误与漏洞,曾为了课设而废寝忘食,最终在规定的时间内完成了任务。最后的结果无论如何,我认为已经对得起自己几个月来的努力,成果无论是巨大还是微小,都为自己本科生涯画上了一个句号。在以后的工作学习中,要戒骄戒躁,将本次的经验作为财富,努力的前进。
    最后,感谢在我本科学习生活的过程中给予我指导帮助的老师、同学们。还要感谢坚持到最后的自己,谢谢。
    1 评论 1 下载 2018-09-27 01:14:29 下载需要18点积分
显示 915 到 930 ,共 15 条
eject