分类

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

资源列表

  • 基于Android的益智闯关类游戏“超级小猫”的设计与实现

    摘 要在科技文化高速发展的今天,人们对电子游戏娱乐的需求也日益高涨。从最初的运行于掌上游戏机的“贪吃蛇”和“俄罗斯方块”到如今风靡全世界的网游无不体现了游戏的魅力。而随着智能手机终端的涌现,更是为电子游戏提供了高速发展的平台。特别是iPhone智能手机和Android智能手机的面世,为掌上休闲游戏与应用提供了更完善的的开发环境。
    本文基于Google公司为广大智能手机开发者提供的Android系统环境,综合运用J2SE、软件工程、游戏算法、图像处理等技术研究并开发了一款名为“超级小猫”的益智闯关类游戏,游戏基本功能包括:玩家通过触屏操作,对小猫实现左、右、跳的动作,小猫在玩家的操作下在场景中会遇到敌人和意想不到的障碍,只有操作小猫顺利走完场景,方可进入下一关,关卡过完,游戏结束。此款游戏虽然玩法简单,但会不断的带给玩家惊喜和乐趣,会让玩家有防不胜防的感觉。
    关键词:Android;手机游戏;Java;超级小猫
    AbstractIn the culture of science and technology rapid development today, the people of electronic games and entertainment needs also rising. From the initial run on the handheld game of the” snake” and” Tetris” to the now popular all over the world online games embody the charm of the game. With the intelligent mobile phone terminal emerge in large numbers, is for the electronic game provides a platform for rapid development of. Especially iPhone intelligent mobile phone and Android intelligent mobile phone available, for pocket casual games and Applications provides a better development environment.
    This text based on Google company for the majority of the smart mobile phone provided by the developers of Android system environment, using J2SE, software engineering, game algorithm, image processing technology research and the development of a “ super cat” puzzle game to pass through, the game features include: the basic game player through the touch screen operation to realize left, right, cat, jumping, the cat in the game player operation in the scene will encounter the enemy and beat all obstacles, only operation kitten smooth walk scene, can enter the next level, crossing over, game is over. The game while playing simple, but will always bring surprises and fun game player, will make the game player are impossible to guard against.
    Key words: Android ; Mobile Phone Game; Java; Super Cat
    1 绪 论1.1 手机游戏的研究背景自上世纪80年代第一部手机诞生以来,随着计算机技术的进步,手机也经历了从模拟到GSM、2.5G再到今天的3G手机如此一个发展历程。特别是进入21世纪后手机的硬件性能得到很大的提高,当前主流手机普遍配有主频500MHZ的微处理器和500MB容量的存储器。与之同步发展的还有支持手机运行的手机操作系统,从当初的单片机系统到后来的专用嵌入式系统,到后来开始为智能手机设计开发出可以更高效管理手机软硬件资源的操作系统,其中有不少属于开源项目的手机操作系统。目前全球的智能手机操作系统主要以Google公司开发的Android系统、苹果公司开发的iPhone系统、诺基亚公司开发的Symbian系统、微软公司开发的WindowsMobile系统和Linux系统为代表。从图1.1为2011全球智能手机操作系统市场份额统计,Android仅仅经过两三年的发展就抢占了43%的市场份额,并保持着2%的增长速度。中国是最大的手机市场,2011年的统计数据表明,Android已取代了iPhone成为中国市场额最大的智能手机操作系统。

    Android系统是Google公司于2007年推出的一款手机操作系统,目前能安装于HTC、摩托罗拉、联想、华为等知名品牌手机。它是基于Linux2.6.1内核的一个开源手机操作系统,目前最高版本为2.4,集成了GPS电子地图导航、重力感应器、多点触控等重要功能,开发者可以开发出丰富的运用。由于Android智能手机操作系统是一个开源项目,具有很强的开放性和可扩展性,吸引了大量的手机软件开发人员为这些手机系统开发应用软件及休闲游戏,极大的丰富了手机功能。为服务广大的开发人员与谷歌手机用户,谷歌公司在因特网搭建了一个应用软件商店。开发人员开发出的手机应用软件可以陈列于应用软件商店中供用户挑选购买,解决了软件工程师最薄弱的软件营销环节,作为目前广大手机软件工程师最大的创收途径而博得广泛的热衷。
    电脑游戏是很多人不可或缺的娱乐活动,八九十年达的“贪吃蛇”和“超级玛丽”给大家留下了欢乐的欢乐记忆,也为游戏创作者带来了巨额财富。近二十年来,随着计算机硬件性能的提升及软件开发者的努力,游戏作品不论是创意还是视听感觉都有飞跃的发展。当前电脑游戏行业非常繁荣,竞争也非常激烈,随着智能手机的问世并普及,越来越多的游戏开发人员把目光转向了手机游戏这个新兴的行业。手机通常重量轻,随身携带方便,现代人们压力大,非常希望在工作之余,上下班途中可以通过娱乐来放松自己,此时,手机游戏便能发挥出它轻松休闲的特点。受手机性能的制约,以往运行于手机之上的游戏一般较为简单,但随着微电子技术的发展,手机性能越来越优良,现在已经可以开发出类似于PC机上的游戏。特别是随着3G移动网络的迅猛发展,可以预料在十年之内目前如火如荼的网络游戏也将转战至手机平台。
    1.2 国内外手机游戏的研究现状在手机应用开发领域当前较为火热的有J2ME语言、Object C语言和Android SDK。J2ME 作为Java2的一个组成部分,以其发展时间长而成为较为普遍的技术。其最大的优势在于移植性强,使用J2ME技术开发的程序,可以经过简单的修改以后,能在不同厂商的手机上运行。同时由于Java语言的普及从而开发周期大大缩短。但是,也因为兼顾移植性,导致其开发出的功能受到一定的限制,又因为其运行于JVM上并受手机性能的影响所以运行速度普遍较慢。Object C语言基于标准的ANSI C,其语法源于Smalltalk消息传递风格,作为iPhone手机上的标准开发语言而受到青睐。相对于J2ME最大特点是软件运行效率高速度快,界面也继承了苹果的华丽风格,遗憾的是开发出的应用软件只能运行于iPhone手机上。谷歌与开发手机联盟合作开发的Android SDK是近两年诞生的充满活力和生命力的手机开发语言,是一个标准的、开放的移动电话软件平台,不存在任何以往那种阻碍移动产业创新的专有权障碍。相对于其他开发语言,Android最大的优点就是开放性,吸引了众多的开发者的支持,短短两年时间开发出大量的手机应用软件,丰富了手机的应用功能。另外对网络友好,支持大部分网络功能,并与Google应用无缝结合,可以轻松获取Google服务。

    Android和手机应用市场正处于起步阶段,市场和消费者需要个接受的过程,众多开发者也都在积极探索盈利模式。Android开发者实现盈利主要依赖两方面因素,一是全球Android手机终端的爆发,另外就是海外付费应用程序的模式逐渐形成。目前Android应用开发个人或团体虽未取得规模盈利,但也开始逐渐向盈利模式转移,获得不错收益。图1.2是中国移动应用软件超市2011年的统计数据,消费者下载量最高的依然是应用开发,其次便是游戏开发,占据39%的下载量,而传统的增值业务则只占5%,在移动互联网时代,开发者纷纷逃离营运商的束缚,转向利润较为丰厚的应用程序和游戏开发。由此图也可以看出手机游戏软件在消费者中的受欢迎程度,如果想在未来手机应用程序开发中实现规模则需要加大手机游戏方面的开发力度。
    目前在手机上开发出较为成功的游戏主要有FTG格斗游戏、PUZ益智类游戏、RPG角色扮演游戏、SLG模拟/战棋式战略游戏以及体感游戏和宠物养成游戏六类。其中格斗游戏诞生于街机,曾经的“街霸”是其典型代表。FTG系统的核心是对战斗部分进行碰撞检测计算。益智游戏最大的创意是其游戏规则,玩家需要以自己的智慧对游戏规则进行思考,判断从而通关以获得成就感。PUZ游戏对玩家操作要求不高是手机游戏中受众面最广的游戏,最具代表是“推箱子”。角色扮演游戏是最能与用户共鸣的游戏,开启了一扇通往虚拟现实的大门,可以让玩家沉浸于打怪升级的快乐之中,“仙剑奇侠传”是其经典代表。RPG游戏的核心是代表玩家角色成长的升级系统,游戏都贯穿一个故事情节,所以其创意是多元的、立体的,让开发者有更广阔的创作空间。战棋类游戏是玩家在虚拟地图上指挥一定角色与敌方展开战斗,典型的有“地雷战”。体感游戏主要依赖手机中的重力感应等装置开发出的新型游戏,玩家可以通过摇晃、旋转、移动等动作来操作游戏。宠物养成游戏则因为其人性化游戏情节而吸引人们注意力。
    手机游戏研发的重点是游戏情节和游戏界面。游戏情节的创作依赖人的想象力和创造力,主题及素材可以来源于热门小说也可来古老传说,例如中国的经典名著“三国志”被日本光荣公司搬去后创作出名噪一时的经典同名游戏。还有著名网络小说“诛仙”也被创作为当前流行的同名网络游戏。这些文学作品为手机游戏的开发提供了大量的可创作素材。同时,行业竞争的激烈也导致雷同的不少游戏情节出现,这就要求创作人员不断发挥才能,构思出更加丰富的游戏主题。游戏界面方面随着手机性能不断提高的影响,也愈加华丽。从当初粗糙单色的俄罗斯方块到后来颜色丰富的2D游戏,再到现在开始流行的极具立体效果的3D游戏。游戏的操纵方面也有革命性发展,从最初按键操纵到后来的屏幕多点触控再发展到重力感应操纵。
    1.3 本课题研究的目的、意义及研究内容由于本人对游戏的酷爱、对代码的敏感和对童年红白机时代游戏的怀念,在Android平台下通过掌握的Java语言基础和对Android知识的深入学习,研究并开发了一款益智闯关类游戏“超级小猫”,并通过整个学习、设计和实现的过程,能提高自己的代码量和读写代码能力,为以后步入Android应用开发工作打下基础。
    2 Android平台研究2.1 Android特性Android系统是工作于智能手机之上的操作系统,处处体现了与手机相关的特性。由于手机硬件配置的关系Android系统与PC机上的操作系统有着很大的差异。只有在充分了解Android系统特性才能开发出适合手机运用的软件。以下为Android SDK2.2的主要特性:

    可重用和可替换组件的程序应用框架
    针对移动设备优化过的Dalvik虚拟机
    集成了基于开源Webkit引擎的网页浏览器
    经过优化过的自定义2D图形库和基于OpenGL ES 1.0库 可选硬件加速
    SQLite结构化的数据存储数据库
    支持常规的音频视频多媒体格式,如MPEG4、H.264、MP3、AAC、AMR、JPG、PNG、GIF GSM电话(硬件决定)
    蓝牙、GPRS-EDGE、3G和Wifi无线网络
    相机、GPS全球定位、导航和感应加速器
    丰富的开发环境包含设备模拟器、工具、调试器、内存和性能分析工具,以及Eclipse集成开发环境插件

    2.2 Android系统架构研究Android平台是一组面向移动设备的软件包,它包含了一个操作系统、中间件和关键应用程序。Android是一组面向移动设备的软件包(目前不仅限于移动手机领域,Android也已经进入其他嵌入式领域,如汽车中控台,MP3/MP4等),包含了一个操作系统,中间件和关键应用程序。Android SDK 的预览版提供了必需的开发工具和应用程序接口,基于这些,我们就可以使用Java编程语言在Android平台上开发应用程序了。
    Android系统架构基本分为应用(Applications),应用框架(Application Frameworks),库函数(Libraries),运行时环境(Android Runtime),Linux 内核(Linux Kernel)。下方图表显示了Android操作系统的主要组件。

    1.应用程序
    Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用Java语言编写的。
    2.应用程序框架
    开发人员也可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用;任何一个应用程序都可以发布它的功能块并且任何其它的应用程序都可以使用其所发布的功能块(不过得遵循框架的安全性限制)。同样,该应用程序重用机制也使用户可以方便的替换程序组件。
    3.系统运行库
    Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。Android 包括了一个核心库,该核心库提供了Java编程语言核心库的大多数功能。
    4.Android运行时
    每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。Dalvik被设计成一个设备可以同时高效地运行多个虚拟系统。 Dalvik虚拟机执行(.dex)的Dalvik可执行文件,该格式文件针对小内存使用做了优化。同时虚拟机是基于寄存器的,所有的类都经由Java编译器编译,然后通过SDK中 的“dx” 工具转化成“.dex”格式由虚拟机执行。Dalvik虚拟机依赖于linux内核的一些功能,比如线程机制和底层内存管理机制。
    5.Linux 内核
    Android 的核心系统服务依赖于 Linux 2.6 内核,如安全性,内存管理,进程管理, 网络协议栈和驱动模型。 Linux 内核也同时作为硬件和软件栈之间的抽象层。
    2.3 Android应用开发研究Android系统开发采用Java语言,使用由Google Android SDK和Eclipse IED进行应用程序的开发。
    在Android开发中Eclipse IDE 和用于 Eclipse 的 Android Developer Tools 插件是至关重要的开发工具。Android 应用程序是用 Java 语言编写的,但是是在 Dalvik VM(非 Java 虚拟机)中编译和执行的。在 Eclipse 中用 Java 语言编程非常简单;Eclipse 提供一个丰富的 Java 环境,包括上下文敏感帮助和代码提示。Java 代码通过编译后,Android Developer Tools 可确保适当地将它打包,包括 AndroidManifest.xml 文件。
    在Android SDK 附带 Android Emulator,是一个运行在PC端的手机模拟器,开发者可以使用这个模拟器来运行自己编写的应用程序。
    2.4 Java语言的特点
    简单性:Java的语法比c++的相对简单,另一个方面就是Java能使软件在很小的机器上运行,基础解释其和类库的支持的大小约为40kb,增加基本的标准库和线程支持的内存需要增加125kb
    分布式:Java带有很强大的tcp/ip协议族的例程库,Java应用程序能够通过url来穿过网络来访问远程对象,由于servlet机制的出现,使Java编程非常的高效,现在许多的大的web server都支持servlet
    面向对象性: Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。总之,Java语言是一个纯的面向对象程序设计语言
    健壮特性:Java采取了一个安全指针模型,能减小重写内存和数据崩溃的可能性
    安全性:Java用来设计网路和分布系统,这带来了新的安全问题,Java可以用来构建防病毒和防攻击的system.事实证明Java在防毒这一方面做的比较好
    中立体系结构:Java编译其生成体系结构中立的目标文件格式可以在很多处理器上执行,编译器产生的指令字节码(Javabytecode)实现此特性,此字节码可以在任何机器上解释执行
    可移植性:Java中对基本数据结构类型的大小和算法都有严格的规定所以可移植性很好
    多线程:Java处理多线程的过程很简单,Java把多线程实现交给底下操作系统或线程程序完成.所以多线程是Java作为服务器端开发语言的流行原因之一
    applet和servlet:能够在网页上执行的程序叫applet,需要支持Java的浏览器很多,而applet支持动态的网页,这是很多其他语言所不能做到的

    3 Android手机游戏引擎及相关技术研究3.1 超级小猫游戏概述3.1.1 游戏主题创意超级小猫游戏属于益智闯关类游戏,与该类其他游戏相似,由玩家操作人物进而完成闯关。玩法类似与超级玛丽游戏,从设计之初,想加入自己独特的创意,让玩法简单的游戏在不经意间给玩家惊喜和乐趣。比如:砖块居然可以在你不知不觉中掉下来砸死玩家控制的小猫;玩家控制走的好好的小猫竟然可以突然掉入一个陷阱;玩家碰到问号里面的道具的时候,出现蘑菇由于惯性吃了后才发现,蘑菇会有毒等创意。我相信大部分玩家会小有惊喜,防不胜防的感觉。
    3.1.2 游戏程序流程本程序采用面向对象的设计模式,对游戏中的所有物体赋予对象的概念和属性。在主程序的控制方法中,游戏将以状态标识变量控制对屏幕显示内容进行控制,实时反映整个游戏的进行状态。下面是程序流程图:

    打开快捷方式,首先出现的是游戏Logo画面,一张代表童年天真无邪的图片出现,运用了渐变效果,渐渐出现,渐渐消失,大约用时5秒游戏自动跳转到主菜单页面。在主菜单页面中,设置了开始游戏和游戏帮助按钮,其中开始游戏是通过算法和绘图方法画进去的,并且有闪烁的效果,游戏帮助是前期准备素材是对主菜单图片处理后加进去的,分别点击进入下一个页面,点击监听都是坐标监听方法。当点击开始游戏时,进入人数显示界面,游戏后台执行Loading类即加载方法 ,对游戏中用到的所有属性进行初始化,方便后面的调用。随后进入游戏主程序。点击游戏帮助,进入帮助界面,提示玩家如何操作,如何过关。游戏中的所有障碍物和敌人都是在游戏主程序显示的同时画好的,整个关卡场景是在一个长宽都为10000像素的画布中,可以随意增、删,增加了游戏的可塑性。当玩家开始游戏时,程序后台不断计算着超级小猫和障碍物、敌人的横纵坐标,即碰撞检测,也是这个游戏的核心所在。当判断玩家、敌人或者障碍物死亡是,会有相应的方法实现,在屏幕中显示方法采用重绘方法,玩家死亡一次,Loading中life会自减1,显示到Loading页面中。游戏主程序页面加入了,打开音乐,关闭音乐,退出游戏3个按钮,也是方便玩家的控制。
    3.2 超级小猫游戏设计流程3.2.1 需求
    功能:实现主人公小猫的前进、跳跃、顶金币、顶砖块、踩敌人、吃道具等功能并有关卡设置
    操作:设置屏幕虚拟键,使玩家通过触控虚拟键对小猫的操作
    音效:游戏有背景音乐,按钮、跳跃和死亡音效
    动画效果:主人公和敌人的移动过程、死亡后的效果,砖块及问号被顶后的效果

    3.2.2 开发环境
    硬件:Microsoft Windows 7操作系统
    IDE:Eclipse
    版本:Android2.2

    3.2.3 游戏设计思想和模块划分本游戏采用Java面向对象思想,将游戏中所有的对象通过划分模块进而创建各自的类。设计初期,通过对程序需求的分析,划分了游戏背景模块、游戏控制模块,游戏地图模块、道具模块、特效模块、玩家模块和敌人模块主要几个模块。而后,通过几大模块,对游戏中的对象归类,通过不断的补充各个类的属性,完善程序。

    3.2.4 游戏具体设计1.游戏背景模块
    该模块主要实现了游戏公共背景,包括天空不断漂浮的白云,主程序显示的分数、金币个数、时间、小山和大山,虚拟键左、右、上、音乐开、音乐关、退出游戏。由BG类和JOY类实现。
    BG类
    该类负责画出分数、金币个数、时间、小山、大山和不断漂浮的3朵白云,之所以要把这些对象放入一个bg类中,是由于它们都是一旦绘制出来就成系统本身的了,不受玩家控制而影响。而把受玩家控制的公共背景对象写入其他类中。金币图片是一个帧动画播放位图,大、小山是用for循环有规律的画入画布中,白云的绘入是静态的,但通过让其横坐标变化和for语句判断来实现循环在页面漂浮的效果。实现代码如下:
    public void upData(){ //分别以1和2的速度循环移动 yunX1-=2; if(yunX1<-50) { yunX1=490; } yunX2-=1; if(yunX2<-50) { yunX2=490; } yunX3-=2; if(yunX3<-50) { yunX3=490; }}
    JOY类
    在此类中画出虚拟键左、右、上、音乐开、音乐关,并加入触屏监听方法。触屏的监听是一个难点,要精确计算出所绘图片宽高像素大小和玩家点击屏幕范围,这样才能准确监听到玩家所需的操作。
    2.游戏控制模块
    该模块主要实现了让游戏能按照预期的顺序播放画面,综合了游戏用到的所有类,根据状态执行各个类的线程。由MC类实现。
    MC类
    该类继承了Android中的SurfaceView绘图类,实现Runnable接口。该类引用了整个程序的所有类。创建初期,定义状态标识线程执行顺序即页面切换,是通过后续类的完成进而对MC类的补充完成。
    3.游戏地图模块
    该模块实现了游戏地图信息的保存,增加了游戏的扩展性。由map类和TiledMap类实现,地图信息保存在一个二维数组中位图大小经过处理为16*16,屏幕宽为480,高为320,即地图信息为20行,列根据关卡的长度自定。
    4.道具模块
    该模块中实现了游戏中道具毒蘑菇的控制。由类DJ类实现,主要控制毒蘑菇的移动方法和碰撞检测。
    5.特效模块
    该模块实现了公共图片金币的动画效果、游戏主人公死亡后显示图片效果、碰到毒蘑菇显示图片效果、杀死敌人后敌人消失效果、碰撞普通砖块后的爆炸效果、碰撞问号砖块后弹出金币效果。主要由TX、TX1、TX2、TX3、TX4、TX5、TXManager几个类分别实现。
    6.玩家模块
    该模块实现了游戏主人公在场景中根据玩家的控制自由移动的功能。由Player类实现。是游戏设计的难点。
    7.敌人模块
    该模块实现了游戏中所有敌人的控制。由NPC、NPC01、NPC02、NPC03、NPC04_guike、NPCManager几个类分别实现。
    3.3 游戏设计过程中遇到的问题和技术难点3.3.1 图片问题为了游戏美观和地图设计的方便,要把图片处理成跟屏幕分辨率宽高都成比例,借助Batch Image Resizer和PS图片处理工具分别对游戏素材进行缩放和去除图片背景的操作。游戏中的主人公是位图播放,Android中提供给我们播放帧动画的方法。在绘图中,Android中的绘图类SurfaceView,默认采用双缓冲技术,保证了屏幕不出现闪烁现象。
    3.3.2 控制问题游戏的控制分为系统控制和玩家控制,由系统控制的只需设置其相应的方法,在需要的时候执行相应的线程。由玩家控制的主要是对主人公的移动控制,在编写代码的过程中,左、右移动相对简单,让它跳起来一度用了不少时间学习,对跳跃加入重力引擎方法 成功解决了实现跳跃高度一定,速度变化,有真实感的跳跃动作。
    3.3.3 碰撞检测游戏的核心重点在于碰撞检测方法的编写,即使绘图再美,玩家控制的再好,到了不该过去的地方照样过去,失去了游戏的规则。碰撞检测包括,游戏主人公与敌人的碰撞、主人公与障碍物的碰撞、敌人与障碍物的碰撞、道具与主人公的碰撞

    由图3.3表示,主人公与障碍物的碰撞,当在范围①时,表示在障碍物上面,可以站在上面进行跳的动作,否则就是在空中,进行下降的动作。当跳到④的范围时,表示玩家与障碍物碰撞,障碍物执行自己的方法(消失或者变化),玩家自由下落。主人公与敌人的碰撞中,当主人公在范围①—④时出于碰撞的临界点,碰撞后玩家和敌人执行自己的方法(死亡图片或者爆炸消失)。敌人与主人公的碰撞与主人公与障碍物的碰撞类似,道具与主人公的碰撞与主人公与敌人碰撞类似。
    3.3.4 音效控制在本程序中对主人公的跳跃,死亡,点击主菜单按钮都加入了音效,此外游戏中还有背景音乐。这个功能的实现用了Android中的SoundPool和MediaPlayer,前者控制音效,后者控制背景音乐。部分代码如下:
    主控制类MC中声明
    //声明SoundPoolstatic SoundPool sp;//记录背景音乐文件idstatic int soundId_long; //记录死亡音乐文件idstatic int death;//记录点击菜单音乐文件idstatic int press;//声明一个音乐播放器private static MediaPlayer mediaplayer;
    主方法中
    //实例SoundPool播放器setSp(new SoundPool(4, AudioManager.STREAM_MUSIC, 100));//加载音乐文件获取其数据ID//跳跃音效的初始化setSoundId_long(getSp().load(context, R.raw.jump, 1));//死亡音效的初始化death = sp.load(context, R.raw.dead, 1);//点击音效的初始化press = sp.load(context, R.raw.coin, 1);//背景音乐的初始化setMediaplayer(MediaPlayer.create(context, R.raw.mario));//设置循环播放getMediaplayer().setLooping(true); (new Thread(this)).start() ;
    然后分别在事件响应中加入响应方法
    //点击音乐开时 if(joy.music1down==true){ MC.getMediaplayer().start();} //点击音乐关时if(joy.music2down==true){ MC.getMediaplayer().pause();}
    在Player类中加入
    //当点击跳跃键时 if(joy.up_down==true){ m=1; vy=-18; //状态为跳跃 changeFS(2); //此时加入点击音效 MC.sp.play(MC.getSoundId_long(), 1f, 1f, 0, 0, 1); }
    3.3.5 页面控制为了实现能按照流程执行,在主控制类MC中用
    switch(CanvasIndex){ case: Berak;}
    语句对页面进行的切换控制。根据各个页面时时反馈的CanvasIndex值确定跳入哪一个页面。CanvasIndex值设置为0、5、10、15、20、25、30、35、40分别代表帮助页面 、logo页面、主页面、加载页面、主程序页面、结束页面、第二关切换页面、第二关加载页面、第二关主页面。用switch控制能简单有效的切换到下一个页面而退出上一个页面。
    3.4 开发过程简述游戏的开发不同于应用开发,游戏里面更多的用了绘图方法和游戏算法,所涉及的类不是分步一个一个写出来的,很多都是同时创建的,并且经过无数次的调试 。类中的属性也是时时根据需要添加进去的,使其功能逐步完善。在次过程中通过学习相关内容,阅读相关代码,不断的尝试和失败中找到属于自己的编写风格,应用到实例开发中。
    4 程序分析和具体实现4.1 游戏Logo页面程序中有个logo类,该类负责绘制logo页面,页面状态为5,程序主控制类MC默认设置状态为5即打开程序后就显示logo页面。在这个类中加入了渐变效果。效果如图4.1所示:

    public void render(Canvas g){ Paint paint=new Paint(); paint.setColor(0xff000000); g.drawRect(0,0,480,320, paint); paint.setAlpha(a); g.drawBitmap(im, 30,0, paint);}public void upData(){ t++; if(t>150) { a-=10 } if(a<=0) { a=0; MC.CanvasIndex=10; t=0; }
    在480*320的画布上面绘制logo页面背景图片,由a值控制显示,下面的线程方法中,用for语句控制a值,t++直到150,这个是图片渐渐出现的效果,当t>150时,a-=10,这个是图片渐渐消失的效果,整个过程大约有5秒钟,然后自动跳转到游戏主菜单。
    4.2 游戏主菜单分析程序中CanvasMain类实现了主菜单的功能。有开始游戏按钮和游戏帮助按钮,开始游戏按钮使用了图片闪烁播放效果。效果如图4.2所示:

    if(t%5==0){ g.drawBitmap(im1,185,180,paint);}public void upData(){ t++; if(t>5) { t=0; }}
    用了if语句判断,使图片交替出现,达到闪烁效果,增加美感。对按钮的监听是用了Android中的触屏监听方法,监听玩家点到屏幕的范围判断操作。
    //按键监听public void penDown(float _x,float _y){ //判断是否点击图片区域坐标 if(Math.abs(240-_x)<40&&Math.abs(193-_y)<40) { //游戏开始,初始化游戏数据 CanvasUI.jinbiCount=0; CanvasUI.life=3; CanvasUI.score=0; CanvasUI.time=0; //跳转页面到Loading MC.CanvasIndex=15; } if(Math.abs(240-_x)<40&&Math.abs(280-_y)<40) { //播放音效 MC.sp.play(MC.press, 1f, 1f, 0, 0, 1); MC.CanvasIndex=0; }}}
    其中
    if(Math.abs(240-_x)<40&&Math.abs(193-_y)<40)

    if(Math.abs(240-_x)<40&&Math.abs(280-_y)<40)
    两个if语句经过仔细计算后得出的,即在按钮大小的范围内,方便能准确监听到用户操作。当点击开始游戏时,初始化游戏数据,设定状态变量MC.CanvasIndex=15,传给MC,告诉MC应该显示开始游戏的页面了。同理,点击 游戏帮助时,由MC控制显示帮助页面。效果如图4.3所示:

    4.3 开始游戏过程的后台逻辑当玩家点击开始游戏后,玩家看到的只有一个载入页面,显示的是玩家现在人数,而后台要经过迅速的线程加载。在MC类中实现。首先,响应的是Loading类,如下图所示:

    初始化地图信息、人物信息、敌人信息、道具信息,随后修改状态变量CanvasIndex值为20,传给MC。
    case 20: if(joy.music1down==true) { //开始背景音乐 MC.getMediaplayer().start(); } if(joy.music2down==true) { //暂停背景音乐 MC.getMediaplayer().pause(); } //执行公共图片绘制 joy.upData(); //执行Player线程 p.upData(this,joy,mp,tm,dm,nm); //执行地图线程 mp.upData(p, cx, cy); //执行背景线程 bg.upData(); //执行地图线程 btm.upData(p, cx, cy,mp,nm,tm); //执行道具线程 dm.upData(mp,p, tm); //执行特效线程 tm.upData(mp,tm,p); //执行胜利线程 win.upData(p, cx, cy); //执行敌人线程 nm.upData(mp,cx,p,tm); //执行主页面线程 cui.upData(p,tm); //跳出 break;

    4.4 游戏过程的后台逻辑当进入游戏,玩家可以通过触屏操作对主人公进行操作,在JOY类中写了触屏监听。实现代码如下:
    public void penDown(float _x,float _y,MC mc) { //左 if(Math.abs(40-_x)<20&&Math.abs(280-_y)<20) { leftdown=true; } //右 if(Math.abs(120-_x)<20&&Math.abs(280-_y)<20) { rightdown=true; } //退出 if(Math.abs(340-_x)<30&&Math.abs(295-_y)<15) { quit_down=true; } //上 if(Math.abs(410-_x)<20&&Math.abs(280-_y)<20) { up_down=true; } //music 开 if(Math.abs(180-_x)<30&&Math.abs(295-_y)<15) { music1down=true; } //music 关 if(Math.abs(260-_x)<30&&Math.abs(295-_y)<15) { music2down=true; } } public void penUp(float _x,float _y) { //左 if(Math.abs(40-_x)<20&&Math.abs(280-_y)<20) { leftdown=false; } //右 if(Math.abs(120-_x)<20&&Math.abs(280-_y)<20) { rightdown=false; } //退出 if(Math.abs(340-_x)<30&&Math.abs(295-_y)<15) { quit_down=false; } //上 if(Math.abs(410-_x)<20&&Math.abs(280-_y)<20) { up_down=false; } //music 开 if(Math.abs(180-_x)<30&&Math.abs(295-_y)<15) { music1down=false; } //music 关 if(Math.abs(260-_x)<30&&Math.abs(295-_y)<15) { music2down=false; }
    监听结果会反馈给Player类,激活类中的移动方法实现移动等动作。游戏中,在玩家移动主人公行走时,地图中看似不动的砖块会随机的掉落 下来随时可能砸死玩家,这就开始调用掉落砖块的方法了。在游戏加载中已经通过BT_MapManager类中的create方法绘制到指定地方,当走到一定的地方,调用Bt_map02类中的掉落砖块的方法,让它自由下路直到砸到玩家或者落出游戏屏幕。与掉落砖块相同的还有踩空砖块,玩家可能在跳跃的过程中无意踩到一个空砖块,踩到的地方会出现一个实砖块,而玩家会在实砖块上继续自动跳第二次。
    游戏场景的问号砖块是暗藏着金币和道具的,吸引玩家不顾一切的顶它 。当顶到金币砖块后,需要调用Player类中的与问号碰撞检测方法。当顶到道具问号砖块后,则调用DJ类的drawBitmap()方法绘出毒蘑菇。效果如图4.6所示:

    upData()方法让毒蘑菇移动,碰到障碍物方向相反,在空中自由下落,碰到玩家消失等效果,当碰到玩家时,此时设置了特效,在TX_Manager类中调用creat()方法绘制一张”居然有毒”的图片。效果如图4.7所示:

    当顶到金币砖块后,金币砖块变成了实砖,被顶砖块上面有一个金币出现的特效,调用TX_Manager类中creat()方法。同时传给程序公共图片类CanvasUI中参数score 和jinbiCount,使得score加200,jinbiCount加1。当分数到了2000或者金币数量到了20后life会加1,增加生命让玩家能在当前关卡多闯几次。效果如图4.8所示:

    游戏的地图信息是在进去游戏的同时画好的,画布总大小是10000*10000,高度是一定的,是320,宽度是根据map类中保存的地图二维数组信息的列数决定,我们也可以根据需要增删游戏地图长短。
    当移动过程中碰到敌人,玩家可以选择跳过敌人或者踩死敌人两个方法。敌人的出现是根据主人公的横坐标判断的,增加游戏的可玩性,玩的次数多了会发现靠小技巧可以躲过一些敌人的出现。另外,游戏中还还原了超级玛丽中特有的水管,不过本程序中玩家不能下水管,但增加了另外的创新,有些水管会根据自己坐标和玩家横纵坐标的比较来判断是否突然从水管里面冲出一朵食人花,由于玩家跳跃的高度和跳跃的距离是一定的,如果由于玩家起跳的地方不是很好,会被告诉的食人花吃掉,可谓惊险。具体代码实现是在NPCManager类中调用creat()方法在指定的坐标绘出食人花,并且通过类Bt_map03类的控制方法,控制食人花的出现效果。代码如下:
    public void upData(Player p,float cx, float cy,map mp,NPCManager nm) { if(Math.abs(x-p.x)<15&& Math.abs(y-(p.y-30))<15) { y-=15; } }
    食人花以15的速度冲出水管直到消失到屏幕外面。
    在本程序场景中见的最多的就是普通的红砖,玩家可以通过控制跳跃键顶砖块或者跳到砖块上面,顶到的砖块会出现爆炸效果然后消失,玩家在被顶的砖块地方自由下落。
    此外,本程序中加入了一定的陷阱,即玩家在控制小猫正常移动中会突然陷入脚下的陷阱,陷阱的加入是按照固定的坐标位置加的,但对于玩家来说,出现的几率是随机的,只有在一个陷阱中死亡才能在下次的过关做准备,在一定程度上增加了游戏的难度和可玩性。
    小猫的死亡决定条件有:掉入空白场景、碰到敌人而不是踩到敌人、被下落砖块砸到、被毒蘑菇毒死,小猫死亡,重新载入加载页面和场景地图,所有属性将被重新初始化,游戏重新开始。死亡后会调用TX_Manager类中g.drawBitmap()方法绘出一个显示“啊哦!挂了,下次努力哟!”的图片,同时调用Player类中死亡方法,让小猫先上升一段距离,然后下降,直到降出屏幕外。音效是调用TX_Manager类中switch语句中死亡时的方法MC.sp.play(MC.death, 1f, 1f, 0, 0, 1)。效果如图4.9所示:

    小猫死一次,生命输减1,直到初始值3的生命值减为0,游戏结束。游戏也设置了关卡,判断方法是Player类中的方法。
    //判断是否过关 if(x+mc.cx>4800&&y>320) if(x+mc.cx>4800) { nextLevel=true; if(nextLevel==true) { MC.CanvasIndex=30; } } }
    主要依据的是小猫的坐标,当坐标符号判断条件,就意味着已经到达某个地方,当前场景结束,跳入下个场景加载画面,游戏继续,若游戏在第二关中还有生命值,死亡后从第二关开始,由Player类中的两个if语句判断。
    //根据第几关判断死亡后该回到第几关 if(MC.CanvasIndex==40) { MC.CanvasIndex=35; } if(MC.CanvasIndex==20) { MC.CanvasIndex=15; }
    即根据当前的场景状态变量,决定死亡后初始化哪个页面,这样增加了游戏功能的完整性和给玩家继续玩下去的信心。
    当游戏结束,Player类把状态变量CanvasIndex变为25传给MC,显示GameOver页面。调用GameOver类中的绘图方法,绘出背景图片,使用下面的逻辑方法,使页面在5秒后自动跳转到主菜单页面。
    public void upData() { t++; a+=20; if(a>=255) { a=255; } lx+=vlx; rx+=vrx; if(lx>450||lx<0) { vlx=-vlx; } if(rx>450||rx<0) { vrx=-vrx; } if(t>150) { MC.CanvasIndex=5; t=0; } }}
    5 总 结5.1 安装到真实Android手机测试使用Eclipse完成代码的调试之后,下载包中自动生成的Myw_Mario.apk文件下载到Android手机中安装。然后点击快捷方式运行。
    5.2 发现BUG和解决情况经过在真实手机上面的运行,发现手机上面贞播放速度大于虚拟机上面的速度,玩家用手指触动屏幕操作起来更方便,画面更流畅。但是由于在设计之初,只想着能实现基本功能,对于代码的利用率和更好的功能没有过多的研究,故还是有很多的不足之处需要日后改善。
    目前发现的bug和未完善的功能列表如下:

    虚拟按键有时候会出现连键的情况,具体表现为不按会一直走,或者一直跳知道再次按下键后才会停止。分析原因可能是在触屏监听的线程中有问题,但至今没用发现
    如果玩家操作很犀利,使得小猫移动速度过快,可能会导致出现的敌人运动轨迹不正常,停止不前
    敌人的种类比起当年的原版超级玛丽游戏少很多
    缺少一直想加入的变大蘑菇道具和可以使主人公发出炮弹的道具

    参考文献[1] 汪永松,Android平台开发之旅[M]机械工业出版社,2010.8
    [2] 李华明,Android游戏编程之从零开始,清华大学出版社,2011,12
    [3] 李兴华,Android开发实战经典,清华大学出版社,2012,3
    [4] 杨丰盛,Android应用开发揭秘[M] 机械工业出版社,2010,1
    [5] 吴亚峰 苏亚光,Android 2.0游戏开发实战宝典[M] 人民邮电出版社,2010,7
    [6] 郭宏志,Android应用开发详解[M] 电子工业出版社,2010,6
    [7] 赵亮 张维, 基于Android技术的界面设计与研究[J] 电脑知识与技术,2010.6
    [8] 詹璇 黄颖, 基于J2ME的RPG手机游戏架构设计与技术实现[J]电脑编程技巧与维护,2010.7
    [9] 李硕, Java程序在手机游戏中的应用[J]电脑编程技巧与维护,2010.5
    [10] 苏志同 石绍坤,手机游戏开发架构的研究[J] 计算机工程与设计,2010.4
    1 评论 1 下载 2019-02-16 21:09:01 下载需要8点积分
  • 基于JSP实现的学院成绩管理系统

    一、需求分析学院管理系统中学生成绩管理是重要的一环,本系统旨在让学生、班级管理员、学院管理员可以从不同角度查看学生的成绩信息。同时学院管理员可以进行信息的录入。三种用户分别拥有相应的权限。
    1.1 用户角度需求分析用户分为三种:普通学生、班级管理员和学院管理员。其中:
    班级学生可以查看自己已修课程的成绩,并且通过第几学期、课程是否通过、课程类别来组合查询自己的课程情况。下载自己成绩单。
    班级管理员可以查看班级的总体成绩和班级学生的基本成绩信息。下载班级成绩单。以及进入自己所管理班级学生主页的权限。
    学院管理员可以通过 Excel 文件录入学生信息、课程信息、学生成绩信息,还可以录入学生项目信息和学生竞赛信息。可以查看各专业基本成绩信息,查看每班的基本成绩信息,查看每班各学科详细成绩。分专业下载成绩单。以及进入班级管理员主页和学生主页的权限。
    1.2 界面功能角度分析
    登录界面:提供三个权限的用户登录
    注册界面:提供普通学生和班级管理员注册
    修改密码界面:提供普通学生和班级管理员修改密码
    学生界面:查看自己已修课程的成绩,通过第几学期、课程是否通过、课程类别来组合查询自己的课程情况。已修学分和剩余应修学分。下载成绩单
    班级管理员界面:查看班级基本信息,查看班级各个学生的基本成绩。下载成绩单。进入班级学生界面
    学院管理员界面:分专业查看专业基本信息,每个专业各班的基本成绩,查询班级各科成绩。通过 Excel 文件批量录入学生信息、课程信息、成绩信息。单个录入学生项目、竞赛情况信息。进入班级管理员界面

    二、系统功能分析系统的功能都是围绕从不同角度查看学生成绩信息开发的。同时进行了不同用户权限的控制和数据插入数据库之前的校验。
    权限由低至高一共分为三层:班级学生、班级管理员、学院管理员。高级权限用户可享有较低级权限用户的所有服务。
    2.1 注册在注册界面选择类别进行注册。普通学生选择班级学生,输入账号密码注册。班级管理员选择班级管理员,输入账号密码注册。学院管理员有默认账号,不提供注册服务。
    错误提示及原因:

    学号不存在:学院管理员未导入此学号相关信息
    学号已注册过:此学号已被注册
    格式错误:账号、密码不和要求


    2.2 登录在登录界面选择类别进行登录。普通学生选择班级学生,输入账号密码登录。班级管理员选择班级管理员,输入账号密码登录。学院管理员选择任一个输入账号密码登录。类别选择错误会登录失败。
    错误提示及原因:

    账号或密码错误:账号密码不匹配或类别选择错误
    学号未注册学号未注册:此学号未注册过
    格式错误:账号、密码不和要求
    班级不存在:当前系统中未查找到需要注册的班级的信息


    2.3 修改密码修改密码的进入方式为在学生界面和班级管理员界面点击右上角导航栏后选择修改密码一项进入。学院管理员不提供修改密码服务。
    错误提示及原因:

    旧密码错误:输入的旧密码和当前用户的密码不一致
    格式错误:旧密码、新密码格式不和要求

    2.4 我的主页我的主页的进入方式为在自己主页点击右上角导航栏后选择我的主页一项进入。
    当前用户若跳转到其他页面时提供跳转至自己主页的服务。
    2.5 账号注销账号注销的进入方式为在学生界面点击右上角导航栏后选择账号注销一项进入。
    用户一旦注销会删除他/她在本系统的所有信息。另外两类用户不提供注销服务。
    2.6 退出系统退出系统的进入方式为在自己主页点击右上角导航栏后选择退出系统一项进入。
    用户一旦点击退出系统,会删除本次会话在服务器端保存的所有的信息。页面会自动跳转至登录界面。

    2.7 学生主页班级学生可从登录界面进入,班级管理员可以从班级管理员界面进入,学院管理员进入班级管理界面后再进入。
    学生主页提供查看自己的基本成绩信息服务;由学期、类别、是否通过组合查询自己的成绩服务;已修学分、剩余应修学分服务;成绩单下载服务。

    2.8 班级管理员主页班级管理员可从登录界面进入。学院管理员可从学院管理界面进入。
    该主页提供查看本班级的基本成绩信息服务;查看每个同学的基本成绩信息服务;成绩单下载服务。点击学号可进入班级成员主页。
    其中基本信息是整个班级的基本信息。各同学详细信息一项只能查看已注册过本系统的学生,无法查看到未注册的学生的详细成绩信息。

    2.9 学院管理员主页学院管理员主页只能由学院管理员由登录界面进入。
    该主页分成三个子页面:计科页面、信息页面、信息录入页面。其中:

    计科页面查看计科基本信息,计科各班级基本信息,计科某班级某课程详细信息
    信安页面查看信安基本信息,信息各班级基本信息,信安某班级某课程详细信息
    信息录入界面可以通过 Excel 文件批量录入学生信息、课程信息、成绩信息。可以逐个添加学生项目信息、学生竞赛信息。

    说明:查看班级详细信息时只有存在选课记录的班级能被显示出来。无选课记录的班级不会显示。Excel 文件只能是 xlsx 文件,不能是 xls 文件。三种文件的格式要求必须和附录中提供的模板文件一致,且成绩文件中的学号和课程号录入必须先于成绩文件。
    数据导入的提示及原因:

    文件数据已导入:整个文件中的数据全部正确导入数据库
    文件不为空:未选择文件就点击上传按钮
    文件格式不允许:上传的文件不是 xlsx 文档
    数据导入失败:以上两种原因的错误外,其他任何异常都会被归纳为“数据导入失败” 。一般是由数据格式错误或者导入顺序不正确引起的问题

    专业基本信息

    班级课程详细成绩

    信息录入

    三、系统图解3.1 系统流程图图解
    3.2 核心功能图解
    四、数据库设计4.1 实体分析
    学生:学号、姓名、专业、班级、密码、GPA、级别
    课程:课程号、课程名、类型、学分、重要系数
    班级:班号
    班级管理员:账号、密码、级别
    竞赛:名称、年度
    项目:名称、年度

    一个学生可以选多门课,一门课可以被多个学生选择,每次选课有成绩、选课号、学期、学年。一个班级有多个学生,每个学生只有一个班级。一个班级有一个管理员,一个管理员管理一个班级。一个项目可以有多个学生参与,一个学生可以参与多个项目,每次参与项目有级别、是否是负责人。一个竞赛可以被多个学生参与,一个学生可以参与多个竞赛,每次参与有奖项、是否是负责人。
    4.2 E-R 图
    4.3 表结构
    学生:学号、密码、姓名、专业、班级、GPA、级别
    课程:课程号、课程名、类型、重要系数、学分、年度
    选课:选课号、学生学号、课程号、分数、年度、学期
    班级管理员:账号、密码、班号、级别
    竞赛和项目:ID、名称、年度、奖项/级别、是否是负责人、学号、类别

    4.4 视图系统中一共建了四个视图,以其中一个举例说明。班级管理员界面可以看见自己所管理班级学生的基本成绩信息来至于视图 classstudentscores。此视图是由学生表、课程表和选课表连接得到的。具体定义如下:
    SELECT `scores`.`stu_id` AS `id`, `studentinfos`.`name` AS `name`, `studentinfos`.`pwd` AS `pwd`, `studentinfos`.`cclass` AS `cclass`, `studentinfos`.`gpa` AS `gpa`, sum(`courses`.`credit`) AS `allcre`, `getStudentPassRate` (`scores`.`stu_id`) AS `passrate` FROM( (`studentinfos` JOIN `scores`) JOIN `courses` ) WHERE ( ( `studentinfos`.`id` = `scores`.`stu_id` ) AND ( `scores`.`cou_id` = `courses`.`id` ) AND (`scores`.`score` > 59) ) GROUP BY `scores`.`stu_id`
    4.5 触发器系统中使用到一次触发器。在学院管理员录入学生成绩时,触发器根据新录入的学生成绩的系统中已有的学生选课成绩计算出录入之后的学生 GPA。由于三种权限的用户都要使用 GPA,所以此触发器处在整个系统的核心位置。定义如下:
    BEGIN DECLARE myavg DOUBLE; DECLARE allcre DOUBLE; DECLARE temp DOUBLE; DECLARE sum DOUBLE DEFAULT 0.0; DECLARE t INTEGER; DECLARE t1 DOUBLE; DECLARE t2 DOUBLE; DECLARE done INT DEFAULT FALSE; DECLARE cur CURSOR FOR SELECT scores.score, courses.coefficient, courses.credit FROM studentinfos, scores, courses WHERE studentinfos.id = scores.stu_id AND scores.cou_id = courses.id AND stu_id = new.stu_id; DECLARE CONTINUE HANDLER FOR NOT found SET done = TRUE; SELECT SUM(courses.credit) INTO allcre FROM studentinfos, scores, courses WHERE studentinfos.id = scores.stu_id AND scores.cou_id = courses.id AND stu_id = new.stu_id AND score >= 60; OPEN cur; read_loop : LOOP FETCH cur INTO t, t1, t2; IF done THEN LEAVE read_loop; END IF; IF (t < 60) THEN SET temp = 0.0; END IF; IF (t >= 60) THEN SET temp = 1.0; END IF; IF (t >= 64) THEN SET temp = 1.6; END IF; IF (t >= 66) THEN SET temp = 1.7; END IF; IF (t >= 68) THEN SET temp = 2.0; END IF; IF (t >= 72) THEN SET temp = 2.3; END IF; IF (t >= 75) THEN SET temp = 2.7; END IF; IF (t >= 78) THEN SET temp = 3.0; END IF; IF (t >= 82) THEN SET temp = 3.3; END IF; IF (t >= 85) THEN SET temp = 3.7; END IF; IF (t >= 90) THEN SET temp = 4.0; END IF; SET sum = temp * t1 * t2 + sum; END LOOP; CLOSE cur; SET myavg = sum / allcre; UPDATE studentinfos SET gpa = myavg WHERE id = new.stu_id; END
    4.6 储存过程系统使用到一次存储过程。在获得班级总体的 GPA 时,使用了带输入输出参数的储存过程。参数:IN classID varchar(20),OUT avgpa double。定义如下:
    BEGIN DECLARE cgpa DOUBLE; DECLARE sum DOUBLE DEFAULT 0.0; DECLARE done int DEFAULT FALSE; DECLARE num int; DECLARE cur cursor for select gpa from studentinfos where cclass = classID; DECLARE CONTINUE HANDLER for not found set done = TRUE; set sum=0.0; set num=0; open cur; read_loop:LOOP FETCH CUR INTO cgpa; if done then leave read_loop; end if; set sum = sum + cgpa; set num = num+1; end loop; close cur; set avgpa = sum/num; SELECT sum,avgpa; END
    4.7 游标系统中使用到了两次游标及游标的循环。以触发器中的游标举例说明:
    DECLARE t INTEGER;DECLARE t1 DOUBLE;DECLARE t2 DOUBLE; DECLARE done INT DEFAULT FALSE; DECLARE cur cursor for SELECT scores.score, courses.coefficient, courses.credit from studentinfos, scores, courses where studentinfos.id = scores.stu_id and scores.cou_id = courses.id and stu_id = new.stu_id; DECLARE CONTINUE HANDLER for not found set done = TRUE; SELECT SUM(courses.credit) into allcre from studentinfos, scores, courses where studentinfos.id = scores.stu_id and scores.cou_id = courses.id and stu_id = new.stu_id and score>=60; open cur;read_loop:loop FETCH cur into t, t1, t2;
    4.8 函数系统中共使用了 6 次函数。以获得班级挂科率函数举例说明:
    参数:classID varchar(20);
    返回类型:double
    定义:
    BEGIN DECLARE sum DOUBLE; DECLARE notPassSum DOUBLE; select COUNT(*) into sum from scores,studentinfos where scores.stu_id = studentinfos.id and studentinfos.cclass = classID; select COUNT(*) into notPassSum from scores,studentinfos where scores.stu_id = studentinfos.id and studentinfos.cclass = classID and scores.score < 60; if(sum = 0) THEN return 0; end if; RETURN notPassSum/sum; END
    4.9 索引系统中除了 Navicat 自动添加的索引之外,使用到一次索引。在选课表中给成绩加上降序索引,因为后台获得的数据很多都是按成绩降序获得的。
    定义:
    create INDEX scoredesc on scores(score DESC);

    4.10 关系完整性
    实体完整性:主键均不为空且不重复
    域的完整性:成绩信息中的成绩、年度、学期默认值为 NULL
    参照完整性:成绩信息中的学号参照学生信息中的学号,成绩信息中的课程号参照课程信息中的课程号,项目竞赛信息中的学号参照学生信息中的学号。删除和更新采取的操作都是级联

    成绩表的完整定义如下:
    CREATE TABLE `scores` ( `id` INT (20) NOT NULL AUTO_INCREMENT, `stu_id` VARCHAR (20) NOT NULL, `cou_id` VARCHAR (20) NOT NULL, `score` INT (20) DEFAULT NULL, `year` INT (20) DEFAULT NULL, `term` INT (20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `cou_id` (`cou_id`), KEY `stu_id` (`stu_id`), KEY `scoredesc` (`score`), CONSTRAINT `scores_ibfk_1` FOREIGN KEY (`cou_id`) REFERENCES `courses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `scores_ibfk_2` FOREIGN KEY (`stu_id`) REFERENCES `studentinfos` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = INNODB AUTO_INCREMENT = 443 DEFAULT CHARSET = utf8
    五、后台开发数据库一端除了使用基本的增删查改,还使用到了视图、触发器、存储过程、游标、函数、索引等技术。但如索引等技术不能在后台代码中很好的体现出来。所以将能在后台中明显体现的技术列举如下。
    例中的代码为 Dao 层的代码,即 sql 语句。事务的控制在 Service 层中。
    5.1 增加系统中多次使用到了增加,以导入学生信息时的增加为例说明:
    public void insert(StudentInfos studentInfo) throws SQLException{ String sql = "insert into studentinfos values(?,?,?,?,?,?,?)"; queryRunner.update(sql, studentInfo.getId(), studentInfo.getPwd(), studentInfo.getName(), studentInfo.getMajor(), studentInfo.getCclass(), studentInfo.getGpa(), studentInfo.getLevel()); }
    5.2 删除系统中多次使用到了删除,以学生注销时的级联删除为例:
    public void signOffAccount(String id) throws SQLException{ String sql1 = "delete from candp where stuid = ?"; String sql2 = "delete from scores where stu_id = ?"; String sql3 = "delete from studentinfos where id = ?"; queryRunner.update(sql1, id); queryRunner.update(sql2, id); queryRunner.update(sql3, id); }
    5.3 修改系统中使用到的修改不多,以修改密码为例:
    public void updatePwd(String id, String pwd) throws SQLException{ String sql = "update studentinfos set pwd = ? where id = ?"; queryRunner.update(sql, pwd, id); }
    5.4 查找系统中多次使用到了查找。以查询班级某课程详细信息为例:
    public ArrayList<StudentCourseScoresBean> getCourseScores(String id) throws SQLException{ String sql = "select name as name, type as type, score as score, coefficient as coefficient, score as gpa, " + "credit as credit from courses, scores where courses.id = scores.cou_id and stu_id = ?"; ArrayList<StudentCourseScoresBean> query = (ArrayList<StudentCourseScoresBean>) queryRunner.query(sql, new BeanListHandler<StudentCourseScoresBean>(StudentCourseScoresBean.class), id); return query; }
    5.5 调用函数系统多次调用了函数,以获得班级挂科率为例:
    public Double getClassNotPassRate(String id) throws SQLException{ String sql = "select getClassNotPassRate(?) as rate"; Double rate = (Double) queryRunner.query(sql, new ScalarHandler("rate"), id); return rate; }
    5.6 调用存储过程系统中使用到了一次存储过程,代码如下:
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql:///College-Achievement-Management-System"; private static final String USERNAME = "root"; private static final String PASSWORD = "root"; public Double getClassGpa(String id) throws SQLException, ClassNotFoundException{ Class.forName(DRIVER_CLASS); Connection connection = (Connection) DriverManager.getConnection(URL, USERNAME,PASSWORD); String sql = "{CALL getClassGpa(?,?)}"; CallableStatement cstm = connection.prepareCall(sql); cstm.setString(1, id); cstm.registerOutParameter(2, Types.DOUBLE); cstm.execute(); return cstm.getDouble(2); }
    5.7 事务控制系统中多次使用到了事务的控制,以增加成绩信息为例。
    public void add(ScoresInfos si) { Session session = HibernateUtils.getCurrentSession(); Transaction tx = session.beginTransaction(); try { cid.add(si); } catch (SQLException e) { e.printStackTrace(); tx.rollback(); } tx.commit(); }
    说明:Transaction 是一个事务控制的类。beginTransaction()是用来开启事务的方法。rollback()事务回滚的方法。commit()是事务提交的方法。
    六、总结6.1 课程设计成果此次数据库原理课程设计没有选择实验指导书上的题目,而是自己想了一个题目,但题目灵感来至于实验指导书上的学校成绩管理系统。因为综合自己能力和时间考虑,做学校级别的难度太大,所以选择做了学院级别的成绩管理系统。
    该系统以 Java Web 技术为基础,实现了学院成绩管理的基本功能。学院管理员可以进行信息的录入,专业信息的查询。班级管理员可以进行班级信息的查询。学生可以进行自己成绩的查询。而且高权限用户兼有低权限用户的功能。
    除了功能的开发,对于非法访问也进行了处理,如低权限用户访问高权限用户页面等情况做了拦截处理。提高了系统的健壮性。
    6.2 不足及改进方法系统的健壮性不够。尤其是在通过 Excel 文件导入数据的时候,对相应错误处理不够完善,用户只知道有问题,不知道到底哪里出了问题。没有使用 Spring 层框架,导致代码冗余情况较为严重。没有使用到高级查找如外链接的技术。
    改进方法为不通过 Excel 文件导入数据,通过 csv 文件导入数据更方便快捷,而且容错性较好。使用 Spring 框架减小代码冗余性。增加系统功能以使用到外链接的功能。
    6.3 收获过本次数据库系统课程设计掌握了基础的以 Java 为核心的网站开发技术。掌握了数据库的分析设计。并且综合应用数据库系统原理课上讲的增删查改、视图、存储过程、游标、触发器、函数、关系完整性等知识到数据库系统课程设计中。提升了自己的专业竞争力。
    1 评论 1 下载 2019-02-15 21:03:33 下载需要12点积分
  • 基于LL(1)的语法分析器

    需求分析构造一个自定义语法分析程序,实现语法分析器,基于LL(1)语法分析方法对输入语句进行分析,并输出结果。
    内容描述此程序用java编写。程序读取一个文本文件,并对其中的序列进行语法分析。使用LL(1)方法自顶向下进行分析,输出分析过程中的匹配情况和产生式序列。
    思路方法
    自定义文法G
    对G进行预处理,消除左递归、二义性,形成文法G’
    计算所有非终结符的First、Follow
    构造Prediction Parsing Table
    根据输入队列和状态栈的栈顶元素进行分析,将终结符进行匹配或非终结符产生子项,循环处理至输入队列队尾
    输出匹配情况和产生式序列

    假设输入的序列仅含有i, +, -, *, /, (, )符号, 相关分析过程描述:
    自定义文法
    G:E-> E+T | E-T | TT-> T*F | T/F | FF-> (E) | T文法预处理
    G’:E-> TCC-> AC | εa-> +T | -TT-> FDD-> BD | εB-> *F | /FF-> (E) | i计算非终结符的First、Follow集




    First
    Follow




    E
    {(,i }
    {+,-,ε}


    T
    {(,i }
    {+,-,),$ }


    F
    {(,i }
    {+,-,*,/,),$ }


    A
    {+,- }
    {+,-,),$ }


    B
    {+,-,*,/,),$ }
    {+,-,*,/,),$ }


    C
    {+,-,ε}
    {+,-,*,/,),$ }


    D
    {*,/,ε}
    {+,-,),$ }



    构造PPT




    +
    -
    *
    /
    (
    )
    i
    $




    E




    E->TC

    E->TC



    C
    C->AC
    C->AC



    C->ε
    C->ε



    A
    A->+T
    A->-T








    T




    T->FD

    T->FD



    D
    D->ε
    D->ε
    D->BD
    D->BD

    D->ε

    D->ε


    B


    B->*F
    B->/F






    F




    F->(E)

    F->i



    程序实现数据结构定义PPT格式如下public class ParsingTable { static String[][] table= {{"null", "null", "null","null","E->TC","null","E->TC"}, {"C->AC","C->AC","null","null","null","C->#","null"}, {"A->+T","A->-T","null","null","null","null","null"}, {"null","null","null","null","T->FD","null","T->FD"}, {"D->#","D->#","D->BD","D->BD","null","D->#","null"}, {"null","null","B->*F","B->/F","null","null","null"}, {"null","null","null","null","F->(E)","null","F->i"}}; public ParsingTable(){ } public String getExpression(int i,int j){ return table[i][j]; }}
    输出格式List<String>
    核心算法 public LLParser(){ output=new ArrayList<>(); identifier=new Stack<>(); identifier.push('E'); table=new ParsingTable(); } public void analyze(String input){ int i=0; while (input.charAt(i)!='$'){ char x=input.charAt(i); char y=identifier.peek(); if (x==y){ identifier.pop(); output.add("match: "+x); i++; continue; } else if (table.getExpression(getIndex(y),getIndex(x)).equals("null")){ output.add("error"); break; }else{ String expr=table.getExpression(getIndex(y),getIndex(x)); output.add(expr); String id=expr.substring(expr.indexOf(">")+1); identifier.pop(); for (int j=id.length()-1;j>=0;j--){ if (id.charAt(j)!='#') identifier.push(id.charAt(j)); } continue; } } }
    程序有状态栈和输入队列,在输入字符序列最后加上终止符,放进队列,状态栈中压入非终结符。读取栈和队列的第一个元素分析,如果匹配成功,则弹出,如果不匹配,则查询PPT表的相关产生式,把新元素压栈,循环至队尾。如果过程中终结符不匹配,则输出error。
    测试运行输入内容如下:

    输出内容如下:

    1 评论 2 下载 2019-02-15 08:26:25 下载需要3点积分
  • 基于C++的小型公司工资管理系统

    一、要求1.1 问题描述某公司需要存储雇员的编号、姓名、性别、所在部门,级别,并进行工资的计算。其中,雇员分为经理、技术人员、销售人员和销售经理。四类人员的月薪计算方法如下:经理拿固定月薪;技术人员按小时领取月薪;销售人员按其当月销售额的提成领取工资;销售经理既拿固定月薪也领取销售提成。
    设计一程序能够对公司人员进行管理,应用到继承、抽象类、虚函数、虚基类、多态和文件的输入/输出等内容。
    1.2 功能要求
    添加功能:程序能够任意添加上述四类人员的记录,可提供选择界面供用户选择所要添加的 人员类别,要求员工的编号要唯一,如果添加了重复编号的记录时,则提示数据添加重复并取消添加
    查询功能:可根据编号、姓名等信息对已添加的记录进行查询,如果未找到,给出相应的提 示信息,如果找到,则显示相应的记录信息
    显示功能:可显示当前系统中所有记录,每条记录占据一行
    编辑功能:可根据查询结果对相应的记录进行修改,修改时注意编号的唯一性
    删除功能:主要实现对已添加的人员记录进行删除。如果当前系统中没有相应的人员记录, 则提示“记录为空!”并返回操作;否则,输入要删除的人员的编号或姓名,根据所输入的信息删除该人员记录,如果没有找到该人员信息,则提示相应的记录不存
    统计功能:能根据多种参数进行人员的统计。例如,统计四类人员数量以及总数, 或者统计男、女员工的数量,或者统计平均工资、最高工资、最低工资等信息
    保存功能:可将当前系统中各类人员记录存入文件中,存入方式任意
    读取功能:可将保存在文件中的人员信息读入到当前系统中,供用户进行使用

    1.3 问题的解决方案根据系统功能要求,可以将问题解决分为以下步骤:

    应用系统分析,建立该系统的功能模块框图以及界面的组织和设计
    分析系统中的各个实体及它们之间的关系
    根据问题描述,设计系统的类层次
    完成类层次中各个类的描述
    完成类中各个成员函数的定义
    完成系统的应用模块
    功能调试
    完成系统总结报告

    二、题目分析首先需要用到面向对象编程的知识,题目中明确可以看出需要定义职员基类,包含编号、姓名、性别、所在部门,级别的基本属性,派生出经理和销售人员以及技术人员,销售经理兼有销售人员和经理的性质,即销售经理继承于销售人员和技术人员,派生类除基本属性还有其他工资计算所需要的数据,数据的存储可以采取链表的形式,方便查找和储存,其他功能的实现都是基于链表节点的查找、修改、删除。最后的保存和读取采取文件流。
    程序流程图

    三、类结构设计类之间的继承关系


    其中,employee类为基类,定义了的编号、姓名、性别、所在部门,级别提供了基类指针,基本信息输入输出的接口。
    technician类采用公有继承方式继承自employee类,增加了工作时间和每小时工资数,基本工资计算,以及工作时间的输入。
    manager采取虚继承方式继承自employee类,增加了基本工资数,基本工资计算,以及基本工资的输入。
    salesman采取虚继承方式继承自employee类,增加了销售额数和销售额提成,基本工资计算,以及销售额的输入。
    Salesmanager多重继承方式继承自manager和salesman,公有继承manager和salesman的输入,增加基本工资计算。
    四、工程结构组织本项目使用Dev-CPP实现,工程文件结构图如图2所示。

    本项目一共由28个文件组成,各文件的功能如下表(表1)所示。



    序号
    文件名
    说明




    1
    Face.h
    Face类定义头文件,其他界面函数声明,界面声明


    2
    Face.cpp
    Face类实现文件,标题,进度条及界面实现


    3
    employee.h
    职员类定义头文件


    4
    employee.cpp
    职员类实现文件


    5
    manager.h
    经理类定义头文件


    6
    manager.cpp
    经理类实现文件


    7
    salesman.h
    销售人员类定义头文件


    8
    salesman.cpp
    销售人员实现文件


    9
    technician.h
    技术人员类定义头文件


    10
    technician.cpp
    技术人员类实现文件


    11
    salesmanager.h
    销售经理类定义头文件


    12
    salesmanager.cpp
    销售经理类实现文件


    13
    control.h
    控制类定义头文件


    14
    controldestory.cpp
    控制类析构函数实现文件


    15
    addinformation.cpp
    控制类添加信息函数实现文件


    16
    cancelinformation.cpp
    控制类删除信息函数实现文件


    17
    changeinformation.cpp
    控制类修改信息函数实现文件


    18
    countinformation.cpp
    控制类统计信息函数实现文件


    19
    errorjudge.cpp
    输入整数时其他字符处理函数实现文件


    20
    gotoxy.cpp
    光标跳转到xy函数实现文件


    21
    keepinformation.cpp
    控制类保存信息函数实现文件


    22
    readinformation.cpp
    控制类读取信息函数实现文件


    23
    searchinformation.cpp
    控制类查询信息函数实现文件


    24
    showface.cpp
    控制类界面函数实现文件


    25
    showinformation.cpp
    控制类显示信息函数实现文件


    26
    tongjiformation.cpp
    控制类统计信息函数实现文件


    27
    main.cpp
    主函数文件


    28
    myheader.h
    头文件文件



    五、运行主要界面载入程序进度条

    程序主界面

    添加信息界面

    查询信息界面

    删除信息界面

    修改信息界面

    显示信息界面

    计算工资界面

    统计员工信息界面

    六、课程设计所用知识点总结


    序号
    知识点
    说明




    1
    函数
    本系统共定义了52个函数,包括带返回值、传入基本数据类型、指针、操作符重载、虚函数纯、虚函数、函数重载。


    2
    数组
    本项目定义了6数组,用于存储姓名、性别、部门等


    3
    文件
    本项目使用txt格式文件做数据持久化存储


    4
    类与对象
    定义了7个类


    5
    安全头文件包含
    基本全部实现头文件安全包含


    6
    继承
    使用单继承、虚继承、多重继承。


    7
    文件流
    使用文件流输入输出


    8
    存储方式
    使用数据结构链表实现存储


    9
    异常处理
    Try catch throw语句进行整数错误检测


    10
    操作符重载
    重载插入提取运算符



    七、项目总结这次课设首先做好了程序基本分析,设计类的继承派生关系,想到用链表进行存储从最基本的显示功能开始采取瀑式开发逐步添加新的功能,直到实现所有功能。
    本次课设较为满意的地方是整体界面实现以及功能的完整性,我认为是难点的是文件流的读入读出方式,以及链表的一系列功能。
    本次最大的收获是对面向对象编程有了较好的理解,充分将学到的知识转化为了实体。
    1 评论 1 下载 2019-02-14 19:11:11
  • 基于C语言实现的校园导航系统

    1 需求分析
    设计并显示某学校的校园平面图

    地点(地点名称、地点介绍)路线(公里数)均不少于10个(文件存储)
    提供图中任意地点相关信息的查询
    提供图中任意地点的问路查询

    任意两个地点之间的一条最短的简单路径(最短路径长度——中转次数最少)任意两个地点之间的一条最佳访问路线(带权(公里数)最短路径长度)任意两个地点之间的所有简单路径
    提供图中所有地点的最佳布网方案
    增加新地点和路线、撤销旧地点和路线

    2 概要设计2.1 功能模块图
    2.2 各个模块详细的功能描述。
    地点信息:通过输入地点编号或者名称查看地址信息的简介
    显示地图:展示每个地点间的路线及长度
    中转最少:任意两个地点之间的一条最短的简单路径;(最短路径长度——中 转次数最少)
    最佳路径:任意两个地点之间的一条最佳访问路线;(带权(公里数)最短路径长度
    全部路径:任意两个地点之间的所有简单路径
    最佳布网:提供图中所有地点的最佳布网方案
    添加删除:增加新地点和路线、撤销旧地点和路线

    3 详细设计3.1 功能函数的调用关系图
    3.2 重点设计及编码求两点之间的所有路径:深度优先遍历
    //寻找所有简单路径void FindAllPath(AdjList *G){ char star[20], end[20]; printf("输入【起点】-->【终点】:"); scanf("%s %s", star, end); int starIndex = SelectIndex(G, star); int endIndex = SelectIndex(G, end); if(!starIndex || !endIndex) { printf("输入地点不存在\n"); return; } int i, w, v, instack[MAXEX + 1]; v = starIndex; w = -1; for(i = 1; i <= G->vexnum; i++) instack[i] = 0; SeqStack *S = InitStack(); Push(S, starIndex); instack[v] = 1; ArcNode *p; while(!Empty(S)) { v = Top(S); p = G->vertex[v].head; while(p != NULL) { if(w != -1) { while(p != NULL && p->adjvex != w) p = p->next; if(p != NULL) { p = p->next; while(p != NULL && instack[p->adjvex] == 1) p = p->next; } } else { while(p != NULL && instack[p->adjvex] == 1) p = p->next; } if(p == NULL) break; w = p->adjvex; Push(S, w); instack[w] = 1; if(w == endIndex) { printf("【"); for(i = 0; i <= S->top; i++ ) { printf("%s ", G->vertex[S->data[i]].name); } printf("\b】\n"); break; } p = G->vertex[w].head; w = -1; } w = Pop(S); instack[w] = 0; } }
    4 测试数据及运行结果地点信息

    显示地图

    中转最少

    最佳路径

    全部路径

    最佳布网

    添加地点

    添加边


    删除边

    删除地点

    参考文献
    《数据结构与算法》主编:王曙燕
    0 评论 1 下载 2019-02-14 08:56:14 下载需要8点积分
  • 基于PHP的食堂管理系统

    一.课程设计概述1.1 概述饭堂消费管理系统由基于B/S 的PHP+ MySQL 开发,基本兼容IE6和Chrome, 开发环境是Windows XP加PHPNow(Win32 下绿色免费的Apache + PHP + MySQL 环境套件包| 简易安装、快速搭建。
    二.设计任务分析2.1 数据流图
    2.2 E-R图

    2.3 业务角色本管理系统共有四种业务员: 普通消费者,消费记录查看者,进货记录者,和管理员

    普通消费者: 只有模拟刷卡消费的权限
    消费记录查看者: 有查看所有消费记录和查询某个学生消费记录的权限 (proid=2)
    进货记录者: 添加进货记录(proid=1)
    管理员: 所有权限(proid=0)

    2.4 数据库表设计
    add_custom_record: 消费记录表
    add_material: 食材进货
    custom: 消费者信息
    food: 食物
    jobs: 员工职位表
    manage: 帐号表
    material: 食材表
    student: 学生信息表
    supply: 供应商
    worker: 员工表

    2.5 文件目录说明
    文件

    admin_manage.php: 管理员的管理页面auth_login.php: 对登录的用户名和密码进行认证conn.php: 连接数据库init.php: 应用系统的初始化,用于生成表和创建一个管理员login.html: 用于输入用户名和密码logout.php: 注销update_password.php: 用于修改密码,输入旧密码和新的密码update_pw.php: 用于修改密码,连接数据库,修改值util.php: 工具文件,有过滤殊字符和重定向
    目录

    目录 student: 对学生信息的增,删,查目录 custom: 对消费者的增,修,查目录 post: 对职位的增,删,改,查目录 worker: 对员工的增,删,改查目录 food: 对食物的增,删,改查目录 supply: 对供应商的增,删,改查目录 material: 对食材的增,删,改查目录 add_material: 添加,查询食材进货目录 manage: 对管理帐号的增,删,查修目录 add_custom: 模拟刷卡消费,查询消费记录目录 template: 模板文件目录 static: 存放静态文件,如图片文件,CSS文件

    SQL创建表
    $student = "create table if not exists student ( sid char(10) primary key, name char(10) not null, sex char(3) not null, tel varchar(13)) engine=innodb";
    // 消费者$custom = "create table if not exists custom ( cid char(10) primary key , cur_money float, foreign key (cid) references student(sid) on delete cascade on update cascade ) engine=innodb";
    //供应商$supply = "create table if not exists supply ( sid int primary key auto_increment, name char(10) not null, sex char(3) not null, tel varchar(13), address char(100), description char(200), add_time datetime)";
    //职位$jobs = "create table if not exists jobs ( jid int primary key auto_increment, name char(100) not null, salary float )"; //薪水
    //饭堂员工$worker = "create table if not exists worker ( wid int primary key auto_increment, name char(10) not null, sex char(3) , jid int, birth date, foreign key (jid) references jobs(jid))";
    // 食材$material = "create table if not exists material ( mid int primary key auto_increment, name char(20), description char(200))";
    // 食物$food = "create table if not exists food ( fid int primary key auto_increment, name char(20), description char(200), price float not null)";
    // 食材进货表$add_material = "create table if not exists add_material ( aid int primary key auto_increment, mid int, sid int, price float not null, amount float not null, add_time datetime, charge int, foreign key (mid) references material(mid), foreign key (sid) references supply(sid), foreign key (charge) references worder(wid))";
    // 消费记录$custom_record = "create table if not exists add_custom_record ( id int primary key auto_increment, cid char(10), money float not null, operator int not null, add_time datetime, add_date date, foreign key (cid) references custom(cid))";
    // 帐号表$manage = "create table if not exists manage ( id int primary key auto_increment, username char(20) not null unique, password char(32) not null , proi int not null )";
    三. 用户界面






    四. 总结4.1 不足本次课程设计的餐厅管理系统中并没有实现前台的备份,由于时间比较紧,加上在MySQL中在可以用mysqldump 来实现备份整个数据库,此外有些功能没有完成,如统计,但本系统加入了很多没有要求的功能,如食材管理,供应商管理等。
    4.2 得意之处在系统初始化时,要生成九个表,我把这个过程写成了php文件,所以可以很快的完成系统的布署(这点学wordpress)。
    4.3 心得体会网页的UI设计得十分简单,一来修改方便,二来不用写很多代码,看来起还一目了然。
    尽管这是一个十分简单的课程设计,用PHP实现起来不难,但是当要处理多个对象时,每个很要添,删,查,改,需要一点耐心,难怪现在涌现了那么多框架,它们就是希望将程序员从这些乏味的工作中解求出来的。
    0 评论 3 下载 2019-02-13 20:08:45 下载需要8点积分
  • Linux内核:用户空间与内核空间的数据传递的研究与实现

    摘 要Linux是一个自由、开放源代码的类UNIX操作系统,目前为止Linux已经被移植到各种硬件平台,其支持的种类远远超出其他操作系统。Linux内核是以C语言写成,并符合POSIX标准的操作系统,其将内存分为“内核空间”和“用户空间”两部分,驱动程序和操作系统在内核空间运行,应用程序在用户空间运行,linux内核空间程序需要与用户空间程序进行数据交换。本文首先调查linux内核空间的分布情况,并将内核间与传统进程间的通信方式做一个对比,找到传统进程间的通信方式不能用于内核与用户空间通信的原因,接着分别介绍Linux内核空间与用户空间通信的八种通信方式:内核启动参数、模块参数、Sysctl、系统调用、Netlink、Procfs、Seq_file、Debugfs;然后,本文还将研究Linux内核的编译方法和内核程序的编写,以及Linux内核模块的运行环境,并将这八种通信方式的实现在Linux3.2.1内核版本中测试成功;最后,本文对比了这8种通信方式的优劣,并给出了相应的应用场景。
    关键词: Linux;内核空间;用户空间;数据通信
    AbstractLinux is a free UNIX-like operating systems and open source, and the current Linux has been ported to a variety of computer hardware platform that supports the kind of far beyond any other operating system. Linux kernel is written in C and POSIX-compliant operating system, and its memory is divided into “kernel space” and “user space” , operating systems and drivers run in kernel space, the application runs in user space, linux kernel module and user module application program needs to exchange data. First, this paper will investigate the distribution of linux kernel space, and compare the communication between the kernel and the traditional way, find the reason of that traditional inter-process communication can not be used to communicate with the kernel space and the user, then we will introduce eight ways to communicate between Linux kernel space and user space: kernel boot parameter module parameters, Sysctl, system calls, Netlink, Procfs, Seq_file, Debugfs; next, this paper will also study the methods of build the Linux kernel, as well as Linux operating environment kernel modules, and communication methods to achieve the eight tested successfully in Linux3.2.1 kernel version; finally, we compare the advantages and disadvantages for the eight communication ways between linux kernel space and user space, also this paper presents the application scences for the eight ways.
    Keywords: Linux; kernel space; user space; data communication
    第一章 引言1.1 Linux内核通信简介Linux是一种自由、开放源代码的类UNIX操作系统,Linux内核最早是在芬兰赫尔辛基大学计算机系的Linus Torvalds于1991年10月5日发布[1],目前为止Linux内核已经被移植到各种硬件平台,其支持的种类远远超出其他操作系统。
    Linux内核是用C语言写成的并符合POSIX标准的操作系统[2],从技术上说Linux内核并不是一个完整的操作系统,所以一般情况下Linux内核被打包成Linux发行版,比如Debian(及其派生版本Ubuntu),Fedora(及其相关版本Red Hat,CentOS)和openSUSE等,Linux发行版包含Linux内核和一些库,通常带有大量可以满足各种需求的应用程序。
    由于内核本身有很大的局限性,比如在终端上不能打印,不能做大延时的处理等[3],所以当我们需要做这些工作的时候,就需要将在内核态收集到的数据传送到用户态进程中进行处理,来满足用户的需求,这样,内核空间与用户空间进程通信的方式就显得尤为重要。所以本文将调查研究linux内核空间与用户空间通信的八种通信方式:内核启动参数、模块参数、Sysctl、系统调用、Netlink、Procfs、Seq_file、Debugfs,并在Linux3.2.1内核版本中实现和测试这八种通信方式,并对这八种通信方式的优劣及应用场景作了对比总结。
    1.2 本文的主要工作为了调查研究Linux内核空间与用户空间的通信方式,本文首先将了解Linux内核对内存空间的划分情况;接着介绍Linux内核间通信与传统进程间的通信有何异同,包括传统的进程间通信为什么不能用于内核间通信;为了进行通信测试,本文还将研究Linux内核的编译方法和内核程序的编写,以及Linux内核模块的运行环境;本文最主要的内容是介绍八种通信方式原理及其实现,并全部在Linux3.2.1内核版本中测试成功,并且对这八种通信方式进行了研究对比,对比的内容主要包括每种通信相对其它通信方式的不足和优势,而正是这些不足和优势使得每一种通信方式都有其合适的应用场景,这些场景覆盖了Linux内核工作的方方面面,得以让我们能够使用如此优秀的Linux内核。
    第二章 Linux内核概述为了调查研究Linux内核空间与用户空间的通信方式,本章将介绍Linux内核环境,第一节将讲解Linux内核是如何把内存空间划分成内核空间和用户空间的,了解内核程序与用户程序的通信是跨越内存的,将比传统进程间通信要复杂的多;第二节将熟悉内核态与用户态的概念,以此来了解即便是运行在用户空间的用户态程序也可能在一定条件下转为内核态程序;第三节将介绍Linux内核模块的运行环境,并将解释传统进程间通信方式不能用于内核空间和用户空间通信的原因。
    2.1 内核空间和用户空间Linux采用了段页式存储管理方式,Linux的虚拟地址空间为0~4G,如图1,Linux内核将这4G空间分为两部分,0~3G(0xC0000000~0xFFFFFFFF)的部分为用户空间,供用户进程使用,3~4G(0x00000000~0xBFFFFFFF)的部分为内核空间,专门供内核使用[4]。

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制[5],用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。
    2.2 内核态与用户态目前广泛使用的x86架构CPU一共有0~3四个特权级,其中0级最高,3级最低。Linux内核使用了0级和3级两个特权级,运行在3级特权级上的程序被称为用户态程序,运行在0级特权级上的程序被称为内核态程序。内核态程序可以执行任何指令,用户态程序只可以执行非特权执行,用户态程序在系统调用、异常或外围设备的中断三种情况下可以切换到内核态下运行[6]。
    2.3 Linux 内核模块的运行环境与传统进程间通信在Linux操作系统的计算机中,CPU在任何时候都只会有如下四种状态:

    在处理一个硬中断
    在处理一个软中断
    处于内核态,即运行Linux内核
    出于用户态,即运行一个用户进程

    其中,前三个在内核空间中运行,第四个在用户空间中运行[7]。
    内核模块是一段具有独立功能的程序,其全称是动态可加载内核模块,它可以在系统运行时动态的在内核空间加载和卸载,加载进内核的程序便立刻在内核空间中工作起来。Linux内核模块有三种运行环境:用户上下文、硬中断和软中断。这三种运行环境都有其局限性,大致分以下两种,如表-1:



    内核态环境
    介绍
    局限性




    用户上下文
    内核空间程序的运行和一个用户进程相关,因此内核中会有一个用户上下文环境。
    不可以直接将本地变量传递给用户空间的内存区,因为内核空间和用户空间的内存映射机制不同。


    硬中断和软中断环境
    硬中断或软中断过程中代码的运行环境,比如 IP 数据报接收的代码运行环境等。
    不可直接向用户空间内存区传递数据; 代码在运行过程中不可以阻塞。



    Linux 传统的进程间通信有很多种,这里将介绍其中的一些传统进程间通信方式无法用于内核空间与用户空间通信的原因,如表-2:



    通信方法
    无法用于内核空间与用户空间通信的原因




    管道
    管道通信局限于父进程和子进程间的通信。


    消息队列
    消息队列在硬中断和软中断中不可以无阻塞地接收数据。


    信号量
    信号量无法在内核空间和用户空间之间使用。


    内存共享
    内存共享依赖信号量通信机制。


    套接字
    套接字在硬、软中断中不可以无阻塞地接收数据。



    第三章 八种通信方式本章将分成八节内容,每一节将介绍一种内核空间与用户空间的通信方式,每种通信方式的介绍包括技术理解、程序实现和测试结果截图三部分。
    3.1 内核启动参数内核开发者在用户空间可以通过 bootloader 向Linux内核传输启动参数数据,从而达到控制内核启动行为的目的[8]。
    要在内核启动时分析启动参数,首先需要在内核中定义一个分析参数的函数,并使用内核提供的宏__setup把分析函数注册到内核中,该宏定义在 linux/init.h 头文件中,因此要使用它必须包含该头文件:
    __setup("para_name=", parse_func)
    para_name 为内核启动参数的名字,parse_func函数为分析参数的函数,它负责把传递到内核的参数的值转换成内核变量的值并设置内核变量。
    为了测试这种通信方式,首先需要在Linux内核源代码中加入一个专门用于分析内核启动参数的程序,附录一的程序代码便是本节测试使用的程序。这个例子测试了参数分别是一个整数、逗号分割的整数数组以及字符串三种情况的使用。为了避免与内核其他部分混淆,在Linux3.2.1内核源码的根目录下创建了一个新目录 examples,然后把附录一的程序拷贝到 examples 目录下并命名为 setup_example.c,另外为该目录创建一个 Makefile 文件:
    obj-y = setup_example.o
    如果要把examples程序编译到内核中还需要修改源码根目录下的 Makefile文件的一行:
    core-y := usr/ examples/
    按照内核构建的步骤构建新的内核并安装,设置Linux内核启动参数,只需要修改 /etc/default/grub 文件中的GRUB_CMDLINE_LINUX_DEFAULT配置项:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash setup_example_int=1234 setup_example_int_array=100,200,300,400 setup_example_string=Thisisatest"
    执行
    update-grub
    命令更新系统启动参数并重新启动该内核,就可以使用下面的命令查看结果了:
    $ dmesg | grep setup_examplesetup_example_int=1234setup_example_int_array=100,200,300,400setup_example_int_array includes 4 intergerssetup_example_string=Thisisates
    内核启动参数测试结果如图:

    3.2 模块参数和sysfs上一节中介绍的用户空间与内核空间通信方式是直接编译内核来把程序添加到内核中的,另外也可以使用内核模块[9]的方式来向内核添加程序,这样就可以可以在内核运行时通过命令行在加载模块时传递参数,然后通过sysfs[10](一个基于内存的文件系统)来设置或读取模块数据,这是第二种介于内核空间与用户空间通信的方式。
    ​ 在模块中,我们声明了三个变量:
    static int my_invisible_int = 0;static int my_visible_int = 0;static char * mystring = "Hello, World";
    通过宏 module_param 来声明这些变量,以告诉内核该如何处理这些变量:
    module_param(my_invisible_int, int, 0);module_param(my_visible_int, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);module_param(mystring, charp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);module_param有三个参数,第一个是参数名,也即已经定义好的变量名,第二个参数则为参数类型,第三个参数用于指定该参数的访问权限,如果为 0,则该参数在 sysfs 文件系统中不被可见,访问权限可以是 S_IRUSR, S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH 和 S_IWOTH 的组合,它们分别是用户读,用户写,用户组读,用户组写,其他用户读和其他用户写,这和文件的访问权限设置是一致的。
    下面是本节测试代码的运行结果:
    # insmod module_param.ko my_invisible_int=2 my_visible_int=3 mystring="welcome"# dmesg | grep mymy_invisible_int = 2my_visible_int = 3mystring = 'welcome'# ls /sys/module/module_param/parameters/mystring my_visible_int# echo "change" > /sys/module/module_param/parameters/mystring# cat /sys/module/module_param/parameters/mystringChange
    模块参数运行结果如图:

    3.3 Sysctl用户程序可以使用sysctl方式在内核运行的任何时候来获得或者设置内核配置参数,通常sysctl可操作的配置参数保存在/proc/sys目录下,用户程序也可以直接对这个目录中的文件进行读写来实现获取和设置内核配置的目的,例如,可以通过
    cat /proc/sys/net/ipv4/ip_forward
    来获得内核IP层是否允许转发IP包的当前配置,可以通过
    echo 1 > /proc/sys/net/ipv4/ip_forward
    把内核 IP 层立即设置为允许转发 IP 包。一般地, Linux发行版会提供了一个系统工具 sysctl,它可以直接在命令行中设置和读取内核的配置,下面是使用 sysctl 工具来获取和设置内核配置的例子:
    # sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 0# sysctl -w net.ipv4.ip_forward=1net.ipv4.ip_forward = 1# sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1
    sysctl命令的参数 net.ipv4.ip_forward 实际上被转换为对应的 proc目录下的/proc/sys/net/ipv4/ip_forward文件,选项 -w 表示设置该内核配置的值,没有选项则表示读取该内核配置的值。
    由于sysctl工具依赖proc文件系统,因此在没有 proc 文件系统的情况下,就不可以使用sysctl工具来进行工作了,这时可以使用内核提供的系统调用函数 sysctl 来读取或设置内核配置:
    int _sysctl(struct __sysctl_args *args );
    该调用函数的参数类型为:
    struct __sysctl_args { int *name; int nlen; void *oldval; size_t *oldlenp; void *newval; size_t newlen; unsigned long __unused[4];};
    name是一个数组,nlen表示该数组的长度,name数组的每一项表示目录的一段信息,比如
    int name[] = { CTL_NET, NET_IPV4, NET_IPV4_FORWARD };
    该数组中,CTL_NET表示的是net目录,NET_IPV4表示ipv4,NET_IPV4_FORWARD表示ip_forward,更多定义可以在sysctl.h文件中查看;oldval、oldlenp表示旧值和旧值的大小,newval、newlen表示更新后的值及其所占内存的大小。
    下面是使用sysctl系统调用设置ip_forward的结果:
    # cat /proc/sys/net/ipv4/ip_forward 1# ./ip_forward Input the ip_forword value(0 or 1):0The ip_forword old value is : 1The ip_forword new value is : 0# cat /proc/sys/net/ipv4/ip_forward 0
    sysctl测试结果如图:

    3.4 系统调用系统调用在内核中实现,通过一定的方式提供用户使用,是内核提供给应用程序的接口,用户程序一般通过调用系统调用来对底层硬件进行操作。
    一般用户程序工作在用户态,当用户程序需要使用系统资源的时候,便可以通过系统调用让自己进入内核态。系统调用其本质是内核为用户特别开放的一个中断,例如Linux内核就是使用的int 80h中断。
    中断一般会有中断号和中断处理程序,不同的中断具有不同的中断号,不同的中断号又对应着不同的中断处理程序。内核维护着一个中断向量表,这个向量表的第n项包含了指向中断号为n的中断处理程序的指针。当中断到来的,cpu会暂停当前执行的代码,根据中断号在中断向量表中找到对应的中断处理程序并调用。中断处理程序执行完成之后,cpu会继续执行之前暂停的代码。
    通常意义上,中断有两种类型,一种为硬件中断,另一种为软件中断。由于中断号是有限的,操作系统不会舍得用一个中断号来对应一个系统调用,而更倾向于用一个或少数几个中断号来对应所有的系统调用。比如Linux使用int0x80,Windows使用int0x2e。这样中断号只有一个,因此需要一个系统调用号来区分不同的系统调用,这个系统调用号通常就是系统调用在系统调用表中的位置,一般在执行int命令前会被放置在某个固定的寄存器里,对应的中断代码会取得这个系统调用号,并调用正确的函数,目前在Linux3.2.1内核中系统调用号已经用到了348,如果在增加一个系统调用函数,则可以使用349。
    下面的例子便是通过在linux内核中自定义增加一个系统调用函数[11],然后通过用户程序调用,实现用户空间和内核空间的数据通信的。
    首先在内核源码kernel目录下新加了一个内核程序mysyscall.c,内容如下:
    #include <linux/kernel.h>asmlinkage long sys_mysyscall(int number,char *str){ printk(KERN_ALERT "Mysyscall the args is : %d,%s\n",number,str); return number;}
    所做的事情就是在内核中输出用户程序传过来的参数,并返回用户的一个参数。然后就是在kernel/Makefile中添加一行:
    obj-y += mysyscall.o
    在头文件include/linux/syscalls.h中加入:
    asmlinkage long sys_mysyscall(int number,char *str);
    在arch/x86/kernel/syscall_table_32.S中加入:
    .long sys_mysyscall
    在arch/x86/ia32/ia32entry.S中加入:
    .quad sys_mysyscall
    为系统调用程序分配一个系统调用号,在arch/x86/include/asm/unistd_32.h中设置:
    #define __NR_mysyscall 349#define NR_syscalls 350
    __NR_mysyscall就是这个系统调用程序的系统调用号,NR_syscalls是系统调用函数的总数。
    编译内核并安装重启之后就可以测试了,下面是测试函数:
    int main(){ char *str = "message from user-space!"; printf("%ld\n", syscall(349,20,str)); return 0;}
    测试函数syscall的第一个参数是系统调用号,后面的参数便是系统调用函数的参数,测试结果如下:
    $ ./syscall20$ dmesg | grep MysyscallMysyscall the args is : 20,message from user-space!
    系统调用测试结果如图:

    3.5 Netlinknetlink是一种介于在内核空间与用户空间之间进行数据传输的特殊的通信方式。它通过为为用户程序提供了一组标准的socket 接口,并为内核模块提供一组特殊的API的方式,实现了全双工的通讯连接[12][13]。类似于TCP/IP使用AF_INET地址族,netlink socket使用另一种地址族AF_NETLINK。netlink socket在内核头文件
    include/linux/netlink.h
    中定义自己的协议类型。
    Netlink Socket 用户程序使用标准的socket API函数:
    socket(), sendmsg(), recvmsg()和close()
    使用socket()函数创建一个socket:
    int socket(int domain, int type, int protocol)
    domain参数是socket域(地址族),在netlink应用中设置为AF_NETLINK,
    type参数为数据包类型,这里设置为SOCK_RAW或者SOCK_DGRAM。
    protocol参数是一个表示协议类型的整数,这里使用了自定义的协议类型:
    #define NETLINK_TEST 25
    在用户空间,可以通过调用socket()来创建一个socket,但是在内核空间,则必须使用内核提供的netlink_kernel_create函数来创建,需要注意的是这个函数在Linux2.6版本的内核中变化是非常大的,这里介绍从Linux2.6.37内核开始到Linux3.2.1都在使用的函数原型:
    struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module);
    其中struct net参数是一个网络命名空间namespace,在这里使用init_net这个全局变量;unit参数表示一个整数表示的netlink协议类型,这里使用前文自定义的 NETLINK_MYTEST;input参数为内核模块中定义的netlink消息处理函数,函数指针input的skb参数实际上就是netlink_kernel_create函数返回的 struct sock指针。
    从内核中发送netlink消息使用:
    int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);
    ssk参数是由 netlink_kernel_create()函数返回的netlink socket, skb是需要发送的消息内容,noblock参数设置当接收缓冲区不可用时是阻塞还是立即返回一个失败信息。
    从内核空间关闭netlink socket:
    netlink_kernel_create()函数返回的netlink socket为struct sock *nl_sk,我们可以通过访问下面的API来从内核空间关闭这个netlink socket:
    sock_release(nl_sk->socket);
    附录二中的程序代码是关于NetLink的一个测试的例子,下面是这个例子的测试结果:
    # insmod ./netlink_kern.ko# ./clientstate_smgwaiting received!Received message: hello i am kernel# rmmod netlink_kern# dmesg | tail -n 4my_net_link_3: create netlink socket ok.Message received:Hello you!my_net_link:send message 'hello i am kernel'.my_net_link: self module exited
    NetLink测试结果如图:

    3.6 Procfsprocfs是一种比较老的数据交换方式[14],内核中的很多数据都是通过procfs出口给用户的,另外内核中的很多配置参数也是通过procfs来让用户设置的[15]。除了sysctl出口到/proc下的配置参数,procfs提供的大部分内核配置参数是只读的。
    Procfs提供了如下API:
    struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);
    该函数返回一个普通的proc条目,name参数是该proc条目的名称,mode参数设置了该proc条目的访问权限, parent参数则是该proc条目所在的目录,当然如果要在/proc目录下建立proc条目,parent应当设为NULL。否则parent应当是proc_mkdir函数返回的struct proc_dir_entry结构的指针。
    extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
    该函数用于删除proc条目,name参数是要删除的proc条目的名称,parent参数是proc条目所在的目录。
    struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)
    该函数用于创建proc目录,name参数是proc目录的名称,parent参数是proc目录所在的目录。
    为了使用这些函数接口和结构struct proc_dir_entry,需要在模块中包含头文件linux/proc_fs.h。
    struct proc_dir_entry结构体中需要设置两个函数,分别用于读取用户数据和写用户数据,两个函数原型为:
    int procfs_read( char *page, char **start, off_t off, int count, int *eof, void *data );ssize_t procfs_write( struct file *filp, const char __user *buff, unsigned long len, void *data );
    下面是测试procfs的一个例子,用户程序client接收用户输入的字符串,通过procfs传递给内核模块transform,而transform模块做的事情是把传进来的字符串进行大小写转换,并再由procfs传回client用户程序,下面是测试结果输出:
    $ sudo insmod ./transform.ko$ ./clientuser input :abckern output:ABCuser input :Hellokern output:hELLOuser input :quit
    procfs测试结果如图:

    3.7 seq_file内核在通过procfs来向用户空间输出信息的时候,由于 procfs 的一个缺陷,对于内容大于1个内存页(通常为4KB)的数据需要多次读,因此处理起来比较麻烦。因此 Alexander Viro基于procfs方式又实现了一套新的功能,这便是seq_file,这种通信方式使得内核输出大文件信息非常容易。
    seq_file的所有功能均定义在头文件linux/seq_file.h中,使用seq_file需要定义并设置一个seq_operations结构(类似于file_operations结构):
    struct seq_operations { void* (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void* (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v);};
    start函数指定seq_file文件的读开始位置,并返回实际的读开始位置;next函数将当前读位置移动到下一个可读位置,如果到达文件末尾,则返回NULL,否则返回实际的下一个读位置;stop函数会在读完seq_file文件后被调用,它与文件操作的close函数有些类似,可以做些最后的清理工作,比如释放先前动态分配的内存等;show函数用于输出格式化文本,成功返回0,否则返回错误码。
    在设置好结构struct seq_operations之后,还需要使用seq_open函数将该结构与对应的struct file结构关联起来,例如,附录三中的struct seq_operations定义为:
    struct seq_operations exam_seq_ops = { .start = exam_seq_start, .stop = exam_seq_stop, .next = exam_seq_next, .show = exam_seq_show};
    对应的open函数的定义:
    static int exam_seq_open(struct inode *inode, struct file *file){ return seq_open(file, &exam_seq_ops);};
    设置struct file_operations结构:
    struct file_operations exam_seq_file_ops = { .owner = THIS_MODULE, .open = exam_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release};
    除了open函数,其它的都是seq_file提供的函数,在这里直接使用就可以了。
    然后,创建一个/proc条目并把它的文件操作函数绑定到exam_seq_file_ops:
    struct proc_dir_entry *entry;entry = create_proc_entry("exam_seq_file", 0, NULL);if (entry) entry->proc_fops = &exam_seq_file_ops;
    接下来就可以使用了,附录三中的内核模块将初始化一个链表,这个链表所保存的数据量大于一个内存页的大小,最后通过seq_file一次性将这些数据全部传递到用户空间中:
    for (i=0;i<500;i++){ mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC); sprintf(mydrv_new->info, "Node No: %d\n", i); list_add_tail(&mydrv_new->list, &mydrv_list);}
    链表总共有500个节点,每个节点的数据大小为11到13个字节不等,这样一次性输出到用户的数据大约有6KB,大于一个内存页的大小了,用户空间直接使用cat命令查看输出结果:
    # insmod seqfile_exam.ko# cat /proc/seqfile_examNode No: 0Node No: 1...Node No: 498Node No: 499
    3.8 debugfs内核开发者在开发过程中经常需要向用户空间输出调试信息,printk函数虽然可以在内核中输出信息,但它并不是最好的,因为调试信息只在开发中需要,而printk将会一直输出,因此在开发完毕后将需要清除不必要的printk语句,无疑这将给开发人员带来无穷的工作量,而且如果开发者希望在用户空间就能够改变内核行为时,printk便一点用处都没有了。
    Greg Kroah-Hartman在Linux2.6.11引入了debugfs,它是一个非常小的虚拟文件系统,专门供开发人员输出内核调试信息使用,并可以在编译内核时选择是否构建到内核中,如果该文件系统没有被构建内核时,使用它提供的API的内核代码不需要做任何改动。
    下面函数分别用于在debugfs文件系统下创建目录和文件:
    struct dentry *debugfs_create_dir(const char *name, struct dentry *parent); struct dentry *debugfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, struct file_operations *fops);
    fops参数为进行文件操作的file_operations结构指针,这和seq_file提供的文件操作是完全一样的,当然,在一些情况下,仅需要使用一些简单并可以控制的变量来调试就够了,debugfs提供了4个这样的API来操作简单的数据类型:
    struct dentry *debugfs_create_u8(const char *name, mode_t mode, struct dentry *parent, u8 *value);struct dentry *debugfs_create_u16(const char *name, mode_t mode, struct dentry *parent, u16 *value);struct dentry *debugfs_create_u32(const char *name, mode_t mode, struct dentry *parent, u32 *value);struct dentry *debugfs_create_bool(const char *name, mode_t mode, struct dentry *parent, u32 *value);
    name和mode参数分别指定文件名和访问权限,value参数是可以由用户程序控制的内核变量指针。
    下面给出了一个使用debufs的示例:
    # mkdir -p /debugfs# mount -t debugfs debugfs /debugfs# insmod ./debugfs_exam.ko# ls /debugfsdebugfs-exam# ls /debugfs/debugfs-exambool-var u16-var u32-var u8-var# cd /debugfs/debugfs-exam# cat u8_var0# echo 200 > u8_var# cat u8_var200# cat bool_varN# echo 1 > bool_var# cat bool_varY
    第四章 八种通信方式的优劣对比本文详细地讲解了八种用户空间与内核空间的数据交换方式,这八种方式各有优劣,下面就每种方式的优劣做一下对比:



    方式
    不足
    优势




    内核启动参数
    传递数据是单向的,只能向内核传递。且可以传递的数据量非常小。
    可以在启动内核的时候就进行传递数据,可以改变内核的启动方式。


    模块参数和sysfs
    可以传递的数据量非常小,动态传参需要依赖sysfs文件系统。
    可以在内核运行时传递数据,并可以动态的进行更改。


    Sysctl
    传递数据量非常小,需要了解内核每个配置文件所在procfs文件系统中的路径信息。
    可以在内核运行的任何时刻来改变内核的配置参数,也可以在任何时候获得内核的配置参数。


    系统调用
    添加额外的系统调用函数需要重新编译内核。
    只需要调用系统调用函数即可进行数据传递。


    Netlink
    随着Linux内核版本的升级,内核态的netlink接口变化速度太快,版本相关性很高。
    用户态使用netlink的方式类似于socket的使用方式,可以支持大量的数据传输,netlink是一种异步通信机制,且支持多播。


    Procfs
    依赖procfs文件系统,一般对于大于一个内存页(通常为4KB)的数据大小的传输会比较麻烦。
    使用procfs文件系统,可以方便的通过操作文件的方式进行数据传输。


    seq_file
    依赖procfs,且传递数据是单向的,仅用于内核向用户空间传输大量的数据。
    对procfs传递大量数据的不足进行了改进,使得内核输出大文件信息更容易。


    debugfs
    依赖debugfs文件系统,仅用于简单类型的变量处理。
    专门设计用来内核开发调试使用,方便内核开发者向用户空间输出调试信息。



    其中内核启动参数、模块参数和sysfs、Sysctl三种方式主要用于向内核传递配置信息,这些配置信息将很可能改变内核的行为或状态,对于这三种方式,Sysctl方式最是简便,不仅提供了Sysctl这个内核接口以便在程序中调用,而且还提供了sysctl命令以便在命令行中直接执行,因此,修改内核配置信息则首选sysctl方式。
    系统调用方式则是内核中实现的提供给用户使用的内核接口,如果用户程序需要使用系统资源时,便可以通过系统调用的方式让自己进入内核态,由于内核已经提供了足够多的接口,所以我们一般只使用内核提供的就够了,如果需要自己定义一个系统调用函数,则需要编写内核程序和重新编译内核了。
    Procfs、Seq_file、Debugfs三种方式都是基于文件系统的,因此这三种方式在用户空间的操作接口就是文件操作接口,而且在内核中的接口也比较类似。
    Netlink方式则是用途最多的一种方式,由于其不需要重新编译内核,因此要比内核启动参数和系统调用方式要方便许多。Netlink工作方式非常类似socket的工作方式,因此可传输的数据量大小要比其他方式多得多。Netlink是一种异步通信方式,所以不需要等待接收者收到消息。由于Netlink方式拥有很多优点,所以其应用场景也是很多的,比如路由daemon、防火墙、socket监视等等。
    第五章 总结本文调查研究了八种Linux内核空间与用户空间的通信方式,简要介绍了每种通信方式的工作原理,并对每种通信方式用一段简单的程序代码测试其可行性,最后对这八种方式做了一个对比,分析每种方式的优劣,并给出了相应的应用场景。随着Linux内核版本的不断更新,Linux内核空间与用户空间的通信方式也在不断的衍变,因此不断学习是一个内核开发者所必备的素质。
    参考文献[1] Torvalds L B. Free minix-like kernel sources for 386-AT[N/OL].http://groups.google.com/group/comp.os.minix/msg/2194d253268b0a1b?pli=1.1991-10-5 [2014-5-8].
    [2] [美]Michael Beck. Linux内核编程指南[M]. 第三版. 清华大学出版社,2004.
    [3] [美]Danile P Bovet,Marco Cesati,陈莉君. 深入理解LINUX内核[M].北京:中国电力出版社,2004.
    [4] 董昱, 马鑫. 基于netlink机制内核空间与用户空间通信的分析[J]. 测控技术, 2007(09):57-58+60.
    [5] 张磊,刘艳霞. Linux的虚拟内存管理和Cache机制探析[J]. 焦作大学报, 2004(04).
    [6] 陈浩. Linux下用户态和内核态内存共享的实现[J]. 电脑编程技巧与维护, 2011(4).
    [7] 康望星, 马光胜, 黄烨明, 等. 嵌入式Linux的中断处理技术研究[J]. 信息技术, 2005(8).
    1 评论 1 下载 2019-02-13 09:16:49 下载需要7点积分
  • 基于Java的网上书城

    主要技术关键字:JSP、servlet、Ajax、jstl、JavaScript、注册登录、分页、购物车、增删改查
    开发环境:Eclipse、MySQL 5.7、Tomcat 8.0
    数据库表结构设计
    books表结构:

    items表结构:

    orders表结构:

    userinfo表结构:

    项目包结构
    MVC设计模式M(model层)
    biz包:业务处理
    dao包:数据访问,对数据库的一些封装操作
    entity包:实体类,javabean构建,View层和数据库之间的桥梁作用

    V(view层)1. Jsp页面:与用户进行交互的界面。

    C(controller 层)1.servlet包:控制层,处理View层Jsp页面发来的请求。
    注册登录模块Register
    注册页面中form表单
    验证用户名、验证密码、验证邮箱
    <form method="post" action="RegisterServlet" onsubmit="return checkRegister()"> <dl> <dt>用 户 名:</dt> <dd><input class="input-text" type="text" id="username" name="username" onblur="isUsernameLegal()"/><span id="usernull"></span><span id="alreadyExsits"></span></dd> <dt>密  码:</dt> <dd><input class="input-text" type="password" id="password" name="password" onblur="isPasswordLegal()" /><span id="nullpassword"><font color=\"green\">密码至少8位</font></span><span id="simplepassword"></span></dd> <dt>确认密码:</dt> <dd><input class="input-text" type="password" id="rePassword" name="rePassword" onblur="isRepasswordLegal()" /><span id="nullrePassword"></span><span id="uneq"></span></dd> <dt>Email地址:</dt> <dd><input class="input-text" type="text" id="email" name="email" onblur="isEmailLegal()" /><span id="nullemail"><font color=\"green\">请输入正确格式的邮箱</font></span><span id="errorInput"></span></dd> <dt></dt> <dd class="button"><input class="input-reg" type="submit" name="register" value="" /></dd> </dl> </form>
    Ajax(异步 JavaScript 和 XML ):验证用户名是否已被注册,页面部分数据刷新,而无需加载整个网页,提高网站的访问效率。
    /** * Ajax检查用户是否已经被注册 * 异步:发送请求时不等返回结果,由回调函数处理结果 * @returns {Boolean} */function isExists() { var xmlHttp; //定义 xmlHttp XmlHttpRequest对象从服务器异步获取数据后通过javascript修改页面局部信息 try { //根据不同浏览器初始化不同的xmlHttp浏览器对象 //IE6以上浏览器 xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { //FireFox xmlHttp = new XMLHttpRequest(); } catch (e) { try { //IE5.5+ xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { alert("您的浏览器不支持Ajax"); return false; } } } var username = document.getElementById("username").value; //获取用户名 /** * open(提交方式[get|post],url(servlet路径),同步或异步[false|true]) * RegisterServlet: servlet路径 * 打开和后台服务器的链接 */ xmlHttp.open("POST", "RegisterServlet?action=check&username="+username, true); //路径中不能有空格 xmlHttp.send(null); //传送数据 /** * onreadystatechange:调用回调函数 * readyState:请求状态,代码是未初始化,1:初始化,2:发送请求,3:开始接受数据,4:接受结果完毕 * status: http状态码 200成功,404路径错误,500后台代码错误, */ xmlHttp.onreadystatechange = function() { if(xmlHttp.readyState==4 && xmlHttp.status==200) { var usernull = document.getElementById("usernull"); var result = xmlHttp.responseText; //接受服务器端传过来的数据,写出来的数据都是String类型 if(result == "true") { usernull.innerHTML = "<font color='red'>当前用户名已被注册</font>"; return false; } else if(result == "false") { usernull.innerHTML = "<font color='green'>当前用户名可用</font>"; return true; } } }}
    Login
    LoginServlet中doGet方法:
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username");//获取页面传过来的参数 String password = request.getParameter("password"); //登录操作 boolean flag = userBiz.checkLogin(username, password); if(flag) { request.getSession().setAttribute("loginuser", username);// response.sendRedirect("main.jsp"); response.sendRedirect("SearchServlet"); //首页直接显示 } else {//登录失败 response.setContentType("text/html;charset=utf-8");// response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.println("<script type='text/javascript'>"); out.println("alert(\"登录失败!请重新登录\")");//反斜杠转义 out.println("open(\"login.jsp\", \"_self\");");//重新打开新的页面, _self在原窗口打开 out.println("</script>"); out.close(); } }
    网站首页及搜索图书模块
    分页展示Jstl标签库 c:forEach,循环遍历展示图书信息。
    <c:forEach var="book" items="${books }"> <tr> <td><input type="checkbox" name="bookId" value="${book.bid }" /></td> <td class="title">${book.bookname }</td> <input type="hidden" name="title" value = "${book.bid }:${book.bookname}"/> <td>¥${book.b_price }</td> <input type="hidden" name="price" value = "${book.bid }:${book.b_price}"/> <!-- b_price跟数据库字段名相对应 --> <td>${book.stock }</td> <input type="hidden" name="stock" value = "${book.bid }:${book.stock}"/> <td class="thumb"><img src="${book.image }" /></td> <input type="hidden" name="image" value = "${book.bid }:${book.image}"/> </tr></c:forEach>
    <!--分页开始--> <%if(request.getAttribute("current") != null) { %> <div class="page-spliter"> <a href="SearchServlet">首页</a> <%for(int i = 1; i <= totalPage; i++) { %> <%if(i==no) { %> <!-- 如果不是当前页显示为链接 --> <span class="current"><%=i %></span> <%continue;} %> <a href="SearchServlet?currentPage=<%=i %>"><%=i %></a> <%} %> <a href="SearchServlet?currentPage=<%=totalPage %>">尾页</a> </div> <%} %><!--分页结束-->
    servlet处理
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String currentPage = request.getParameter("currentPage");//获取当前页 int no = currentPage == null?1:Integer.parseInt(currentPage);//如果当前页为空,则默认为1,否则转化为相应的int// String currentSearchPage = request.getParameter("currentSearchPage"); String bookname = request.getParameter("keywords");//获取你输入的书名 List<Book> books = null; if(bookname==null || bookname.equals("")){ books=bookbiz.findAll(3, no);//查询所有。3:代表每页显示的条数,no:当前显示页面 request.setAttribute("totalPage", (bookbiz.count()/3 + 1));//将总页面数存入request } else { books = bookbiz.findBookByName(bookname,3, no);//根据书名模糊查询出数据 System.out.println(books.size()); request.setAttribute("totalPage", (books.size()/3 + 1));//将总页面数存入request } request.setAttribute("books", books);//将查询出的数据存入request request.setAttribute("current", no);//将当前页存入request request.getRequestDispatcher("main.jsp").forward(request, response);//页面转发 }
    关键字匹配查询输入“Java”关键字,展示搜索结果

    购物车模块增
    注意:
    当库存不足时,防止库存数被减到负值。
    if(oneStock > 0) {//如果库存大于0 //查看当前图书是否已经存在于购物车中 //查看购物车列表是否已经存在该图书,对比两个图书id for (int j = 0; j < bookcart.size(); j++) { Book existBook = (Book) bookcart.get(j); if(existBook.getBid() == bid) {//如果购物车中的id==被选中的图书id,则购物车中的数量+1 bookcart.remove(j); existBook.setCount(existBook.getCount()+1);//购物车中商品数量+1 double totalPrice = existBook.getPrice(); bookcart.add(existBook);//商品总价 System.out.println("总价"+totalPrice); isNotExists = false; //修改库存 isOrNotChangeStock = bookbiz.changeStock(bid, "-1"); break; } } if(!isNotExists) { continue; } book.setBid(bid); //判断当前获取的图书信息是否为指定bid下的信息 for (int j = 0; j < title.length; j++) {// request.setCharacterEncoding("utf-8"); //解决页面图书标题中文显示乱码 String title_temp =new String(title[j].getBytes("ISO-8859-1"),"utf-8");//取标题 if(title_temp.indexOf(bids[i]+":")<0) {//indexOf匹配bids是否包含在title_temp中,如果没有返回-1 continue; } if(image[j].indexOf(bids[i]+":")<0) { continue; } if(price[j].indexOf(bids[i]+":")<0) { continue; } if(stock[j].indexOf(bids[i]+":")<0) { continue; } //添加指定bid下的图书信息 book.setBookname(filter(title_temp, bids[i])); book.setImage(filter(image[j], bids[i])); book.setPrice(Double.parseDouble(filter(price[j], bids[i])));// book.setPrice(Double.valueOf(filter(price[j], bids[i]))); book.setStock(filter(stock[j], bids[i])); book.setCount(1); //修改库存 bookbiz.changeStock(bid, "-1"); bookcart.add(book); } }
    删移除操作
    protected void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获取图书id String bid = request.getParameter("bid"); //2.获取购物车 List<Book> bookcart = (List<Book>) request.getSession().getAttribute("bookcart"); //3.循环查找购物车中相同的图书id for(int i = 0; i< bookcart.size(); i++) { Book book = bookcart.get(i); if(Integer.valueOf(bid) == book.getBid()) { //4.移除图书 bookcart.remove(i); //5.修改库存 bookbiz.changeStock(Integer.parseInt(bid), book.getCount()+""); break; } } //6.将购物车保存到session中// request.getSession().setAttribute("bookcart", bookcart); request.getSession().setAttribute("bookcart_count", bookcart.size()); System.out.println(request.getSession().getAttribute("bookcart_count")); }
    改1.前端shopping.jsp页面,添加onblur失去焦点事件,当鼠标离开输入框时执行JavaScript代码,调用update方法。
    <c:set value="1" var="count"></c:set> <c:forEach var="book" items="${bookcart }"> <tr> <input type="hidden" id="hidden_bid_${count }" name="hidden_bid_${count }" value="${book.bid }"/> <td class="thumb"><img src="${book.image }" /></td> <td class="title">${book.bookname }</td> <td><input class="input-text" type="text" id="nums_${count }" name="nums_${count }" value="${book.count }" onblur="update(${bookcart_count}, ${count }, ${book.bid })"/></td> <!-- onblur事件 --> <input type="hidden" id="hidden_${count }" name="hidden_${count }" value="${book.price }"/> <td>¥<span id="price_${count }"></span></td> <input type="hidden" id="hidden_book_total_price_${count }" name="hidden_book_total_price_${count }" /> <td><span id="remove_${count }"><a href="#" onclick="del(${book.bid})">移除</a></span></td> <!-- href链接, --> </tr> <c:set value="${count+1 }" var="count"></c:set></c:forEach>
    2.异步更新图书购买量数据,并向控制层Servlet中返回一个“update”
    /** * Ajax修改购物车中的数量 */function update(size_str, i, bid_str) { //初始化XMLHttpRequest对象 var xmlHttp; try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlHttp = new XMLHttpRequest(); } catch (e) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { e.message(); alert("你的浏览器不支持Ajax"); } } } //2.获取修改后的数量 var num_str = document.getElementById("nums_"+i); //3.打开服务器链接 xmlHttp.open("POST", "ModifyCartServlet?action=update&bid="+bid_str+"&count="+num_str.value, true); //4.传值 ,无参传null值 xmlHttp.send(null); //5.设置回调函数 xmlHttp.onreadystatechange = function() { if(xmlHttp.readyState == 4 && xmlHttp.status == 200) { count(size_str);//调用Servlet中的方法 } }}
    3.servlet中通过前端返回的请求调用相应的方法。
    String action = request.getParameter("action");if(action.equals("update")) { updateCart(request, response);}else if(action.equals("remove")) { remove(request, response);}
    4.控制层Servlet更新购物车数据,并修改库存。
    protected void updateCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获取图书id和修改后的数量 String bid = request.getParameter("bid"); String count = request.getParameter("count"); //2.获取购物车 List<Book> bookcart = (List<Book>) request.getSession().getAttribute("bookcart"); //3.查找购物车中修改过的图书,并修改相应的信息 for (Book book : bookcart) { if(Integer.valueOf(bid) == book.getBid()) { //4.获取修改前的图书数量 int old_count = book.getCount(); //5.设置当前图书的新数量 book.setCount(Integer.valueOf(count)); System.out.println("当前图书新数量"+count); int nowStock = 0; try { nowStock = basedao.queryStock(Integer.parseInt(bid)); } catch (Exception e) { e.printStackTrace(); } //6.修改库存 bookbiz.changeStock(Integer.parseInt(bid), (old_count-Integer.parseInt(count))+""); break; } } }
    查与搜索图书模块类似
    我的订单模块
    1.获取订单信息,存入List中
    <% List orders = (List) request.getAttribute("orders"); %>
    2.订单展示
    <c:forEach var="order" items="${orders }"> <tr> <td id="id_${td_id}">${order.oid }</td> <td id="user_${td_id}">${order.username }</td> <td id="crdt_${td_id}">${order.createdate }</td> <td id="total_${td_id}">${order.total_price }</td> <td class="thumb"><img src="${order.image }" /></td> <td>${order.bookname }</td> <td>${order.b_price }</td> <td>${order.total_price/order.b_price }</td> </tr></c:forEach>
    用户退出当用户退出登录,清空session。
    <%--用户退出登录 --%><% request.getSession().removeAttribute("loginuser"); request.getSession().removeAttribute("bookcart"); request.getSession().removeAttribute("bookcart_count"); response.sendRedirect("login.jsp");%>
    一些注意细节1.为防止游客非法访问页面,在每一个前端页面body之后加上登录检测代码。
    <% String username = (String)session.getAttribute("loginuser"); if(username == null) { response.sendRedirect("login.jsp"); }%>
    2.当库存不足时,防止库存数被减到负值。
    if(oneStock > 0) {//如果库存大于0 //...}
    3.href中加上空链接,否则该页面中的session不会刷新,必须手动刷新。
    <span id="remove_${count }"><a href="#" onclick="del(${book.bid})">移除</a></span>
    网站云部署一个经过部署的网站才是一个完整的网站
    阿里云平台
    租用ecs云主机,搭建Java运行环境,jdk、mysql、Tomcat
    域名解析
    项目打包成.war文件上传至云服务器。
    启动服务器
    通过域名访问并测试网站:http://ycuer.xin:8080/books/
    1 评论 2 下载 2019-02-12 08:15:38 下载需要7点积分
  • 基于JSP的网上花店系统的设计与实现

    摘 要二十一世纪是一个集数字化,网络化,信息化的,以网络为核心的社会。中国的网民充分领略到“畅游天地间,网络无极限” 所带来的畅快。随着Internet的飞速发展,使得网络的应用日益的广泛。如电子商务,电子政务,网上医疗,网上娱乐,网络游戏,网络教学等。本次毕业设计的题目就是网上花店系统。
    本论文就毕业设计的内容,系统地阐述了整个网上花店系统的功能及实现。实现了从商品管理,商品分类和查询,到购物车实现,用户订单处理,再到系统管理。基本上实现了网上购物的功能流程,能够实现用户与商家在网上进行商品交易。本系统界面简单直观,易于操作和使用,交互性强,完全基于Internet网络。
    经过分析,使用 SUN公司的JSP开发工具,利用其提供的各种面向对象的开发工具,尤其是数据窗口这一能方便而简洁操纵数据库的智能化对象,首先在短时间内建立系统应用原型,然后,对初始原型系统进行需求迭代,不断修正和改进,直到形成满意的可行系统。
    关键字:Jsp,网上花店,SQLServer2000
    AbstractThe 21st century is a collection digitization, the network, the informationization, take network as core society. China’s web cams understand fully “enjoy a trip to between the world, the network does not have carefree which the limit” brings. Along with the Internet swift development, causes the network application day-by-day widespread. If electronic commerce, E-government, on-line medical service, on-line entertainment, network game, network teaching and so on. the this graduation project’s topic is the flower shop system.
    The present paper on graduation project’s content, elaborated systematically the flower shop service system’s function and realizes. Has realized from the merchandise control, the classification of goods and the inquiry, realizes to the shopping cart, user order form processing, arrives at the system administration again. Basically has realized on-line shopping function flow, can realize the user and the business on-line carries on the commodity exchange. This system contact surface simple direct-viewing, simplify operation and use, interactive is strong, completely based on the Internet network.
    After the analysis, use SUN Corporation’s JSP development kit, uses each kind of object-oriented development kit which it provides, particularly the data window this can convenient and the succinct operation database intellectualized object, first establishes the system application prototype in a short time, then, carries on the demand iteration to the initial prototype system, revises unceasingly and improves, until forms the satisfied feasible system.
    Keywords: JSP, Flower Shop System, SQLServer2000
    1 开发背景1.1 引言网上商店也被称为电子商店,是电子商务的一个重要组成部分。网上商店作为电子商务的一种具体形式,在Internet上已经非常普及。由于充分利用和结合了最新的信息技术,网上商店具有传统商店无可比拟的优势,如经营成本低,受地域限制和时间限制少,市场扩张力极大等。目前Internet上的网上商店每天以惊人的速度增加。网上购物越来越成为现代年轻人的首选,足不出门便能选购好自己称心如意的商品,鲜花更是人们的首选,在百忙之中有可能你无法亲自到花店选择你想要的花,但是此系统借助发达的网络和现代的科技技术可以让你打开网页就能完成鲜花的订购。进入21世纪网络信息发布已经逐步取代了传统的媒体发布,网上发布的范围更广,持久性更好。花店的信息如果在网上发布更有利于花店的业务发展。本论文主要涉及软件,数据库与网络技术等。涵盖知识面广,可有效地提高消费者综合运用所学知识分析解决问题的能力,增强消费者对事物的理解与掌握能力,培养消费者掌握科学的研究方法,正确的设计思想,独立思考,勇于进取,探索创新,为今后进一步学习与工作奠定了良好的基础。
    1.2 目的和意义如今是一个信息高速发达的社会,尤其以网络的高速发展为代表。鲜花的买卖业务是一个与我们的日常生活密切相关的,长辈的生日,节日的祝福及问候,对心爱的人表达爱意,看望病号等等的时候。越来越多的人选择够买一束鲜花来表达自己的情谊!本系统正是利用了网络这个很好的平台为顾客提供方便,不用你出门,打开你的电脑上网就可以很方便的完成鲜花的选购,做到足不出门就可以完成一切。
    基于jsp+JavaBean的网上花店的总体设计目标是为注册用户提供方便的网上订购鲜花的功能。本系统分为前台和后台管理两部分,注册用户登入前台后,可以浏览和购买网上花店中的所有商品,购买商品后还可以得到相应的积分奖励,并且可以在网站中留言。管理员通过后台登录后可以对系统中的商品以及注册的用户和用户的留言进行管理。为了满足上面提出的功能要求,基于jsp+JavaBean的网上花店需要达到以下目标。

    支持用户注册和登录
    用户登录后可以根据价格、类型和其他关键词进行商品查询
    用户可以购买商品,但是可以在提交之前取消购买这些商品
    支持用户在线留言
    支持管理员通过后台登录,并对系统中的各种信息进行维护

    1.3 开发设计思想本系统采用MyEclipse和Tomcat服务器为主要开发工具,SQLServer作为管理系统数据的数据库。整个系统采用Struts框架结构,符合MVC模式。MVC模式要求对应用分层,虽然要做额外的工作,但产品的结构清晰,可扩展性强、产品的应用通过MVC模式可以得到更好地体现。这恰恰是电子银行系统所需要的。电子银行系统的功能会随着客户的要求不断的扩展,因此这个系统需要不断的进行功能的升级和修改。如果采用传统的JSP+JavaBean的三层模式进行开发,将使程序的业务逻辑层和表示层之间耦合过紧,不利于日后的生机和扩展。
    通过该毕业设计能够看出,在进行设计之前。设计者既考虑了系统的需求,有兼顾了运行的效率,以及今后系统的功能扩展等。使用MVC模式开发系统,这使得本系统具有运行效率好、扩展能力强、技术规范等特点。
    1.4 开发目标网上花店是一种新兴的商品销售方式,优势在于经营成本低,在任何地方只要可以上网的地方就可以随时随地的订购你所想要的花朵传情达意,但是也有弊端,就是网站需要经常的维护等等存在着一些问题,只有解决好这些问题网上花店才会更加的普及起来。理论系统功能设计目标如下:

    实用性强:我们努力使系统符合实际操作流程的习惯,并尽量减少用户的输入,易学易用的友好的用户界面,满足各层次的用户使用的需求
    先进的程序结构:使用当代前卫的软件编程,能延长其生命周期,易于维护与管理
    安全可靠性高:后台维护功能齐全,根据平台在各个阶段不同的使用情况,管理人员可以设置相应的操作权限,增加系统注册,分配各个栏目的管理权限,实现系统的维护,保证系统的安全、可靠
    使用模块化设计的方法:使系统具有良好的可扩充性,以适应其不同阶段的发展需要,便于后来者分析、维护
    操作简单,维护方便:每个子系统都具有相对独立的系统维护功能

    2 开发工具和开发环境简介2.1 Java Server Page 简介Java Server Page或简称为JSP是由Sun公司在Java语言上开发出来的一种动态网页制作技术,它提供了一种建立动态网页的简单方法,并为开发人员提供了一个Server端框架,基于这个框架,开发人员可以综合使用 HTML,XML,JAVA语言以及其他脚本语言,灵活,快速地创建和维护动态网页,特别是目前的商业系统。作为JavaTM 技术的一部分,JSP能够快速的开发出基于所有Web服务器和应用服务器环境,独立于平台的应用程序,而且具有非常强的可伸缩性。同时,JSP把用户界面从系统内容中分离开来,使得设计人员能够在不改变底层动态内容的前提下改变祖国网页布局。
    这样跨平台的特性己包含了目前大部份网站服务器配置环境,再加上它的语法写作方式可媲美ASP般的易学易懂,而且在网络安全技术方面甚至已超越ASP技术。所以,相信很快的时间内极有可能取代现有网页编译技术,而成为商业网站的新标准。在本节中我们将会讨论JSP技术的特性,让您更加了解这项新技术。
    2.2 JSP技术的发展现状JSP技术被设计成为一个开放的、可扩展的及用于创建动态Web页面的标准。开发人员可以使用JSP技术来创建具有可移植性的Web应用程序。开发结果可以在各种Web服务器和Web应用服务器上运行,而不用考虑具体的开发平台或开发工具。
    为了给动态Web页面提供一个开放的工业标准,JSP规范的1.0版本的提出,是向这个方向迈出的第一步。在1.0版本中,其基础由核心标记集、隐含对象以及开始创建动态Web页面所需的基本功能构成。已经有许多Web服务器,Web应用服务器和工具开发商为他们的产品添加了对JSP1.0的支持。
    Jsp 1.0 版本中加入了一些新的特色,特别是加入了XML的语法和输出缓冲等功能,以解决重定向的错误,而同时一些在0.92版本中的功能被取消了。
    随着XML 越来越热门,在不久的将来XML将成为主流。因此,在JSP1.0的语法中开始了对XML的支持,并希望能够与其兼容。不过,在JSP1.0版本中对XML的支持还不是必须的,但在将来的JSP1.1版本中对它的支持将成为必然。
    JSP1.1与JSP1.0两个版本基本上同时开发的。但是,JSP1.1需要新的Servlet API的支持,所以JSP1.1将会等新的Servlet API 公布之后才会推出。除了JSP标准的语法标记之外, JSP1.1将会提供有可扩展的自定义语法标记机制,这样使用者就可以定义自己的语法标记,并且可以在任何支持JSP1.1的引擎上运行它们,而且供应商们也会有选择地扩展或扩充JSP规范中的功能。JSP引擎能够强有力地支持多种脚本语言和对象模型。在不断扩充和使用JSP技术同时。也将保持JSP技术对平台和服务器的可移植性。
    2.3 JSP技术的特点JSP技术有以下几个显著的优点。
    1.应用程序内容与页面显示分离
    用JSP技术,Web页面开发人员可以使用HTML或者XML标识来设计和格式化最终页面,并使用JSP标识或者小脚本来生成页面上的动态内容(内容是根据请求变化的,例如本系统中的时时获得在线考试的每个考生的信息等)。生成内容的逻辑被封装在标识和JavaBeans组件中,并且捆绑在脚本中,所有的脚本在服务器端运行。由于核心逻辑被封装在标识和JavaBeans中,所以Web管理人员和页面设计者,能够编辑和使用JSP页面,而不影响内容的生成。
    在服务器端,JSP引擎解释JSP标识和脚本,生成所请求的内容(例如,通过访问JavaBeans组件,使用JDBC技术访问数据库或者包含文件),并且将结果以HTML(或者XML) 页面的形式发送回浏览器。这既有助于作者保护自己的代码,又能保证任何基于HTML的Web浏览器的完全可用性。
    2.可重用组件
    绝大多数JSP页面依赖于可重用的、跨平台的组件(JavaBeans或者Enterprise JavaBeans组件)来执行应用程序所要求的复杂的处理。开发人员能够共享和交换执行普通操作的组件,或者使得这些组件为更多的使用者和客户团体所使用。基于组件的方法加速了总体开发过程,并且使得各种组织在他们现有的技能和优化结果的开发努力中得到平衡。
    3.采用标识
    Web页面开发人员不会都是熟悉脚本语言的编程人员。JSP技术封装了许多功能,这些功能是在易用的、与JSP相关的XML标识中进行动态内容生成所需要的。标准的JSP标识能够访问和实例化JavaBeans组件,设置或者检索组件属性,下载Applet,以及执行用其他方法更难于编码和耗时的功能。
    4.适应平台
    几乎所有平台都支持Java,JSP+JavaBeans几乎可以在所有平台下通行无阻。从一个平台移植到另外一个平台,JSP和JavaBeans甚至不用重新编译,因为Java字节码都是标准的与平台无关的。
    5.数据库连接
    Java中连接数据库的技术是JDBC,Java程序通过JDBC驱动程序与数据库相连,执行查询、提取数据等操作。Sun公司还开发了JDBC-ODBC bridge,利用此技术Java程序可以访问带有ODBC驱动程序的数据库,目前大多数数据库系统都带有ODBC驱动程序,所以Java程序能访问诸如Oracle、Sybase、MS SQL Server和MS Access等数据库。
    此外,通过开发标识库,JSP技术可以进一步扩展。第三方开发人员和其他人员可以为常用功能创建自己的标识库。这使得Web页面开发人员能够使用熟悉的工具和如同标识一样的执行特定功能的构件来进行工作。
    JSP技术很容易整合到多种应用体系结构中,以利用现存的工具和技巧,并且能扩展到支持企业级的分布式应用中。作为采用Java技术家族的一部分,以及Java 2(企业版体系结构)的一个组成部分,JSP技术能够支持高度复杂的基于Web的应用。
    由于JSP页面的内置脚本语言是基于Java的,而且所有的JSP页面都被编译成为Java Servlets,所以JSP页面具有Java技术的所有好处,包括健壮的存储管理和安全性。作为Java平台的一部分,JSP拥有Java编程语言“一次编写,各处运行”的特点。
    利用JSP技术,动态信息由JSP页面来表现,JSP页面由安装在Web服务器或者使用JSP的应用服务器 上的JSP引擎执行。JSP引擎接受客户端对JSP页面的请求,并且生成JSP页面作为对客户端的响应。
    JSP页面通常被编译成为Java Servlets,这是一个标准的Java扩展。页面开发人员能够访问全部的Java应用环境,以利用Java技术的扩展性和可移植性。当JSP页面第一次被调用时,如果它还不存在,就会被编译成为一个Java Servlets类,并且存储在服务器的内存中。这就使得在接下来的对该页面的调用中,服务器会有非常快的响应 。
    JSP页面可以包含在多种不同的应用体系结构或者模型中,可以用于由不同协议、组件和格式所组成的联合体中。基于JSP的动态信息发布技术是一个开放的、可扩展的建立动态Web页面的标准。不论采用什么创建工具,开发人员都可以使用JSP页面来创建可移植的Web应用,在不同的Web应用服务器上运行。
    2.4 Eclipse简介Eclipse最初是IBM公司的一个软件产品。2001年11月,其1.0版本正式发布。刚开始的时候,Eclipse的声誉并不好,受到业界的很多批评。到了2003年3月,Eclipse 2.1版本发布的时候,引起了业界的轰动。下载Eclipse的人峰拥而至,甚至导致了服务器拥塞。目前IBM已将Eclipse作为一个开源项目捐给了开源组织Eclipse.org。Eclipse具有出色而有独创的平台特性,吸引了众多公司加入它的平台。
    Eclipse平台的主要任务是为开发者提供能够使用各种工具无缝集成的机制和需要遵守的规则。这些机制通过具有良好的定义的应用程序接口、类和方法表现出来。本质上,Eclipse是一个具有基本微内核、开放的和可扩展的体系结构。
    Eclipse是建立在插件机制上的。插件是Eclipse平台功能的最小单元。它可以开发和提交。所有的Eclipse平台功能都由插件实现,除了平台运行的小内核。插件机制可以发现集成和运行插件。它可以划分Eclipse平台本身。不同的插件提供不同的工作空间和工作台,甚至平台运行时本身也有自己的插件。
    2.5 Struts简介Struts是Apache软件组织提供的一项开放源代码项目,它为JAVA WEB应用提供了模型-视图-控制器(Model-View-Controller,MVC)框架,尤其适用于开发大型可扩展的WEB应用。Struts这个名字来源于在建筑和旧式飞机中使用的支撑金属架。Struts为WEB应用提供了一个通用的框架,使得开发人员可以把精力集中在如何解决实际业务问题上。此外,Struts框架提供了许多可供扩展和定制的地方,使得应用程序可以方便地扩展框架来更好地适应用户的实际需求。
    当一个设计师在设计一个方案时,首先要构建该方案的框架结构,有了这份蓝图,实现过程才会有条不紊、井然有序。同样,软件开发者在开发一个软件项目的时候,也需要构思一个软件应用的框架。通过框架表现出软件各个模块之间的关系。这样可以提高软件开发的速度和效率,使得软件更易于维护。而于WEB开发应用,要设计这样一个蓝图框架并不是一件容易的事,Struts就是这样一种框架,Struts的所有功能都建立在已有的Java Web组件上,它只是采用了MVC模式把这些元素组织起来,使它们协同工作
    3 网上花店系统分析3.1 系统的功能分析为了最终实现目标系统,必须设计出组成这个系统的所有程序和文件(或数据库)。模块是数据说明、可执行语句等程序设计对象的集合,它是单独命名的而且可通过名字来访问。模块化就是把程序化分成若干个模块,每个模块完成一个子功能,把这些模块集起来组成一个整体,可以完成指定的满足问题的要求。
    首先对现有系统进行分析,现有系统是信息的重要来源。分析已有系统的功能和实现,从而确定新系统的设计目标和模型。由于条件有限,调研主要是在网上进行。即通过在网上已有的在线考试系统和了解其具备的功能。
    1. 从用户的角度
    从用户的角度通过注册后可以浏览页面进行选花订花,把自己喜欢的商品添加到购物车中,并生成订单。同时可以通过页面进行商品的查询,使用户更加方便的查询到自己需要的商品。
    2.从管理员的角度
    从管理员的角度可以通过登入管理员的页面进行对用户的管理,添加和删除用户,并对用户的订单进行管理和商品进行管理。
    3.2 系统可行性研究可行性研究阶段的主要任务是在系统初步调查的基础上,对新系统是否能够实现和值得实现等问题做出判断,避免在花费了大量的人力和物力之后才发现系统不能实现或新系统投入使用后没有任何实际意义而引起的浪费,对新系统可行性的分析,要求用最小的代价在尽量短的时间内确定系统是否可行。
    3.2.1 技术可行性分析在线考试系统的开发是一项复杂的系统工程。为了保证系统开发成功,必须采用工程化的系统开发方法,并研究出一些符合工程化标准的开发方法。这些方法旨在指导开发者进行工程化的系统开发,从而加快系统开发的速度,保证质量以及降低开发成本。工程化的系统开发方法确实在开发实践中取得了一定的效果。此次开发使用JSP作为开发语言,采用Servlet技术,Tomcat5.0作为Web服务器。
    3.2.2 运行可行性分析随着计算机知识的普及和推广,越来越多的人掌握了计算机的基本使用方法和技能。随着Internet的发展,用户对于网络、WINDOWS等环境下的软件使用比较熟悉,对于新鲜事物,用户表现出极大的兴趣和热情。
    3.2.3 经济可行性分析网上花店系统给人们带来了方便同时给商家带来了成本的节省,成为一种全新的鲜花销售模式,商家可以免去店面的成本和雇佣人员的成本,更大限度的做到还利益于消费者,所以构建一个网上花店系统在经济上是完全可行的。
    就本系统而言,随着电脑的普及,为消费者在网络上进行购物提供有利的条件。而一个网上花店系统可以为消费者提供软件条件,这样消费者便可以足不出户的买商品了。商家可以从中获得利润,两全其美。
    通过以上的分析,开发网上花店系统是完全可行的。
    4 系统的概要设计4.1 数据库系统概述本系统的前端开发是使用JSP技术,通过JavaBeans进行逻辑控制和数据库连接,而后台数据库采用的是SQL Server 2000。SQL Server 2000数据库管理系统是一项全面完整的数据库与分析产品。SQL Server 2000非常容易学习、使用,介绍、学习资料比较多,SQL Server 2000全面支持Web功能的数据库解决方案,与此同时,SQL Server 2000还在可伸缩性与可靠性方面保持着多项基准测试纪录,而这两方面特性又都是企业数据库系统在激烈市场竞争中克敌致胜的关键所在。无论以应用程序开发速度还是以事务处理运行速度来衡量,SQL Server 2000都堪称最为快捷的数据库系统。
    4.2 系统功能模块设计1.用户功能模块
    用户注册后通过输入用户名和密码就可进入系统进行商品的选购,可以按照商品的种类和商品的价钱对商品进行查询,进入商品页面后如有称心如意的商品便可以添加到购物车里,当选购结束时就可以生成订单。
    2.管理员功能模块
    当管理员选择页面下的链接,链接到管理员的登录页面,输入正确的管理员用户名与密码就可以进入管理员模块,在此可以对用户进行操作,同时可以对订单进行操作,同时可以完成对商品的日常操作及网页的维护。
    4.3 数据库表设计本系统包括十个表:用户资料表users、种类表sort、管理员表admin、商品表goods、留言表leaveLanguage、安全记录表safelog、订单表orders、订单细节表ordersDetails、订购鲜花公司表produce、鲜花公司细节表produceDetails。
    详情请见下面数据库表:
    用户资料表users

    种类表sort

    管理员表admin

    商品表goods

    留言表leaveLanguage

    安全记录表safelog

    订单表orders

    订单细节表ordersDetails

    订购鲜花公司表produce

    鲜花公司细节表produceDetails

    5 程序设计及主要功能5.1 程序说明本系统采用MyEclipse和Tomcat服务器为主要开发工具,SQLServer作为管理…统数据的数据库。整个系统采用Struts框架结构,符合MVC模式。MVC模式要求对应用分层,虽然要做额外的工作,但产品的结构清晰,可扩展性强、产品的应用通过MVC模式可以得到更好地体现。这恰恰是网上花店系统所需要的。网上花店系统的功能会随着客户的要求不断的扩展,因此这个系统需要不断的进行功能的升级和修改。
    5.2 系统的主要功能和具体的模块划分5.2.1 用户注册页面用户登入系统后,必须先注册一个帐户,通过输入其中的必要的信息在数据库中建立一个用户,然后在主页面输入相应的用户名和密码便能开始商品的选购。

    5.2.2 用户购物模块通过输入用户名和密码进入鲜花选购页面,在此处可以进行鲜花的选购,同时可以通过鲜花的种类和鲜花的价钱对鲜花进行分类的查询,是商品一目了然的呈现在眼前,同时可以进行高级查询,更加方便按照消费者自己的喜好进行商品的选择。

    5.2.3 商品选购模块点击每幅商品的图片便可以进入到商品的页面,上面会有对商品的具体介绍,会给消费者一些选购的建议。方便消费者的选择。

    5.2.4 购物车模块在选购商品结束后可以把中意的商品添加到自己的购物车中,同时可以产生订单也可以继续购物去选择下一个商品。

    5.2.5 高级查询模块在高级查询模块中可以根据关键字,商品分类,价格范围,商品名称

    5.2.6 后台管理员模块在后台管理员模块中,管理员可以对用户的基本信息、用户的订单、用户的留言以及网站的日常维护进行管理。

    6 总结经过两个月的不懈努力,和指导老师的谆谆教导,以及向同学们的虚心请教,充分利用大学四年所学的专业知识,通过大量阅读与设计相关的专业参考文献,我终于完成了本次毕业设计,已经基本上实现了网上花店系统的各项功能。
    通过这次毕业设计充分的让书本上的知识和实践结合,真正做到把知识用活,通过这次毕业设计让我对Java和Jsp有更深一步的认识,学会使用MyEclipse和Tomcat结合进行基础的软件开发。对团队的协作,对软件开发方法和手段,有了一定的认识,丰富了我的软件开发的经验。
    本系统的缺陷也是明显的,如安全性,效率问题等等。由于时间,能力,资料有限等原因,有些功能实现的并不完美,在已经完成的程序中,也存在许多不尽人意的算法,也没有统一优化,系统有待进一步改善,而这些问题也让我充分认识到了软件开发的困难。
    参考文献[1] 孙卫琴,李洪成.《Tomcat 与 Java Web 开发技术详解》.电子工业出版社,2003年6月
    [2] BruceEckel.《Java编程思想》. 机械工业出版社,2003年10月
    [3] Thomas Connolly Carolyn Begg. Database Systems [M].北京:电子工业出版社,2004.7
    [4] FLANAGAN.《Java技术手册》. 中国电力出版社,2002年6月
    [5] LEE ANNE PHILLIPS.《巧学活用HTML4》.电子工业出版社,2004年8月
    [6] Brown等.《JSP编程指南(第二版)》. 电子工业出版社 ,2003年3月
    [7] 萨师煊,王珊.《数据库系统概论》.高等教育出版社,2002年2月
    [8] 耿祥义,张跃平.《JSP实用教程》. 清华大学出版社,2003年5月1日
    [9] 朱红,司光亚.《JSP Web编程指南》.电子工业出版社, 2001年9月
    [10] Steven Holzner著.Java技术内幕.北京:机械工业出版社.2002年1月
    1 评论 1 下载 2019-02-12 12:24:11 下载需要8点积分
  • 基于汇编语言的打字软件

    汇编-打字软件,记录时间,显示成绩
    流程图

    效果图



    0 评论 1 下载 2019-02-12 08:08:21 下载需要4点积分
  • 基于WinPcap和MFC实现的网络嗅探器

    一、实验目的
    掌握嗅探器的工作原理
    熟悉 WinPcap 的使用
    掌握基于 WinPcap 网络嗅探器的开发过程

    二、实验内容开发出一个 Windows 平台上的网络嗅探工具,能显示所捕获的数据包,并能做相应的分析和统计。主要内容如下:

    列出监测主机的所有网卡,选择一个网卡,设置为混杂模式进行监听
    捕获所有流经网卡的数据包,并利用 WinPcap 函数库设置过滤规则
    分析捕获到的数据包的包头和数据,按照各种协议的格式进行格式化显示
    将所开发工具的捕获和分析结果与常用的嗅探器,如 Wireshark,进行比较,完善程序代码

    三、开发环境
    WinPcap 支持库
    Visual C++ 语言,MFC 支持库
    Visual Studio 2008 集成开发环境
    Windows 2007 操作系统

    四、系统设计本次课程设计主要完成基于 WinPcap 的网络嗅探器软件的设计与实现。通过WinPcap 支持库我们可以获取本机网卡信息,并读取经过一个网卡的所有数据包。
    通过对数据包的结构化分析,我们可以获得所有数据包的类型及具体内容,然后通过 MFC 可视化控件显示到软件界面上。系统流程图如图 1 所示。

    五、模块实现本次课程设计所实现的嗅探器主要分为四个模块:WinPcap 抓包模块、数据包分析模块、程序界面模块、交互设计模块。各模块的实现如下。
    5.1 WinPcap 抓包模块该模块主要包括:获取本机网卡信息、设置数据包过滤规则、抓取数据包三个功能。其程序框架图如图 2 所示。

    5.2 数据包分析模块该模块是软件实现的重点模块。我们捕获的数据包是以太网帧,通过对帧结构中部分字段进行判别,我们可以识别出该数据包具体属于哪种协议的数据包。
    在本次试验中,我们具体分析 TCP,UDP,ICMP,ARP,HTTP 数据包。部分数据包首部定义如下:
    //Mac头部,总长度14字节typedef struct ethernet_header{ u_char dstmac[6]; //目标mac地址 u_char srcmac[6]; //源mac地址 u_short eth_type; //以太网类型}ethernet_header;/* 4 bytes IP address */typedef struct ip_address{ u_char byte1; //IP地址第1个字段 u_char byte2; //IP地址第2个字段 u_char byte3; //IP地址第3个字段 u_char byte4; //IP地址第4个字段}ip_address;//IP头部,总长度20字节typedef struct ip_header{#if LITTLE_ENDIAN u_char ihl:4; //首部长度 u_char version:4;//版本 #else u_char version:4;//版本 u_char ihl:4; //首部长度#endif u_char tos; //服务类型 u_short tot_len; //总长度 u_short id; //标识号#if LITTLE_ENDIAN u_short frag_off:13;//分片偏移 u_short flag:3; //标志#else u_short flag:3; //标志 u_short frag_off:13;//分片偏移#endif u_char ttl; //生存时间 u_char protocol; //协议 u_short chk_sum; //检验和 struct ip_address srcaddr; //源IP地址 struct ip_address dstaddr; //目的IP地址}ip_header;//TCP头部,总长度20字节 TCP头部与TCP数据包不是一个概念;typedef struct tcp_header{ u_short src_port; //源端口号 u_short dst_port; //目的端口号 u_int seq_no; //序列号 u_int ack_no; //确认号#if LITTLE_ENDIAN u_char reserved_1:4; //保留6位中的4位首部长度 u_char offset:4; //tcp头部长度 u_char flag:6; //6位标志 u_char reserved_2:2; //保留6位中的2位#else u_char offset:4; //tcp头部长度 u_char reserved_1:4; //保留6位中的4位首部长度 u_char reserved_2:2; //保留6位中的2位 u_char flag:6; //6位标志 #endif u_short wnd_size; //16位窗口大小 u_short chk_sum; //16位TCP检验和 u_short urgt_p; //16为紧急指针}tcp_header;//UDP头部,总长度8字节typedef struct udp_header{ u_short src_port; //远端口号 u_short dst_port; //目的端口号 u_short uhl; //udp头部长度 u_short chk_sum; //16位udp检验和}udp_header;//ICMP头部,总长度4字节typedef struct icmp_header{ u_char type; //类型 u_char code; //代码 u_short chk_sum; //16位检验和}icmp_header;
    在捕获数据包之后,我们需要对特定字段进行识别,以判断该数据包具体属于哪个协议,分析数据包流程如图 3 所示:

    5.3 程序界面模块

    界面上方的 button : 用于用户交互,控制整个软件的运行
    界面上方的 Combo Box : 用于选择监听的网口
    左上方的 Group Box :用户统计不同数据包的个数
    左方中间的 Group Box : 用于设置数据包过滤器
    中间主体部分 List Control: 结构化显示捕获数据包的信息
    左下方的 Tree-view Control: 分析数据包各个字段代表的含义
    右下方的 Edit Control: 显示数据包的内容

    5.4 交互设计模块结构化显示数据包:
    // 显示选中的网卡数据包pkt信息到List Control列表框中void CYGSnifferDlg::DispSelectedtoListCtrl(packet *tmp_pkt){ packet *pkt = new packet; const struct pcap_pkthdr *header = new pcap_pkthdr; const u_char *pkt_data = new u_char; pkt = tmp_pkt; header = pkt->header; pkt_data = pkt->pkt_data; //No int iNoCount = m_listAdapterInfo.GetItemCount(); int iNoDisp = iNoCount + 1; TCHAR strNo[10]; _itow_s(iNoDisp,strNo,10); //TimeStamp struct tm lTime = {0,0,0,0,0,0,0,0,0}; struct tm *plTime = &lTime; char strTime[16]; time_t local_tv_sec; local_tv_sec = header->ts.tv_sec; localtime_s(plTime,&local_tv_sec); strftime(strTime,sizeof strTime,"%H:%M:%S",plTime); //Length int iLength = header->len; TCHAR strLength[10]; _itow_s(iLength,strLength,10); //Ethernet - Mac ethernet_header *eth_hdr = (ethernet_header *)pkt_data; TCHAR eth_srcMac[18]; TCHAR eth_dstMac[18]; CString eth_strType = NULL; GetMacAddress(eth_srcMac,eth_hdr->srcmac); GetMacAddress(eth_dstMac,eth_hdr->dstmac); GetMacType(eth_strType,ntohs(eth_hdr->eth_type),true); // ntohs() is to swap network to host // IP ip_header *ip_hdr = (ip_header *)(pkt_data+14); // get ip pos TCHAR ip_srcAddr[16]; TCHAR ip_dstAddr[16]; CString ip_strProtocol = NULL; GetIPAddress(ip_srcAddr,&ip_hdr->srcaddr); GetIPAddress(ip_dstAddr,&ip_hdr->dstaddr); GetIPType(ip_strProtocol,ip_hdr->protocol,true); IsHTTP(pkt_data); //=========== show ============ m_listAdapterInfo.InsertItem(iNoCount,strNo); USES_CONVERSION; m_listAdapterInfo.SetItemText(iNoCount,1,A2W(strTime)); m_listAdapterInfo.SetItemText(iNoCount,2,strLength); m_listAdapterInfo.SetItemText(iNoCount,3,eth_strType); m_listAdapterInfo.SetItemText(iNoCount,4,eth_srcMac); m_listAdapterInfo.SetItemText(iNoCount,5,eth_dstMac); m_listAdapterInfo.SetItemText(iNoCount,6,ip_strProtocol); m_listAdapterInfo.SetItemText(iNoCount,7,ip_srcAddr); m_listAdapterInfo.SetItemText(iNoCount,8,ip_dstAddr); if(pkt) { delete pkt; pkt = NULL; } if(header) { delete header; header = NULL; } if(pkt_data) { delete pkt_data; pkt_data = NULL; }}
    显示数据包详细信息(以 IP 协议为例):
    // 显示IP协议的详细信息void CYGSnifferDlg::ShowIPDetail(HTREEITEM &hItem,const u_char *pkt_data){ // =================== IP ====================== ip_header *ip_hdr = (ip_header *)(pkt_data+14); hItem = m_treeDetailInfo.InsertItem(TEXT("IP LAYER")); CString str = NULL; // Version u_char ip_version = ip_hdr->version; str.Format(TEXT("Version = %d"),ip_version); m_treeDetailInfo.InsertItem(str, hItem); // Header Length u_char ip_length = ip_hdr->ihl; str.Format(TEXT("Header Length = %d"),ip_length); m_treeDetailInfo.InsertItem(str, hItem); // Type of service u_char ip_tos = ip_hdr->tos; str.Format(TEXT("Service Type = %0X"),ip_tos); m_treeDetailInfo.InsertItem(str, hItem); // Total Length u_short ip_totalLen = ip_hdr->tot_len; str.Format(TEXT("Total Length = %d"),ntohs(ip_totalLen)); m_treeDetailInfo.InsertItem(str, hItem); // Identification str.Format(TEXT("Identification = %d"),ntohs(ip_hdr->id)); m_treeDetailInfo.InsertItem(str, hItem); // Flags TCHAR ip_strFlag[4]; u_short ip_flag = (ip_hdr->flag); _itow_s(ip_flag,ip_strFlag,4,2); str.Format(TEXT("Flag = %03s"),ip_strFlag);// 填充字符串方法:CString szTemp; szTemp.Format("%06d", n); //n=123(000123)|456(000456) m_treeDetailInfo.InsertItem(str, hItem); // Flagment offset u_short ip_flagoff = ip_hdr->frag_off; str.Format(TEXT("Flagment offset = %d"),ip_flagoff); m_treeDetailInfo.InsertItem(str, hItem); // Time to live u_char ip_ttl = ip_hdr->ttl; str.Format(TEXT("Time to live = %d"),ip_ttl); m_treeDetailInfo.InsertItem(str, hItem); // IP Protocol CString ip_strProtocol = NULL; u_char ip_protocol = ip_hdr->protocol; GetIPType(ip_strProtocol,ip_protocol,false); // get ip protocol by call function -> GetIPType() str.Format(TEXT("IP Protocol = %s"),ip_strProtocol); m_treeDetailInfo.InsertItem(str, hItem); // Header CheckSum u_short ip_chksum = ip_hdr->chk_sum; str.Format(TEXT("Header CheckSum = %0X"),ntohs(ip_chksum)); m_treeDetailInfo.InsertItem(str, hItem); // Source IP TCHAR ip_srcAddr[16]; TCHAR ip_dstAddr[16]; GetIPAddress(ip_srcAddr,&ip_hdr->srcaddr); GetIPAddress(ip_dstAddr,&ip_hdr->dstaddr); str.Format(TEXT("Source IP = %s"),ip_srcAddr); m_treeDetailInfo.InsertItem(str, hItem); str.Format(TEXT("Dest IP = %s"),ip_dstAddr); m_treeDetailInfo.InsertItem(str, hItem);}
    显示数据包内容:
    // 显示Packet Data数据信息,最下方控件显示;void CYGSnifferDlg::GetDataInfo(CEdit & eText, packet *pkt){ const struct pcap_pkthdr *header = pkt->header; const u_char *pkt_data = pkt->pkt_data; u_int pkt_dataLen = header->len; // 得到单个Packet_Data(注意:不是packet)数据包的长度 CString strText = NULL; CString chrAppend = NULL; u_int eRows = 0; for(u_short i=0; i<pkt_dataLen; i++) { CString strAppend = NULL; if(0 == (i%16)) // 取余,换行 { eRows++; if(0 == i) { strText +=chrAppend; strAppend.Format(TEXT(" 0X%04X -> "),eRows); strText += strAppend; } else { strText +=TEXT("==>> ") +chrAppend; strAppend.Format(TEXT("\x0d\x0a 0X%04X -> "),eRows); //0x0d:回车; 0x0a:换行;0X:表示16进制显示;%04x表示以4位的16进制显示并以0填充空位; eRows即显示行数(16进制格式显示) strText += strAppend; } chrAppend = ""; // reset null } strAppend.Format(TEXT("%02x "),pkt_data[i]); strText += strAppend; if(i>2 && pkt_data[i-1]==13 && pkt_data[i]==10)//如果遇到回车、换行,则直接继续,以免使显示字符换行 continue; strAppend.Format(TEXT("%c"),pkt_data[i]); chrAppend += strAppend; } if(chrAppend !="") strText +=TEXT("==>> ") +chrAppend; eText.SetWindowTextW(strText);}
    六、运行结果将过滤器设置为选择 IP 和 UDP,程序运行结果如图 5 所示。

    七、实验总结7.1 遇到的问题本次实验主要遇到的问题有两点:数据包的分析和 MFC 编程。
    由于开始对网络协议及数据包结构不清楚,不知道如何解析数据包,也不知道怎样区分不同协议的数据包。于是在网上查了很多关于网络协议分层、数据包分析的资料,才终于明白各层协议之间的关系,并知道了怎样区分不同协议,并成功完成数据包分析工作。
    问题二在于 MFC 编程,本科阶段我主攻 JAVA,C++ MFC 只是略有涉及。本次实验为了实现可视化,我找了很多资料,自学 MFC 编程,遇到问题的时候就像周围擅长 MFC 的同学请教,最终完成该软件的编写,虽然还有一些不足的地方,但也是一次成功的尝试。
    7.2 收获与体会通过本次实验,我对网络协议有了更深的理解,同时学会了如何处理数据包。对整个计算机网络知识的理解有了质的提升。同时,我还自学了 MFC 编程,编程水平有较大提高。总而言之,这次实验让我收获良多!
    1 评论 2 下载 2019-02-11 17:10:11 下载需要8点积分
  • 基于JAVA的仿QQ聊天器

    第一章 引言本系统是基于java开发的聊天室。有用户注册、用户登陆、修改密码、忘记密码、添加好友、用户聊天、群聊功能。如果服务器还没有启动,则客户端是不可以登陆、注册、忘记密码,如果在运行过程中,服务器断开则系统会有提示,聊天对象如果下线发送消息后会有弹窗提示,添加好友后有是否添加好友成功提示。
    第二章 概要设计2.1 使用平台及技术此项目运行在Windows 10上,使用Eclipse作为IDE,用MySQL作为数据库。以Java为主要设计语言。
    2.1.1 Java的优点
    简单性
    Java实现了自动的功能收集,简化了内存管理的工作,这使程序设计更简便,同时减少了出错的可能。

    面向对象
    Java提供了简单的类机制和动态的架构模型。对象中封装了它的状态变量和方法。很好地实现了模块化和信息隐藏;而类则提供了一类对象的原型,通过继承和重载机制,子类可以使用或重新定义父类或超类所提供的方法,从而既实现了代码的复用,又提供了一种动态的解决方案。
    Java同样支持继承特性,java的类可以从其他类中继承行为,但java只支持类的单重继承,即每个类只能从一个类中继承。
    Java支持界面,界面允许程序员定义方法但不立即实现,一个类可以实现多个界面,利用界面可以得到多重继承的许多优点而又没有多重继承的问题。

    多线程
    多线程使应用程序可以同时进行不同的操作,处理不同的事件。在多线程机制中,不同的线程处理不同的任务,他们之间互不干涉,不会由于一处等待影响其他部分,这样容易实现网络上的实时交互操作。多线程保证了较高的执行效率。

    安全性
    用于网络、分布环境下的java必须要防止病毒的入侵,java不支持指针,一切对内存的访问都必须通过对象的实例变量来实现,这样就防止了程序员使用欺骗手段访问对象的私有成员,同时也避免了指针操作中容易产生的错误。
    2.1.2 MySQL介绍MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。
    MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
    MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
    2.2 环境的搭建过程2.2.1 Eclipse的安装在 https://www.eclipse.org/downloads/ 中选择64bit的选择下载安装到本地D盘。
    2.2.2 MySQL的安装在 https://www.mysql.com/downloads/ 中选择Microsoft Windows版本下载到本地,然后安装(按默认安装即可),设置密码MySQL Root Password并记住。
    2.2.3 JDBC驱动包的下载与引入由于Eclipse不自带JDBC驱动,然而JDBC驱动是Java连接数据库必要的包,因此按以下步骤下载引入包。

    JDBC的下载
    在浏览器输入 https://dev.mysql.com/downloads/connector/j/ ,选择zip文件下载到本地,解压到合适的位置。

    JDBC包的导入
    在项目处右键 ->Build Path ->Add External Archives ->选择下载好的jdbc驱动包.jar文件 ->打开。
    2.3 功能和设计方法总体而言,在客户端:当用户登录后,生成唯一的socket, 存放在Client实体类中,在整个客户端就一个Client类和一个socket。有一个窗口控制器——ChatUIList,用来记录用户和好友聊天框是否打开,当收到消息后,首先在ChatUIList中查询是否有好友的窗口,如果没有则新建聊天框弹出并显示消息,如果存在与好友的窗口则将消息追加到原聊天框并重新着重显示该窗口。在客户端还拥有一个“命令控制中心”——ChatTread类,在ChatTread类中判断并处理来自服务器中的命令(消息),如果是“message”那么客户端收到是来自好友的消息,如果是“requeste_add_friend”则是好友申请命令,类似有“WorldChat”, “accept_add_friend”, “refuse_to_add”, “changepwd” 等命令。在服务端:有多个socket,用SockList管理连接成功的用户名及其socket。同样在服务端也有一个“命令控制中心”——ServerTread类,它负责处理来自客户端的命令(消息),判断命令的类型,并正确处理他们,给出处理结果和判断是否处理成功,将处理后的命令转发给正确的用户。
    2.3.1 注册功能设计一个注册UI(RegisterUI)类,在打开程序后,模仿QQ在左下方有一个注册按钮,点击注册按钮后弹出注册页面,用户填完必填信息后由客户端将命令发送给服务端(如果服务器在线),服务器收到“register”命令后,连接数据库判断,如果注册成功则返回注册成功消息并弹窗提示,如果失败则弹窗提示注册失败。
    2.3.2 登录功能打开客户端后,类似QQ有登录按钮,当用户填完用户账号和用户密码并提交后,客户端将登录请求发送给给服务端判断(如果服务端在线),如果密码正确则用户登录成功,显示朋友列表(FriendsUI),否则提示密码错误或账号不存在。
    2.3.3 忘记密码在客户端右下侧有忘记密码按钮,用户点击按钮后弹出忘记密码页面(ForgetUI),用户填写用户账号后客户端将消息发往服务器,服务器在数据库中检测该账号是否存在,如果存在则显示提示问题,如果不存在则提示账号不存在。用户填写完后续相关信息后,点击“重置密码”按钮后,如果找回密码答案正确则向服务器发送修改密码请求,如果失败则弹窗提示密码错误。最后服务器将处理结果(修改密码是否成功)返还给客户端。
    2.3.4 单独聊天(私聊)用户登录成功后,双击好友后,首先判断用户与该好友是否有聊天框存在,如果不存在则创建新的聊天框(ChatUI)并在ChatUIList中登记,如果存在则将改聊天框突出显示。用户可以再聊天页面(ChatUI)发送消息,如果好友不在线,服务器会返回好友不在线提示,客户端弹窗提示,如果好友在线收到消息则无提示(类似Linux,没有消息就是好消息)。好友收到消息时,在ChatUIList中查询是否有与该好友的聊天窗口,如果没有则新建窗口显示并在ChatUIList中注册,如果存在则直接将消息追加到聊天窗口上并突出显示。
    2.3.5 多人聊天(群聊)这里实现的多人聊天式世界喊话,即在线用户都能收到世界喊话的消息,没有好友限制,实现与单独聊天类似。不同的是,服务器收到“世界喊话”命令后,在SocketList中查询当前在线用户,并将世界喊话消息发送给这些用户。
    2.3.6 添加好友在好友列表页面左下角有添加好友按钮,点击该按钮后弹出添加好友框(AddFriendUI),在添加好友框中重复输入两次欲添加的好友name便可向服务器发送好友请求。当用户收到好友请求后,同意或拒绝都像添加方反馈,添加成功后重新登录便可刷新好友列表。
    2.3.7 修改密码在好友列表右下方有修改密码按钮,点击该按钮后弹出修改密码框(ChangePwdUI),在这里只需要重复输入两次新密码即可修改密码,是否修改成功服务器都会做出应答,客户端有弹窗提示。在服务端对数据库进行操作,由于可能数据库会出错,如果数据库未成功修改密码,那么要提醒客户
    第三章 详细设计3.1 流程图介绍3.1.1 系统总体结构设计
    3.1.2 系统主体处理流程
    3.1.3 系统流程逻辑
    3.2 主要类图说明3.2.1 客户端中主要类图3.2.1.1 UI类图
    3.2.1.2 消息处理、控制类图
    3.2.2 服务端中主要类图3.2.2.1 UI类图
    3.2.2.2 消息处理、控制类、数据库相关图
    3.3 数据库表展示数据库名为myqquser, 此数据库中若干个表,一个用户表(tb_user),用来存储用户的信息,如用户名,用户密码,用户问题,用户答案;每一个用户有一个好友表,好友表里存着用户的好友名。
    3.3.1 数据库构成截图此时数据库中有三个用户分别为inforSec、 zzz、sdust,tb_user中存放着用户的信息,inforSec_friends中存放着inforSec的好友信息,zzz_friends存放着zzz的好友信息,sdust_friends中存放着sdust的好友列表。

    3.3.2 用户表(tb_user)的结构及存放的数据
    3.3.3 某一用户好友表结果及数据
    3.4 主要算法、类的描述3.4.1 客户端中主要类、算法的描述3.4.1.1 UI类MainFrame、FriendsUI、ChatUI、ChangeUI、AddFriendUI、RegeditUI、ForgetUI分别为主窗口页面、好友列表页面、聊天窗口、修改密码窗口、添加好友窗口、注册窗口、忘记密码窗口。这一部分主要是显示处理和逻辑处理。
    3.4.1.2 消息处理、通信类ChatUIList类主要记录客户端打开的聊天页面,处理与好友的消息弹窗。Client类中有socket负责和服务器通信。ChatTreat类是客户端的消息处理中心,处理来自服务器的各种消息并做出相应。
    3.4.2 服务器端主要类、算法的描述3.4.2.1 UI类服务器端只有一个UI页面——StartServerFrame,因为服务器没有太多消息要显示,所以一个启动窗口即可。
    3.4.2.2 消息处理、通信类Service类中有socket,负责与客户端建立通信,每个建立的通信都存储在SocketList中,供服务器查询哪些用户上线。ServerThread类是服务器端的控制中心,负责处理来自用户端的消息,并转发给正确的用户,有时还会对数据库进行操作。
    3.4.2.3 数据库处理相关DBHelper类负责和数据库建立连接,UserService类负责处理具体的和数据库交互的内容,如查询用户账号和密码是否匹配、修改密码、注册用户、忘记密码、添加好友等操作。
    第四章 测试结果与截图注册用户


    登录

    忘记密码,修改密码

    用户聊天,添加好友,群聊

    第五章 总结这次设计经过不懈的努力,程序基本满足聊天的需要。完成后的程序实现了用户注册、用户登录、忘记密码、写消息、发消息、回复、群聊、添加好友、修改密码等功能。给用户较好的体验。完成了登录页面、注册页面、好友列表、忘记密码、修改密码等页面框架,使得程序较美观。由于一个人完成这个项目,时间比较紧张,对网络传输的一些知识不太熟悉,所以没有完成文件传输功能。
    完成的项目功能不太完善,虽然完成了群聊(世界喊话),但不能和好友列表中的具体几个人进行群聊。在进行项目时候,UI部分代码的编写不够仔细,所以部分UI不够美观。世界喊话的实现太过草率,整个页面的利用率低。后续可以在世界喊话页面增加群聊功能,在数据库中为每个人创建一个群表,记录每个人加入的群,完成真正的群聊。对UI部分进行优化,使整个项目更美观。
    程序设计期间,通过询问老师、同学和网上搜索相关解答等多种方式学习到很多课堂上没有的知识,增强了解决实际问题的能力。在此之前,对于java编程技术没有很深入的认识,对其工作方式和原理缺乏透彻的认识。在这段时间里,我认真的学习了java语言开发,对编程思想有了进一步的体会,养成了一些良好的编程习惯。程序虽然完成了,但是距离完美还存在一定的差距,用java开发大型项目还有很多我需要去努力学习的知识。希望自己能不断学习和实践,争取以后做得更好。
    0 评论 2 下载 2019-02-11 14:25:47 下载需要10点积分
  • 基于JAVA实现的简单聊天工具

    一、描述本聊天程序,是基于TCP传输协议的两用户即客户端与服务端之间的通信,是一种一对一的通信方式。程序只涉及单线程编程,所以只能实现两个端口之间的通信,通信包括文本信息的输入传输显示,以及单个文件的传输。
    1.1 连接服务器聊天时应该是一端先运行服务端,会显示本机的ip地址以及监听的端口号,此时服务端等待客户端连接。
    另一端运行客户端,然后客户端输入服务端的ip地址以及服务端的监听端口号,连接服务端,连接成功后就能实现两端之间的通信了。
    1.2 聊天界面连接成功后,每一端会弹出一个聊天界面,包含一个聊天记录显示窗口,一个文字输入窗口,还有三个按钮,分别是发送,发文件,清除。
    1.3 发送、发文件及清除按钮发送就是发给对方消息。点击发文件按钮回将此时文本输入框中的内容发给对方,同时显示在自己的聊天记录窗口中。注意当你发送的内容为空时,会提示你不能发送空内容,弹出一个提示窗口。
    发文件按钮是用来选择要发送的文件的。按发文件按钮后会出项一个文件选择对话框,然后用户可以选择要传输的文件,选择文件后,需要等待对方接收才能发送,对方接收后,会提示可以发送了,此时点击发送按钮便可以发送此文件,如果对方取消接收则会提示发送失败。当对方想发文件时,文件接收端会弹出一个文件路径选择对话框,用户可以通过此对话框选择文件的保存路径,当然用户也可以点击取消按钮,取消文件接收。
    清除按钮用来清除聊天记录。当你点击清除按钮后,第一个文本区的内容即聊天记录就删除了。
    二、设计2.1 总体设计聊天程序是通过socket先链接,再聊天通信的软件。而本程序实际上是服务端与客户端之间通信的程序,应该先运行服务端,并且监听一个服务端口,等待客户连接。然后客户端通过服务端的ip以及监听的端口号,即socket连接服务端,然后进行通信。
    考虑到在dos界面下只能实现两个人时之间的同步通信,而对于更加方便的异步通信却无法实现,所以应该加入图形界面,这样通过在聊天窗口中对话通信,实现异步通信的目的。聊天就是文本形式的对话,在客户端与服务端之间传输,而文件传输也是如此,因此聊天最重要的就是对输入输出流的操作,通过输出流向对方发消息,通过输入流接收对方发来的消息。
    2.1 通信连接服务端端先实例化一个ServerSocket对象,打开一个监听端口5555,通过此对象的accept()方法,监听此端口,等待客户的连接。此时,客户端通过Socket(ip,5555)实例化一个Socket对象,并且试图去连接由此ip与5555构成的Socket。这是服务端的accept()方法便监听到客户端的连接请求,accept()方法就会返回于此客户端Socket连接的一个本机Socket对象,这样就实现了服务器端与客户端的连接。
    2.2 绘制聊天界面通过JFrame类绘制一个窗口界面,然后添加了两个JTextArea类的对象作为对话输入窗口和聊天记录输出从窗口,添加JButton类的实例按钮,用于发送消息、清除记录和选择文件。另外实例化JFileChooser类的两个文件及文件夹选择窗口。
    2.3 聊天聊天是通过客户端与服务器端的输入流与输出流实现的。就是将用户输入的文本或者发送的文件内容通过输出流发送给对方,而接收消息则是通过输入流接收的。
    由于输入流的writeUTF(String)方法是阻塞的,并且键盘输入的操作也是阻塞的,所以仅仅在dos界面下,不能实现异步通信。为了解决异步通信的问题,本程序加入了图形界面,通过图形界面的消息就可以实现异步通信了。
    文件发送功能基本思想,第一步,先告诉对方要发送文件了以及文件名将字符串”##**##send”+file.getName()写入输出流中,然后等待对方接收;第二步,如果收到对方同意接收文件即”##**##start”后,开始发送文件,当文件发送完成时,提示用户发送完成即将”##**##end”写入输出流;否则,如果收到”##**##nostart”,表示对方不想接收,则取消发送,用户可以选择再次发送。
    文件接收方当收到以”##**##send”开头的消息时,便知道对方想要发送文件了,这是程序会弹出一个文件路径选择窗口,让用户选择要保存文件的路径,用户可以点取消,会将”##**##nostart”写入输出流,表示不想接收文件;也可以选择路径,点确定,会将”##**##start”写入输出流,表示接收文件,此时程序会建立一个文件输出流,指向刚才选择的路径文件,用于将对方发来的文件数据写入文件中。
    2.4 类图
    三、运行界面截图聊天窗口截图


    文件选择对话框截图

    四、总结4.1 优点程序通过图形界面的方式,实现了聊天的异步通讯功能,可以适时地接收并显示对方发过来的数据。并且实现了文件传输的功能,可以发送单个文件,这将极大地方便用户的使用。
    聊天程序是通过TCP进行传输,可靠性强,结合Java的图形界面编程,实现了聊天可以窗口化的目的,界面更加友好,使用户更方便使用。
    程序结构清晰,层次分明,函数封装较好,代码的重用性更强。
    4.2 不足本程序只能实现一个客户端与服务端的通信,不能多用户同时连接服务器,也没有实现多用户之间同时聊天的功能。而且连接时必须先运行服务端,在运行客户端进行连接,而且只有在客户端知道服务端的ip地址以及端口号的时候才能连接聊天。有一定的局限性。
    聊天记录只有清除,没有实现保存的功能,不能再次查询已经删除的聊天记录,没有数据的支持,不能实现用户的登陆和注册功能,想要推广此程序还有一定的差距。
    此程序还有很多的不足之处,所以还有很大的改进空间。
    4.3 收获通过编写聊天程序,首先练习了基于TCP的socket编程,了解了Java网络编程的基础,并且学会了网路传输数据和接收数据,从而学会了编写简单的网络聊天程序。
    同时,再次熟悉了文件输入输出流,以及对文件和文件夹的操作水平。同时,用更好的掌握了Java的图形界面的编程,并且熟悉了Java文件对话框,button按钮以及文本输入框等的使用。
    1 评论 1 下载 2019-02-11 12:17:52 下载需要6点积分
  • 基于Android的家庭学校联系平台APP开发与实现

    摘要学校教育与家庭教育的不一致,容易产生教育断层的局面,而现有的校讯通等家校互动平台,又存在教师与家长单向沟通等方面的问题。如今信息技术的飞速发展为家校共育的健康发展提供了强有力的保障,“互联网+”已成趋势。学校和教师要一改过去传统的沟通交流方式,借用互联网与手机短信相结合的家校互动信息平台,扩展沟通交流渠道,架起家校沟通的桥梁,从而让家校教育形成合力,提高教育的时效性,促进学生健康成长。因此,本文就目前“互联网+教育”的新趋势进行了说明,以明确进行研究的目的。随后,将对本平台实现的过程和原理进行一一探讨,以让读者能了解实现其的具体方法。最后,为了实现这个软件平台,本文对这个软件作了系统分析和系统设计,最终实现了该软件。通过测试,该平台能正常运行在Android系统下的智能设备,也验证了本文所探讨的设计正确性和重要性。
    关键词:互联网、家校互动、家校协作、功能应用
    AbstractNowadays, the inconsistency of school education and family education may easily result in education gap. And the existing home-school interactive platforms like School Paper still have some problems including the issue of one-way communication between teachers and parents. The rapid development of information technology has provided a strong guarantee for the healthy development of home-school coeducation. Currently, the” Internet+” mode has become a trend, which requires the schools and teachers update the traditional communication ways with parents. They can combine the internet with mobile phone text message to establish a home-school interaction information platform, expanding the communication channel, setting up the bridge of communication so as to produce a unified strength of home-school education, improving the efficiency of education and promote the healthy growth of students. Therefore, This paper puts forward the aim of this research by describing the current trend of “Internet +Education”. In addition, It explores the process and principles of the new platform to make the readers understand the specific methods. Finally, in order to achieve this software platform, this paper makes a systematic analysis and system design. Through the test, the platform can run in the intelligent equipment of Android system, thus verifying the correctness and importance of the design discussed in this paper.
    Key words: Internet; home schoolinteraction; home school cooperation; Function Application
    1. 概述1.1 研究背景和意义社会的发展,智能手机的普及让各种各样的手机应用APP成为我们生活中必不可少的一部分,教育行业也搭上了这趟车,走上了“互联网+”教育,为了方便学生、家长、学校三体互动,让家长和学生能及时收到学校发送的消息,满足用户之前的实时交流,“爱吖校推”应运而生。
    “爱吖校推”是一款基于Android的家校互动平台。随着社会的发展,各种APP在手机行业发展的助推下应用越来越广泛。某权威调研机构表示,2016年,Android系统已经占领市场份额高达81.3%,而大名鼎鼎的iPhone屈居第二,仅占17.8%,更令人惊奇的是,Android的市场份额还在持续增多。
    社交是人类社会性群体的基本属性。而“爱吖校推”就是一款基于教育行业的社交类APP。它支持所有的即时通讯应该包含的功能,文件发送、位置发送、音视频通话、图片、视频等,同样也支持校方和教师发送公告作业并推送到相应群体的Android智能终端。在当前微信用户如日中天的基础上,“爱吖校推”采用微信朋友圈的方式,支持消息发送、点赞、评论、拍照、秒拍、微视频等群体社交,真正进入微社交时代。这是一件非常有意义的事情。
    1.2 国内现状家校互动的需求长期已有,它的研究和设计从本世纪初就开始了,而且也取得了不小的成效。但在早期由于技术的限制,所以存在信息的单项沟通,比如早期的“校讯通”。它就是单纯的通过收发短信来达到家校信息交流,教师得不到任何的反馈。之后随着移动互联网的发展,微信平台如鱼得水,其双向沟通性让一线教师感觉是雪中送炭,但其信息筛选性一直为人诟病。虽然微信等即时通讯软件一定意义上解决了家校互动的问题,但这样的处理,无疑是增加了教师的工作量。
    近年来,有 “爱上学”、“和校园”、“爱学习”、“校讯通”等已经运行的家校互动支持平台20多个,这些平台主要实现信息发布查看等功能,对于信息的及时推送功能匮乏,加之在校大学生多用QQ群或者微信群作为沟通平台,常常使得通知公告信息错过,导致了学生之间的信息不对等,而国内超高量的外出打工家长,想看到没有手机的孩子实在困难。所以“爱吖校推”在操作简单的基础上,优化了拍照和微视频,让老师可以把学生的学校情况实时地分享给大家。消息的离线推送也使得信息的准确到达率迅速提升,而附带音视频通话的及时通讯板块,也是拉近了学生、家长、教师之间的距离。
    1.3 论文的思路和结构该论文分十个部分进行逐一讲解:
    首先把概述作为了第一部分,该系统平台的研究背景和意义以及国内现状作为主要讲述内容。
    第二部分是研究方案和架构概述,主要阐述了本课题预计花费时间的设计方案、软件开发设计方案以及设计目标,最后做了架构概述。
    第三部分是需求分析,主要从用户需求、性能需求和功能需求三方面阐述了需求板块需要具备的东西。本部分还用了用例图、用例说明增加相关人员的理解。
    第四部分是概要设计,主要从Android端和服务器端分别阐述了功能总体设计,然后画了数据库E-R图,最后是系统类图和界面设计。
    第五部分是详细设计,主要从数据库设计、CS协议通信、时序图三个方面阐述。
    第六部分是系统实现,主要介绍了本次系统实现所用到的开发工具,并展示了开发界面总览和核心功能代码的讲解。
    第七部分是软件测试,分版块进行功能、性能、安全、交叉事件以及兼容性板块进行测试并修改系统bug。
    第八、九部分是本次毕业设计中的收获和结论以及自己的感想。
    最后是本文所参考的各种有价值的资料列表。
    2. 研究方案和架构概述2.1 预计花费时间设计方案
    花费7天查阅了关于即时通讯的资料以及小米推送的官方文档,并对其进行分析整理
    花费10天查阅了一些技术博客和相关论坛以及GitHub上比较火的框架和项目
    花费15天进行数据库设计,并对系统框架做一个全局性思考
    花费10天编写后台API数据接口,并做简单测试
    花费1个月编写Android端代码,并对后台数据可行性进行验证修改
    最后进行常规测试,并在各大机器上运行,以保证不会出现致命Bug

    2.2 软件开发设计方案采用MVC开发模式,按照功能可划分为:发通知,发作业,互评点赞,图片并茂,即时通讯,小米推送等模块。
    功能模块细化:

    班级圈:班级圈包含教师可发放通知、作业,基本支持图文并茂社区化和微视频上传。家长可查看自己班级的每一条信息,以及进行互评回复点赞
    即时通讯:即时通讯板块主要依赖于环信,在环信SDK的大前提下,引入基本的即时通讯和音视频通话
    社区板块:社区板块是用户只要在一个班级即可进行类似朋友圈的交流,依然可以进行互评点赞回复
    发现板块:发现板块主要是为加载一些广告和活动
    我的板块:我的板块主要是提供用户信息的更改设置等
    推送板块:当前推送继承了Google推送、华为推送和小米推送,以最大的可能提高推送接收率

    2.3 本课题的设计目标模块功能实现的目标:

    班级圈:保证班级圈数据的正常显示,非本班人员应该不具备查看该班信息权限的能力。采用广播、接口回调及其其它方式完成数据的传递和更新
    即时通讯:保证音视频通话的离线呼起,保证长连接的引用,保证用户能正常收发消息
    社区:同班级圈
    发现:保证广告的通畅性和可行性
    我的板块:保证用户信息的修改处理正常,做到信息不泄漏
    推送:保证推送成功率与正确率
    交互性良好:采用materialdesign 风格设计,以及动画效果的引用,使用户和软件具有更加青睐的交互体验,并通过信息圈子推送,增加了用户粘性
    代码风格佳:在编码过程中,严格要求分包逻辑,采用模块化分包,并对代码进行合理的封装处理,使代码更加模块化,让其他人能更易上手
    实用性:通过不断的更新产品功能和接收用户反馈,让该产品更加地符合消费者思维

    2.4 架构概述本系统采用C/S架构,分为客户端和服务器端。
    客户端被分为了表现层、业务逻辑层和数据访问层三个层面。

    表现层:主要通过Android应用页面来展示数据,以及一系列事件响应的UI控件
    业务逻辑层:主要用于业务逻辑的处理。通常由业务服务Service类和业务实体类Entity组成
    数据访问层:本系统采用的数据库是MySQL,采用XAMPP进行服务器搭建,采用PHP作为后台数据接口编写,用花生壳做域名解析,以达到Android客户端与服务器之间的访问

    3. 需求分析需求分析是“爱吖校推”应用分析的必要阶段,下面分软硬件需求、功能需求和用户需求三方面做介绍,
    3.1 软硬件需求本系统的软硬件需求如下:

    在Android平台上运行,系统在4.0以上
    后台数据库:MySQL
    开发环境:JavaJDK1.7 ,Windows 10
    开发工具:AndroidStudio、Eclipse For PHP、XAMPP、
    个人计算机:华硕飞行堡垒笔记本

    3.2 功能需求“爱吖校推”作为一款功能性软件,其功能需求相当重要。以下为“爱吖校推”的功能需求:
    发通知、发作业
    发通知和发作业,是学校教师特有的功能,教师可以通过“爱吖校推”平台进行通知和作业的发放,每当发一条信息,该班的所有人员便可以收到来自服务器的信息推送,提醒家长打开APP查看。每一条通知和作业家长都可以点赞和互评和回复。这样让家长和学校的关系更贴切,也增加了信息筛选机制,从而避免了QQ群、微信群等多余信息的影响。
    传视频、传照片
    传视频是在社区和通知作业板块均具备的功能,紧跟微视频的时代步伐,教师可以把孩子在学校的精彩表演,录制下来发到班群里,家长便可以看到。家长也可以把孩子在家里做的有意义的事情放到社区,让同一个班级的家长朋友们借鉴。良好的图文并茂社区化,不仅增进了家长和学校的交流,还增进了家长之间的联系和友谊。
    即时通讯
    即时通讯板块是一个整体的板块,基本包含QQ微信的所有功能,依然可以发图片、发消息、发语音、发定位、音视频通话等。意在增加朋友之间的联系和家长和学校教师的一对一交流和多对多交流。
    发现板块
    发现板块主要是轮播的一些优秀且有利于教师家长的APP功能板块,并且会组织一些活动,邀请大家参加。
    离线推送
    离线推送在家校互动平台软件中是一个必备功能,也算是一个核心功能,有它才能保证用户真正收到来自教师发放的信息,以及即时通讯过来的消息。同时音视频通话等即时性要求较高的功能,也得依赖它。并且,推送信息到通知栏的方式向用户传递信息,也是可以增加用户粘性。
    3.3 用户需求在“爱吖校推”应用的开发过程中,为了尽量满足学校老师和家长用户的要求。目前得到的需求有:

    图片显示清晰,但不能太大,以免浪费流量
    微视频的压缩要处理好,不能太浪费流量
    即时通讯要通畅
    要具备离线推送,确保家长用户能收到教师发送的作业和通知
    要有权限管理,不能让外班人员看到本班的消息
    公告和作业不能插广告
    应用不能经常闪退
    应用不能太大,也不能太占内存
    运行要流畅,不能出现卡顿现象

    3.4 用例图3.4.1 登录板块
    3.4.2 班圈板块
    3.4.3 消息板块
    3.4.4 发现板块
    3.4.5 我的板块
    3.5 用例说明3.5.1 UC1用户登录
    3.5.2 UC2用户注册
    3.5.3 UC3找回密码
    3.5.4 UC4发布信息
    3.5.5 UC5查看所有信息
    3.5.6 UC6查看信息详情
    3.5.7 UC7点赞评论回复
    3.5.8 UC8查看联系人和会话
    3.5.9 UC9聊天
    3.5.10 UC10音视频通话
    3.5.11 UC11修改个人信息
    3.5.12 UC12修改孩子信息
    3.5.13 UC13退出登录
    4. 概要设计4.1 系统功能总体设计图4.1.1Android端功能总体设计图
    4.1.2 服务器端功能总体设计图
    4.2 数据库E-R图设计
    4.3 系统类图4.3.1 APP端登录板块
    4.3.2 APP端主页板块
    4.3.3 APP端班圈板块
    4.3.4 APP端发布板块
    4.3.5 APP端消息板块
    4.3.6 APP端发现板块
    4.3.7 APP端我的板块
    4.4 界面设计图片选取界面设计

    图片选取界面设计

    登录界面设计

    手机号验证界面设计

    主界面设计

    课程表界面设计

    联系人列表界面设计

    聊天界面设计

    音频呼叫界面设计

    发布信息界面设计

    发布界面设计

    我的板块界面设计

    按住拍界面设计

    5. 详细设计5.1 数据库详细设计文档本软件的数据库为MySQL数据库,主要是搭建在XAMPP上结合PHP存在。主要分为以下几个数据表:
    5.1.1 用户表设计(aiya_user)


    字段
    属性
    备注




    username
    varchar(20)(主键)
    账号(手机号)


    password
    varchar(20)
    密码


    nickname
    varchar(20)
    用户昵称


    type
    int(1)
    用户类型 (1教师、2 家长、3管理员)


    classid
    int(10)(外键)
    班级id


    avatar
    Varchar(100)
    用户头像地址


    birthday
    date
    生日,实际存储为时间戳


    address
    text(null)
    用户地址


    child_name
    varchar(20) (null)
    孩子姓名


    child_avatar
    text(null)
    孩子头像地址



    5.1.2 班级信息表设计(aiya_class)


    字段
    属性
    备注




    classid
    int(10)(主键自增)
    班级id


    classname
    varchar(50)
    班级名称


    schoolname
    text
    学校名称



    5.1.3 主贴表设计(aiya_main)


    字段
    属性
    备注




    mainid
    int(10)(主键自增)
    帖子id,


    classid
    int(10)(外键)
    班级id


    username
    varchar(20)(外键)
    用户名


    time
    timestamp
    发布时间,实际存储相当于long型时间戳


    infotype
    int(1)
    主贴类型(1 代表公告 2 代表作业 3 代表动态)


    content
    text
    帖子内容



    5.1.4 评论表设计(aiya_comment)


    字段
    属性
    备注




    infoid
    int(11)(主键自增)
    信息id


    mainid
    int(10)(外键)
    主贴id,用于识别隶属于哪一条帖子的评论


    username
    varchar(20)(外键)
    用户名,用于识别发布人信息


    time
    bigint(20)
    发布时间,long型时间戳


    content
    text
    发布内容


    reply
    varchar(20)(外键)
    用户表username作为外键,用于回复@功能



    5.1.5 点赞表设计(aiya_praise)


    字段
    属性
    备注




    praiseid
    int(10)(主键自增)
    点赞信息id


    mainid
    int(10)(外键)
    主贴表外键,用于识别赞的是哪一条主贴


    username
    varchar(20)
    用户表外键,用于识别是谁点赞了



    5.1.6 主贴图片表设计(aiya_pic)


    字段
    属性
    备注




    picid
    int(10)(主键自增)
    图片id


    mainid
    int(10)(主贴表外键)
    主贴id


    url
    text
    图片存放地址



    5.2 CS协议通信文档说明:返回格式为code,msg,data三个字段,code为0是代表请求逻辑正确,-1为请求异常;
    5.2.1 用户系统获取用户是否注册APP
    接口地址: /user/usable_mobile.PHP
    方式和返回:GET JSON
    请求参数:



    名称
    类型
    说明




    mobile
    string
    用户手机号



    注册
    接口地址: /user/register.PHP
    方式和返回:POST JSON
    请求参数:



    名称
    类型
    说明




    username
    string
    用户手机号


    password
    string
    用户密码


    nickname
    string
    昵称


    birthday
    string
    生日,传递long型时间戳


    avatar
    string
    头像上传的地址



    登录
    接口地址: /user/login.PHP
    方式和返回:POST JSON
    请求参数:



    名称
    类型
    说明




    username
    String
    用户手机号


    password
    string
    用户密码



    重置密码
    接口地址: /user/reset_pwd.PHP
    方式和返回:POST JSON
    请求参数:



    名称
    类型
    说明




    username
    string
    用户手机号


    password
    string
    用户新密码



    上传头像
    接口地址: /user/avatar.PHP
    方式和返回:POST JSON
    请求参数:



    名称
    类型
    说明




    file
    File
    需要上传的文件



    更新头像url
    接口地址: /user/update_avatar.PHP
    方式和返回:GET JSON
    请求参数:



    名称
    类型
    说明




    username
    string
    用户名


    iconUrl
    string
    头像地址


    type
    int
    类型1为自己, 2为孩子



    5.2.2 信息系统异步获取主贴等信息
    接口地址: /info/info_main.PHP
    方式和返回:GET JSON
    请求参数:



    名称
    类型
    说明




    classid
    int
    班级id,用于识别可见度


    infotype
    int
    信息类型 1公告2作业3动态


    count
    int
    信息起始数,默认一次获取10条,需要更改联系后台



    获取发布信息人的信息
    接口地址: /info/get_user.PHP
    方式和返回:GET JSON
    请求参数:



    名称
    类型
    说明




    username
    string
    用户名



    评论信息
    接口地址: /info/insert_comment.PHP
    方式和返回:POST JSON
    请求参数:



    名称
    类型
    说明




    mainId
    int
    主贴id


    username
    string
    用户名


    content
    string
    评论内容


    reply
    string
    回复人用户名



    更新点赞信息
    接口地址: /info/praise.PHP
    方式和返回:GET JSON
    请求参数:




    类型
    说明




    mainId
    int
    主贴id


    username
    string
    用户名



    5.3 时序图5.3.1 登录时序图该时序图是实现用例UC1用户的登录。

    用户进入LoginActivity登录界面后按照提示输入账号名(必须为正确的手机号)和密码(不少于6位)
    先采用StringUtil工具类对输入数据进行验证,再把LoginPresenter把输入的数据传递给网络交互类AppService,让其与服务器进行数据交互并返回给LoginPresenter,通过回调机制让View层显示相关信息,若是登录成功则正确跳转到应用主页面,否则显示相关错误信息


    5.3.2 发布时序图该时序图是实现用例UC4发布信息。

    用户进入发布页面,可以输入相关话题信息,也可上传附件(微视频和图片不共存)
    如果上传附件,则调用压缩相关的工具类进行附件压缩,如果压缩失败,则显示相关错误信息
    未输入信息无法点击发布,如果点击发布按钮,则让ReleasPresenter处理相关逻辑,并把发布话题的信息传递给AppService类做网络访问处理,服务器返回相关信息,采用回调机制让View显示出相关信息
    如果发布成功,则返回到主页面,并发送广播提示主页面进行数据刷新


    5.3.3 圈子信息时序图该时序图是实现用例UC6查看信息详情。

    用户在主页面可以看到话题相关信息(包括通知、作业、社区)
    如果点击任何一条信息,则可以跳转到详情页面,可以查看到相关点赞信息和评论信息
    点击评论可以对该条话题信息进行评论,点击评论人可对该用户进行回复


    5.3.4 聊天时序图该时序图是实现用例UC9聊天。

    用户可以从会话页面或者联系人页面进入聊天页面ChatActivity
    可以发送任何的文本消息,也可以点击下方“加号”按钮进行语音图片视频等文件的发送
    可以直接调用音视频通话,向对方发起通话
    任何的与服务器交互逻辑均交给EMClient类进行处理
    被呼叫的用户可以选择拒绝音视频通话并把相关信息返回给EMClient类
    监听类收到EMClient返回的信息后处理相应回调,显示相关信息


    6. 系统实现6.1 开发工具简介Android Studio :AndroidStudio 是Google推广的一款全新的Android开发工具,采用全新的Gradle方式进行编译,同时对原有的Eclipse开发的项目进行了支持。在2016年年底,Google宣布停止对Eclipse的支持与维护,彻底地宣布了Android Studio作为“Google亲儿子”的地位。其强大的市场占有率成为了使用趋势,我们不能墨守成规,需要向着新趋势看齐。
    XAMPP: XAMPP(Apache+MySQL+PHP+PERL)原来的名字叫 LAMPP,但最新的几个版本被更名为XAMPP,主要是为了避免误解。它作为一款建站集成软件包,功能非常完善,其强大的兼容性更是征服了用户,不仅提供了Windows、Mac等主流操作系统,更是对Linux、Solaris等其它操作系统做了支持。更完美的是,它还支持包含简体中文、繁体中文、英文、韩文等多国语言包。但XAMPP最著名的还是它的便捷性,使用XAMPP只需要下载、解压、启动三个步骤就能让Apache服务器运行在机器上,并且还支持读取PHP文件以及集成了MySQL的使用。 EclipseFor PHP:这款软件是Eclipse分支下专用于开发PHP的一款IDE,支持PHP5和PHP7,在这里,我们主要用它来开发后台接口板块。
    6.2 开发界面总览6.2.1 Android开发界面总览Android 源码分包预览

    Android 资源文件预览

    6.2.2 PHP开发界面总览
    6.2.3 数据库操作页面总览
    6.3 核心功能代码6.3.1 图片压缩处理项目中的图片压缩来源于我GitHub已经开源的一个开源库,目前项目已经得到超700 Stars,主要采取BitmapFactory的内部类Options以及Bitmap下的createScaleBitmap方法对图片进行质量压缩和尺寸压缩。
    思路:

    Bitmap是一个相当大的对象,特别容易导致OOM,所以我们在压缩的时候并不能直接采用Bitmap,而采用BitmapFatory.Options。它有一个相当强大的属性:inJustDecodeBounds,当这个属性为true的时候,调用decode前缀的方法返回的就不是一个完整的Bitmap对象,而是null。因为它禁止这些方法为Bitmap分配内存,当设置这个属性为true时,便会复制Options的三个属性,它们分别是outWidth,outHeight和outMimeType。相当于不读取这个图片,却获取到了它的参数,的确很厉害。
    另外一个不得不说的属性就是inSampleSize了,可以理解为压缩比率,设置好这个比率,就能调用decodeXXXX方法获得缩略图了,如果图片大小都一致,则可以定死它。可问题是我们的图片大小通常是不一致的,那我们压缩的重中之重就是获得这个正确的比率。因此,咱们完全能够经过我们想要的长宽,通过多次循环比对,从而达到等比例压缩。
    然而, inSampleSize官方注释告诉我们一个必须注意的点:因为inSampleSize只能是2的整数次幂,意味着如何我们通过循环算出来inSampleSize为6的话,这时候只能向下取得整数次幂,也就是4。这样明显是达不到我们想要求的标准的
    Bitmap的createScaleBitmap这个方法成功消除了我们的焦虑,我们可以借用这个方法把我们之前得到的较大的缩略图进行二次缩小,直到完全符合我们的要求

    核心代码为:




    6.3.2 相机适配处理图片选取来自于我维护的一个开源库ImagePicker,目前GitHub Star数超过1300+,主要通过从数据库读取所有图片信息并返回到一个List中,该List将把所有图片的path存储在一起,然后把这些图片放在RecyclerView中显示,项目UI完全仿照微信做处理。为了解决Intent传值限制,我在项目中采用单例加锁的方式得以解决。
    针对Android的适配上也是下了不少功夫,主要表现在Android 6.0 的动态权限处理,以及Android 7.0的相机打开限制,当然还有必不可少的MIUI系统坑和三星机器的图片旋转问题。
    下面谈下解决方案:
    6.0动态权限处理:
    在Android 6.0 (API 23)开始,Android开始引入动态权限处理,即除了在之前的AndroidManifest.xml文件中申明权限,还需要在使用到权限的时候弹出用户是否授权的框。只需要重写onRequestPermissionsResult方法即可。示例代码如下:


    对于调用系统相机拍照后图片旋转:
    经常会遇到一种情况,拍照的时候看到照片是正的,但是当APP获取到这张图片的时候,却发现旋转了90度(也有可能是180,270,不过90度比较多见,这应该是手机传感器导致的)。为了解决这种不一定在所有机器上都出现的问题,我们可以引入Android系统提供的ExifInterface类来解决各个属性的操作。ExifInterface可以不用加载图片就获取到图片的长宽、旋转角度等多种属性,我们可以通过ExitInterface获取图片的旋转角度degree来进行处理,当满足degree不为0的时候,调用Matrix的postRotate进行角度旋转,核心代码为:


    对于部分机型调起相机会回不去APP的适配处理(拍完照闪退问题):
    这也是相机适配中必须处理的地方,由于Android系统厂商的ROM不一致,会让一些ROM对自带相机应用做优化,当某个APP通过Intent进入相机拍照界面时,系统会把这个APP最上层的Activity销毁回收,只需要重写onSaveInstanceState和onRestoreInstanceState方法对数据进行恢复和保存即可,核心代码为:

    Android 7.0调用系统相机的处理:
    由于Android 7.0 手机开始推广,所以我们也不得不处理7.0的权限问题。在Android 7.0 以后,file:// 不被允许作为一个附件的Uri的意图,否则会抛出FileUriExposedException,在这样的情况下,我们只需要用FileProvider即可解决。核心代码如下:
    <provider Android:authorities="${APPlicationId}.provider" Android:name=".ImagePickerProvider" Android:exported="false" Android:grantUriPermissions="true"> <meta-data Android:name="Android.support.FILE_PROVIDER_PATHS" Android:resource="@xml/provider_paths"/></provider>


    7. 软件测试基于Android等移动终端平台的APP软件测试与传统的软件测试不同,它不仅要求兼容性良好,而且要求响应时间要在一定的限制范围。比如用户的操作响应时间一般不能超过3-5秒,APP启动时间也不能太长。而对于Android操作系统,庞大的第三方厂商定制,导致Android系统各有差异。一个APP软件必须满足不用的屏幕分辨率都能正常显示,并且能够正确的完成相应功能。如果在某个环境下,界面功能显示不全,则会导致软件功能无法正确使用,也就失去了安装此软件的意义,所以对其兼容性的要求也是很重要的一个方面。
    7.1 功能模块测试功能模块的测试是最基本的测试。我通过找出APP的测试点,然后采用两款手机,小米3S(Android 5.0)和小米5S(Android 7.0)以及Windows抓包工具Fidder分别对“爱吖校推”的功能模块和网络接口进行完整测试,在测试过程出现了几个小问题。

    图片选择页面出现选择异常,而后得以解决,因为导包错误,导致指向了另外一个文件
    发布信息后没有刷新页面的Bug,后面采用广播提醒UI刷新得以解决

    在解决完相关bug后,进行了新一轮的测试,下面是简单的测试情况:
    7.1.1 用户登录注册模块测试该模块测试主要是验证用户的注册登录是否能正常使用,任何不正确逻辑都应该给出相应的提示。在注册时,手机号必须符合规范,密码不得少于6位,否则提示输入不规范。注册时需要输入两次密码,并且密码相同,验证码输入必须正确,否则提示相应错误。登录板块,第二次登录应该自动登录。



    测试项目
    测试方法
    预期结果
    结论




    用户注册
    在注册界面输入用户名,密码,其他信息(符合要求)
    注册成功
    与预期结果一致


    用户注册
    在注册界面不输入内容或者输入信息不符合要求
    注册失败
    与预期结果一致


    用户登录
    在登录界面输入用户名,密码,且用户名和密码匹配
    登录成功
    与预期结果一致


    用户登录
    在登录界面不输入内容或者输入信息不正确
    登录失败
    与预期结果一致


    用户登录
    没有退出当前账号,第二次进入该系统
    自动登录成功
    与预期结果一致



    7.1.2 信息发布模块测试该模块测试主要是验证能否正常发布信息和上传图片及微视频,当没有输入信息时候应当不能点击发送按钮。附件上传前要注意压缩,并且上传后应该在班圈信息中得到正常显示,中间有任何出错需要提示相应错误。而且在6.0以上系统的手机应该动态申请权限。在发布通知或者作业页面,应当发起推送到该班级圈子下的家长手机中。



    测试项目
    测试方法
    预期结果

    结论




    信息发布
    不输入任何文字点击发布
    发布按钮不能点击

    与预期结果一致


    信息发布
    输入信息点击发布
    发布成功,班圈显示刷新显示本条内容

    与预期结果一致


    信息发布
    点击图片上传,进入图片选择页面,选择后点击确定返回
    选择图片后在信息发布页面能显示正常的图片信息,并且首次使用该功能应该弹出申请权限的对话框

    与预期结果一致


    信息发布
    点击微视频上传,进入微视频录制页面,点击上传后返回
    信息发布页面正常显示该条微视频的缩略图,点击缩略图能正常播放视频,首次使用该功能应该弹出动态申请权限的对话框

    与预期结果一致


    信息发布
    发布信息,查看Fidder抓包情况
    Fidder抓包信息应当显示和接口预期一致

    与预期结果一致


    信息发布
    发布班级通知或者作业的时候,查看Fidder抓包情况和该班级圈子的家长用户手机情况
    Fidder抓包信息应该和接口预期一致,并且该班级圈下的家长应该收到信息推送

    与预期结果一致



    7.1.3 信息交流模块测试该模块测试主要是测试信息能否正常地点赞评论回复,在该功能中,如果本用户之前未点赞(灰色),应当把点赞按钮置为点赞状态(红色),点赞数+1。点击班圈某条信息,可以正常进入到该信息的详情页面,并可以评论,返回后正常显示相关信息。



    信息交流
    测试方法
    预期结果
    结论




    信息交流
    点击班圈的某条信息
    应该能正常进入详情页面
    与预期结果一致


    信息交流
    点击点赞按钮
    在未点赞的时候应该为灰色,点赞后应该为红色,可以取消点赞,相应数目应该变化
    与预期结果一致


    信息交流
    点击评论按钮
    进入信息详情页面,并且弹出键盘
    与预期结果一致


    信息交流
    点击评论的人
    应该直接开始弹出软键盘,并且置为回复该用户的状态
    与预期结果一致


    信息交流
    点击返回
    如果该条信息详情有所更新,应当提醒班级圈正常显示点赞情况和评论数目情况
    与预期结果一致



    7.1.4 即时通讯模块测试即时通讯模块测试主要是测试添加好友,音视频通话,聊天,发送附件,好友列表等能否正常显示,以及APP置于后台能否正常收到离线推送的即时通讯消息。



    即时通讯
    测试方法
    预期结果
    结论




    即时通讯
    点击消息Tab
    能查看到最近联系人
    与预期结果一致


    即时通讯
    点击联系人Tab
    能正常显示联系人相关信息
    与预期结果一致


    即时通讯
    点击某条会话或者联系人
    能正常进入聊天页面,并能正常显示信息和聊天
    与预期结果一致


    即时通讯
    点击音视频通话
    进入音视频通话页面,被呼叫用户应当能正常收到此信息,并可选择挂断,发起者可以收到用户B接受或者拒绝的反馈,若是接受,应当正常进行音视频聊天
    与预期结果一致


    即时通讯
    用户B应用置于后台,用户A给用户B发送文本消息
    用户B手机能收到信息推送
    与预期结果一致


    即时通讯
    用户B应用置于后台,用户A向用户B发起音视频呼叫
    用户B应当直接呼起音视频通话页面,并能选择接受或者拒绝
    与预期结果一致



    7.3 性能测试性能测试需要验证APP在各种外界压力下是否能正确响应;在执行单一操作时候的响应时间;重复操作一功能,系统资源占用情况;我们在项目中采用了LeakCanary开源框架,并把它移植到项目中检查内存泄漏情况。并且使用Android内存泄漏分析工具(MemoryAnalyzer)检测内存使用情况,最终通过分析优化了下面两个方面:

    图片压缩不要将整个图片以Bitmap读入内存,防止OOM的发生,替换为ExitInterface类获取图片信息,并采用BitmapFatory的decodeXXX方法以及Bitmap的createScaleBitmap进行尺寸压缩,最后再进行质量压缩得以解决
    项目中有些地方采用了static静态对象,持有Context等导致内存久久不能释放,后面替换了ApplicationContext得以解决
    测试过程中发现启动白屏现象较为严重,所以增加闪屏页得以缓解

    7.4 安全测试随着移动互联网的飞速发展,而作为产业模式下的移动平台,自然备受关注,依托此平台的APP的安全性进而成为人们的焦点。所以我对软件权限等进行了细致检查,得到以下结果:

    没有任何的泄密权限或者非法访问情况
    没有出现任何的自启动,没有捆绑其他任何软件
    数据加密均正常,不存在泄密危险

    7.5 交叉事件测试交叉事件测试,又叫事件或者冲突测试。意思是当APP在运行中,与此同时被另外的事件干扰,比如接入电话,查看短信后是否会导致APP崩溃或者数据丢失等异常。如果执行干扰的冲突事件后,应用APP依然能正常运行,不会出现崩溃、终端死机或者丢失数据等问题,则视为我们的交叉事件测试通过。
    在交叉事件测试中,我着重检查了几个方面:
    APP运行时,前台后切换或者横竖屏切换出现了数据的丢失,经过修改后得以解决;
    APP运行时,能正常接收电话和短信;
    运行“爱吖校推”,并不会影响其他功能的使用,依然能正常的查看QQ消息、微信消息等。
    7.6 兼容性测试在Android众多的第三方定制系统的大背景下,各种各样奇葩的兼容性问题一定存在,虽然在我们开发中采用的测试真机是公认最容易出问题的MIUI手机,但依然不能以偏概全,在兼容性测试阶段,我采用腾讯云真机租用做了基本所有定制系统的兼容性测试。在兼容性测试中,我着重处理了:
    Android 7.0 后不能直接通过Uri调用系统相机,检查出问题后,采用了文件FileProvider得以解决。
    在三星手机的测试中,出现了拍照后旋转问题,最后在代码中通过ExitInterface等操作解决了这个问题。
    8. 结论本次毕业设计针对越来越被看好的“互联网+”教育,着眼于促进教育现代化发展,加强学校与家长的沟通交流。设计过程中采用较多的Design美学理念和动画效果,增加用户粘性。提供推送服务,极大的满足了用户不丢失重要班级信息。社区化的设计,帮助用户群体更好的交流。
    由于各方面的原因和经验匮乏等问题,本应用的一些细节处理还不那么完美,但我依然会完善下去。开发这款应用,让我学到很多,比如很多当前Android火热的框架,Retrofit、Rx、即时通讯、推送以及图片压缩等,尤其是后台板块的学习,PHP作为当前比较热门的语言,我直接从零学习到一步一步搭建起自己的后台,收获巨大。
    参考文献[1] 明日科技.Android从入门到精通[M].北京:清华大学出版社,2012.9
    [2] 郭霖.第二行代码[M].北京:清华大学出版社,2016.11
    [3] 李刚.疯狂Android讲义(第3版)[M].北京:电子工业出版社,2015.
    [4] 郭金尚.Android经典项目案例开发实战宝典[M].北京:清华大学出版社,2013.9
    [5] 刘金桥. 基于web的贝佳宠物医院管理系统设计与实现 2015-06-03
    [6] 许瑾.第一次开发Android程序的历程[J]. 科技资讯,2014.29.20
    [7] 丁丽萍.Android操作系统的安全性分析[J].信息网络安全,2012.3:58-60
    [8] 王珊.数据库系统概论.北京:电子工业出版社,2015
    [9] (美)赞德斯彻.深入PHP:面向对象、模式与实践(第3版)[M].机械工业出版社,2009.4
    [10] 杨宇.PHP典型模块与项目实战大全[M].清华大学出版社,2012.1
    [11] (美)林恩.贝伊利,迈克尔·莫里森着苏金国,徐阳译O’Reilly:HeadFirst PHP &MySQL(中文版)中国电力出版社2010 386
    [12] 马千里. 基于安卓手机的“视界”应用程序的设计和实现2016-05-31
    2 评论 20 下载 2018-11-26 09:44:50 下载需要15点积分
  • 基于Python的QR二维码的生成与识别程序

    摘 要进入二十一世纪之后,高新技术产业得到了极其迅速的发展。计算机、互联网、物联网、云计算等领域的发展,使得整个社会的信息化程度极大提高。随着技术的不断成熟,目前的一维条形码已逐渐向二维码过渡。本课题即是南京苏宁易购电子商务有限公司为方便进行固定资产管理和提高资产清点速度而进行的。
    本文详细阐述了一种使用QR二维码协助进行资产管理的Python程序设计开发过程。通过本程序,用户可对需要编码或解码的资产信息执行编、解码操作,并生成相应的QR二维码图像或获得原始编码信息,所有生成的QR二维码图像以PNG文件格式进行储存。在用户界面方面,本课题通过Web.py框架实现了二维码的在线编解码功能,用户可通过网页进行相关操作。
    本课题另外提供了单笔资料编码及批量资料编码功能,方便用户针对不同的使用情况进行编码操作,所生成的QR二维码可以应用于Android和IOS系统,使用手机软件扫描即可获得原始编码的资产信息。
    关键词:QR二维码 Python 资产管理 网页
    AbstractWhen entering 21 Century, the high-tech industry has developed rapidly. With the development of computer sciences, internet, internet of things, cloud computing, etc., the informatization level of our society is greatly improved. Now, the trend of replacement of traditional 1D bar code with 2D code is becoming increasingly evident. This project is for asset management and speeding up asset inventory implemented by Suning Yigou Ecommerce Ltd.
    This paper describes a development process which uses QR two-dimensional code to assist asset management by Python. Through this program, a user can perform coding, decoding operation on his needs to encode or decode the asset information, and generate the corresponding QR two-dimensional code image or obtain the original code information, all QR code image which is generated by the program is stored with PNG file format. In the user interface, this project uses the Web.py framework to complete the online two-dimensional code encoding and decoding feature, users can manipulate on the Webpage.
    This task provides mass generation and one time generation. The generated QR code image is accussable on Android and IOS system. When scanned by mobile phone apps, then the asset information will show up.
    Key Words: QR code; Python; Assets management; Web site
    第一章 绪论1.1 课题背景在公司企业的管理中,除了员工管理,公司的资产管理业非常重要。良好的资产管理,可以有效地降低企业成本,准确反映企业的经营成果和业绩,杜绝腐败现象;反之,则会造成资源利用率低,甚至资产丢失,给公司和企业造成经济损失。
    本人在南京苏宁易购电子商务有限公司IT运营总部实习期间,需要处理关于IDC机房资产的管理问题。为防止资产丢失和损坏,提高资产的使用率,部门领导要求每月进行一次资产清点,以便及时掌握机房的资产信息,为购买新设备、淘汰旧设备和资源调度提供依据。每月资产清点时,都要到机房里查看每个设备的铭牌,登记信息,任务相当繁琐,尤其是对于大型的设备,非常不便。因此领导考虑以后逐渐采用二维码方式进行资产管理,以提高清点的速度,减轻人工劳动强度。拟将资产数据库中的资产记录编入二维码,贴于设备的易见位置。这样,在进行资产清点的时候,可以使用带存储功能的扫码枪,将机房中的需要清点的资产全部扫描一遍,就可以很容易地得到所有的资产信息,并且可以将采集到的信息直接数字化,方便与资产管理数据库进行核对。这样一来,既可以快速、有效地了解到资产动态,又减轻了工作人员的负担。
    本课题的主要任务就是编写生成QR二维码的程序,将IDC机房内的服务器、路由器等资产信息生成QR二维码,并设计网页,通过网页的形式提供生成QR二维码的功能。网页上提供两种生成QR二维码的方式:单次录入和批量生成,以及解码功能:上传QR二维码图像,解码后显示其中包含的信息。本课题可以使得资产清点工作变得更加方便快捷,提高资产管理的效率。
    1.2 研究现状进入二十一世纪之后,高新技术产业得到了极其迅速的发展。计算机、互联网、物联网、云计算等领域的发展,使得整个社会的信息化程度极大提高。在计算机还未普及的时候,公司对于资产的管理主要通过纸质媒介记录,既不方便查阅,也不方便保存和更改。后来,计算机走进人们的生活,公司也开始使用相应的数据库之类的软件进行资产管理,但对于资产的频繁变动和更改等问题,仍显乏力。
    一维条形码的出现使得这种状况得到了改善。一维条形码是将宽度不等的多个黑条和白条,按照一定的编码规则排列,用以表达一组信息的图形标识符[1]。一维条形码可以标出物品的生产国、制造厂家、商品名称、生产日期、图书分类号、邮件起止地点、类别、日期等信息,因而在商品流通、图书管理、邮政管理、银行系统等许多领域都得到了广泛的应用。当公司企业使用一维条形码进行资产管理时,只需将资产进行编号,把资产编号编码进条形码,打印成标签贴于资产上即可。在清点资产时,使用扫码枪扫描一维条形码标签后就可以得到条形码中的资产编号,然后根据该资产编号就能在公司的资产数据库中查询到该资产的详细信息。
    随着社会的发展,信息量的暴增,人们的需求激增,一维条码已经无法满足需求,因此出现了二维码。相比于一维条码,二维码能存储更多的信息,可以记载更复杂的数据,比如图片、网络链接等。二维码分为堆叠式二维码和矩阵式二维码两种。堆叠式二维码是在一维条形码的基础上发展而来的,堆叠式二维码是通过将多个一维码在纵向进行堆叠而产生的,代表码制为:Code 16K、Code 49、PDF417等。矩阵式二维码是在一个矩形空间内通过在不同位置放置黑、白像素进行编码,代表码制有: Aztec、Maxi Code、QR Code、 Data Matrix等。在二维条码的众多码制中,目前应用最广泛,最常见的是Quick Response Code,即众所周知的QR二维码。
    在我国,QR二维码的使用正处于发展阶段,在工业、媒体、营销、物联网等领域,都得到了较为广泛的应用。
    QR二维码是由日本丰田子公司DENSO WAVE公司于1994年发明的一种二维条形码[2]。QR是Quick Response的简称,意为快速响应。由此可见QR码的特性之一就是扫描读取速度快。DENSO WAVE公司开发QR二维码的最初目的是为了对生产车间中的汽车零配件进行管理和跟踪,这也是对资产进行管理的一种形式,因此,使用QR二维码进行资产管理是有历史依据的。
    图1.1展示了一个包含“hello world!”的QR二维码图形:

    1.3 工作环境和背景在苏宁云商集团股份有限公司旗下的购物平台南京苏宁易购电子商务有限公司实习期间,隶属IT基础运维部门的IDC机房监控组,领导就安排部门的实习生相互合作,完成相关任务。因为考虑到使用的便捷性,还要求整个项目要能以网页的形式提供服务,要具有批量生成和单次生成两个功能,如果有可能的话再加上一个简单的用于测试的解码功能。我主要负责将信息编入二维码并存储二维码图像的功能,其他部分,例如,与资产数据库交互,发布到服务器上运行等任务,交由其他同事负责。
    程序的编写需要有特定的开发环境,包括操作系统环境和编程语言环境和编程工具。因此,在编写程序之前需要首先确定课题的开发环境。
    1.3.1 操作系统由于运维部门的日常工作是负责维护服务器的正常运行,平时的工作环境都是Linux操作系统,而且我个人也比较喜欢Linux操作系统,希望能在这方面学得一技之长,平时一直使用Linux的Ubuntu Kylin14.04 64Bit发行版操作系统作为主用系统。因此,本课题的所有程序代码均在Ubuntu Kylin14.04 64位操作系统下编写完成。
    1.3.2 编程语言网页采用Html(HyperText Markup Language)语言编写,版本Html 5。
    由于整个运维部门的运维脚本正在准备使用Python语言进行改写,以后的主用脚本也将是Python。Python是一种面向对象的脚本语言,1991年由荷兰人Guido van Rossum首次公开发行。Python具有简洁的语法规则,易读易改,扩展性很好。并且我之前也学习过一些Python语言,对Python有一些基础,因此,综合考虑,决定选用Python语言来编写程序。在本课题中,使用的Python版本为2.7.6,Ubuntu Kylin14.04 64位操作系统自带,无需额外安装。
    1.3.3 编码库和解码库本课题由于侧重于应用层次,因此决定选择已有的QR二维码的编码库和解码库。Python语言具有非常良好的扩展性,有非常丰富的库可以利用。在Python的官方包管理网站,https://pypi.python.org/pypi 上可以找到各种各样的库。经过查找和了解,确定使用qrcode和zbar两个Python库完成QR二维码的编码和解码工作。课题中使用到的Qrcode版本为4.0.4,zbar版本为0.10 。由于系统默认不带有这两个库,因此需要自行安装。
    库模块的安装步骤为:

    先安装Python的包管理程序pip。在终端命令行下键入命令进行安装:
    $ sudo apt-get install python-pip

    通过pip安装qrcode和zbar:
    $ sudo pip install qrcode zbar

    安装好包之后,在python程序中使用时只需import qrcode zbar即可
    1.3.4 网页web框架课题使用网页形式来表现程序的功能,那么HTTP服务器是不可少的。由于本课题规模较小,处于开发阶段,离真正放到服务器上运行还有一段距离,因此决定选用Web框架自带的开发服务器运行和调试,就不再安装Apache、Nginx等专业的HTTP服务器了。
    在Web框架的选择上,我选用了web.py框架。Web.py是一个基于Python语言的轻量级框架,相比于其他框架具有简单易用,扩展简单的优点。课题中使用的web.py版本为0.37。由于该框架系统默认不安装,因此需要另外安装。安装过程类似于qrcode和zbar,在命令行键入命令:
    $ sudo pip install web.py
    即可成功安装web.py模块,当需要在程序中使用时,只需import web即可使用web.py模块的相关功能。
    1.4 主要工作和内容安排本文的主要内容包括:

    QR二维码的由来和优点
    介绍QR二维码的由来,发展过程以及相比于其他二维码的优点等。
    QR二维码的结构
    介绍QR二维码的图形构成,包含的内容,特性和规范标准。
    建立网页
    以网页的形式提供将资产信息生成QR二维码的功能,用户在网页上提交资产信息并提交,网页将信息交给后台程序处理并返回结果。
    Python后台程序
    用户通过网页提交的信息从前端传递给后台处理程序,后台程序根据资产信息生成对应的二维码并保存到指定位置。
    本文的内容安排:

    第二章主要简介QR二维码的特点和格式规范等内容
    第三章主要进程程序设计,包括前端网页和后台程序的功能,以及整个程序的运行流程
    第四章主要是对整个程序的分析,展示程序的运行结果,对过程中出现的错误进行分析和总结
    第五章是总结与展望,主要对全文所做的工作进行了总结


    第二章 QR二维码概述QR二维码专利的持有者,DENSO WAVE公司公开了QR二维码的规格,允许所有人免费使用QR二维码,持有专利权但并不执行。这是使得QR二维码得到广泛应用的一大前提,使得使用者节省了许多专利使用费用。
    2.1 QR二维码的结构2.1.1 相关名词概念QR二维码呈正方形,通常由许多更小的黑色和白色的正方形组成,正方形的最小单位叫一个Module。
    QR二维码本身有两个相关的指标:版本(Version)和纠错等级(Error Correction Code Level)。
    版本是指QR二维码的尺寸,由多少Module组成。版本1是21*21的正方形(单位Module),每增加一个版本,正方形的边长就会增加4个Module,公式是:

    其中,S表示QR二维码的边长,单位为Module,V为版本数。
    根据QR二维码的ISO国际标准,最高版本为40,所以QR二维码的最大尺寸是177*177的正方形。
    QR二维码具有一定的纠错能力,当QR二维码图像被部分遮挡时,仍然能够被正确地读取。因此,在营销行业,经常能见到有些QR二维码被图标遮挡住,但被扫描时仍然能够被正确识别。纠错等级就是用来表征QR二维码可被遮挡的最大面积的指标。QR二维码有四种纠错等级:

    L水平:约7%的字码可被修正
    M水平:约15%的字码可被修正
    Q水平:约25%的字码可被修正
    H水平:约30%的字码可被修正

    2.1.2 图像结构QR二维码的图像结构分为两部分:功能图形和编码区域。功能图形包含:位置探测图形、位置探测图形分隔符、定位图形和校正图形[3]。编码区域包含:格式信息、版本信息、数据和纠错码。

    位置探测图形分隔符用于将位置探测图形与其他区域分隔开来,使得扫描时便于检测。位置探测图形、定位图形、校正图形都是用来在扫描时确定编码区域位置的。位置探测图形是3个形似中文的“回”字的图形,位于整个QR二维码图形的左上角、右上角和左下角。位置探测图形的规格是确定的,在任何版本,任何大小的QR二维码中,都是7*7的正方形。

    定位图形是临近二维码矩阵的上边和左边的两条由黑色和白色的module交叉排成的两条线,与位置探测图形最外层的黑边平齐。定位图形是为了防止在扫描二维码时,由于角度问题或者二维码表面不平时,扫描图形时发生读取错位。
    校正图形形似位置探测图形,只是形状小了一号,中间的黑色区域是1*1的module。校正图形存在于version 2及更高版本,version 1中不存在,随着版本的增加,校正图形的数量也会相应地增加。校正图形和定位图形都是为了提高识别和读取精度的,防止在版本较大的时候无法读取识别二维码。
    格式信息是15bit的二进制信息,分为2组存储,相互冗余。格式信息中的15位二进制信息中5bits是数据位,10bits是纠错码位。数据位中,前2个bits代表使用哪种纠错等级,后3个bits表示使用何种掩码。纠错等级分为L、M、Q、H,四种,正好可以由2位二进制数表示,同样,8种掩码使用3位二进制数代表。10个bits的纠错码是通过前5位的数据位进行BCH算法得到的BCH纠错码。

    版本信息中存储的是QR二维码的版本号,只有version 7及更高版本才存在版本信息。版本信息由6bits的版本号和12bits的纠错码组成,共18bits。除去功能图形和格式、版本信息,QR二维码中剩下的部分用来放置数据码和纠错码。


    在生成最终的QR二维码之前还要进行添加掩码的操作。掩码是为了使得二维码图形中的黑白module能够均匀分布,防止过多的黑色或白色module连在一起导致识别困难。掩码图形与数据区进行XOR异或运算,不影响功能图形。

    2.1.3 特点QR二维码具有超高速识读、360°全方位识读、支持多种编码格式、抗扭曲、可纠错等特点,使得QR二维码优于其他形式的二维条码,并得以广泛地应用。除此之外,QR二维码还可以进行较高程度的美化,非常适合于市场营销,产品推广,使顾客能够得到很好的视觉欣赏感受,使产品更受关注。
    2.2 QR二维码的编解码过程简介2.2.1 编码过程
    步骤1:得到需要进行编码的数据Data
    步骤2:分析Data的数据长度和字符集,选择需要纠错等级ECL和版本号Version,如果不指定Version,则根据Data的数据长度选择最适合的版本Version
    步骤3:将Data按照相应的字符集转换成比特流形式,经过一些细节操作后形成8位一组的数据码字序列Data Stream
    步骤4:根据所选择的ECL对Data Stream进行分块,在每一块内应用RS算法(Reed-Solomon error correction,里德-所罗门纠错算法),生成纠错码字序列EC Stream
    步骤5:将Data Stream和EC Stream进行穿插放置,得到两者的混合码字序列,就是要写入到QR二维码图形中的数据区的数据
    步骤6:根据版本Version选择将功能图形放到空白矩阵中,包括位置探测图形、位置探测图形分隔符、定位图形、校正图形[3]。同时将数据和纠错码字按照黑色为1,白色为0的规则写入到矩阵中
    步骤7:为了防止黑色和白色的Module分布不均匀而导致扫描读取的难度增加,还要选择合适的掩码图形,将已经完成的QR矩阵和掩码图形进行异或操作,使得黑色和白色的Module在分布上趋于均匀
    步骤8:最后,将格式信息和版本信息放入矩阵中,QR二维码图形就完成了


    2.2.2 解码过程
    步骤1:扫描图像,通过位置探测图形找到QR二维码的位置
    步骤2:读取QR二维码中的格式信息,得到纠错等级和使用的掩码代号
    步骤3:读取版本信息,获取该QR二维码所使用的版本
    步骤4:通过再次执行异或运算,去除掩码图形
    步骤5:在位置探测图形、定位图形、校正图形的帮助下,读取QR二维码数据区的内容,得到二进制的比特流
    步骤6:通过纠错算法检查是否有错,如果有错则根据纠错码字进行纠错,恢复真实数据


    第三章 程序设计整个课题的网页应用程序分为两部分,一部分是前端网页代码,第二部分是后端的Python处理代码。
    网页应用的整体功能和流程如下图所示:

    分为3个功能块,单次录入、批量生成和解码功能。每个功能使用同级的Div标签区分,通过<style>设置每个Div的位置和表现式样等属性。下面依次介绍前端程序和后端代码。
    3.1 前端程序前端的网页内容保存在index.html文件中,网页的式样也写在该文件中使用<style></style>标签对确定,不以单独的CSS文件指定网页元素的式样。前端网页如下图所示:

    网页中,用黑色粗斜体表示的“单次录入”、“批量生成”和“解码”分别表示三个功能,每个功能区都是一个Div元素,通过Form表单提交输入的数据。“单次录入”是让使用者手动录入某个资产的相关信息,然后将这些信息编入QR二维码图形中。“批量生成”适用于需要批量生成资产对应的二维码的情况,只需上传资产列表文件,确定尺寸和纠错级别即可。这里的尺寸指的是生成的QR二维码的像素尺寸,html代码中对该选项进行了一些限制,防止由于生成的QR二维码图像太大导致页面崩溃或者响应时间过长等问题。
    浏览器通过Http协议与服务器通信时,遵循Http协议定义的4种与服务器交互的基本方法,分别是GET,POST,PUT,DELETE。常用的是GET和POST方法,PUT和DELETE很少使用,因此不做讨论。GET方法用于发送请求,并从服务器获得数据,而POST方法用于在服务器上创建数据,即向服务器上传数据。使用GET方法提交的表单数据,请求的数据会附在URL之后,以问好“?”分割URL和传输数据,多个参数用与符号“&”连接。POST方法把提交的数据放置在是HTTP包的包体中,可以提交较大的数据,因此,向服务器上传文件的时候基本都使用POST方法。在网页上提供的三个功能中,由于“单次录入”需要发送的数据长度较短,因此使用GET方法;而“批量生成”和“解码”功能由于需要上传txt文件和图像文件,数据量较大,所以采用POST方法。
    资产列表文件是类似于下图所示的结构:

    资产列表文件中从左往右的各个字段代表的意思分别为:资产编号、资产名称、品牌、型号、SN(Serial Number)、使用部门、负责人。各字段之间使用制表符table分隔,每条记录对应一个资产。通常资产编号在公司的资产管理系统中,都是唯一的,因此,可以将资产编号作为生成的QR二维码的文件名,并保存。Index.html的代码如下:
    $def with(msg)<!DOCTYPE html><html> <head> <title>苏宁易购</title> <meta content="text/html;charset=utf-8" http-equiv="content-type"></meta> <meta name="description" content="QR code"></meta> <link href="/static/favicon.ico" rel="shortcut icon" type="image/x-icon" /></head> <body> <div id="main"> <div id="header"> <a href="/"><img src="/static/suning_logo2.gif" alt="logo"></a> <div > <p>苏宁易购-IT运营总部</p> <p>服务器资产标签生成界面</p> </div> </div> <div id="code" > <div id="bat" > <label>批量生成</label> <form method="post" enctype="multipart/form-data" action="/qr" target="a"> <div class="fixed"></div> <ul class="none"> <li class="first">资产列表文件</li> <li class="second"><input type="file" name="chl" /></li> </ul> <ul class="none"> <li class="first">尺寸</li> <li class="second"> <select name="chs" class="listbox"> <option value="120x120">小</option> <option value="230x230" selected="selected">中</option> <option value="350x350">大</option> </select> </li> </ul> <div class="fixed"></div> <ul class="none"> <li class="first">纠错级别</li> <li class="second"> <select name="chld" class="listbox"> <option value="L">L</option> <option value="M" selected="selected">M</option> <option value="Q">Q</option> <option value="H">H</option> </select> </li> </ul> <div class="fixed"></div> <ul class="none"> <li class="first"></li> <li class="second"> <input type="submit" value="批量生成 →" /> </li> </ul> </form> </div> <div id="encode"> <label>                              单次录入</label> <form method="get" action="/qr" target="a"> 资产编号: <input type="text" name="Number" /></p> 资产名称: <input type="text" name="Name" /></p> 品牌: <input type="text" name="Brand" /></p> 型号: <input type="text" name="Type" /></p> Serial Number: <input type="text" name="SN" /></p> 使用部门: <input type="text" name="Department" /></p> 负责人: <input type="text" name="Head" /></p> <input type="submit" value="生成"> </form> </div> <div id="decode"> <label>解码 </label> <form method="post" enctype="multipart/form-data" action="/decode"> <input type="file" name="qrimg" /><br/> <input type="submit" value="解码"/> </form> <p>$msg</p> </div> </div> </div> </body></html>
    由于用于式样代码太长,并且不是主要关注点,因此,上述代码中将其省略,完整代码详见附件文件。
    3.2 后端程序后端程序使用Python的web.py框架编写,主程序为main.py,主要内容和类、函数定义如下图所示:

    Main.py文件的主要代码如下:
    #!/usr/bin/env python# -*- coding: utf-8 -*-import cStringIOimport urllibimport webimport qrcodeimport zbartry: from PIL import Imageexcept ImportError: import Imagefrom lib.mime import ImageMIMEfrom lib import charsetclass Index(object): """首页 """ def GET(self): …class QR(object): """处理传来的数据并显示 QR Code 二维码图片 """ def handle_parameter(self, chl, chld, chs): … def show_image(self, **args): … def GET(self): … def POST(self): …class Decode(object): """解码 """ def decodeQR(self, fileName): … def POST(self): …if __name__ == '__main__': web.config.debug = True urls = ( '/', 'Index', '/qr', 'QR', '/decode', 'Decode', '/.*', 'Index' ) # 应用html模板 render = web.template.render('templates') app = web.application(urls, globals()) app.run()
    下面对main.py中的相关代码进行解释。由于程序过长,因此省略了函数的具体定义与函数体的内容,只给出了main.py中的类定义和函数定义以及其他主要内容。
    第一行的“ #!/usr/bin/env python ”叫做shebang,用于在Linux/UNIX系统环境中指定使用何种解释器来运行该脚本文件,这里使用Python解释器。
    第二行的“ # -*- coding: utf-8 -*- ”用于指定main.py文件所使用的字符集,这里设置成uft-8。后面的“ import ”和“ from … import ”用于导入库文件,功能上类似于C/C++中的#include。qrcode和zbar分别是QR二维码的编码和解码模块,web是wep.py框架的模块名,cStringIO和urllib用于处理网页请求中的一些细节问题。Image模块用于进行图像操作,ImageMIME模块用于确定图片的MIME类型,charset模块用于确定字符串的编码格式。
    “ if __name__ == ‘__main__‘: ”表示如果main.py作为主程序运行时,则执行下面的代码块,否则不执行。这是Python语言的特性,使得main.py文件在必要时可以被其他的py文件import,可以在其他的py文件中调用mai.py文件中定义的类和方法,是提高代码复用的一种技术。“ web.config.debug = True ”这条语句用于启用debug模式,开发过程中,如果出现错误,会给出具体的错误信息,方便debug。Urls是一个元组,用于确定网页运行时的URL处理规则,这里可以看出,对根目录’/‘的请求调用’Index’类来处理,对“ /qr ”的请求调用“ QR ”类来处理,对“ /decode ”的请求调用“ Decode ”类来处理,对于不属于这3种url规则的请求,一律使用“ Index ”处理。
    urls = ( '/', 'Index', '/qr', 'QR', '/decode', 'Decode', '/.*', 'Index' )
    “ render = web.template.render(‘templates’) ”将index.html文件所在的templates文件夹载入成一个web.template.render()对象并负值给变量render,以便后续调用。
    “ app = web.application(urls, globals()) ”调用web.application()函数,产生一个web应用对象,赋给变量app以便后续调用。该函数的调用参数为urls元组和globals()函数,意思是以urls定义的url规则,和在全局名字空间内定义的内容新建web应用对象。这里指之前定义的三个类class Index、class QR和class Decode。
    “ app.run() ”表示启动web应用,开始响应页面请求。
    Main.py在全局范围内定义了三个类,class Index、class QR和class Decode,每个类中定义了一些方法函数。根据Web.py框架的开发文档,在url规则中指定的类中定义的GET和POST方法用于在浏览器请求对于的url时作出响应。Handle_parameter()函数用于分析和处理所要生成的QR二维码的参数。Show_image()函数用于根据调用参数生成QR二维码。Main.py中的类、函数的调用关系如下图所示:

    Main.py程序通过命令行来运行。打开终端Terminal程序,进入到main.py所在的目录下,执行命令:python main.py,即可运行程序,在本机上会运行web.py自带的开发服务器,此时在浏览器中访问 http://0.0.0.0:8080 即可访问前端界面。

    生成二维码的直接代码在QR类中的show_image()函数中,通过qrcode模块实现。
    qr = qrcode.QRCode( version=version, error_correction=error_correction, box_size=box_size, border=border, )
    调用qrcode模块,传入相应的版本、纠错等级、module大小和空白区域尺寸四个参数,产生一个QRCode类的对象qr。然后往这个对象qr中加入待编码信息:
    qr.add_data(content)qr.make(fit=True)im = qr.make_image()
    content代表需要被编码进QR二维码的信息,qr.make(fit=True)表示将content信息编入QR二维码,参数fit=True表示对content内容进行判断,如果content中的数据超出qrcode.QRCode()中传入的version参数所能支持的最大数据量的话,则根据content内容的长度和纠错等级选择最合适的version版本。qr.make_image()用于将qr对象生成图片对象,即最终的QR二维码图形。
    3.3 程序运行流程整个程序运行时,从网页上接受用户输入,将输入信息编码成相应的QR二维码图形,并在后台将图像保存到指定的目录下。下面分别介绍“单次录入”、“批量生成”和“解码”三个功能的程序工作过程。
    3.3.1 单次录入当用户使用单次录入方式时,工作过程为:
    输入资产信息,如下图所示:

    点击‘生成’按钮,将在浏览器中打开一个新的标签页,显示生成的相应QR二维码:

    在生成的过程中,后台程序会在main.py所在目录下的tags目录下保存一个QR二维码的文件,文件名为输入信息中的资产编号,保存为png格式的图片。

    3.3.2 批量生成使用批量生成功能时:
    选择txt格式的资产列表文件,选择生成的QR二维码的尺寸和需要的纠错等级。默认为中等大小和M级别的纠错等级。

    点击‘批量生成’按钮,根据文件中的每条资产记录,后台在main.py所在目录下的tags目录下生成相应的QR二维码图片,文件名为资产编号,png格式的图片。资产列表.txt文件中有20条资产记录,对应地生成了20个QR二维码图片,最后一个201405291535.png是上一小节中演示时生成的。

    3.3.3 解码使用解码功能时,选择需要进行解码的QR二维码文件,然后上传即可。这里选择4.3.1中单次录入信息生成的那个QR二维码文件201405291535.png。

    解码结果中,可以发现,每个字段之间使用冒号隔开,这样分隔的好处是将数据格式化,使得在实际使用时,只需用扫码枪扫描即可得到格式化的数据,方便后续对数据操作。

    解码时,程序会在main.py所在目录下的uploads目录下保存上传的QR二维码图像。

    第四章 结果与分析4.1 可用性验证对于使用单次录入和批量生成的QR二维码,用手机软件进行扫描,测试是否可以被正常扫描,得到资产信息。以201405291535.png和2014010105.png为例,进行测试。

    4.2 测试过程与结果为了保证夸平台性,测试时使用基于android和iOS两种手机系统的手机进行测试,使用的具体型号为小米3和iPhone 5S。为了提高准确度和可行度,在两款手机上分别使用手机QQ和微信进行扫描,读取信息。
    读取结果如下所示,其中黑色背景的截屏是微信,白色背景的截图是手机QQ:



    a
    b
    c
    d










    e
    f
    g
    h









    在验证结果图中,(a)(b)(c)(d)四张图是在基于iOS系统的iPhone 5s 手机上分别使用微信和手机QQ进行扫描201405291535.png和2014010105.png时的结果,(e)(f)(g)(h)则是在基于Android系统的小米3手机上分别使用微信和手机QQ扫描201405291535.png和2014010105.png的结果。
    4.3 分析从测试结果来看,单次录入的资产信息和批量导入的资产信息都能成功地写入QR二维码,生成对应的二维码图像。使用不同手机平台的不同扫码软件都成功地扫描识别程序所生成QR二维码图形,并成功解码出其中包含的信息,且成功率较高。
    功能上还有些欠缺,如果能做成在网页上提交资产信息之后,能返回生成的QR二维码图形的下载链接的话,用户体验会更好。
    第五章 总结与展望5.1 总结本课题简要的了解了QR二维码的相关内容,大致学习了QR二维码的编码和解码过程。并且依赖qrcode和zbar模块实现了一个较为基础的,用于提高企业资产管理效率的QR二维码生成程序。通过网页形式提供服务,实现三个功能,单次录入,批量生成和解码。
    在课题过程中,遇到不少的问题,经过自己查找文献和想知道教师求教,都已基本解决。在所遇的问题中,我觉得最困难的就是乱码问题。在单次录入资产信息进行编码时,生成的QR二维码可以被手机软件扫描解码出正确的资产信息,但是当使用解码功能进行解码时,会发生乱码的情况,无法得到正常的中文资产信息。仔细观察后发现,资产信息中的数字和字母部分的信息在乱码中存在,因此猜测是由于中文字符导致的乱码。经过查阅资料,向导师求教,了解到乱码的原因在于,在解码后,从QR二维码中得到的字符串没能与网页显示时定义的UTF-8字符集相匹配,因此导致在网页上显示时发生乱码。因此,要想解决该问题,只需将解码出的字符串转换成UTF-8编码即可在网页上显示出正常的资产信息。
    5.2 展望本课题虽然完成了将资产信息编入QR二维码的功能,但是仍然存在一些问题。比如程序的健壮性不高,对异常的处理不够,容易崩溃;对于批量生成方式,生成QR二维码图像后,不能从网页前端获得所有的图像文件,只能看到最后一条资产信息的二维码图形,使用稍显不便。
    对于QR二维码将来的发展趋势,我个人认为会逐渐趋于平稳,人们会更多地关注于它的安全方面的问题。因为目前已经有将QR二维码用于金融支付行业,一旦涉及到金融行业,那么它的安全性问题就要接受考验。
    因时间和个人编程能力和精力有限,没能在短时间内对程序进行更多完善。希望在以后能有机会继续完善该课题。
    参考文献[1] 宋卫海,宋士银.二维条码PDF417码技术的应用及发展前景[J].山东轻工业学院学报:自然科学版,2001,15(3):19-24.
    [2] 潘继财.二维条码技术及应用浅析[J].商场现代化,2009 (9):118-120.
    [3] 冯汉禄,黄颖为,牛晓娇,等.QR 码纠错码原理及实现[J].计算机应用,2011,31(A01):40-42.
    [4] 吴昊,孙焘,冯林.在手机与PC间收发二维条码的研究与实现[J]. 2007,17(2):178-180.
    [5] 杨军,刘艳,杜彦蕊.关于二维码的研究和应用[J].应用科技,2002,2(11):11-13.
    [6] 陈荆花,王洁.浅析手机二维码在无联网中的应用及发展[J].电信科学,2010,3(4):39-43.
    [7] 董宇晖.基于Python的设计模式研究[J].电子技术与软件工程,2013,21(11):74.
    [8] 刘彦伟,王根英,刘云.QR码信息加密的研究与实现[J].铁路计算机应用,2013.21(11):37-41.
    [9] 中国物品编码中心.QR Code二维码技术与应用[M].北京:中国标准出版社,2002.
    [10] Mark Lutz[美].Python学习手册[M].第4版.李军,刘红伟.北京:机械工业出版社,2011.
    [11] 曾子剑.基于QR二维码编解码技术的研究与实现 [D].成都:电子科技大学计算机科学与工程学院,2010.
    [12] 张成海,郭卫华.QR Code二维码:一种新型的矩阵符号[M].北京:中国标准出版社,2000.
    [13] Jason Briggs[美].趣学Python编程[M].尹哲.北京:人民邮电出版社,2011.
    [14] 罗春玲.二维条码QR的纠错改进研究[D].湖北:武汉理工大学,2012.
    [15] 周利红,刘书家.QR码图像处理和译码方法研究[J].北京工商大学学报:自然科学版,2008,26(1):63-66.
    [16] 王世慧,杜伟.手机QR码在图书馆的应用探析[J].现代情报,2011,31(6):151-154.
    [17] 胡孝鹏,董强,于忠清.基于图象处理的QR码识别[J].航空计算技术,2007,37(2):99-102.
    [18] 刘宏伟,严妍.快速响应码的识别和解码[J].计算机工程与设计,2005,26(6):1560-1562.
    [19] 徐彩虹,刘志,冯晓斐.一种字符二维码的设计与实现[J].浙江工业大学学报,2012,40(5):518-523.
    [20] Haworth A, Williams P. Using QR Codes to Aid Accessibility in A Museum[J]. Journal of Assistive Technologies, 2012, 6(4): 285-291.
    [21] Massis B E. QR codes in the library [J]. New Library World, 2011, 112(9/10): 466-469.
    [22] ISO/IEC, Inc. Information technology - Automatic identification and data capture techniques - QR Code 2005 bar code symbology specification [EB/OL]. http://raidenii.net/files/datasheets/misc/qr_code.pdf, 2014.
    [23] Soon T J. QR code[J]. Synthesis Journal, 2008: 59-78.
    [24] 陈皓.二维码的生成细节和原理[EB/OL].http://coolshell.cn/articles/10590.html,2013.
    [25] Lincolnloop. Pure python QR Code generator [EB/OL]. https://github.com/lincolnloop/python-qrcode, 2013.
    [26] Weiser M. Ubiquitous computing[J]. Computer, 1993, 26(10): 71-72.
    [27] Sanner M F. Python: A Programming Language for Software Integration and Development[J]. J Mol Graph Model, 1999, 17(1): 57-61.
    [28] DiPierro M. Web2py Enterprise Web Framework[M]. Wiley Publishing, 2009.
    0 评论 1 下载 2019-02-11 08:34:54 下载需要9点积分
显示 0 到 15 ,共 15 条
eject