分类

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

资源列表

  • 基于C语言的飞机票预订系统

    1 解题思路本题需要综合使用数据结构的知识。以此,将航班数据设计成链表形式即定义结构体,其中包含飞机序号,登机口作为数据域,next作为指针域,将此结构体称为Node。将乘客信息设计成特殊的结构体,结构体中包含乘客姓名,性别,目的地,舱位,座位号和身份证,并且用数组包含每个乘客的信息。再设计一个结构体,其中包含刚刚的数组,以及乘客总人数,将此结构体称为Sqlist。而函数执行时,首先将Sqlist初始化。然后进入switch选择,通过选择来执行不同的函数。首先创建航班链表,通过判断输入的飞机序号是不是等于0来判断是否输入完毕。然后输入乘客数据,输入所在飞机号和该飞机乘客容量,使用for循环依次将数组赋值。之后可以通过飞机链表和乘客数据分别查询航班的信息和乘客的信息。并且可以删除飞机信息。最后是将所输入的信息保存成txt格式的文件,以及可以从txt格式的文件中读出数据进行处理。
    2 函数调用图
    3 各函数功能// 创建飞机链表int createPlane(Node *L); // 删除飞机节点int deleteNode(Node *L); // 容器初始化int initSqList (sqList *S); // 构造乘客容器int createSqList (Node *L, sqList *S); // 创建新乘客int createCustomer(int i); // 搜寻航班int searchPlane(Node *L); // 搜寻航班int searchPlane(Node *L); // 保存数据int reserve(Node *L, sqList *S); // 读取数据int read(Node *L, sqList*S); // 主函数,流程控制int main();
    4 测试










    2 评论 127 下载 2018-11-05 09:16:45 下载需要3点积分
  • 基于Java和Sql Server 2000实现的学生选课管理系统

    一、课程设计任务完成学生选课管理系统的开发。
    二、需求描述本系统是一个单机版的小型的学生选课管理系统,在本系统中,开发了一个面向管理员、学生和教师这三个对象的教务平台,对学生提供的服务有登录、选课、、修改登录密码、和查询成绩这几个服务,所选课程总数不能超过3门;对教师提供的服务有登录、修改登录密码和登录成绩;对管理员提供的服务有登录开设学生和教师帐号、删除学生和教师帐号的服务。
    三、系统总体设计3.1 系统架构设计
    架构

    单机
    运行软件环境

    windows XP SP3jdk1.6
    开发环境

    硬件平台
    CPU:P41.8GHz内存:256MB以上
    软件平台:windows XP SP3 、jdk1.6
    操作系统:WindowsXP数据库:SQL Server 2000、SQLServer SP4补丁开发工具:Eclipse 3.3分辨率:1024*768


    3.2 功能模块设计
    各模块功能:

    登录界面:登录界面是有帐号,密码两个JTextField,管理员帐号一字母A开头,教师帐号一字母T开头,学生帐号以字母S开头,登录帐号或密码输入错误会弹出相应的提示对话框
    学生信息管理模块:管理员用于添加和删除学生信息的模块
    教师信息管理模块:管理员用于添加和删除教师信息的模块
    教师密码修改模块和学生密码修改模块:管理员添加的用户帐号的初始密码与用户的帐号相同,用户通过密码修改模块可以自己需改密码
    教师录入成绩模块:教师可以浏览选修自己课程的学生的信息并且录入该学生的成绩
    学生选择选修课模块:该模块通过表格的形式将所有课程列出来,学生可以根据个人兴趣选择不同的课程,每个学生选择的课程数目不能超过3门,而且不能重复选课,否则会弹出对话框,提示用户查看已经选择了的课程
    学生查询成绩模块:通过表格的形式将该学生选择了的课程列出来,如果教师有录入成绩,则可以看到自己的课程成绩

    3.3 数据库设计E-R图

    关系模式

    学生(学生学号,学生姓名,教师性别,教师生日,所在班级)
    教师(教师编号,教师姓名,教师性别,教师生日,教师职称,所在院系)
    课程(课程号,课程名,学分,选课人数)
    选课(课程号,学生学号,成绩)
    授课(课程号,教师编号,上课地点)

    数据库管理系统
    Microsoft SQL Server 2000
    数据库命名
    StudentManager
    数据库表
    Logon(登录帐号密码),主要用来保存用户登录信息。



    字段名
    数据类型
    长度
    是否为空
    是否主键




    UserID
    char
    10
    不为空
    主键


    Passwordr
    charr
    10




    StudentTable1(学生信息表),主要用来保存学生信息。



    字段名
    数据类型
    长度
    主键否
    是否为空
    描述




    StudentID
    Char
    10
    主键
    不为空
    学生学号


    StudentName
    Char
    10

    不为空
    学生姓名


    StudentSex
    Char
    2

    不为空
    学生性别


    StudentBirthday
    Datetime
    8


    学生生日


    Class
    char
    16


    所在班级



    TeacherTable1(教师信息表),用来储存教师的基本信息。



    字段名
    数据类型
    长度
    主键否
    是否为空
    描述




    TeacherID
    Char
    10
    主键
    不为空
    教师编号


    TeacherName
    Char
    10

    不为空
    教师姓名


    TeacherSex
    Char
    2

    不为空
    教师性别


    TeacherBirthday
    Datetime
    8


    教师生日


    Post
    char
    10


    教师职称


    Department
    char
    20


    所在院系



    CourseTable(课程信息表)



    字段名
    数据类型
    长度
    主键否
    是否为空
    描述




    CourseID
    Char
    16
    主键
    不为空
    课程编号


    CourseName
    Char
    16

    不为空
    课程名


    Point
    Char
    8

    不为空
    学分


    StuNumber
    Datetime
    4

    不为空
    选课人数



    ScoreTable(学生成绩表),用于存储学生成绩。



    字段名
    数据类型
    长度
    是否为空
    是否主键




    CourseID
    char
    16
    不为空
    主键


    StudentID
    char
    10
    不为空
    主键


    Score
    int
    4




    TeachTable(j教师授课表)



    字段名
    数据类型
    长度
    是否为空
    是否主键




    CourseID
    char
    16
    不为空
    主键


    TeacherID
    char
    10
    不为空
    主键


    Location
    int
    10




    数据库账户及权限 (截图)
    学生账户表

    教师账户表

    用户登录表

    数据库存储过程
    AllCourse
    create proc AllCourseasbegin select x.CourseID,x.CourseName,x.Point ,y.TeacherName,y.Post,z.Location,x.StuNumber from Course x,TeacherTable1 y,TeachTable z where x.CourseID=z.CourseID and y.TeacherID=z.TeacherIDend
    DeleteLogon
    create proc DeleteLogon(@id char(10))asbegin delete from Logon where UserID=@idend
    DeleteStudent
    create proc DeleteStudent(@id char(10))asbegin delete from StudentTable1 where StudentID=@idend
    DeleteTeacher
    create proc DeleteTeacher(@id char(10))asbegin delete from TeacherTable1 where TeacherID=@idend
    InsertLogon
    create proc InsertLogon(@id char(10))asbegin insert into Logon values(@id,@id)end
    InsertStudent
    create proc InsertStudent(@userid char(10),@username char(10),@sex char(2),@birth datetime,@class char(10))asbegin insert into StudentTable1 values(@userid ,@username,@sex,@birth,@class)endreturn
    InsertTeacher
    create proc InsertTeacher(@userid char(10),@username char(10),@sex char(2),@birth datetime,@post char(10),@department char(10))asbegin insert into TeacherTable1 values(@userid ,@username,@sex,@birth,@post,@department)endreturn
    IsExistsStu
    create proc IsExistsStu(@id char(10))asbegin select * from StudentTable1 where StudentID=@idend
    IsExistsTea
    create proc IsExistsTea(@id char(10))asbegin select * from Teachertable1 where TeacherID=@idend
    ProcAllStu
    create proc ProcAllStuasbegin select * from StudentTable1end
    ProcAllTea
    create proc ProcAllTeaasbegin select * from TeacherTable1end
    ProcLogon
    create proc ProcLogon(@userid char(16),@password char(10))asbegin select * from Logon where UserID=@userid and Password=@passwordendreturn
    ProcModify
    create proc ProcModify(@id char(10),@password char(16))asbegin update Logon set Password=@password where UserID=@idend
    ProcStudent
    create proc ProcStudent(@id char(10))asbegin select * from StudentTable1 where StudentID=@idend
    SelectCourse
    create proc SelectCourse(@id char(10),@courseid char(16))asbegin insert into ScoreTable values(@courseid,@id,null)end
    SelectedCourse
    create proc SelectedCourse(@id char(10))asbegin select * from ScoreTable where @id=StudentIDend
    SelectedCourseNum
    create proc SelectedCourseNum(@id char(10))asbegin select COUNT(*) from Scoretable where StudentID=@idend
    SelectedDetail
    create proc SelectedDetail(@id char(10))asbegin select x.CourseID,x.CourseName,x.Point ,y.TeacherName,y.Post,z.Location,s.Score from Course x,TeacherTable1 y,TeachTable z,ScoreTable s where @id=s.StudentID and x.CourseID=z.CourseID and z.TeacherID=y.TeacherID and x.CourseID=s.CourseIDend
    3.4 系统界面设计3.4.1 窗体功能描述登录界面Logon.java

    管理员以帐号Admin密码123登录成功进入管理员的信息管理界面,通过点击“学生信息管理”和“教师信息管理”进入不同的管理界面,学生信息管理界面如下:

    教师信息管理界面如下:

    以学生帐号(如:帐号:S001001,密码:S001001)登陆成功后进入如下界面,首先显示的 是学生的基本信息:

    点击“课程列表”按钮进入如下界面:

    根据自己的跟人兴趣选择课程,选择的课程数目不能超过3门否则弹出对话框如下:

    点击“确定”跳转到已选课程列表。
    在主界面点击“已选课程”按钮也可以进入下面的界面查看已经选择的课程:

    以教师帐号(如:帐号:T01001,密码:T01001)登陆成功后进入如下界面,首先显示的是教师的基本信息:

    点击“录入成绩”可以通过表格来录入学生的成绩,界面如下图所示:

    3.4.2 页面/窗体关系结构图
    四、系统实现技术小结为了方便管理,将数据库的封装分成两部分,数据库资源配置文件和封装数据库操作的类SqlManager.java。
    数据库资源配置文件sysConfig.properties
    #Sepecify the system type: window or unixsystem-type=windows#specify the database's typedatabase-type=sqlserver#specify some parametersDBhost=localhostDBport=1433DBname=StudentManagerDBuser=saDBpassword=
    封装数据库操作的类:SqlManager.java
    import java.sql.*;import java.util.*;import javax.swing.JOptionPane;public class SqlManager { private static SqlManager p=null; private PropertyResourceBundle bundle; private static String jdbcDriver=null; private static String split=null; private String DBType=null; private String DBhost="localhost"; private String DBname=""; private String DBport=""; private String DBuser=""; private String DBpassword=""; private Connection Sqlconn=null; private Statement Sqlstmt=null; private String strCon=null; private SqlManager(){ try{ bundle=new PropertyResourceBundle(SqlManager.class. getResourceAsStream("/sysConfig.properties")); this.DBhost=getString("DBhost"); this.DBname=getString("DBname"); this.DBport=getString("DBport"); this.DBuser=getString("DBuser"); this.DBpassword=getString("DBpassword"); String system_type=getString("system-type"); if(system_type!=null){ if(system_type!=null){ if(system_type.toLowerCase().equals("widows")) split=";"; else if(system_type.toLowerCase().equals("unix")) split=":"; } String database_type=getString("database-type"); this.DBType=database_type; if(database_type!=null){ if(database_type.toLowerCase().equals("mysql")){ jdbcDriver="com.mysql.jdbc.Driver"; strCon="jdbc:mysql://"+DBhost+":"+DBport+"/"+DBname; } else if(database_type.toLowerCase().equals("oracle")){ jdbcDriver="oracle.jdbc.driver.OracleDriver"; strCon="jdbc:oracle:thin:@"+DBhost+":"+DBport+":"+DBname; } else if(database_type.toLowerCase().equals("sqlserver")){ jdbcDriver="com.microsoft.jdbc.sqlserver.SQLServerDriver"; strCon="jdbc:microsoft:sqlserver://"+DBhost+":"+DBport+";DatabaseName="+DBname; } } } }catch(Exception e){ e.printStackTrace(); } } public static SqlManager createInstance(){ if(p==null) { p=new SqlManager(); p.initDB(); } return p; } private String getString(String s) { return this.bundle.getString(s); } public void initDB(){ System.out.println(strCon); System.out.println(jdbcDriver); try{ Class.forName(jdbcDriver); }catch(Exception ex){ System.err.println("Can't Find Database Driver."); } } public void connectDB(){ try{ System.out.println("SqlManager:Connecting to database..."); Sqlconn=DriverManager.getConnection(strCon,DBuser,DBpassword); Sqlstmt=Sqlconn.createStatement(); }catch(SQLException ex){ System.err.println("connectDB"+ex.getMessage()); } System.out.println("SqlManager:Connect to database successful."); } public void closeDB(){ try{ System.out.println("SqlManager:Close connection to database..."); Sqlstmt.close(); Sqlconn.close(); }catch(SQLException ex){ System.err.println("closeDB:"+ex.getMessage()); } System.out.println("Sqlmanager:Close connection successful."); } public int executeUpdate(String sql){ int ret=0; try{ ret=Sqlstmt.executeUpdate(sql); }catch(SQLException ex) { System.out.println("executeUpdate:"+ex.getMessage()); } return ret; } public ResultSet executeQuery(String sql){ ResultSet rs=null; try{ rs=Sqlstmt.executeQuery(sql); }catch(SQLException ex){ System.err.println("executeQuery:"+ex.getMessage()); } return rs; } public static void main(String args[]){ SqlManager.createInstance().connectDB(); SqlManager.createInstance().closeDB(); }}
    五、课程设计体会该系统主要实现了学生选课的功能,这个系统是我独立完成,从需求分析,界面的搭建,到数据库的连接,表格,存储过程和存储过程等的建立,在这段时间的摸索中,我确实学到了很多东西,特别是对以前不太了解的Java Swing组件有了更深刻的了解。比如JTable,对于它的用法我在网上找了很多资料,JTable的建立有各种不同的方法,可以使用DefaultTableModel类来实现,如DefaultTableModel dtm=new DefaultTableModel(new Object [] {“”,”课程编号”,”课程名称”,”学分”,”任课教师”,”教师职称”,”上课地点”,”以选人数”},0));然后再table.setModel(dtm); 或者继承AbstractTableModel类,还有对于如何在JTable中添加Swing组件,原本我是直接新建一个JcheckBox对象直接添加到表格的单元格里,结果发现只能显示出一串字符串,上网查找后才知道,要用DefaultCellEditor来添加Swing组件,再设置setCellRenderer(new MyTableRenderer()) 这是一个实现了TableCellRenderer接口的JCheckBox。TableCellRenderer可以看做是Swing组件的呈现器,这样Table就会把内容显示绘制交给JCheckBox了。
    对于数据库,我尽量将对数据库的操作放在存储过程中,这样的抽象和封装使得源程序代码更加容易理解,而且在web应用系统中也可以避免发生不安全的状况,我想这是一个号的程序员应当要养成的习惯,在这次的课程设计中,层次化,模块化,抽象化也是我学到的一个重要的经验,参考一些资料后发现模块化能使程序设计更加简单,设计代码时目标更加明确,效率更高,以前虽然也知道这些道理,但自己真正实施起来却感到无从下手,比如前面的数据库操作和数据库资源配置文件,就是我从书中看来的,这样做的好处是,在程序中操作数据库的时候避免了使用很多try和catch语句,是代码更加简洁,更容易理解,此外需要连接不同的数据库时只要修改数据库的资源配置文件sysConfig.properties就可以了。原本我是想用jsp 做一个web应用程序的,因为对于学生选课系统做成单版的确实没什么实用性,但是我对jsp还不太熟悉,所以这次先做个单机版的,以后我会尝试用jsp来做这个系统。
    2 评论 94 下载 2019-04-29 20:06:31 下载需要12点积分
  • 基于C语言的外卖管理系统

    一. 设计目的此次课设我的主题是外卖管理系统,则希望可以模拟网上订餐,店家工作,专人管理的过程。并实现注册与登录以及基本信息的输出。
    二. 设计内容系统分为三端登录,分别为管理员,用户以及店主;两端注册,分别为用户以及店铺,其中店铺的注册成功需要得到管理员的审核。
    管理员可以实现店铺的增删改查以及自己信息的查看修改和处理,其中,密码修改需要输入旧的密码,三次错误可以找回密码,输入手机号,若手机号匹配得当,则可产生三位数的随机验证码,输入验证码可修改密码。用户可以实现菜品查看与购买,订单查看以及修改,(但对于已超过三分钟的订单不能修改,因为已经配送),本人信息查看以及修改,若查看订单时统计总消费为0,则提示去购买菜品。店家可以实现菜品增加删除查看,业绩的查看,业绩为0时可提示自我反思和提升。
    在本系统中,限制了用户的余额,当购买时余额不足时,需要进行账户的充值,而购买结束后,账户的余额也会相应的减少,这也正是本系统的重要部分,因为网上订餐主要就是体现在其的购买上。
    三.概要设计
    管理员端可以查看店铺信息,店铺信息修改(分为增删改查),处理申请店铺,和修改本人信息
    用户端分为用户相关(为用户本人对自己信息的操作),查看订单,查看菜品,购买菜品,以及订单排序
    管理员端为增添菜品,删除菜品,修改菜品,查看菜品以及业绩查看(统计总订单与总收入)
    注册分为用户注册和点击注册,其中店家注册需要的到管理员的认证,认证通过之后才可登录

    3.1 功能模块图
    3.2 各个模块详细的功能描述
    管理员登录:管理员可以查看店铺信息,处理店铺(包括店铺的增加删除和录入),认证店铺(认证申请的店铺),查看本人信息,修改本人信息(分为修改电话,修改地址,修改密码,其中修改密码需输入就得密码,当时那次输入不正确的时候可以找回密码)
    用户登录:用户可以查看所有的菜品,可以够买菜品(购买时字需要输入菜的种类或者菜名就可以搜索到相应的菜,购买之后需扣除余额,余额不足时会提示购买失败,以及月充值)。查看订单,即统计输出所有的订单,当没有订单时会提示去购买。订单排序分为按才菜名升序以及按总价降序
    店家登录:可以查看本家的菜品,可以增加或者删除本家的菜品,可以修改菜品的名称,单价以及菜系,查看业绩,即统计输出用户在本店产生的所有的订单
    注册:分为用户注册以及店家注册,其中用户注册时当用户名有重复时提示重新输入,当设置的密码不足8位时提示可以修改或者放弃修改,输入完基本信息之后需要绑定银行卡和设置支付密码,当银行卡不足15位时提示重新输入。注册成功之后即可返回登录。店家输入完基本信息之后,需要得到管理员的审核,当审核成功之后才可登录

    四、详细设计4.1 功能函数的调用关系图
    4.2 各功能函数的数据流程图4.2.1 用户申请
    4.2.2 店铺删除
    4.2.3 菜品查找
    4.2.4 店铺订单统计
    4.3 重点设计及编码4.3.1 找回密码修改密码连续三次输错旧密码可以找回密码,输入绑定的电话号正确即可发送验证码到文件,输入正确的验证码即可重新输入新密码。

    4.3.2 购买时余额不足提示充值
    4.3.3 通过调用时间函数修改订单的数量时有时间限制,超过三分钟即提示订单已经配送不可修改,为超过三分钟则提示尽快修改
    五、测试数据及运行结果5.1 正常测试数据和运行结果5.1.1 输出店铺
    5.1.2 删除店铺
    5.1.3 输出并统计店铺订单
    5.1.4 查看本家店铺菜品
    5.1.5 添加菜品
    5.2 异常测试数据及运行结果5.2.1 注册用户
    5.2.2 购买菜品余额不足时
    4 评论 196 下载 2018-10-21 15:23:43 下载需要13点积分
  • 基于深度学习的手写数字识别算法Python实现

    摘 要深度学习是传统机器学习下的一个分支,得益于近些年来计算机硬件计算能力质的飞跃,使得深度学习成为了当下热门之一。手写数字识别更是深度学习入门的经典案例,学习和理解其背后的原理对于深度学习的理解有很重要的作用。
    本文将采用深度学习中的卷积神经网络来训练手写数字识别模型。使用卷积神经网络建立合理的模型结构,利用卷积层中设定一定数目的卷积核(即滤波器),通过训练数据使模型学习到能够反映出十个不同手写提数字特征的卷积核权值,最后通过全连接层使用softmax函数给出预测数字图对应每种数字可能性的概率多少。
    本文以学习基于深度学习的手写数字识别算法的过程为线索,由简入深,从最基础的感知器到卷积神经网络,学习和理解深度学习的相关基本概念、模型建立以及训练过程。在实现典型LeNet-5网络结构的同时,通过更改超模型结构、超参数进一步探索这些改变对模型准确率的影响。最后通过使用深度学习框架Keras以MNIST作为训练数据集训练出高识别率的模型并将其与OpenCV技术结合应用到摄像头上实现实时识别数字,使用合理的模型结构,在测试集上识别准确率达到99%以上,在与摄像头结合实际应用中的识别效果达到90%以上。
    关键词:深度学习,卷积神经网络,MNIST,OpenCV
    ABSTRACTDepth learning is a branch of traditional machine learning, thanks to the recent years, computer hardware computing power of the quality of the leap, making the depth of learning has become one of the popular. Handwritten digital recognition is the classic case of advanced learning, learning and understanding the principles behind the depth of learning for the understanding of a very important role.
    In this paper, the convolution neural network in depth learning will be used to train the handwritten numeral recognition model. The convolution neural network is used to establish a reasonable model structure. A certain number of convolution cores (ie, filters) are set in the convolution layer. The training data are used to study the convolution of the model to reflect ten different handwritten digital features Kernel weight, and finally through the full connection layer using soft max function gives the predicted digital map corresponding to the probability of each number of the probability of how much.
    In this paper, we study the basic concepts, model establishment and training process of the depth learning based on the process of learning the handwritten numeral recognition algorithm based on the depth learning. The basic concepts, the model establishment and the training process are studied and understood from the most basic sensor to the convolution neural network. In the realization of the typical LeNet-5 network structure at the same time, by changing the super-model structure, super-parameters to further explore the impact of these changes on the model accuracy. Finally, by using the depth learning framework Keras to MNIST as a training data set to train a high recognition rate model and combine it with OpenCV technology to apply real-time identification numbers to the camera,using a reasonable model structure, the recognition accuracy is achieved on the test set More than 99%, with the camera in the practical application of the recognition effect of more than 90%.
    Key words: deep Learning, convolution neural network, MNIST, OpenCV
    1 绪论1.1 数字识别研究现状早期的研究人员在数字识别[1]这一方向已经取得了不错的成果,如使用K-邻近分类方法,SVM分类方法,Boosting分类方法等。但这些方法多少都会有不足之处,例如K-邻近方法在预测时需要将所有的训练数据集加载至内存,然后用待测数字图片与训练集作对应像素点差的和,最后得出的差值最小的则为预测结果。显然这样的方法在正常的图片准确度上并不可靠,对于待测手写数字的要求也很高。目前识别率最好的模型应该还属基于深度学习的CNN,最典型的例子LeNet-5,美国最早将其商用到识别银行支票上得手写数字[2]。可见基于深度学习的手写数字识别在准确率上是相当可靠。
    1.2 深度学习的发展与现状机器学习[3]发展大致分为两个阶段,起源于浅层学习,发展于深度学习,深度学习是机器学习的一个重要分支。
    在20世纪80年代末期,反向传播算法的应用给机器学习带来了希望的同时也掀起了基于统计模型的机器学习热潮。通过实践,人们成功发现利用反向传播算法可以使一个人工神经网络模型在大量有标签训练样本中学习统计一定的规律,在此之上进而对无标签事物进行预测。该阶段的的网络模型因为只含有一层隐含层的缘故被称之为浅层模型,浅层模型在参数个数、计算单元以及特征表达上有一定瓶颈。90年代,学术界相继提出各种各样的浅层学习模型,如支持向量机(SVM,supportVector Machine)、Boosting、最大熵方法等。这些模型相比当时的神经网络模型不论是在效率上还是在准确率上都有一定提升[4]。
    直到2006年,加拿大多伦多大学教授、机器学习领域泰斗Geoffrey Hinton和他的学生在Ruslan Salakhutdinov在《科学》上发表的一篇关于“deep learning”的综述文章,正式开启了深度学习的浪潮[5]。深度学习火 起来的标志事件是2012年Geoff Hinton的博士生Alex Krizhevsky、Ilya Sutskever使用深度学习在图片分类的竞赛ImageNet上取得了识别结果第一名的好成绩,并且在识别精度上领先于同样使用深度学习进行识别的Google团队。这里的谷歌团队不是一般的团队,而是由机器学习领域的领跑者Andrew Ng和Google神人 Jeff Dean带领下有着他人团队无法企及的硬件资源和数据资源支持的团队,而打败这个顶级团队的仅仅是两个研究深度学习不久的“小毛孩”,这令学术界和工业届哗然的同时,也吸引了工业界对深度学习的大规模投入。Google收购了Hinton的DNN初创公司,并邀请Hinton加入了Google;LeCun加盟Facebook并出任AI实验室主任;百度成立了自己的深度学习研究所,并邀请到了原负责Google Brain的吴恩达。深度学习发展之快令人吃惊,在2016年初谷歌建立的AlphaGo系统在与围棋世界冠军李世石的对弈中,最终AlphaGo以4:1的大比分应得了比赛的胜利。可以看出,人工智能的第三波浪潮也是和深度学习密不可分的。深度学习里最经典的模型是全连接的神经网络、卷积神经网络CNN以及循环神经网络RNN。还有一个非常重要的技术就是深度强化学习技术,而AlphaGo所采用的就是该技术。
    深度学习的成功归功于三大因素——大数据、大模型、大计算。同时这个三个方向也是当前研究的热点。受益于计算能力的提升和大数据的出现,深度学习在现在的条件下可以将模型层次加深到上亿神经元的计算量的等级。GPU对深度学习计算的支持也放低了进行深度学习研究的门槛,使更多的初学研究人员可以踏足这个领域,为这个领域不断注入新鲜血液,不断提出新的思考,开阔新的应用方向。
    深度学习的本质是通过构建多层隐藏层的机器学习模型和大量的训练数据,在训练中不断调整参数以寻找能反应数据集特点的特征,从而提升类似数据的分类或预测准确性。“深度模型”是手段,而“特征学习”是目的。有别于传统的浅层学习,深度学习强调了模型除输入输出层外的隐藏层数量,通常有大于2层以上的隐藏节点。除此之外深度学习突出了特征学习的重要性,通过逐层特征变换,将样本在原空间的特征表示变换到一个新特征空间,从而使分类或预测更加容易。
    深度学习算法中重要的算法之一就是卷积神经网络算法,目前主要应用在图像分类、图像分割、目标检测等相关计算机视觉领域。上文中提及到的2012年取得图片分类的竞赛ImageNet冠军的团队就是使用改进后的卷积神经网络,使得识别准确率达到了质的飞跃。
    1.3 研究意义数字识别已经应用到了生活中的点滴,如停车场停车按车牌号计费,交通电子眼抓拍违章,大规模数据统计,文件电子化存储等。
    阿拉伯数字作为一种全球通用的符号,跨越了国家、文化以及民族的界限,在我们的身边应用非常广泛。数字的类别数目适当,仅有10类,方便对研究方法进行评估和测试。通过研究基于深度学习的手写数字识别方法有助于对深度学习的理解,具有很大的理论实践价值。手写数字识别的方法经验还可以推广应用到其它字符识别问题上,如英文字母的识别。
    本文设计将训练好的卷积神经网络模型与摄像头相结合,实现对摄像头画面中出现的数字实时识别。
    1.4 论文结构在本文基于深度学习的手写数字识别算法实现中,第一章主要对数字识别的研究现状、深度学习的发展与现状及本文的研究意义作以介绍;第二章内容为本文数字识别核心技术卷积神经网络的基本原理;第三章内容为本文采用的深度学习框架Keras相关使用;第四章内容为本文在经典LeNet5结构的基础上进行单一变量改动,以探究不同因素对模型识别率的影响,总结调参经验;第五章内容为对训练好的手写数字识别模型的实际应用,包含了图像处理和数字图像识别两部分。
    2 卷积神经网络基本原理本文采用深度学习中的卷积神经网络实现对手写数字的识别,卷积神经网络是被设计用来处理多维组数据的,如常见的彩色图像就是三个颜色通道组合。手写数字图片是典型的2D型图像数据,使用卷积神经网络可以有效通过训练提取去手写提数字的特征,本章对卷积神经网络的基本原理作以分析。
    2.1 卷积神经网络2.1.1 卷积神经网络概述卷积神经网络(CNN),是人工神经网络的一种。它是一种特殊的对图像识别的方式,属于非常有效的带有前向反馈的网络。
    常规的神经网络不能很好地适应所有的图像,例如在CIFAR-10的训练集中,图片的大小只有32*32*3(32宽32高3颜色通道),那么通过输入层后的第一个隐藏层的神经元将达到3072个。看似可以接受的数字,当隐藏层由一层上升到两层三层甚至更多时,后面隐藏层每一个神经元全连接权值与输入积的加和计算量将膨胀到无法想象。与此同时,除了效率的低下外,大量的参数还会导致过拟合的发生。与常规神经网络不同,卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度(此处的深度不是网络结构的层数),是一种立体结构。在卷积网络最后的输出层里,会把三维结构的数据转换为在深度方向的一维分类值。
    卷积神经网络诞生的主要目的是为了识别二维图形,它的网络结构对平移、比例缩放、倾斜或其他形式的变形具有高度不变性。卷积神经网络是近些年来发展迅速,备受器重的一种高效识别算法。它的应用范围也不仅仅局限于图像识别领域,也应用到了人脸识别、文字识别等方向。
    2.1.2 卷积神经网络的重要组成部分1.卷积层
    卷积神经网络的核心是卷积层,它产生了网络中大部分的计算。卷积层里最重要的组成就是卷积核。卷积核也可以理解为是一些滤波器集合。每个卷积核在空间上的尺寸都比较小,但是深度和输入数据是一致的。卷积核的大小是个超参数,可以自行选择。卷积核的内容,相当于上一节中全连接神经网络中药更新的权值w,即卷积核就是我们训练卷积神经网络要学习的东西。训练的过程实际上是在寻找能够反映训练数据特征的滤波器。以下图2-1为例,左边的是卷积层的输入image,中间的为卷积核(filter),右边的为卷积后产生的特征图谱(feature map)。卷积层卷积的过程是如图所示从输入的左上角开始,按照卷积核的大小3 3的方框括起相应的元素,与卷积核元素对应位置的元素作积后加和得到特征图谱的第一个元素。方框按一定步幅从左向右从上到下,依次完成卷积,即可得到特征图谱所有的内容。

    2.子采样层
    子采样层也称作池化层在有些文献中也称之为下采样层,在卷积层之间加入子采样层,可以有效减少输入网络中的数据大小,减小规模,控制网络中参数的数量,进而节约计算资源,减少训练所需时间。同时,采样层还能够有效地控制过拟合的出现。最常用的采样方式有两种,一种是Max Pooling,另外一种是Mean Pooling。Max Pooling 是指在算选定的N N尺寸中保留最大的那个作为采样后得样本值。Mean Pooling 是指在算选定的N N尺寸中取样本的平局值作为采样值。研究人员通过不断试验发现,使用MaxPooling的效果好于Mean Pooling,图2-2为2 * 2 大小的Max Pooling采样过程示意。

    2.1.3 权值共享和局部连接权值共享:图像的一部分的统计特性与其他部分是一样的。这也意味着在这一部分学习的特征也能用在另一部分上,所以对于这个图像上的所有位置,都能使用同样的学习特征。
    局部连接:在处理图像这样的高维度输入时,让每个神经元都与前一层中的所有神经元进行全连接是不现实的。相反,让每个神经元只与输入数据的一个局部区域连接。该连接的空间大小叫做神经元的感受野(receptive field),它的尺寸是一个超参数(其实就是滤波器的空间尺寸)。在深度方向上,这个连接的大小总是和输入量的深度相等。需要再次强调的是,对待空间维度(宽和高)与深度维度是不同的:连接在空间(宽高)上是局部的,但是在深度上总是和输入数据的深度一致。
    2.2 神经网络的前向传播和反向传播所有神经网络在训练过程中都存在这两个过程,向前传播计算节点输出,反向传播更新连接权值。
    2.2.1 神经元神经元是组成神经网络的基本单位,如图2-3所示,神经元和感知器在结构上是基本相似的,同样的它们在激活函数上得不同,决定了其输出结果的不同。对于神经元来说,激活函数一般选择为sigmoid函数或者tanh函数或者REUL函数。传统的神经网络激活函数一般选择sigmoid函数或者双曲正切函数 。函数图像如图2-4所示。

    可以看到sigmoid函数的导数可以用sigmoid函数自身来表示。方便计算,这也解释了早期的神经网络会选择sigmoid函数作为激活函数的原因,这是对于早期计算资源的一种妥协。
    2.2.2 神经网络的连接形式神经网络就是多个神经元按一定规则连接在一起。图2-5是一个简单的全连接神经网络。最左边的层叫做输入层,负责接收输入数据;最右边的层叫输出层,可以从这层获取神经网络输出数据;输入层和输出层之间的层叫做隐藏层,因为它们对于外部来说是不可见的。通过上图可以观察到,神经网络一般有以下结构规则:

    同一层的神经元之间没有连接。
    第N层的每个神经元和第N-1层的所有神经元相连(这就是full connected的含义),第N-1层神经元的输出就是第N层神经元的输入。
    每个连接都有一个权值。


    上面这些规则定义了全连接神经网络的结构。事实上还存在很多其它结构的神经网络,比如卷积神经网络(CNN)、循环神经网络(RNN),他们都具有不同的连接规则。
    2.2.3 神经网络的前向传播神经网络的前向传播中,每一层的输入依赖于前一层的输出和两层两层之间连接的权值,如图2-6所示连接的箭头指向为前向传播方向。

    在图2-7中标出以数字1、2、3……对神经元进行编号,输入用x表示,输出用y表示,每条连接的权重用w表示,隐藏层的神经元输出用a表示。前向传播中,要计算当前层的节点输出必须得到前一层的输出,即前层的输出是后层的输入。以激活函数sigmoid为例,由输入层神经元1、2、3获取输入x1, x2, x3仍旧输出x1, x2, x3。如计算a4节点的输出a4=sigmoid(w41X1 + w42X2 + ws3X3 + w4b)中w4b是节点a4的偏置项。同理可以得a5, a6, a7的输出值,得到了隐藏层所有节点的输出值后可以得到最后输出层的输出值,如y1=sigmoid(w84a4 + w85a5 + w86a6 + w87a7 + w8b)。卷积神经网络所采用的前向传播方式与之相似,卷积层与其它层之间的权值在传播过程中改为卷积层的卷积核权值。
    2.2.4 神经网络的反向传播算法神经网络的反向传播是对网络层之间权值得更新过程,层与层之间的权值更新依赖于后一层的输出,反向传播的名字也由此而来。如图2-7所示,反向传播是有输出层向输入层的计算过程。

    反向传播的计算方法以图2-7为例,在上一小节的基础上按序号标注出对应节点的误差项。反向传播算法,字如其名从反方向开始计算,对权值进行更新。在上一节中已经计算了全连接网络的每个节点的输出。这一节从输出层开始,依次计算输出层、和隐藏层每一节点的误差项。输出节点误差项

    i对应相应的节点标号,t表示目标值,y表示节点实际输出值。
    隐藏节点误差项

    其中wki是节点i到下一层节点k的连接权重。权重值得更新依赖于当前节点的输出和下一层对应节点的误差项
    反向传播算法的本质是对链式求导法则的应用,上述的例子中sigmoid函数的求导可以用函数结果本身表示。
    2.3 优化方法——梯度下降梯度下降的方法是目前训练神经网络最常用的一种更新权值的计算方法,其巧妙应用了数学上利用导数求最值的概念。梯度下降具体细分为三种,批梯度下降、随机梯度下降、小批量梯度下降,本节以线性单元为例介绍梯度下降的计算过程。
    线性单元结构如图2-8所示与感知器相比较,替换了激活函数,线性单元将返回一个实数值而不是0,1分类,因此线性单元可以用来解决回归问题,而非分类问题。

    2.3.1 批梯度下降以线性单元为例,设置激活函数为f(x)=x,和感知器的输出结果相似的,因为激活函数得到什么值输出什么值,所以其输出为y=h(x)=w.x+b其中w0仍旧等于b。
    在数学上可以用两个数的方差来表示其相差程度的大小,在这里使用 来训练数据表示通过激活函数后的输出值,用 来表示训练数据所对应的真实标签值,则有:

    把e称作单个样本误差,这里的1/2是用来消除求导后系数2的,并不影响最终结果。当有N个训练数据时,用所有样本的误差和来表示模型的误差E,即

    将上式2代入式3中结合输出函数y化简后得

    此时E(w)称之为目标函数,此函数是关于w的多元二次式。在数学上,我们要求一个函数f(x)的极值点,是对该函数进行求导,当f‘(x)=0时,求得极值点(x0,y0)。对于计算机来说,它可以凭借超强的数据计算能力,通过一次一次怎加或减少x的值把函数的极值点试出来。
    如图2-9所示,随机选取一个点x0,每次迭代更新为x1, x2, x3, …直到找到极小值点。这里要引入梯度的概念,梯度是指向函数值上升最快方向的一个向量。那么我们对梯度取反方向,就能找到函数值下降最快的方向。

    由此得出梯度下降算法的公式

    对于目标函数E(w)来说则为

    最后得

    根据上式来更新w,每次要遍历训练数据中的所有样本进行计算,这种算法叫做批梯度下降。批梯度下降的不足之处在于面对数据量巨大的样本时,由于每次更新都学要遍历所有样本数据,纵使现在的计算机有再强大的计算能力,这也将使得因为计算量异常庞大,而让训练花费更多时间。虽然卷积神经网络的结构特点可以有效降低计算量,但从硬件性能上的限制考虑,本文不采用批梯度下降的方式对权值进行优化。
    2.3.2 随机梯度下降对于精度要求不高的的模型来说,可以使用随机梯度下降,随机梯度下降每次更新w值时只计算一个样本,由于样本的噪音和随机性,并不能保证每一次都是沿着下降的方向更新,但是总体看上去是沿着下降的方向更新的,最后收敛到最小值的附近。随机梯度下降大大提升了训练大规模样本数据的效率。尽管随机梯度下降可以大幅提高模型训练效率,但其波动较大,在有限迭代次数内不能得到稳定的训练结果,故本文不采用该方法对权值进行更新。
    2.3.3 小批量梯度下降随机梯度下在迭代的过程中并不是每次都朝着整体优化的方向,在迭代开始的时候可以很快收敛,但是训练一段时间后收敛会变得很慢。小批量梯度下降结合了批梯度下降和随机梯度下降的优点,使算法的训练过程在提高速度的同时,也保证了最终参数的准确率。本文采用小批量梯度下降的方法对权值进行更新,通过设置合理的批量大小和迭代次数能够在短时间内使模型快速得到优化。
    2.4 小结本章从卷积神经网络的组成结构、模型权值共享和局部连接的特点、网络的前向反向传播过程、以及模型的优化方法等基本原理作以分析。在后续的章节里,将理论应用于实践。
    3 Keras深度学习框架Keras使用简单易上手,只要有Python编程经验即可快速将理论付诸于实现。本文使用深度学习Keras来实现LeNet5的经典模型结构,并在实现经典结构的基础上改变参数,探究不同参数对模型训练的影响,本章对Keras的使用和配置作以介绍。
    3.1 Keras简介Keras是一个有Python编写而成以TensorFlow或Theano 作为后台的深度学习框架。当前作为深度学习框架对于GPU运算的支持是必不可少的,Keras同时支持CPU和GPU。Keras在linux下安装起来非常方便,不像caffe那样需要各种各样的支持库,有一点问题就得重新编译安装。Keras对用户的使用体验支持的相当好,它高度模块化的同时还具有可扩充性。因为是用Python编写的,所以Keras同时适用于Python2和Python3两个版本。Keras的使用更像是搭积木,深度学习里大多数你需要的东西,如网络层、激活函数、优化器、等,它都有对应的API可以进行直接调用。Keras没用单独的模型配置文件,模型的配置、运行、保存都可以写在同一个Python文件中。
    3.2 Keras编程3.2.1 Keras模型构建Keras提供了两种构建模型的方法,一种是序贯模型,另一种是函数式模型。函数式模型应用的范围更加广泛,确切的说序贯模型是函数式模型的一种特殊情况。因此两种模型在有些API的方法上是一样的。序贯模型最容易上手操作,它就像盖楼房那样,从第一层,一层一层按照你所设计的模型结构按顺序从输入层到输出层,依次进行声明即可。这也就意味着模型只有层与层之间的关系,不能存在跨层连接。函数式模型则没有对跨层连接的限制。
    3.2.2 Keras常用函数及用法本文使用的是序贯模型,主要用到的函数有下面这些。

    要开始使用序贯模型建立网络,需要先在Python文件的头部引入Sequential模块,创建Sequential的对象
    为模型添加层,通过创建的对象调用Sequential的add方法依次添加层。使用add添加的第一层需要指定有关输入数据shape的参数,而其后层的可以自动推导出中间数据的shape。
    对训练过程进行配置,使用compile,接收三个参数,分别是优化器、损失函数、指标列表。
    开始训练,使用fit函数,其参数有:

    x:输入数据。如果模型只有一个输入,那么x的类型是numpy array,如果模型有多个输入,那么x的类型应当为list,list的元素是对应于各个输入的numpy array
    y:标签,numpy array
    batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步
    epochs:整数,训练的轮数,每个epoch会把训练集轮一遍
    verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
    callbacks:list,其中的元素是Keras.callbacks.Callback的对象。这个list中的回调函数将会在训练过程中的适当时机被调用
    validation_split:0~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。验证集将不参与训练,并在每个epoch结束后测试的模型的指标,如损失函数、精确度等。注意,validation_split的划分在shuffle之前,因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀
    validation_data:形式为(x ,y)的tuple,是指定的验证集。此参数将覆盖validation_spilt
    shuffle:布尔值或字符串,一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”,则是用来处理HDF5数据的特殊情况,它将在batch内部将数据打乱
    class_weight:字典,将不同的类别映射为不同的权值,该参数用来在训练过程中调整损失函数(只能用于训练)
    sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode=’temporal’
    initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。


    3.3 Keras环境配置Keras在Ubuntu下的环境配置如下,下列命令均在终端中执行。
    1.系统升级
    Sudo apt updateSudo apt upgrade
    2.安装Python基础开发包
    sudo apt install -y python-dev python-pip python-nose gcc g++ git gfortran vim
    3.安装运算加速库
    sudo apt install -y libopenblas-dev liblapack-dev libatlas-base-dev
    4.Keras及相关开发包安装
    sudo pip install -U --pre pip setuptools wheelsudo pip install -U --pre numpy scipy matplotlib scikit-learn scikit-imagesudo pip install -U --pre tensorflow-gpusudo pip install -U --pre tensorflowsudo pip install -U --pre Keras
    5.打开Python解释器 import TensorFlow import Keras 无报错则完成安装
    3.4 小结本章主要对本文采用的深度学习框架Keras两种模型建立方法贯序模型和函数模型、模型的编译和训练所用到的函数参数、使用Keras环境配置中所需的Python基础开发包以及后台TensorFlow的安装作以介绍。本文在下一章中使用Keras实现经典LeNet5结构,并在其基础上作以改动探究不同参数对模型识别率的影响,学习调参经验。
    4 经典LeNet-5实验探究本章以MNIST数据集作为训练和测试数据来源,使用Keras深度学习框架在实现LeNet5的基础上对LeNet5模型结构进行改动以探究不同参数对模型识别率的影响,总结调参经验。
    4.1 数据集MNIST介绍MNIST[7]数据集是一个手写体数字数据集,通过了解这个数据集由四个部分组成。可以分为两大类,一类是训练数据,另一类是测试数据。每种数据包含了数据本身和对应的数据标签。训练集中有60000个用例,测试集中有10000个用例。
    MNIST是由NIST的手写体数字二值化图片的数据库SD3和SD1构成的。NIST最初的设计中,SD3是训练集,SD1是测试集。然而SD3比SD1更清晰,并且更容易识别。原因是SD3是从人口统计局的雇员收集的,而SD1是从中学的学生中收集的。从学习经验中得到有效的结论需要测试集是独立于训练集,并且测试集在完整的样本之中。因此,有必要通过混合NIST的数据来建造一个新的数据库。
    MNIST的训练集是由从SD3中的3万张图片和SD1中的3万张图片组成的。测试集是由从SD3中的5000张图片和SD1中的5000张图片。6万张训练集包含了近似250位写手。并且保证了训练集和测试集的写手是不相交的。
    SD1包含了由500位不同的写手写的58527张图片。比较而言,SD3的数据块是一次排列的,而SD1的数据是杂乱无章的。可以识别出SD1中的写手信息,我们根据识别出来的信息,把500位写手的数据分为两部分,前250位分到训练集中,后250位分到测试集。这样训练集和测试集我们现在都有大概3万张图片。在训练集中再加入SD3的数据,从0开始,使其凑够6万。类似的,在测试集中从SD3第35000张图开始,补充测试集到6万张。

    4.2 LeNet-5实现4.2.1 LeNet-5介绍
    经典LeNet-5包含输入输出层共计有8层,除输入输出两层外的每一层都包含需要通过训练学习的参数。每层有多个特征图谱,每个特征图谱都是通过一种卷积核(滤波器)卷积体现输入的一种特征,每个特征图谱有多个神经元。

    C1层是第一个卷积层,得到的时32*32的数据,包含了6中不同的5*5大小的卷积核,输出28*28的特征图谱。可训练参数共计(5x5+1)x6=156个(每个卷积核有25个参数和一个偏置项参数,共6个卷积核)。
    S2层是一个下采样层,输入为28*28的,采样大小为2*2,通过采样后得到6个14*14的图,加上偏置项每个特征图谱有2个可训练参数,共计12个。
    C3层是第二个卷积层,有16种5*5的卷积核,C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后的3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入。输出16个10*10的特征图谱。
    S4层是下采样层,采样大小为2*2,通过采样得到16个5*5的图。这层共计32个训练参数。
    C5层是卷积层,卷积核大小5*5,卷积过后形成共计120种卷积结构。每个都与S4的16个输出相连。
    F6层是全连接层,84个节点,共计10164个可训练参数。

    4.2.2 LeNet实现原LeNet-5采用的是32*32的图片数据大小,本文采用的是MNIST数据集,该数据集的单张图片大小为28*28。本文在不改变LeNet-5的整体结构下将LeNet加以实现,模型细节与原paper作者有细微不同,保留了整体层次结构,因为数据输入尺寸大小的不同导致经过两次下采样后的特征图谱尺寸小于5*5无法进行卷积,所以改变C5层卷积核的大小为3*3。卷积层采用的权值初始化方法采用Keras官方推荐的截尾高斯分布初始化,激活函数采用sigmoid函数。最后得全连接层权值初始化采用正态分布初始化方法。所有初始化方法的随机种子采用固定值,方便结果重现和随后探究中控制单一变量进行结果比较。
    代码实现如下:
    # 输入数据的维度img_rows, img_cols = 28, 28# 使用的卷积滤波器的数量nb_filters = 6# 用于 max pooling 的池化面积pool_size = (2, 2)# 卷积核的尺寸kernel_size = (5, 5)# C1 卷积层1 卷积核6个 5*5model.add(Convolution2D(nb_filters, kernel_size[0], kernel_size[1], border_mode='valid', input_shape=input_shape, kernel_initializer='TruncatedNormal'))model.add(Activation('sigmoid'))# S2 下采样model.add(MaxPooling2D(pool_size=pool_size))# C3卷积层2 16个卷积核 5*5model.add(Convolution2D( 16, kernel_size[0], kernel_size[1], kernel_initializer='TruncatedNormal'))model.add(Activation('sigmoid'))# S4 下采样model.add(MaxPooling2D(pool_size=pool_size))# C5 卷积层3 120个卷积核 3*3model.add(Convolution2D(120, 3, 3, kernel_initializer='TruncatedNormal'))model.add(Activation('sigmoid'))# 转化为一维model.add(Flatten())# F6 全连接层 输出层model.add(Dense(nb_classes, kernel_initializer='random_normal'))model.add(Activation('softmax'))
    模型训练采用克服了批梯度下降和随机梯度下降缺点的小批量梯度下降的参数更新方法。Keras在不设定的情况下,默认对训练数据进行打乱,因为所采用的平台硬件资源限制,该模型采用迭代40批次,每批次更新使用128个样本。
    该模型完成训练后测试准确率达到了98.58%,每次迭代训练和测试率的识别率和损失变化曲线如图4-3所示。

    4.3 模型探究本文将在使用Keras实现经典结构LeNet-5的同时,控制单一变量,在LeNet-5的基础上分别从改变网络结构,卷积核大小和数量、权值初始化方式、激活函数选择这几方面与LeNet5进行比较试验,以掌握一定的调参方法和规则。
    4.3.1 不同网络结构1.实验设计
    在上一节实现的LeNet5的基础上,对模型结构进行改动。直接在原结构S4层的基础上增加5*5的卷积层会导致因为前四层两次下采样而使S4层的输出特征图谱尺寸大小小于5*5无法再次进行卷积,所以CNN1-1直接以新的卷积层代替了S4层的下采样层;CNN1-2在原Lenet5结构上在C5层后添加了按百分之五的概率断开输入神经元的Drop层;CNN1-3在原LeNet5的结构基础上在C5层后添加了新的全连接层。通过使用改变网络结构,探究不同结构的组合对识别率的影响,不同结构对比如表4-1所示。



    网络名称
    层结构










    LeNet5
    Con
    Pooling
    Con
    Pooling
    Con
    Full
    /


    CNN1-1
    Con
    Pooling
    Con
    Con
    Con
    Full
    /


    CNN1-2
    Con
    Pooling
    Con
    Pooling
    Con
    Drop
    Full


    CNN1-3
    Con
    Pooling
    Con
    Pooling
    Con
    Full
    Full



    2.实验结果
    表4-2为不同模型结构经过40轮迭代后,使用测试集测试后的识别率大小,原LeNet5结构的识别率为98.58%,CNN1-1的识别率为98.05%,CNN1-2的识别率为98.49%,CNN1-3的识别率为98.19%。



    网络名称
    LeNet5
    CNN1-1
    CNN1-2
    CNN1-3




    测试识别率
    98.58%
    98.05%
    98.49%
    98.19%



    如图4-4所示为不同结构与LeNet5识别准确率对比折线图,通过对折线图的分析比较,可以看出原LeNet5的识别准确率起点最高,添加了Drop层的CNN1-2其次,添加了卷积层的CNN1-1要通过迭代2次后识别率大幅上升,添加全链接层的CNN1-3因为增加了全连接导致的链接权值数量增加需要迭代5次后才能大幅提高识别的准确率。

    如图4-5所示为不同结构模型与LeNet5损失对比折线图,几种不同模型的损失率与识别率情况相对应,总体上均能在10次迭代以内快速收敛。

    通过表的比较,可以看出不论是添加卷积层、全连接层、还是Drop层,在相同其他条件下,特别是均迭代更新40次的前提下,CNN1-1、CNN1-2、CNN1-3的识别准确率并没有比原LeNet5模型准确率高,均有不同程度的下降。结论是在有限迭代次数内,添加卷积层、Drop层和全连接层,会不同程度降低对测试集识别率,原LeNet5模型结构经典,识别率最高。
    4.3.2 卷积核大小数量1.实验设计
    在LeNet5的基础上进行改动,主要改动卷积核大小和数量。CNN2-1在原LeNet5的基础上将C1和C3层卷积核尺寸大小由55改为33;CNN2-2在原LeNet5的基础上将C3层的卷积核数量由6个改为16个。通过改变卷积核参数,探究卷积核对模型识别率的影响,不同卷积层参数对比如表4-3所示。



    网络名称
    C1
    S2
    C3
    S4
    C5
    F6




    LeNet5
    6*(5*5)
    (2*2)
    6*(5*5)
    (2*2)
    120(3*3)
    10


    CNN2-1
    6*(3*3)
    (2*2)
    6*(3*3)
    (2*2)
    120(3*3)
    10


    CNN2-2
    6*(5*5)
    (2*2)
    6*(5*5)
    (2*2)
    120(3*3)
    10



    2.实验结果
    表4-4为相同模型结构下不同卷积层参数经过40轮迭代后,使用测试集测试后的识别率大小,原LeNet5结构的识别率为98.58%,CNN2-1的识别率为98.25%,CNN2-2的识别率为98.57%。



    网络名称
    LeNet5
    CNN2-1
    CNN2-2




    测试识别率
    98.58%
    98.25%
    98.57%



    如图4-6所示为卷积层不同参数模型识别率变化折线图,通过对折线图的分析,减小卷积核尺寸的大小,会使识别率收敛的起点降低。增加卷积核的数量会使识别率收敛的更快。

    如图4-7所示为卷积层不同参数模型损失变化折线图,通过对折线图的分析,减小卷积核尺寸的大小,会使损失收敛的起点升高;增加卷积核的数量会使损失收敛的更快。

    通过对测试识别率结果进行分析,CNN2-1与LeNet5对比,在其他条件不变的前提下,减小卷积核的大小,明显会使识别率明显下降。CNN2-2与LeNet5对比,在其他条件不变的前提下,增加卷积核的数量,对识别率的影响几乎可以忽略不计,仅降低了0.01%。结论是增加卷积核的数量可以有效提取更多训练集样本的特征,从而提高识别率的收敛速度。
    4.3.2 权值初始化1.实验设计
    如表所示,原LeNet5卷积层采用的权值初始化方式是Keras推荐滤波器使用的Truncated Normal,CNN3-1采用的是权值全0初始化方式;CNN3-2采用的是权值全1初始化方式;CNN3-3采用的是;CNN3-4采用的是。通过改变卷积层的权值初始化方式,探究不同初始化方式对模型识别率收敛速度和准确率的影响,卷积层不同权值初始化方式对比如表4-5所示。



    网络名称
    LeNet5
    CNN3-1
    CNN3-2
    CNN3-3
    CNN3-4




    权值初始化方式
    Truncated Normal
    Zero
    Ones
    Random Normal
    Random Uniform



    2.实验结果
    表4-6为相同模型结构下不同卷积层权值初始化方式经过40轮迭代后,使用测试集测试后识别率大小,原LeNet5结构的识别率为98.58%,CNN3-1的识别率为97.28%,CNN3-2的识别率为8.92%,CNN3-3的识别率为98.41%,CNN3-4的识别率为98.38%。



    网络名称
    LeNet5
    CNN3-1
    CNN3-2
    CNN3-3
    CNN3-4




    测试识别率
    98.58%
    97.28%
    8.92%
    98.41%
    98.38%



    如图4-8所示为卷积层不同权值初始化方式模型识别率折线图,通过对折线图的分析,在其他参数不改变的前提下,CNN3-1的识别率需要经过6次迭代后才快速收敛;CNN3-2的识别率,在40次迭代中一直没有收敛。CNN3-3的识别率与LeNet5识别率收敛过程几乎一致,仅收敛起点比LeNet5略高;CNN3-4的识别率也与LeNet5识别率收敛过程极其相似。

    如图4-9所示为卷积层不同权值初始化方式模型损失折线图,通过对折线图的分析,与识别率的收敛过程相似,采用全1的初始化方式损失居高不下。

    通过对表4-6的分析,CNN3-1卷积层采用权值全0初始化,相比LeNet5在识别准确率上1.3个百分点;CNN3-2卷积层采用全1初始化,相比LeNet5在识别率上大幅降低;CNN3-3卷积层采用初始化方式,相比LeNet5在识别准确率上有略微降低;CNN3-4卷积层采用初始化方式,相比LeNet5在识别准确率上也略微降低。结论是不同于LeNet5的权值初始化方式在其他参数条件不变的前提下会使模型识别率有所降低。在迭代次数规模较小的情况下,不能采用全1对权值进行初始化。
    4.3.3 激活函数选择1.实验设计
    如表所示,LeNet5卷积层采用的是Sigmoid的函数,CNN4-1采用的是Tanh函数,CNN4-2采用的是Reul函数,CNN4-3采用的是Softplus函数,卷积层不同激活函数对比如表4-7所示。



    网络名称
    LeNet5
    CNN4-1
    CNN4-2
    CNN4-3




    卷积层激活函数
    Sigmoid
    Tanh
    Reul
    Softplus



    2.实验结果
    表4-8为相同模型结构卷积层使用不同激活函数经过40轮迭代后,使用测试集测试后识别率大小,原LeNet5结构的识别率为98.58%,CNN4-1的识别率为98.89%,CNN4-2的识别率为98.44%,CNN4-3的识别率为98.62%。



    网络名称
    LeNet5
    CNN4-1
    CNN4-2
    CNN4-3




    测试识别率
    98.58%
    98.89%
    98.44%
    98.62%



    如图4-10所示为卷积层不同激活函数模型识别率折线图,通过对折线图的分析,采用tanh函数作为卷积层激活函数的CNN4-1识别率的收敛起点相当高,通过第一次迭代就已经达到90%以上;采用reul函数作为卷积层激活函数的CNN4-2识别率收敛起点币LeNet5低,但在经过一次迭代后迅速收敛,收敛速度大于LeNet5;采用新激活函数softplus作为卷积层激活函数的CNN4-3,收敛起点和速度都高于LeNet5。

    如图4-10所示为卷积层不同激活函数模型识别率折线图,通过对折线图的分析,卷积层采用Tanh和Softplus作为激活函数的模型损失收敛起点低,卷积层采用Reul函数作为激活函数的模型损失收敛起点高。

    通过表分析可知,在其他参数不改变的前提下,采用tanh函数作为卷积层激活函数的CNN4-1比卷积层采用sigmoid函数的LeNet5在识别率上高出了0.31%。这是出乎意料的结果。CNN4-2采用的是最近学术界推荐替代sigmoid和tanh的reul函数,理论上来说使用reul的效果应该会更好,出乎意料的是它比卷积层采用sigmoid函数的LeNet5识别率低了0.14%;CNN4-3采用的是近期出现的新函数softplus,其识别率比LeNet5高0.04%。结论是在有限迭代次数内,卷积层采用tanh或softplus,能够有效提高识别准确率,并提高识别率收敛的速度。
    4.4 小结本章通过深度学习框架Keras实现了以MNIST数据集作为训练数据的经典LeNet5结构,并通过设计实验探究得出以下结论,LeNet5模型在迭代次数为40次的前提条件下,添加卷积层、drop层、全连接层都会对模型识别率产生负影响;增加卷积层中的卷积核数量可以有效提高模型识别率收敛的速度;在有迭代限次数内采用全1的卷积层权值初始化方式会对模型识别率产生巨大的影响;卷积层激活函数采用Tanh函数可以有效提高模型识别率。
    5 手写数字识别算法应用实践本文将用训练好的高识别率手写数字模型应用到摄像头实现对镜头画面中数字的实时识别。本章将介绍如何使用计算机视觉库OpenCV调用电脑摄像头、找到帧画面中的数字并对数字进行识别前的处理,最后调用训练好的手写数字模型将识别结果在原帧画面中显示出来。
    5.1 OpenCV图像处理部分5.1.1 OpenCV介绍OpenCV是一个开源的跨平台计算机视觉库,它可在Linux、Windows和Mac OS操作系统上运行。因为由C和C++编写,所以它很高效,并且提供了许多其他语言的接口如Python、Ruby、MATLAB等。OpenCV的功能有基本的图像处理如去燥、边缘检测、角点检测、色彩变化等。
    本文将运用部分OpenCV的功能,调取电脑自带的摄像头,对每一帧的画面进行处理,在画面中找到数字并截取出来,调用数字识别模型进行识别,并用矩形框出每个数字把识别结果标注在旁边。
    5.1.2 OpenCV安装配置1.安装依赖包
    依赖包包含了linux程序在安装、运行中所必须的相关编译工具、安装工具等。程序本身并不包含这些需要的库,在不重复造轮子的前提下,程序在运行中相应的功能时会需要调用相应的库。如本文所使用的OpenCV需要对Python不同版本的支持、对图片视频读写的支持等。
    sudo apt-get install --assume-yes build-essential cmake git sudo apt-get install --assume-yes build-essential pkg-config unzip ffmpeg qtbase5-dev python-dev python3-dev python-numpy python3-numpy sudo apt-get install --assume-yes libOpenCV-dev libgtk-3-dev libdc1394-22 libdc1394-22-dev libjpeg-dev libpng12-dev libtiff5-dev libjasper-devsudo apt-get install --assume-yes libavcodec-dev libavformat-dev libswscale-dev libxine2-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-devsudo apt-get install --assume-yes libv4l-dev libtbb-dev libfaac-dev libmp3lame-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-devsudo apt-get install --assume-yes libvorbis-dev libxvidcore-dev v4l-utils
    2.编译
    在当前目录下创建build文件夹,进入build文件夹,配置编译选项,最后执行编译命令。
    mkdir buildcd build/cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_TBB=ON -D WITH_V4L=ON -D WITH_QT=O -D WITH_OPENGL=ON -D WITH_CUBLAS=ON -D CUDA_NVCC_FLAGS="-D_FORCE_INLINES"make -j $(($(nproc) + 1))
    3.安装
    以root权限执行安装命令makeinstall,利用输出重定向,创建OpenCV相关动态链接库配置文件,使用动态库管理命令ldconfig,让OpenCV的相关链接库被系统共享,最后更新系统源。
    sudo make installsudo /bin/bash -c 'echo "/usr/local/lib" > /etc/ld.so.conf.d/OpenCV.conf'sudo ldconfigsudo apt-get update
    4.检查安装
    以root权限执行安装checkinstall,执行checkinstall命令检查安装。
    sudo apt-get install checkinstallsudo checkinstall
    5.确认安装
    进入Python解释器输入“import cv2”无报错即成功安装。
    5.1.3 寻找数字1.方案选择
    要进行数字识别,首先要让程序能够寻找到画面中的数字。安装好的OpenCV中有自带的分类器,但是很不幸的是自带的分类器仅有关于人脸识别方向的,如果是做人脸识别方向的研究使用该分类器将会非常方便。至此,本文需要自己开发一个寻找数字的分类器或程序。有两种方案,一是训练一个关于数字识别的级联分类器,二是直接对画面中的元素进行寻找。方案一训练一个级联分类器并不容易,它需要准备正负两种样本。正样本中全是不同字体数字的图片集合,负样本要求是与数字可能出现的场景中非数字本身的物品图片集合。该方案适用于对识别要求严格的商业级软件开发。方案二相对于方案二适用于比较简单的测试环境,可以容忍一定识别误差。训练级联分类器对于负样本要求的范围广泛,数据量巨大。与深度学习目前所遇到的问题一样,虽然目前有大数据的支持,但是数据的标注代价是巨大的。本文以研究学习为目的且最终测试的环境简单,所以采用第二种方案。
    2.方案实施
    在使用摄像头读取画面时,视频流中的每一帧可以看作为一张图片,如图5-1所示以单张写有数字的图片,代替摄像头读取到的单独一帧。

    先使用cvtcolor()函数对图片进行灰度处理,然后对灰度图进行二次腐蚀处理,可以去除一些细微的无关纹理,处理后效果如图5-2所示可以看到经过二次腐蚀后的图像只能够依稀看到九个数字的痕迹。

    对图片进行二次膨胀处理,是数字在原图的基础上轮廓更加清晰,处理后效果如图5-3所示。

    用膨胀后的图和腐蚀后的图做差,可以得到清晰地数字轮廓,方便后续调用函数提取数字的轮廓信息,处理后如图5-3所示。

    对数字轮廓使用Sobel去噪处理,使轮廓更加清晰,将数字中未连接的部分连接在一起,减小轮廓寻找误差,处理后如图5-4所示。

    利用findContours找到画面中所有轮廓,按设置的像素长宽寻找单个数字轮廓记录其相对坐标及长宽值,最后根据记录的信息在原图中将找到的数字标注出来如图5-5所示。

    函数实现代码:
    def where_num(frame): rois = [] # 灰度处理 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) element = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 二次腐蚀处理 gray2 = cv2.dilate(gray, element) gray3 = cv2.dilate(gray2, element) # cv2.imshow("dilate", gray3) # 二次膨胀处理 gray2 = cv2.erode(gray2, element) gray2 = cv2.erode(gray2, element) # cv2.imshow("erode", gray2) # 膨胀腐蚀做差 edges = cv2.absdiff(gray, gray2) # cv2.imshow("absdiff", edges) # 使用算子进行降噪 x = cv2.Sobel(edges, cv2.CV_16S, 1, 0) y = cv2.Sobel(edges, cv2.CV_16S, 0, 1) absX = cv2.convertScaleAbs(x) absY = cv2.convertScaleAbs(y) dst = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) # cv2.imshow("sobel", dst) # 选择阀值对图片进行二值化处理 ret_1, ddst = cv2.threshold(dst, 50, 255, cv2.THRESH_BINARY) # 寻找图片中出现过得轮廓 im, contours, hierarchy = cv2.findContours( ddst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 在保存的轮廓里利用宽度和高度进行筛选 for c in contours: x, y, w, h = cv2.boundingRect(c) if w > 12 and h > 24: rois.append((x, y, w, h)) return rois
    5.1.4 数字处理在对寻找到的数字进行识别前,还需要进一步对单个数字进行处理。要使训练好的模型能够接收待预测的图片,需要将单个数字处理成与模型训练集数据一样的格式。单个数字截取图应被处理为28*28大小,二值化黑底白字的形式。如图5-6所示为MNIST数据集中的一个样本,图5-7为本文经处理过后的单个数字示例。



    MNIST单个数字示例
    经处理后单个数字示例









    函数代码:
    def resize_image(image): # 单个数字灰度化 GrayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 二值化并翻转为黑底白字 ret, thresh2 = cv2.threshold(GrayImage, 120, 255, cv2.THRESH_BINARY_INV) # 给数字增加一圈黑色方框 constant = cv2.copyMakeBorder( thresh2, 20, 20, 20, 20, cv2.BORDER_CONSTANT, value=0) # 调整数字图片尺寸 image = cv2.resize(constant, (28, 28)) return image
    5.2 实现摄像头的手写数字实时识别5.2.1 手写数字识别模型训练结合前文的实验,选择合适的模型结构与参数训练出来的模型测试集上的识别率达到了99%以上,具体结构如下图5-8所示。
    代码如下:
    # 创建贯序模型对象model = Sequential()# 添加卷积层,设置30个尺寸大小为5*5的卷积核,激活函数为relumodel.add(Convolution2D(30, 5, 5,border_mode='valid',input_shape=input_shape))model.add(Activation('relu'))# 添加下采样层model.add(MaxPooling2D(pool_size=pool_size))# 添加drop层,概率设为0.4model.add(Dropout(0.4))# 添加卷积层,设置15个尺寸大小为3*3的卷积核,激活函数为relumodel.add(Convolution2D(15, 3, 3))model.add(Activation('relu'))# 添加下采样层model.add(MaxPooling2D(pool_size=pool_size))# 添加drop层,概率设为0.4model.add(Dropout(0.4))# 添加Flatten使数据一维化model.add(Flatten())# 添加全连接层,设置128个节点,激活函数为relumodel.add(Dense(128))model.add(Activation('relu'))# 添加drop层,概率设为0.4model.add(Dropout(0.4))添加全连接层,设置50个节点,激活函数为relumodel.add(Dense(50))model.add(Activation('relu'))# 添加drop层,概率设为0.4model.add(Dropout(0.4))# 添加全连接层,设置10个节点,激活函数为softmaxmodel.add(Dense(10))model.add(Activation('softmax'))

    5.2.2 模型的保存与调用
    在训练模型的代码中使用model.save(filepath)将Keras训练好的模型和权重保存在一个HDF5文件中,该文件将包含:模型的结构,以便重构该模型;模型的权重训练配置(损失函数,优化器等)优化器的状态,以便于从上次训练中断的地方开始
    使用load_model(filepath)可以加载保存的HDF5文件,并直接对模型进行调用操作

    5.2.3 结果调用训练好的模型,使用model.predict_class()函数对截取出来的单个数字进行识别,将识别结果返回并在原帧画面中标注出来。如图5-9至图5-11所示,本文采取五个不同个人所书写的数字进行测试。



    手写测试结果1
    手写测试结果2








    手写测试结果3
    手写测试结果4






    手写测试结果5







    经测试训练好的手写数字模型能够识别出一定程度畸变的数字,对于不常见的手写体风格不能够做出有效识别。
    5.3 小结本章在前章的实验经验上设计出改进后的手写数字识别模型,在测试集的测试中准确率达到了99%以上。使用计算机视觉库OpenCV实现对摄像头的调用,利用寻找轮廓的方法在摄像头拍摄的帧画面中确定数字的位置并截取,调用训练好的手写数字识别模型对每个数字进行识别,将识别结果在原帧画面中标注返回。
    6 总结深度学习目前在大数据、大计算的支持下引领者新一波的AI热潮。使用MNIST数据集实现卷积神经网络的训练已经成为了深度学习入门的“hello word”。从经典的问题入手学习深度学习,一方面可以很好的入门,另一方面在学习的过程中所遇到的问题大部分通过浏览搜索互联网资源可以得到解决。本文以学习深度学习中重要分支卷积神经网络的过程为线索,由简入深从基本的神经元学起,学习和理解如何对卷积神经网络进行训练。在研究学习训练卷积神经网络的调参优化同时训练出高识别率的手写数字识别模型,并将该模型与OpenCV技术相结合,实现了基于电脑摄像头对画面中手写体数字进行实时识别。通过在对模型参数、结构的实验探究分析,发现并不是对训练集图像的特征提取的越多最终模型识别上得表现就越好,合理组合不同参数,选择合适的训练批次及批次数据大小才能训练出高识别率的模型。通过对模型的实际应用,反映出的问题是虽然在模型在测试集上表现达到近百分百的准确度,但是在应用过程中识别的效果并不完美,有个别数字会在两个结果中间来回跳,这说明理论到实际的应用过程中需要考虑到的实际因素还很多。在生活中我们对于数字精准要求不容闪失,任何一个数字的误差都会导致不可估量的损失。
    在此次毕业设计中我学习了许多知识,从神经网络的原理推到中复习了高等数学的部分知识、从Keras、OpenCV的环境配置中扎实了Linux命令的使用、在使用Keras训练模型的过程中熟练掌握了Python面向对象编程以及使用Matlibplot库绘制实验图像。在将训练好的模型实际应用的过程中锻炼发现问题、解决问题独立思考的能力。通过这次毕业设计,让我对深度学习有了一个清晰地概念,能够将卷积神经网络应用到一些实际问题中,并取得了好的结果。
    参考文献[1] 陈浩翔.手写数字深度特征学习与识别.计算机技术与发展26.7(2016):19-23.
    [2] 张晓.手写数字识别的前景与难点.数码世界1(2016):69-70.
    [3] Andrew Ng.MachineLearning[OL].2016.3.https://www.coursera.org/learn/machine-learning/
    [4] 尹宝才,王文通,王立春.深度学习研究综述[J].北京工业大学学报,2015(1):48-59.
    [5] 郭丽丽,丁世飞.深度学习研究进展[J]. 计算机科学, 2015,42(5):28-33.
    [6] 吴岸城.神经网络与深度学习[M].北京:电子工业出版社,2016.6
    [7] The MNIST Database ofHandwritten Digits[DB].http://yann.lecun.com/exdb/MNIST/
    [8] 赵永科.深度学习21天实战Caffe[M].北京:电子工业出版社,2016.7
    [9] 李丹.”基于LeNet-5的卷积神经网络改进算法.”计算机时代8(2016):4-6.
    [10] Peter Harrington.MachineLearning in Action[M].Manning Publications,2012-4-19
    [11] Andrew Ng,Jiquan Ngiam,ChuanYu Foo,Yifan Mai,Caroline Suen.UFLDL教程[OL].2016.3.http://deeplearning.stanford.edu/wiki/index.php/UFLDL_Tutorial
    [12] Fei-Fei Li,Andrej Karpathy.CS231nConvolutional Neural Networks for Visual Recognition[OL].2016.1.http://cs231n.stanford.edu/
    [13] 吴忠,朱国龙,黄葛峰,等.基于图像识别技术的手写数字识别方法[J]. 计算机技术与发展,2011,21(12):48-51.
    [14] 余凯,贾磊,陈雨强,等.深度学习的昨天, 今天和明天[J]. 计算机研究与发展, 2013,50(9):1799-1804.
    [15] 孙志军, 薛磊, 许阳明, 等. 深度学习研究综述[J]. 计算机应用研究, 2012, 29(8):2806-2810.
    [16] LeCun Y, Bengio Y, Hinton G. Deep learning[J].Nature,2015,521(7553):436-444.
    [17] Krizhevsky A,Sutskever I,HintonG E. Imagenet classification with deep convolutional neuralnetworks[C]//Advances in neural information processing systems.2012:1097-1105.
    4 评论 148 下载 2018-10-03 22:44:16 下载需要13点积分
  • 基于Android的网络聊天软件的设计与实现

    摘 要即时通信(Instant Messaging,IM)软件产生以来,这种通过网络与其它在线用户进行交流的方式,受到了个人公司以及行业的青睐。本文采用软件工程的管理和设计方法,对项目的需求进行了分析,完成了功能用例建模,使用Socket通信技术结合TCP/IP协议原理实现了基于Android操作系统的聊天软件各个模块的分析设计,提出了系统的体系结构和整体架构设计方案,并予以实现,取得了较好的使用价值。
    关键词:Android;即时聊天;客户端/服务器模型;套接;
    AbstractSince the invention of instant messaging (IM), such style which online users communicate with others on the net, has become more and more welcomed by individual, company and software industry. This paper using the software engineering management and design methods to analyze the requirement of project, and implement the construction of function model. With the socket communication technology combined with the TCP/IP protocol finished all of the modules in chat software which runs in Android Operation. Propose and carry out the system architecture and overall architecture design, get a good value in use.
    Key words: Android; Instant Messaging; IM; C/S; Socket;
    1 绪论1.1 项目背景即时通信(Instant Messaging, IM)是随着互联网的出现而新型通信手段,根据通讯软件的发展与分析,作为即时通信工具中最具有增长潜力之一的聊天软件,它为满足人们的需要,将其功能不断的完善,不但可以文字聊天和文件传输,还可以通过语音,视频来聊天,所以其发展十分迅速。而随着3G时代的来临,其与移动客户终端的结合更受到了广泛的关注。本项目来自以下当前受到关注的启发。

    开放性移动设备Android平台目前受到了很多人的喜爱,并且在短短几年取代了诺基亚成为全球最热销的手机
    随着手机成为人们日常生活的必需品,通过PC端进行即时聊天已经不能满足人们的需求,人们需求多终端的聊天即手机和PC的互通

    本系统通过Socket通信实现Android手机间互通,Android手机和PC端的互通。
    1.2 研究的目的和意义即时通信软件作为一种便捷的网络通信技术已经越来越深入人心,应用范围从单纯的网络聊天工具变成工作生活所不可缺的信息交流平台。在互联网日益普及的今天,即时通信的用户规模也呈现出快速增长的态势。
    现阶段,通过手持设备终端和即时通信软件挂钩,把以往的只能应用在PC机上的即时通信软件移植到移动设备中,让用户能够更方便地应用即时通信产品,是即时通信的发展趋势,也是IM系统软件市场发展的一个重要方向。
    1.3 国内外研究的现状1996年11月ICQ在全球发布,拉开了IM(Instant Messaging,即时通信)应用的序幕。就是这样一款小软件在短短数年时间里发展出了一个巨大的IM产业。
    时至今日,即时通信工具早已成为最热门的网络应用之一。国外除了ICQ外还有雅虎的雅虎通、微软的MSN、被eBay收购的Skype和Google的Gtalk都在国际上占有不小比重。
    相较于国内,腾讯QQ无疑是国内即时通信市场的霸主,自从99年进入即时通讯领域并迅速占市场之后,其在国内用户数量始终高居榜首,即使面对微软MSN的强大攻势,腾讯QQ的市场占有率依然稳步增长,到现在占据市场70%以上份额。除了腾讯以外,国内的IM还有Lava,UC等软件。
    1.4 论文的主要内容和组织结构本文的研究工作是设计和实现一个Android平台的即时通信系统,实现移动设备与PC终端的互通。采用TCP/IP 协议,在Google推出的Android平台下进行研究工作。本文的研究内容主要有下面几方面:

    Android平台[1]上即时通信系统的架构:提出整个系统的合理架构以实现整个系统
    TCP/IP协议:介绍TCP/IP协议的内容并对协议进行解析
    移动即时通信系统的实现:根据提出的系统架构,并阐述本移动即时通信系统的设计和具体实现,最终对系统进行演示和运行

    本文在第二章介绍项目所用到的基本知识,对Android平台、TCP/IP协议以及MVC架构进行介绍,讨论在Android平台下程序的开发以及开发所需的环境。
    然后在第三章根据项目具体要求得出需求分析,依照画出的用例模型将系统分为客户端和服务器端。在第四章中详细的阐述的具体的功能的流程和系统实现的原理。
    第五章介绍了一些重要功能实现的代码及解释。
    第六章为程序的演示和运行。
    1.5 本章小结本章主要介绍了项目的背景、研究的目的和意义以及国内外实时聊天系统的研究和应用现状,同时列出来整篇文章的主要章节结构,为后续的部分介绍做出了提纲。
    2 关键技术介绍毕业设计是对四年学习内容的一个综合考验,因此将会涉及到多门课程的知识。结合本课题的研究内容,本章将会把毕业设计过程中所遇到的关键技术进行一个全方位的阐述。
    2.1 Android平台Android操作系统是由谷歌Google和开放手机联盟共同开发发展的移动设备操作系统,其最早的一个发布版本开始于2007年11月的Android 1.0 beta,并且已经发布了多个更新版本的Android操作系统。
    到2010年末数据显示,仅正式推出两年多操作系统的Android在市场占有率上已经超越称霸十年的诺基亚Symbian系统。2012年2月,Android操作系统在全球智能手机操作系统的市场份额已达52.5%,成为全球第一大智能手机操作系统[2]。
    2.1.1 Android特征
    提供访问硬件的API函数,简化像摄像头、GPS等硬件的访问过程
    具有自己的运行时和虚拟机
    提供丰富的界面控件供使用者之间调用,加快用户界面的开发速度,保证Android平台上程序界面的一致性
    提供轻量级的进程间通讯机制Intent,使跨进程组件通信和发送系统级广播成为可能
    提供了Service作为无用户界面,长时间后台运行的组件
    支持高效、快速的数据存储方式

    2.1.2 Android体系结构采用软件堆层的架构,共分为四层如下图2-1[3]:

    Linux 内核

    硬件和其他软件堆层之间的一个抽象隔离层提供安全机制、内存管理、进程管理、网络协议堆栈、和驱动程序等
    中间件层

    由函数库和Android运行时构成
    应用程序框架

    Activity Manager,管理应用程序的生命周期Windows Manager,启动应用程序的窗体Content Provider,共享私有数据,实现跨进程的数据访问Package Manager,管理安装在Android系统内的应用程序Teleghony Manager,管理与拨打和接听电话的相关功能Resource Manager,允许应用程序使用非代码资源Location Manager,管理与地图相关的服务功能Notification Manager,允许应用程序在状态栏中显示提示信息



    应用程序
    提供一系列的核心应用程序包括电子邮件客户端、浏览器、通讯录和日历等

    2.2 TCP/IP协议TCP协议是网络通信的基础核心协议,在Java中也专门提供了Socket的类库,抽象出了基于TCP协议通信的常用方法:TCP协议与三次握手。本节在讲述TCP协议流程前,先来要对一些概念进行说明。
    2.2.1 C/S模型在网络连接模式中,除了等网外,还有一种形式的网络,即客户机/服务器网络。在该模型中,服务器是网络的核心,而客户机是网络的基础,客户机依靠服务器获得所需的网络资源,而服务器为客户机提供网络必须的资源。通过C/S模型可以从分利用两端硬件环境的优势,将任务合理分配到客户端和服务器端来实现,所以C/S模式具有以下几个优点:

    因为在客户端上有一套完整的系统软件,具有很强的交互性,系统工作人员在运用系统时可以获得出错提示、在线帮助等较强功能
    由于C/S模式是配对的点对点的结构模式,因此多采用局域网的协议,并且通常是学校内部固定的从事学生学籍管理工作的用户群,所以安全性较高
    因为C/S模式只有两层逻辑结构,因此网络通讯量低,传输速度快,占用网络资源少

    随着Android手机性能的不断提升,使用C/S架构将一些需要较长时间处理的功能放置到客户端。使用户得到更好的网络传输,提升用户体验。
    2.2.2 TCP协议为了实现客户端同服务器端的通信,客户端首先发送一个“SYN”数据包。如果服务器收到SYN标记,它将发回一个“SYN+ACK”数据包。接着,客户端为了表示收到了这个SYN+ACK信息,会向服务器发送一个最终确认信息(ACK包)。这种SYN,SYN+ACK,ACK的步骤被称为TCP连接建立时的“三次握手”,在这之后,连接就建立起来了,这个连接将一直保持活动状态,直到超时或者任何一方发出一个FIN(结束)信号。这种通信模式也叫客户端/服务器(C/S)模式 如下图2-2 TCP的三次握手。
    由此可见,通过客户端和服务器的“三次握手”,双方可以建立畅通的通信信道,在此信道上双方互相传输数据。

    由于TCP/IP协议能够保证数据的安全传输和正确到达目标主机,本文所涉及到聊天系统采用TCP/IP协议,从而能够实现更加精确即时的消息发送和接受。
    2.2.3 Android的MVC架构在系统客户端中,采用MVC模式的设计思想,提高了程序的模块化程度。在本系统客户端中,结构图如下:

    视图层(View)
    视图提供界面功能显示的机制,主要是用户数据的输入界面和对模型数据的显示功能,同时也包括信息的提示功能。这方面主要是引入了Xml配置文件其中包括样式布局文件(Style)、动画效果布局文件(anim文件夹下)、图片布局文件(drawable文件夹下)。
    控制层(Control)
    主要负责对整个系统的流程进行调度,管理用户界面的逻辑流程,以及用户交互如何影响数据模型和数据模型如何影响用户交互过程这些东西都是有Android中的Activity完成的,在Android开发中,其配置文件AndroidManifest.xml扮演控制器的角色,文件中对各种组件视图进行相应的配置,以供模型在改变时对视图的调用。
    模型层(Model)
    主要对数据库的增、删、改、查等操作。同时包含了信息的接受,发送,请求的提交。具体到在程序中是通过Service实现数据的发送,接受。当一个Activity启动后动态的调用bindService函数将其与Service绑定,关于这一块后面会详细解释。
    2.3 多线程技术多线程在构建大型系统的时候是需要重点关注的一个重要方面,特别是在效率和性能之间做一个权衡的时候。恰当的使用多线程可以极大的提高系统性能。使用多线程的好处有以下几点:

    使用线程可以把占据长时间的程序中的任务放到后台去处理
    用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
    程序的运行速度可能加快
    在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等

    而Java是一种多线程的语言,它可以同时运行多个线程处理多个任务。同时Java内置多线程控制,这样可大大简化多线程应用程序的开发。提供了一个Thread类,它封装了所有有关线程的控制,负责线程的启动、运行、休眠、挂起、恢复、退出和终止等一系列逻辑控制操作,并可检查线程的状态。此外Thread类还实现了对线程行为的优先级的控制。
    2.4 运行环境
    操作系统:Windows XP 或者 Windows 7
    数据库:SQLite(Android提供的一个标准的数据库,支持SQL语句),MySQL 5.0
    Android 版本:Android 2.2
    开发工具:eclipse+android SDK+MySQL5.0

    2.5 本章小结本章主要介绍了Android聊天软件在开发过程中用到的关键技术,包括Android系统的体系结构、C/S模型、TCP协议以及多线程技术。同时介绍了软件开发的硬件和软件环境。
    3 需求分析上章介绍了本系统所需要的软硬件配置以及关键技术。本章将开始着重分析系统的需求,从系统功能和可行性分析两个方面明确系统所要实现的目标,为系统的开发做好铺垫。
    3.1 系统的整体分析本项目根据用户的需求,对即时通信的常用功能进行实现,系统的功能用例图如下3-1系统用例图

    通过用例图可以知道,系统的主要实现功能大体上可以分成:

    用户注册:用户如果没有可用的ID,可填写相关的个人信息(要注册的ID、密码等)进行注册,向服务器提交后服务器返回相应的信息
    用户登录:用户名和密码进行登录即时通信系统
    用户设置:可对用户的基本信息和一些系统选项进行设置
    查看好友信息:登录成功后,可以按照分组查看所有的好友概要信息(用户名、用户ID),并可以查看某个好友的详细信息
    用户添加、删除好友:根据其它用户的ID添加进自己的好友至好友列表
    用户向好友发送信息:用户登录成功后可给某位好友发送信息,如果好友不在系统将提示该好友不在
    用户接收好友的信息:用户登录成功后会监听好友发送过来的信息,并显示在相应的界面上
    用户注销:用户注销,退出本系统

    系统采用C/S架构模式,所以在进一步细化过程中将系统分为服务器端和客户端两部分。
    3.1.1 服务器端在C/S架构中,服务器端起到了连接客户端并且处理客户端请求的作用。服务器的功能模块图如下图 3-2,该部分的功能需求主要实现功能有:

    用户登录验证:将用户提交的账号和密码在服务器端验证
    信息的转发:通过服务器将用户的信息转发给其他用户
    刷新在线好友:接到用户的信息后返回该用户的在线好友信息


    3.1.2 客户端在C/S架构中客户端是核心部分,是该网络基本通信模型。客户端通过服务器获得所需的信息,所以客户端的功能模块图如下图3-3所示。其相应的功能解释已经在上面的系统用例图中解释过,这里不再累述。

    3.2 可行性分析从技术方面将本系统的开发利用MySQL5.0作为本系统的数据库,它是一个支持多用户的新型数据库,适用于中小规模的数据量需求。使用Java作为系统开发的开发环境,它提供完善的指令控制语句、类与对象的支持及丰富的数据类型,给开发高性能系统提供的保障为开发满足客户要求的系统,保证了代码的模块化要求,而代码模块化的提高,非常有利于以后对新系统的扩展与修改。
    从运行方面将本系统为一个小型的聊天系统,所耗费的资源非常的小,现在一般的Android手机无论是硬件还是软件都能够满足条件,因此,本系统在运行上是可行的。
    经过了对Android平台聊天客户端的详细分析.可以确定在Android平台上对基于TCP/IP协议的客户端开发从技术上和运行方面来说是可以实现的。
    3.3 本章小结本章主要明确了需求分析,根据C/S模型将系统分为服务器端和客户端。再通过对系统的进一步分析后,得到了服务器端和客户端的功能模块图。同时从技术和运行两方面论述了Android聊天软件的可行性分析。
    4 Android聊天软件系统设计通过上一章需求分析得到(系统的运行如图4-1所示)系统的需求分析以及大致的设计方向,本章将会从系统实现原理、服务器端、客户端与数据库设计的几个方面深层次的介绍系统所涉及的关键技术,通过这些技术的实现,系统的设计阶段将会告一段落。

    4.1 系统实现原理聊天系统的设计跟普通网站设计有着许多不同的地方,普通网站设计所考虑的因素,例如,普通网站需要对布局进入大量美化以及动画设计等等,而聊天室只要提供满足访客双方直接实时聊天即可。因此,在设计聊天系统的过程中,必须要考虑好以下几个设计要点:
    在Internet上的聊天程序一般都是以服务器提供服务端连接响应,使用者通过客户端程序登录到服务器,就可以与登录在同一服务器上的用户交谈,这是一个面向连接的通信过程。因此,程序要在TCP/IP环境下,实现服务器端和客户端两部分程序。如图4-2所示:
    服务器端启动后先要调用Serversocket()函数建立一个流式套接字,并返回引用新套接字的描述符。然后将此套接字描述符与本机的一个端口建立关联,这由ServerSocket()函数来完成。服务器只有在调用了accept()函数进入等待状态之后才可以接受来自客户端的请求。一旦接收到客户端通过connect()发出的连接请求,accept()将返回一个新的套接字描述符,通过套接字描述符调用相应的流的read()或write()函数即可与客户端进行数据收发。待数据传送完成,服务器和客户端调用closes()关闭套接字。需要说明的是服务器此时关闭的是此前由accept()所返回的新套接字,而不是先前开始创建的套接字。在此套接字被关闭后,服务器将再次处于阻塞状态,以等待下一个客户端的连接请求并重复上述过程。

    4.2 服务器端服务器的流程图如下4-3所示:

    服务器接收到客户的信息,进行相应的判定,将信息交给相应的功能模块进行处理。根据功能需要我将判定分为以下几类如表4-1所示:



    名称
    发送值
    备注




    CHAT
    1
    聊天


    REFRESH
    2
    更新好友


    REGISTER
    3
    注册


    LOGIN
    4
    登录


    ADD
    5
    添加好友


    DELETE
    6
    删除好友


    NOHAVE
    7
    该用户不存在和不在线


    AGREE
    8
    同意添加好友


    REFUSE
    9
    拒绝添加好友



    4.2.1 用户登录验证模块为了保证用户信息的安全,将用户是否合法的判定放在服务器端,通过查询数据库得出判定结果,返回给客户端。
    4.2.2 信息的转发模块采用服务器转发的方式,实现外网聊天的功能。服务器将接收到的信息转发给其他用户。其中信息类型包括CHAT、ADD、AGREE、REFUSE这四种类型。

    CHAT 服务器判定为CHAT,则转发给与之通信的用户
    ADD、AGREE、REFUSE三者均为同一功能——添加好友。当用户点击添加好友时,服务器接收到类型为ADD的消息,并将他转发给要添加的好友。好有用户可以选择同意添加好友和拒绝添加好友,分别向服务器发送一个类型为AGREE和REFUSE的信息

    服务器则根据类型不同完成数据库的添加。
    4.2.3 刷新好友列表模块用户点击刷新列表后,向服务器发送REFRESH类型的消息,服务器将查询数据库将该用的好友列表封装,发送给该用户。
    4.3 客户端4.3.1 用户登录模块当用户登录时,客户端由LoginActivity.java界面收集并验证用户登录信息后,封装成User对象类然后通过建立在Socket的连接之上的对象输出流将用户登录信息发送给服务器,服务器端将请求转发给UserDaoImpl处理,UserDaoImpl在收到信息后将验证数据的完整性并在对象型数据库中查找该用户名是否已经注册,然后将注册用户的信息与登录请求信息进行密码验证,在登录成功后将该用户添加到在线用户列表,最后将登录结果返回给客户端。如果登录成功,客户端将继续启动聊天主界面。具体流程如下图4-4所示:

    4.3.2 用户交互模块用户交互模块的功能示意图如图4-5:当用户登录成功后,客户端有MainActivity.java和ChatWindowsActivity.java界面收集用户要交互的信息后,封装成Messageinfo对象类然后通过建立在Socket的连接之上的对象输出流将用户交互信息发送给服务器,服务器端将请求类型分类转发给不同的方法处理。

    4.3.3 用户注册模块当用户注册时,客户端由RegisterActivity.java 界面收集用户注册信息后,封装成User对象类然后通过建立在Socket的连接之上的对象输出流将用户注册信息发送给服务器,服务器端将请求转发给UserDaoImpl处理,UserDaoImpl在收到信息后将数据完整保存在对象型数据库中,如果注册成功,客户端可以返回到LoginActivity.java界面登录。流程如下图4-6所示:

    4.3.4 用户设置模块当用户登录成功后,客户端有MainActivity.java界面收集用户要交互的信息后,封装成Messageinfo对象类然后通过建立在Socket的连接之上的对象输出流将信息发送给服务器,服务器端将请求类型分类转发给不同的方法处理。用户设置的功能模块图如下图4-7所示:

    4.3.5 用户注销模块当用户点击退出按钮,客户端有MainActivity.java界面收集用户要交互的信息后,封装成Messageinfo对象类然后通过建立在Socket的连接之上的对象输出流将用户退出信息发送给服务器,服务器端将请求类型分类转发给不同的方法处理。用户注销的流程图如下图4-8所示:

    4.4 Android聊天软件用户存储数据库的设计4.4.1 SQLite 简介SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,
    还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于2000年5月. 至今已经有10个年头,SQLite也迎来了一个版本 SQLite 3已经发布。SQLite虽然很小巧,但是支持的SQL语句不会逊色于其他开源数据库,同时它还支持事务处理功能等等。SQLite 支持跨平台,操作简单,能够使用很多语言直接创建数据库。
    4.4.2 数据库设计数据库分为客户端数据库(SQLite)和服务器端数据库(Mysql),下面将通过系统各个实体的E-R图予以详细说明。
    客户端数据库
    为了实现用户的快速登录,记住用户的个性化选择即当用户选中下拉列表中的账号恢复该账号用户设置的选项。建立用户表将用户的User_ID,Password,HeadImage等属性存入SQLite中,设计如下图4-9所示的数据项和数据结构。

    服务器端的数据库
    服务器端主要记录注册用户的个人信息,在线情况和好友,如图4-10所示:

    好友关系表,如图4-11所示:

    三个相应的表结构如下表4-2、4-3、4-4所示:
    客户端用户表



    字段名
    字段类型
    字段说明
    备注




    UserName
    Varchar(10)
    用户名
    主键


    Password
    Varchar(10)
    用户密码
    不能为空


    Mood
    Varchar(50)
    心情或个性签名
    可以为空


    Box_remb
    Integer
    是否记住用户密码
    默认为0


    Box_open
    Integer
    用户是否接受添加好友
    默认为0


    Self_Image
    Blob
    用户头像
    默认为自带头像



    服务器端用户表



    字段名
    字段类型
    字段说明
    备注




    UserName
    Varchar(10)
    用户名
    主键


    Password
    Varchar(10)
    用户密码
    不能为空


    Mood
    Varchar(50)
    心情或个性签名
    可以为空


    state
    Integer
    用在线状态
    默认为0


    Self_Image
    Blob
    用户头像
    null



    好友关系表



    字段名
    字段类型
    字段说明
    备注




    UserName
    Varchar(10)
    用户名
    主键,外键


    FriendName
    Varchar(10)
    好友名
    主键,外键



    4.5 本章小结本章通过对系统实现原理、客户端和服务器端以及数据库的设计,基本完成了程序的整体架构,为编码和实现铺平了道路。
    5 Android聊天软件的实现基于Android的即时通信软件是为给Android手机用户提供随时随地与好友交流的平台,主要针对Android手机用户。根据需求分析、系统概要设计以及更好地用户体验编码与实现分为数据传输的实现、客户端业务逻辑实现、服务器业务逻辑实现和界面UI实现四个部分。
    5.1 数据传输的实现本程序采用C/S模式架构,客户端与服务器端使用socket套接字实现连接,通过多线程技术完成相互通信。
    Android集成了Java的运行环境所以对Java有很好的支持,本系统就使用java.net.Socket包。在服务器和客户端定义相同的Bean结构,通过序列化将对象转换成二进制流传送到服务器端,使用在服务器端定义的Bean进行反序列化,将二进制流恢复为对象代码如下:
    public boolean connect(User user) { boolean isok = false; try { s = new Socket("10.0.2.2", 9999); s.getOutputStream(); oos = new ObjectOutputStream(s.getOutputStream()); oos.writeObject(user); ois = new ObjectInputStream(s.getInputStream()); type=(MessageType) ois.readObject(); gf=(Group_Friend) ois.readObject(); myapp.setChilds(gf.getFriends()); myapp.setGroups(gf.getGroups()); isok=type.isOk(); if(isok){ Client_To_ServerThread ccst=new Client_To_ServerThread(s); //启动该通讯线程 new Thread(ccst).start(); SaveClientSocket.add(user.getUsername(), ccst); } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return isok;}
    5.2 服务器端的实现5.2.1 用户登录的实现查询数据库判定用户是否存在,如果存在则创建一个新的进程,并将这个进程添加到进程管理类中(SaveClientThread)。相关代码如下:
    if(sql.checkUser(user)!=null){ if(sql.checkUser(user).getPassword().equals(password)){//查找数据库成功后 try { Group_Friend gf=new Group_Friend(); UserDaoImpl udi=new UserDaoImpl(); MessageType type = new MessageType(); type.setConnect(true); type.setExist(false); type.setOk(true); gf.setFriends(udi.getFriendList(null, null)); gf.setGroups(udi.getGroups(null)); oos.writeObject(type); oos.writeObject(gf); oos.flush(); //保存该socket 并创建一个线程 Server_To_ClientThread stc=new Server_To_ClientThread(s); SaveTheSocket sts=new SaveTheSocket(); execu.execute(stc); sts.add(user.getUsername(), stc); System.out.println("添加"+user.getUsername()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
    5.2.2 用户退出接收到用户退出的消息,将管理该用的线程关闭,同时将该线程从线程管理中移除。相关代码如下:
    public static HashMap<String,Server_To_ClientThread> hm=new HashMap<String, Server_To_ClientThread>(); public static void add(String id,Server_To_ClientThread stc){ hm.put(id, stc); } public static Server_To_ClientThread getThread(String id){ return hm.get(id); } public static void delete(String id){ hm.remove(hm.get(id)); }}
    5.2.3 更新好友列表接收到用户查询好友的消息,从数据库中查询该用户好友信息,发送给该用户。相关代码如下:
    while(true){ try { ObjectInputStream ois=new ObjectInputStream(s.getInputStream()); Messageinfo message=(Messageinfo)ois.readObject(); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~"+message.getCon()); if(message.getMesType()=="refresh"){ gf.setFriends(udi.getFriendList(null, null)); gf.setGroups(udi.getGroups(null)); oos.writeObject(type); oos.writeObject(gf); oos.flush(); } Server_To_ClientThread stc=SaveTheSocket.getThread(message.getGetter()); System.out.println(message.getGetter()); ObjectOutputStream oos=new ObjectOutputStream(stc.s.getOutputStream()); oos.writeObject(message); } catch (Exception e) { e.printStackTrace(); }}
    5.3 客户端代码的实现由于Android特定的设计Activity与Service、线程之间无法直接交互需要通过Handler、ServiceConnect等实现互通更新UI。
    Activity与Service间信息的交互
    从Linux的概念空间中,Android的设计每个Activity都是一个独立的进程,每个Service也都是一个独立的进程,Activity,Service之间要交换数据属于IPC(进程间通信)。通过Binder实现Activity与Service的通信,Binder是Android系统提供的一种IPC。
    在Activity中实现ConnectService对象,在Service中实现一个类继承Binder,通过BinderService函数绑定Service,在绑定Service的同时将回调ConnectService中的onServiceConnected方法,实例化一个Service对象从而实现在Activity中调用Service中的方法。ConnectService的实现:
    private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service){ // TODO Auto-generated method stub mService = (ConnectSerivce.LocalBinder) service).getService(); } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub mService = null; }};
    Binder的实现
    public class LocalBinder extends Binder { public ConnectService getService() { return ConnectService.this; }}public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return binder;}
    Activity调用Service中的方法
    public booleam mLogin( final User user){ return mService.serviceLogin(user);}
    上面实现了Activity向Service发送数据,而Service和线程向Activity发送数据则要通过Handler来时实现相关代码如下:
    调用Handler
    private void judgeToSend(Message msg){ ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); ComponentName cn = am.getRunningTasks(1).get(0).topActivity; String name=cn.getClassName(); System.out.println("activity名字"+name); if(name.equals("ben.client.MainActivity")){ MainActivity.handler.sendMessage(msg); }else{ ChatWindowsActivity.myHandler.sendMessage(msg); }}
    Handler的实现
    public class MyHandler extends Handler{ private MainActivity main; public MyHandler (MainActivity main){ this.main=main; } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); 具体方法略 } }}
    5.4 客户端UI实现受欢迎的软件除了具有强大的功能外,有好的界面和简洁的操作也是不可缺少的环节。Android系统到目前为止已经更新了多个版本,但是其控件的设置,界面的美化依然有很多不足。为了给提高用户的客户体验,本程序参考腾讯QQ的UI设计理念同时加入自己的一些想法。
    5.4.1 半透明悬浮窗口的实现采用popupwindows作为窗口的载体,通过setAnimationStyle方法加载动画的效果,实现窗口的侧滑出现和消失。Popupwindows的初始化代码如下:
    private void initPop(){ LinearLayout popup = (LinearLayout) getLayoutInflater().inflate(R.layout.popup_help,null); RelativeLayout morph=(RelativeLayout) getLayoutInflater().inflate(R.layout.register, null); pop = new PopupWindow(popup,320,480); pop.setAnimationStyle(R.style.mypopwindow_in); pop.showAtLocation(morph, Gravity.CENTER, 0, 0); pop.setFocusable(true); pop.update(); ImageView image=(ImageView) popup.findViewById(R.id.register_content); image.setBackgroundResource(R.drawable.morph_help); image.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub pop.dismiss(); } });}
    5.4.2 主界面左右滑动切换的实现通过自己定义的ScrollLayout控件,实现左右滑动切换。ScrollLayout继承了ViewGroup,从写了其中的onLayout和onMeasure方法。并且通过继承自定义的接口完成了数据的共享和传递。
    /** * 方法名称:snapToScreen 方法描述:滑动到到第whichScreen(从0开始)个界面,有过渡效果 * @param whichScreen */ public void snapToScreen(int whichScreen) { // get the valid layout page int lastIndex = mCurScreen; whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); System.out.println("whichSc"+whichScreen+"curscreen"+mCurScreen); if (getScrollX() != (whichScreen * getWidth())) { final int delta = whichScreen * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); mCurScreen = whichScreen; invalidate(); // Redraw the layout } for (LayoutChangeListener listener : listeners) listener.doChange(lastIndex, whichScreen); } postInvalidate(); }}
    5.5 本章小结通过本章实现了客户端和服务器端的大致功能,包括用户的注册、登录、设置头像添加删除好友、与好友进行聊天等。在没有进过测试的情况下,程序还有许多未能发现的问题,这些问题将通过系统测试发现,更正。
    6 系统演示与运行为了确认基于Android的聊天软件能够准确而又完整地体现该软件最初设计,同时完成各项具体功能需求,因此对系统的部分主要功能进行了实际运行测试。
    6.1 用户登录演示用户登录系统后进入登录界面,用户登录界面如下图6-1:

    用户登录需填写账号和密码,验证成功后跳转到好友列表界面。同时用户可以点击下拉菜单,选择以往登录记录快速登录。效果如下图6-2所示

    用户在有选择以往登录记录的权利,同时也有删除登录记录的方式。用户在选择完成后效果如下图6-3

    6.2 用户注册演示用户注册界面如下图6-4:

    注册界面需用户填入必填项账号、密码、确认密码。同时用户还可以设置自己的头像(为可选填项)。点击图片设置头像,为了好的用户体验,设置了三种头像设置方式,分别为New Picture(手机拍照)、Photo Albums(本地相册)、System Photo(软件自带的图片)。测试效果如下图6-5和图6-6:


    为了提醒用户怎么设置头像提供了帮助提示测试效果如下图6-7:

    当用户填写完信息,设置头像头点击确定,完成注册。注册成功的界面如下图6-8

    6.3 主界面演示当用户登录成功后跳转到主界面如下图6-9所示:

    主界面包含好友列表界面和最近联系人界面,通过在屏幕上左右拨动或者点击图片实现两个界面的切换。最近联系人界面如下图6-10:

    6.4 聊天界面的演示用户选择好友后进入聊天界面如图6-11所示:

    6.5 本章小结本章主要演示了软件的主要功能,通过运行效果,基本实现了Android聊天软件的功能。但是在文字传送的同时没能实现表情发送的功能。由于个人水平有限,时间也比较紧,只能实现系统的基本操作,对其它功能我会在以后的工作中进一步的学习并完善。
    结束语本文研究基于Android手机平台的聊天软件的设计与实现。从用户的角度,探讨了如何构建一个便捷的即时通信软件。基本满足了结构化、界面友好、速度快以及稳定性等特点。
    系统着重研究并实现了网络应用的部分。根据实现的情况看,具有较友好的聊天界面生成效果和良好的用户体验,以及流畅的网络通信效果。好友通信可以达到基本的聊天要求。
    本课题在大量阅读和参考有关理论和应用资料的基础上,归纳、总结并研究了手机聊天客户端应用,对手机聊天的设计及开发进行了分析,主要做了以下几方面的工作:

    了解了PC端聊天软件相应功能与操作
    掌握多线程方法使用
    熟练掌握Android相关技术

    由于个人的能力和精力有限,此系统在设计和实现中存在一些还有待进一步解决的问题,具体表现如下:

    服务器端的数据库性能较差
    在软件安全上,对用户的账号与密码没有进行保证,没有对不同用户进行限制,使其用户使用手机都能上之前登陆过的账号
    内部沟通方面,没有添加表情等

    通过毕业设计,发现自己在理论研究和实际工作能力等方面都得到了提高,受益匪浅,同时在老师的指导和课题组同学的共同帮助下,及时总结研究成果,这些无疑会对我今后的工作和学习带来很大的帮助。
    参考文献[1] 杨丰盛.Android应用开发揭秘[M].北京:机械工业出版社 2010.1
    [2] 百度百科.[EB/OL] http://baike.baidu.com/view/1241829.htm
    [3] 百度图片.[EB/OL] http://image.baidu.com/
    [4] Bruce Eckel.陈昊鹏(译)Thinking in Java[M].北京:机械工业出版社,2007.6
    [5] 王水、张晓民.软件工程素质导论[M].河南:河南科技技术出版社,2011.2
    [6] 朱丽平.UML面向对象设计与分析[M].北京:清华大学出版社,2007.
    [7] 马晓玉等.Oracle 10g数据库管理应用和开发[M].北京: 清华大学出版社 2007.11
    [8] 马志强.基于Android平台即时通信系统的设计与实现[D].北京: 北京交通大学,2009.
    [9] 谢希仁.计算机网络(第五版)[M].北京:电子工业出版社,2009.
    [10] 王小东.高性能MySQL[M].北京:电子工业出版社,2010.
    [11] 减海峰,张力军.通讯软件发展现状的分析与研究[J].计算机与数字工程 2008年第二期112-124
    [12] 通过服务更新进度通知和在Activity中监听服务进度.[EB/OL] http://blog.csdn.net/liuhe688/article/details/6623924
    [13] Android的消息机制.[EB/OL] http://blog.csdn.net/happy_6678/article/details/5940756
    [14] 自定义布局-ScrollLayout.[EB/OL] http://www.cnblogs.com/wader2011/archive/2011/10/10/2205142.html
    [15] Android学习之Bitmap Drawable byte[]转化.[EB/OL] http://www.cnblogs.com/enricozhang/archive/2011/09/29/2195601.html
    2 评论 46 下载 2019-05-15 10:57:04 下载需要18点积分
  • 基于JAVA的电梯调度模拟

    一、项目要求概述1.1 项目目的
    通过控制电梯调度,实现操作系统调度过程学习特定环境下多线程编程的方法学习调度算法
    1.2 开发环境
    语言:java系统平台:全平台(具备java环境)IDE:Intellij IDEA产品呈现模式:jar包执行环境要求:安装java
    Win:安装java配置环境变量后双击Linux/Mac:命令行:java –jar 电梯.jar

    1.3 基本需求
    模拟20层楼中5架电梯的调度电梯具有最基本的按键可显示电梯的当前状态
    二、调度算法概述2.1 乘客行为概述
    乘客可以在20层楼的任何一层楼按当前楼层的上或者下的按键对电梯提出需求乘客可以按动电梯中的楼层选择按钮来对指定电梯前去哪里,由于ui的设计问题,这一功能被要求在按动请求按钮时一并完成乘客可以在电梯中按动紧急按钮迫使当前电梯停止运作
    2.2 电梯行为概述
    电梯初始状态均为静止,且停泊在第一层电梯通过反复自检自身的状态变量来变更自己的行为行进中的电梯每到一个楼层都自检下客队列,判断当前楼层是否需要开门下客行进中的电梯每到一个楼层都要检查当前楼层乘客等待队列是否有符合当前方向的乘客,判断当前楼层是否要载客,如果在该楼层电梯中没有了乘客且没有应答其他请求,则载上当前楼层人数较多方向的乘客继续行进
    2.3 调度
    乘客按下请求按钮响应流程
    上下方向上有朝这一楼行进且该电梯的最高/低请求大于该楼层:将会等待该电梯到达该楼层来载上该乘客上下方向上没有朝这一楼行进的电梯或是有但是该电梯最高/低请求并没能到达该楼层:将会进行检索静止的电梯队列:静止电梯的选择将位置优先,选择离该楼层最近的静止电梯来响应请求,将该电梯启动,并将在该楼停下的指令塞入该电梯。


    2.3.1 行进电梯到达某一楼层执行操作流程
    电梯检索自身的停止队列中是否有该楼层
    有该楼层:停留并将队列中的乘客全部弹出队列,将停止队列的该楼层弹出没有该楼层:进行下一步
    电梯检索当前楼层的请求队列
    电梯当前停止队列已经为空:
    当前楼层没有请求电梯设置自身状态变量为静止当前楼层有请求电梯选择人多的一个方向载客,将他们弹出请求队列,并设置状态变量然后启动向该方向行进
    电梯当前停止队列并未空
    当前楼层没有请求电梯继续行进当前楼层有请求电梯载上对应方向的乘客,将这些乘客从请求队列弹出,继续行进



    三、类概述
    私有变量
    int name; // 电梯名int currentState; // 当前状态变量int emerState; // 紧急状态变量int currentMaxFloor; // 当前可去的最高的楼层int maxUp; // 当前电梯要去的最高楼层int minDown; // 当前电梯要去的最低楼层Queue<Integer> upStopList; // 电梯下降停止队列Queue<Integer> downStopList; // 电梯上升停止队列JButton buttonList; // ui中的按钮控件队列
    方法
    int getCurrentState(); // 获取currentStatevoid setCurrentState(); // 设置currentStateint getCurrentFloor(); // 获取currentFloorvoid setCurrentFloor(); // 设置currentFloorvoid popUp(); // 将upStopList的第一个元素弹出void popDown(); // 将downStopList的第一个元素弹出void addUp(int pos); // 将位置楼层加入upStopListvoid addDown(int pos); // 将位置楼层加入downStopListint upMax(); // 获取maxUpvoid setUpMax(); // 设置maxUpint downMin(); // 获取minDownvoid setDownMin(); // 设置minDownvoid run(); // 启动电梯线程
    四、线程概述4.1 资源
    电梯
    4.2 任务
    乘客移动
    1 评论 108 下载 2018-11-03 13:20:28 下载需要5点积分
  • 基于JAVA的记事本

    一、绪论1.1 引言现如今,电脑已经成为了每家每户甚至是每个人手头都必有的一种实用性工具,它改变了人们的生活,大大提高了人们的工作效率。在此基础上,电脑端的记事本应用一直是每台电脑所必备的实用性应用,不管是在台式电脑、笔记本电脑或者平板电脑上,都能看到它的身影。其功能基本有如下几种:文件、编辑、格式、查看、帮助,每个功能下又有多个子功能,为使用者提供了多种编辑上的便利,基本能满足人们记事的需求,特别是快速笔记。正因为它的这些特点,才让它成为每台电脑中必不可少的成分。
    1.2 编写目的电脑端记事本是每台电脑的标配,有相当大的实用性,方便人们平时的记事之用,尤其是在快速笔记这方面,更是有非常大的作用,基本能满足人们的记事需求,有很大的开发及继续完善开发的意义。
    基于记事本的诸多优点,本课程设计针对电脑端的记事本进行开发设计,并在原有基础上进行完善,使它的功能更完善、更人性化及更实用化。
    1.3 背景随着人们生活信息化的提高,记事本只拘泥于笔和纸的时代已经一去不复返了,越来越多的电子版记事本进入了人们的生活。但如今的电脑端记事本软件感觉功能不够丰富,缺少一些个性化功能,导致用户体验不是很好,故本课程设计将开发一个加强版的电脑端记事本,来满足用户的需求。
    二、系统可行性研究2.1 系统概述2.1.1 当前系统分析当前电脑系统自带的记事本实现的功能有如下几种:文件、编辑、格式、查看、帮助,每个功能下又有多个子功能:

    “文件”主菜单中有“新建”、“打开”、“保存”、“另存为”、“页面设置”、“打印”、“退出”这几个子功能
    “编辑”主菜单中有“撤销”、“剪切”、“复制”、“粘贴”、“删除”、“查找”、“查找下一个”、“替换”、“转到”、“全选”、“日期/时间”这几个子功能
    “格式”主菜单中有“自动换行”、“字体”这两个子功能
    “查看”主菜单中有“状态栏”子功能
    “帮助”主菜单中有“查看帮助”、“关于记事本”这两个子功能

    2.1.2 目标系统分析在实现系统自带笔记本的功能同时,再添加一些个性化功能,例如为记事本添加上行号(这大大提高了我们程序员看代码的方便性),在状态栏添加上当前时间以及字数统计,让用户能够对自己所写的字数一目了然,大大增强了用户体验。
    此外,此记事本支持用户自定义背景颜色以及字体颜色,增强了趣味性,用户可以根据自己的喜好选择符合自己的主题。
    即实现的功能有:

    “文件”主菜单中有“新建”、“打开”、“保存”、“另存为”、“页面设置”、“打印”、“退出”这几个子功能
    “编辑”主菜单中有“撤销”、“剪切”、“复制”、“粘贴”、“删除”、“查找”、“查找下一个”、“替换”、“转到”、“全选”、“日期/时间”这几个子功能
    “格式”主菜单中有“自动换行”、“字体”、“背景颜色”、“字体颜色”这四个子功能
    “查看”主菜单中有“状态栏”子功能
    “帮助”主菜单中有“查看帮助”、“关于记事本”这两个子功能

    2.2 可行性分析研究2.2.1 技术可行性由于计算机技术和互联网技术的发展突飞猛进,计算机的应用深入各行各业。涌现出各种编程语言。本电脑端记事本采用JAVA语言进行开发设计。JAVA语言是一门面向对象的语言,风格接近C、C++语言,但又舍弃了C和C++语言中易引起错误的指针、运算符重载、多重继承等特性,使开发的程序质量更高。由于开发记事本的难度不高,因此通过JAVA语言在Eclipse编译器上就可以实现开发了。
    综上,技术可行性满足。
    2.2.1.1 Java的基本信息及优势Java是一种可以撰写跨平台应用程序的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。
    与传统程序不同,Sun 公司在推出 Java 之际就将其作为一种开放的技术。全球数以万计的 Java 开发公司被要求所设计的 Java软件必须相互兼容。“Java 语言靠群体的力量而非公司的力量”是Sun公司的口号之一,并获得了广大软件开发商的认同。这与微软公司所倡导的注重精英和封闭式的模式完全不同。
    Sun 公司对 Java 编程语言的解释是:Java 编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和静态的语言。
    Java 平台是基于 Java 语言的平台。这样的平台非常流行。因此微软公司推出了与之竞争的.NET平台以及模仿Java的C#语言。
    Java是功能完善的通用程序设计语言,可以用来开发可靠的、要求严格的应用程序。
    2.2.1.2 Eclipse的基本信息Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

    2.2.2 经济可行性主要分为三方面进行分析,分别是开发的财力物力及时间。

    开发的财力物力:

    笔记本电脑X1其他成本几乎为零,因为该项目开发的难度不大,完成时即刻可以使用,也不需要另外研发硬件设施进行使用,用电脑就行
    开发的时间:

    从一开始的分析设计到最后的测试维护,时间约为一周就可以,时间成本不大,可行性高
    收益:

    由于开发这个程序可以更好地满足人们的日常需求,收益还算不错的

    综上,经济可行性满足。
    2.2.3 操作可行性本程序采用的是图形化界面方式,记事本的操作不难,一般会使用电脑的人都会操作,只需按照图形界面进行操作,而且每个操作都有相关的快捷键提示,不需要相关的操作指导即可使用,可操作性非常强。
    2.2.4 社会可行性根据前期电脑上的记事本的使用情况及普及率来看,记事本的功能是受社会所认可的,人们普遍接受及使用电脑上的记事本,是可以为社会带来利益的。因此,对电脑端的记事本进行再开发完善,发掘它更多的功能并创造出社会价值,可行性是很高的。
    三、需求分析3.1 系统功能概述
    3.2 系统功能描述3.2.1 功能图
    3.2.2 用例描述


    用例名称
    新建




    涉及的参与者
    用户


    用例描述
    在Windows 环境下,新建一个空白txt文档


    前置条件
    记事本系统可用


    正常事件流
    1.点击开始-所有程序-附件-记事本 2.在记事本系统界面,点击文件-新建






    用例名称
    打开




    涉及的参与者
    用户


    用例描述
    在Windows 环境下,新建一个空白txt文档


    前置条件
    记事本系统可用


    正常事件流
    1. 双击打开记事本 2. 左键单击记事本-点击打开






    用例名称
    保存或另存为




    涉及的参与者
    用户


    用例描述
    在Windows 环境下,新建一个空白txt文档


    前置条件
    记事本系统可用


    正常事件流
    1. 打开空白记事本,点击文件-保存-再次打开 2. 点击文件-另存为-保存






    用例名称
    编辑




    涉及的参与者
    用户


    用例描述
    在Windows 环境下,新建一个空白txt文档


    前置条件
    记事本系统可用


    正常事件流
    1. 在编辑区域输入“软件工程课程设计” 2. 在编辑区域输入“1504” 3. 选中内容“软件工程课程设计”,点击编辑-剪切 4. 点击编辑-撤销 5. 选中内容“软件工程课程设计”,点击编辑-复制 6. 点击编辑-粘贴 7. 选中内容“软件工程课程设计”,点击编辑-删除 8.点击编辑-查找,查找内容“软件工程课程设计” 9. 点击编辑-查找下一个






    用例名称
    字体




    涉及的参与者
    用户


    用例描述
    在Windows 环境下,新建一个空白txt文档


    前置条件
    记事本系统可用


    正常事件流
    1. 在记事本系统界面,点击格式-字体 2. 在编辑区域输入“软件工程课程设计”,字体改为Wingdings,点击确定 3. 选择字体-斜体 4. 选择字体-8



    3.3 系统功能实现3.3.1 时序图
    3.3.2 类图(只选取部分函数)
    3.4 系统角色用户可以在系统里创建新文件,编辑文字,并保存,以便查看,还可以进行批处理文件等操作。
    3.5 系统流程图3.5.1 主流程图如下图所示,打开记事本后,可在文本域进行文本输入;或者进行一系列执行操作包括:文件、编辑、格式以及帮助菜单。

    功能描述:打开文件、编辑、格式以及帮助菜单
    输入项:点相关按钮
    输出项:做出指定动作


    3.5.2文件菜单操作流程图文件菜单操作流程如下图,当打开文件菜单时,下拉显示子菜单包括新建、打开、保存、退出等功能。

    功能描述:实现文件新建、打开、保存、另存为、退出功能
    输入项:点相关按钮
    输出项:做出指定动作


    3.5.3 编辑菜单操作流程图编辑菜单操作流程如下图,当打开编辑菜单时,下拉显示子菜单包括复制、粘贴、剪切、全选、查找、替换等功能。

    功能描述:实现文本的剪切、复制、粘贴、撤销、查找、替换、删除功能
    输入项:点相关按钮
    输出项:做出指定动作


    3.5.5帮助菜单操作流程图帮助菜单操作流程如下图,当打开帮助菜单时,下拉显示子菜单关于记事本。

    功能描述:查看帮助信息
    输入项:点帮助按钮
    输出项:显示相关信息


    3.6 数据流图
    3.7 数据字典由于无数据库设计,所以无数据字典描述。
    四、概要设计4.1 系统运行环境4.1.1 操作系统
    操作系统无关性,Windows XP/7/8.1/10、Linux、Mac OS X下安装了Java的运行环境JRE即可运行
    JDK版本:1.8

    4.1.2 使用软件
    代码编写:Eclipse
    数据库:无
    建模工具:Rational Rose(自写)
    文档编写:Microsoft Word 2016

    4.1.3 开发语言
    Java
    4.2 系统总体设计4.2.1 基本设计概念和处理流程4.2.1.1 全局E-R图
    4.2.1.2 分E-R图


    4.2.2 系统总体结构与模块

    4.3 接口设计4.3.1 外部接口4.3.1.1 用户界面
    4.3.1.2 软件接口软件运行平台:Eclipse
    4.3.1.3 硬件接口硬件运行平台:PC端
    4.3.2 内部接口说明各个模块由谁调用,完成什么功能,完成后转入什么状态



    模块
    由谁调用
    功能
    完成后转入的状态




    clock
    NotepadMainFrame
    显示状态栏的时间



    MQFontChooser
    NotepadMainFrame
    字体选择器
    字体变更


    textLine
    NotepadMainFrame
    左边的行号



    状态栏
    NotepadMainFrame
    状态栏显示信息
    显示状态栏


    MainFrameWidowListener
    NotepadMainFrame
    退出窗口选择设置
    退出软件


    actionPerformed
    NotepadMainFrame
    记事本的状态操作
    更换操作模式


    exit,mySearch,paste等
    NotepadMainFrame
    退出,查找,暂停等操作
    记事本状态改变



    4.4 数据结构设计由于没有用到数据库设计,所以无数据结构设计。
    五、详细设计5.1 数据流图
    源点/终点:用户
    处理:编辑(包括撤销、复制、剪贴、粘贴等)、新建记事本、保存、打开记事本、格式、打印
    数据流:输入的字符,记事本,打印文档
    数据存储:本地磁盘

    此记事本的基本系统模型如下图

    细化后的数据流图如下图

    5.2 层次方框图
    5.3 功能模块图
    六、系统实现6.1 关键代码6.1.1 自动换行JTextArea有自己定义的策略。
    //设置文本区的换行策略。如果设置为 true,则当行的长度大于所分配的宽度时,将换行。此属性默认为 falsetextArea.setLineWrap(true);
    6.1.2背景颜色JColorChooser jcc1 = new JColorChooser();JOptionPane.showMessageDialog(this, jcc1,"选择背景颜色颜色",-1);color = jcc1.getColor();textArea.setBackground(color);

    6.1.3 字体颜色jcc1=new JColorChooser();JOptionPane.showMessageDialog(this, jcc1, "选择字体颜色", -1);color = jcc1.getColor();//String string=textArea.getSelectedText();textArea.setForeground(color);
    6.1.4鼠标右击菜单// 创建弹出菜单final JPopupMenu jp=new JPopupMenu(); //创建弹出式菜单,下面三项是菜单项textArea.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if(e.getButton()==MouseEvent.BUTTON3)//只响应鼠标右键单击事件 { jp.show(e.getComponent(),e.getX(),e.getY());//在鼠标位置显示弹出式菜单 } }});

    6.1.5 打印功能 public void Print() { try{ p = getToolkit().getPrintJob(this,"ok",null);//创建一个Printfjob 对象 p g = p.getGraphics();//p 获取一个用于打印的 Graphics 的对象 //g.translate(120,200);//改变组建的位置 this.textArea.printAll(g); p.end();//释放对象 g } catch(Exception a){ } }

    6.1.6 显示时间//用到了线程,每1秒刷新一次时间public class Clock extends Thread{ public void run() { while (true) { GregorianCalendar time = new GregorianCalendar(); int hour = time.get(Calendar.HOUR_OF_DAY); int min = time.get(Calendar.MINUTE); int second = time.get(Calendar.SECOND); NotepadMainFrame.label1.setText(" 当前时间:" + hour + ":" + min + ":" + second); try { Thread.sleep(1000); } catch (InterruptedException exception) { } } } }
    在NotepadMainFrame类中
    JToolBar toolState = new JToolBar();toolState.setSize(textArea.getSize().width, 10);//toolState.setLayout(new FlowLayout(FlowLayout.LEFT));toolState = new JToolBar(); toolState.setSize(textArea.getSize().width, 10);//toolState.setLayout(new FlowLayout(FlowLayout.LEFT)); label1 = new JLabel(" 当前系统时间:" + hour + ":" + min + ":" + second+" "); toolState.add(label1);Clock clock=new Clock();clock.start();
    6.1.7 显示行数和列数label2 = new JLabel(" 第 " + linenum + " 行, 第 " + columnnum+" 列 "); toolState.add(label2); toolState.addSeparator();textArea.addCaretListener(new CaretListener() { //记录行数和列数 public void caretUpdate(CaretEvent e) { JTextArea editArea = (JTextArea)e.getSource(); try { int caretpos = editArea.getCaretPosition(); linenum = editArea.getLineOfOffset(caretpos); columnnum = caretpos - textArea.getLineStartOffset(linenum); linenum += 1; label2.setText(" 第 " + linenum + " 行, 第 " + (columnnum+1)+" 列 "); } catch(Exception ex) { }}});contentPane.add(toolState, BorderLayout.SOUTH);toolState.setVisible(false);toolState.setFloatable(false);

    6.1.8 撤销、返回初始化一个UndoManger,然后通过body.getDocument().addUndoableEditListener(undoMgr)这个方法,就可以把撤销管理器的监听器添加到TextArea中。需要调用撤销的时候就调用unoMgr.undo()方法。
    UndoManager undoMgr=new UndoManager();JtextArea textArea.getDocument().addUndoableEditListener(undoMgr); if(undoMgr.canUndo()){ undoMgr.undo();//撤销} if(undoMgr.canRedo()){ undoMgr.redo();//恢复}
    6.2 程序目录截图
    七、系统测试


    测试评价
    针对实现的记事本的功能模块,基本上达到了预定的要求。
    缺陷(未实现功能):

    右键的菜单响应功能中的从右到左,Unicode码,汉字重选
    保存的文件设置了颜色和字体,打开后还是默认大小
    打印功能还可以再完善,现在只能打印一页多点,具体的设置还不是很懂,等我再多学点
    将自动换行和状态显示默认添加
    2 评论 59 下载 2018-11-04 15:19:13 下载需要6点积分
  • 基于vc++实现的矩阵乘优化软件

    一、实验名称矩阵乘优化软件
    二、实验目的及要求
    C语言实现矩阵x向量算法
    矩阵要求CSR压缩存储格式,测试集选用佛罗里达州立大学测试集 http://www.cise.ufl.edu/research/sparse/matrices//
    SSE优化,LOOP unrolling,software prefetch软件预取,多线程并行
    给出测试界面,运行时间及加速比结果

    三、实验环境
    操作系统(开发):Windows 7/Windows XP
    编程软件(开发):Microsoft Visual Studio 2008
    操作系统(测试):Windows 7
    硬件环境(测试):Acer 4741G,i5双核处理器 460M

    四、实验内容根据实验要求,按照路径组合的形式将其分为三类:

    读入数据至内存方式:单线程读取文件、多线程读取文件、CSR格式读入
    乘法计算:单线程、多线程
    SSE优化:有SSE优化、无SSE优化

    3*2*2=12种 再加上传统算法一共13种算法组合。
    五、算法描述及实验步骤5.1 多线程编程程序使用WIN32提供的多线程编程的接口(windows.h)函数实现,实验中用到的函数如下:
    HANDLE h = CreateThread(NULL,0,MulThread,pParam,0,NULL);//创建多线程DWORD WINAPI MulThread(LPVOID pParam);//线程函数WaitForSingleObject(h,INFINITE);//等待线程完成MulParam * pParam = (MulParam *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,sizeof(MulParam));//为线程分配数据,实现传参MulParam * p = (MulParam *)pParam; //线程内读取参数HeapFree(GetProcessHeap(),0,pParam);//线程内结束后销毁参数HANDLE hMutex = CreateMutex(NULL,FALSE,NULL);//创建互斥变量WaitForSingleObject(hMutex,INFINITE);//进入临界区ReleaseMutex(hMutex);//退出临界区HANDLE inputSemaphore; //创建信号量WaitForSingleObject(inputSemaphore,INFINITE);//查看临界资源是否剩余ReleaseSemaphore(inputSemaphore,1,NULL);//释放临界资源
    5.2 文件预取使用消费者模型实现文件预取,具体实现是在内存中申请一块区域作为缓存,分别被读写线程共享,读数据与模型中的消费者对应,写数据与模型中的生产者对应。使用信号量机制实现读写同步,读写操作时均要获得读锁或写锁才能操作,以实现互斥访问。具体算法实现如下:
    queue<int> input;queue<int> output;HANDLE inputMutex = CreateMutex(NULL,FALSE,NULL);HANDLE outputMutex = CreateMutex(NULL,FALSE,NULL);/** 缓存数据使用后,将其设为可写状态 */void PushInput(int _i){ WaitForSingleObject(inputMutex,INFINITE); input.push(_i); ReleaseMutex(inputMutex); ReleaseSemaphore(inputSemaphore,1,NULL);}/** 缓存数据写满后,将其设为可读状态 */void PushOutput(int _i){ WaitForSingleObject(outputMutex,INFINITE); output.push(_i); ReleaseMutex(outputMutex); ReleaseSemaphore(outputSemaphore,1,NULL);}/** 获缓存写位置 */int PopInput(){ int value; WaitForSingleObject(inputSemaphore,INFINITE); WaitForSingleObject(inputMutex,INFINITE); value = input.front(); input.pop(); ReleaseMutex(inputMutex); return value;}/** 获取缓存读位置 */bool isRight = true;int PopOutput(){ int value; WaitForSingleObject(outputSemaphore,INFINITE); WaitForSingleObject(outputMutex,INFINITE); if(isRight) { value = output.front(); output.pop(); if(fileThreadRunningCount == 0) { isRight = false; ReleaseSemaphore(outputSemaphore,1,NULL); } } else { value = -1; ReleaseSemaphore(outputSemaphore,1,NULL);//TODO 释放所有的阻塞队列 } ReleaseMutex(outputMutex); return value;}
    5.3 SSE优化调用CPU提供的128位寄存器,试验中将4个浮点数一次性压入寄存器,调用乘法指令,计算完后将会把结果写在对应的寄存器中,再将其读出即可,具体算法如下:
    __m128 r1,res;........r1.m128_f32[0]=node->matrix[j];r1.m128_f32[1]=node->matrix[j+1];r1.m128_f32[2]=node->matrix[j+2];r1.m128_f32[3]=node->matrix[j+3];res = _mm_mul_ps(_mm_set_ps(node->v[node->xj[j+3]], node->v[node->xj[j+2]], node->v[node->xj[j+1]], node->v[node->xj[j]]), r1); result[i]=result[i]+res.m128_f32[0]+res.m128_f32[1]+res.m128_f32[2]+res.m128_f32[3];
    5.4 生成CSR格式矩阵文件存储格式:

    第一个数字:矩阵的列数
    第二个数字:矩阵的行数
    第三个数字:下面有效的行数
    行数据:列号 行号 值

    例如:

    表示矩阵:

    向量文件存储格式:

    第一个数字:向量矩阵的行数
    下面表示向量矩阵各行的值

    例如:

    表示矩阵:

    按照文件存储格式,将其转化成CSR存储格式,存放在内存,其具体实现如下:
    int sum=0;int current=0;for(i=0;i<node->row;i++){ for(j=0;j<node->xr[i];j++) { node->matrix[sum]=coo[current].value; node->xj[sum]=coo[current].col-1; sum++; current++; } int t=4-node->xr[i]%4; while(t%4) { node->matrix[sum]=0; node->xr[i]++; node->xj[sum]=0; t--; sum++; } //原先node->xr每个数据表示该行的数据数目,现在修改后表示每行第一个数的位置 if(i) { node->xr[i]=node->xr[i]+node->xr[i-1]; }}
    六、调试过程及实验结果本程序对各个算法的运算加速比进行统计,并用网页形式表现出来,如下图(并行乘法线程数为3,文件预取线程数为3):


    由图中2-5、6-9、10-13,可以看出实现文件读取策略的选择是关键,以CSR格式提取数据效率最低,主要是由于其转化成CSR格式而须做数据排序算法,耗时较大;非预取是以单线程读取文件,由于其读入缓存即可使用,相比CSR方式效率有所提高;而预取使用多个线程读取文件,相比单线程读取文件效率又有相当大的提升
    由图中2-3、4-5、…、12-13,可以看出多线程计算乘法,反而效率略有所下降,这主要是CPU直接从内存及缓存访问数据效率极高,但是多个线程之间的不断切换反而不利于提高效率
    由图中2和4、3和5、…、11和13,看不出SSE优化得效果,可能是高速处理器计算效率极高,由于访存速度跟不上而导致瓶颈

    综上所述,对SSE的优化几乎无影响,对乘法实现多线程效率反而有所下降,而对读取文件的优化,即软件预取是提升最大。
    七、总结在这次短学期中,我们学会了很多,在一开始什么都不知道的情况下,我们查看了很多资料,学会CSR压缩存储,学会用SSE优化,学会用多线程预取文件,并行运算出结果,在这个过程中解决并行时的冲突和同步问题,但是这个过程中还有点不足的事,我们一开始没有想好全局设计,使的后来的交接部分一而再再而三的改动,不过吃一堑,长一智,经验总是在坎坷后的。
    八、附录
    算法实现和分工

    A:CSR压缩 SSE优化 B: 并行运算 预取技术
    项目说明

    文件夹Matrix为程序用C++实现的全部算法,为了更好的分析实验数据,文件夹VIEW_RESULT是用C#实现的结果展现页面
    1 评论 2 下载 2020-05-21 10:01:53 下载需要13点积分
  • 基于JAVA和MYSQL的医院简易挂号管理系统

    一、需求分析1.1 题目要求采用桌面应用程序模式,开发一个医院挂号系统,管理包括人员、号种及其挂号费用,挂号退号等信息,完成登录、挂号、查询和统计打印功能。数据库表如下所示,建立索引的目的是加速访问,请自行确定每个索引要涉及哪些字段。
    T_KSXX (科室信息表)



    字段名称
    字段类型
    主键
    索引
    可空
    备注




    KSBH
    CHAR(6)



    科室编号,数字


    KSMC
    CHAR(10)



    科室名称


    PYZS
    CHAR(8)



    科室名称的拼音字首



    T_BRXX (病人信息表)



    字段名称
    字段类型
    主键
    索引
    可空
    备注




    BRBH
    CHAR(6)



    病人编号,数字


    BRMC
    CHAR(10)



    病人名称


    DLKL
    CHAR(8)



    登录口令


    YCJE
    DECIMAL(10,2)



    病人预存金额


    DLRQ
    DateTime



    最后一次登录日期及时间



    T_KSYS (科室医生表)



    字段名称
    字段类型
    主键
    索引
    可空
    备注




    YSBH
    CHAR(6)



    医生编号,数字,第1索引


    KSBH
    CHAR(6)



    所属科室编号,第2索引


    YSMC
    CHAR(10)



    医生名称


    PYZS
    CHAR(4)



    医生名称的拼音字首


    DLKL
    CHAR(8)



    登录口令


    SFZJ
    BOOL



    是否专家


    DLRQ
    DATETIME



    最后一次登录日期及时间



    T_HZXX (号种信息表)



    字段名称
    字段类型
    主键
    索引
    可空
    备注




    HZBH
    CHAR(6)



    号种编号,数字,第1索引


    HZMC
    CHAR(12)



    号种名称


    PYZS
    CHAR(4)



    号种名称的拼音字首


    KSBH
    CHAR(6)



    号种所属科室,第2索引


    SFZJ
    BOOL



    是否专家号


    GHRS
    INT



    每日限定的挂号人数


    GHFY
    DECIMAL(8,2)



    挂号费



    T_GHXX (挂号信息表)



    字段名称
    字段类型
    主键
    索引
    可空
    备注




    GHBH
    CHAR(6)



    挂号的顺序编号,数字


    HZBH
    CHAR(6)



    号种编号


    YSBH
    CHAR(6)



    医生编号


    BRBH
    CHAR(6)



    病人编号


    GHRC
    INT



    该号种的挂号人次


    THBZ
    BOOL



    退号标志=true为已退号码


    GHFY
    DECIMAL(8,2)



    病人的实际挂号费用


    RQSJ
    DATETIME



    挂号日期时间



    为了减少编程工作量,T_KSXX、T_BRXX、T_KSYS、T_HZXX的信息手工录入数据库,每个表至少录入6条记录,所有类型为CHAR(6)的字段数据从“000001”开始,连续编码且中间不得空缺。为病人开发的桌面应用程序要实现的主要功能具体如下:

    病人登录:输入自己的病人编号和密码,经验证无误后登录
    病人挂号:病人处于登录状态,选择科室、号种和医生(非专家医生不得挂专家号,专家医生可以挂普通号);输入缴费金额,计算并显示找零金额后完成挂号。所得挂号的编号从系统竞争获得生成,挂号的顺序编号连续编码不得空缺

    功能2的界面如下所示,在光标停在“科室名称”输入栏时,可在输入栏下方弹出下拉列表框,显示所有科室的“科室编号”、“科室名称”和“拼音字首”,此时可通过鼠标点击或输入科室名称的拼音字首两种输入方式获得“科室编号”,用于插入T_GHXX表。注意,采用拼音字首输入时可同时完成下拉列表框的科室过滤,使得下拉列表框中符合条件的科室越来越少,例如,初始为“内一科”和“内二课”。其它输入栏,如“医生姓名”、“号种类别”、“号种名称”也可同时支持两种方式混合输入。

    每种号种挂号限定当日人次,挂号人数超过规定数量不得挂号。一个数据一致的程序要保证:挂号总人数等于当日各号种的挂号人次之和,病人的账务应保证开支平衡。已退号码不得用于重新挂号,每个号重的GHRC数据应连续不间断,GHRC从1开始。若病人有预存金额则直接扣除挂号费,此时“交款金额”和“找零金额”处于灰色不可操作状态。
    为医生开发的桌面应用程序要实现的主要功能具体如下:

    医生登录:输入自己的医生编号和密码,经验证无误后登录
    病人列表:医生处于登录状态,显示自己的挂号病人列表,按照挂号编号升序排列。显示结果如下表所示。




    挂号编号
    病人名称
    挂号日期时间
    号种类别




    000001
    章紫衣
    2018-12-30 11:52:26
    专家号


    000003
    范冰冰
    2018-12-30 11:53:26
    普通号


    000004
    刘德华
    2018-12-30 11:54:28
    普通号




    收入列表:医生处于登录状态,显示所有科室不同医生不同号种起止日期内的收入合计,起始日期不输入时默认为当天零时开始,截止日期至当前时间为止。时间输入和显示结果如下表所示。
    起始时间:2018-12-30 00:00:00 截止时间:2018-12-30 12:20:00



    科室名称
    医生编号
    医生名称
    号种类别
    挂号人次
    收入合计




    感染科
    000001
    李时珍
    专家号
    24
    48


    感染科
    000001
    李时珍
    普通号
    10
    10


    内一科
    000002
    扁鹊
    普通号
    23
    23


    保健科
    000003
    华佗
    专家号
    10
    20



    病人应用程序和医生应用程序可采用主窗口加菜单的方式实现。例如,医生应用程序有三个菜单项,分别为“病人列表”、“收入列表”和“退出系统”等。
    考虑到客户端应用程序要在多台计算机上运行,而这些机器的时间各不相同,客户端程序每次在启动时需要同数据库服务器校准时间,可以建立一个时间服务程序或者直接取数据库时间校准。建议大家使用MS SQL数据库开发。
    挂号时锁定票号可能导致死锁,为了防止死锁或系统响应变慢,建议大家不要锁死数据库表或者字段。程序编写完成后,同时启动两个挂号程序进行单步调试,以便测试两个病人是否会抢到同一个号、或者有号码不连续或丢号的现象。
    系统考核目标:

    挂号后数据库数据包括挂号时间不会出现不一致或时序颠倒现象,以及挂号人次超过该号种当日限定数量的问题
    挂号号码和挂号人次不会出现不连续或丢号问题
    病人的开支应平衡,并应和医院的收入平衡
    系统界面友好、操作简洁,能支持全键盘操作、全鼠标操作或者混合操作
    能支持下拉列表框过滤输入
    系统响应迅速,不会出现死锁
    统计报表应尽可能不采用多重或者多个循环实现
    若采用时间服务器程序校准时间,最好能采用心跳检测机制,显示客户端的上线和下线情况

    思考题:当病人晚上11:59:59秒取得某号种的挂号价格10元,当他确定保存时价格在第2天00:00:00已被调整为20元,在编程时如何保证挂号费用与当天价格相符?
    1.2 需求分析对于病人的操作界面而言,需要有题目要求的挂号功能,包括人性化的过滤功能,需要足够的提示信息用于提示当前的操作状态,此外,还需要题目要求中没有提到的对于余额的操作功能;对于医生的操作界面而言,除了需要题目要求的两种统计功能外,还需要额外的过滤功能以供更于便捷的查询。此外,还需要一个统一的登录界面以供医生和病人登录。
    对于程序的功能而言,不仅需要程序具有健壮性,在发生错误的时候不能崩溃,而且要求界面友好,支持多种操作方式,相应迅速。
    二、系统设计2.1 概要设计整个系统分为2个部分:程序部分以及数据库部分。数据库部分用于存储数据并同时服务多个客户端,而程序部分则负责相应用户的输入,与数据库沟通、处理数据并返回处理的结果。而程序部分总体上又分为4个模块:登录界面、医生操作模块,病人操作模块以及数据库连接器。总体的模块图如图 1所示。其中,登录界面用于负责检查用户的登录信息是否正确,并负责唤醒医生操作界面或病人操作界面,而医生操作界面或病人操作界面则调用数据库模块与数据库进行沟通,处理数据并返回结果。
    此外,程序入口也被封装为一个较小的main模块,用于执行包括加载数据库驱动、连接数据库、启动化图形界面引擎在内的初始化操作,以及实现唤醒登录界面的功能。如果不能成功连接数据库则此模块进行相应的处理并退出程序。由于此模块较小,且功能较为简单,因此不列入主要模块中。

    程序的总体状态转移图如图 2所示,首先进入登录界面并等待用户输入登录信息,然后通过查询数据库判断登录信息是否匹配,如果匹配则登录,否则提示错误并等待用户重新输入登录信息。登录成功后通过判断用户点击的是医生登录按钮还是病人登录按钮来判断加载医生登录界面还是病人登录界面。此后进入图形界面引擎控制的事件循环。当有事件到来时(如用户输入、点击等)处理事件、显示返回结果并继续等待下一个事件。所有的状态都是可逆的,也就是说用户可以通过退出按钮来回到上一个界面,以此来增加界面的可操作性。

    2.2 详细设计2.2.1 登录界面设计登录界面的设计较为简单,其功能为检测用户输入的登录信息是否与数据库中的登录信息相同。其应该包含一个用户名框,一个密码框,一个医生登录按钮、一个病人登录按钮以及一个退出按钮。由于图形化界面一般采用事件驱动,因此在按下按钮时进行对于用户输入的处理。按下医生登录按钮以及病人登录按钮的流程图如图 3所示。如果点击的是医生登录按钮或病人登录按钮,则首先判断用户名和密码是否为空。由于设计不允许出现空的用户名和密码,因此提前进行这一步判断有助于减少用户的等待时间以及无效的数据库访问,如果不为空则通过事先建立好的连接在数据库中查询,并通过查询结果进行比对:如果不存在此用户(查询结果为空)或密码比对错误,则做出相应的提示并禁止用户登录,如果用户名和密码均正确则加载对应的登录界面。如果点击的是退出按钮,则执行清理工作并退出界面。值得注意的是,由于医生登录界面与病人登录界面为同一个界面,因此第一步的输入信息不为空的检查可以提取到一个函数中进行,在本程序中validateUserNameAndPassword函数执行这一操作。

    此外,为了提高用户体验,在提示错误重新输入时需要清理掉上次提示的错误,而这些函数绑定在两个输入框的输入触发器上,这一绑定由此类的initialize方法完成。按照JavaFX的规范,initialize方法会在界面初始化时被调用。由于这些函数主要对于FXML的StyleSheet进行修改,与主要逻辑相比重要性较低,因此不做赘述,详见源代码部分。
    2.2.2 数据库连接器设计数据库连接器是整个程序的基础,它位于层次结构的最底层并被医生操作模块或病人操作模块所调用。这一模块在整个界面被初始化之前初始化,又由于没有它整个程序将无法运作,因此如数据库连接器不能正确被初始化,则程序将拒绝运行并退出。
    数据库连接器作为一个单例,为医生操作模块和病人操作模块提供必要的服务。这样可以简化上层的逻辑,将部分逻辑移动到底层,从而降低了整个系统的耦合度,同时便于代码的修改。虽然数据库连接器在全局作为一个单例对象存在,但由于需要考虑到多个客户端同时连接同一个数据库的情况,依然要考虑数据库层面的并发安全性问题。
    考虑到一个数据库仅供给一个人使用,为了简便起见,此类中仅维持一个连接实例,所有的statement均通过这一个连接进行。数据库对上层提供的服务(即公有方法)以及对应的执行语句如表 1所示(以略去参数类型)。此外,还提供了connectDataBase方法供初始化使用以及getInstance方法供获得实例使用。



    方法 (参数)
    说明
    执行的Sql语句




    getWholeTable (table)
    获得表table中的全部内容
    SELECT * FROM \<table>


    getPatientPassword (number)
    获得编号为number的病人的密码
    SELECT password FROM patient WHERE pid=\<number>


    getPatientInfo (number)
    获得编号为number的病人的全部信息
    SELECT * FROM patient WHERE pid=\<number>


    getDoctorInfo (number)
    获得编号为number的医生的全部信息
    SELECT * FROM doctor WHERE docid=\<number>


    getRegisterForDoctor (number, start, end)
    获得起止时间分别为start和end的有关医生编号number的全部挂号信息
    SELECT reg.reg_id,pat.name, reg.reg_datetime,cat.speciallist FROM ( SELECT reg_id,pid,reg_datetime,catid FROM register WHERE docid=\<number> AND reg_datetime>=\<start> AND reg_datetime<=\<end> ) as reg inner join ( SELECT pid,name FROM patient ) as pat on reg.pid=pat.pid inner join ( SELECT reg_id, specialist FROM register_category ) as cat on reg.catid=cat.catid


    getIncomeInfo (start, end)
    获得起止时间分别为start和end的所有医生的收入信息
    SELECT dep.name as depname, reg.docid, doc.name as docname, cat.specialist, reg.current_reg_fount, SUM(reg.fee) as sum FROM ( SELECT * FROM register WHERE reg_datetime>=<start> AND reg_datetime<=<end> ) as reg inner join ( SELECT docid,name,depid FROM doctor ) as doc on reg.docid=doc.docid inner join( SELECT depid,name FROM department ) as dep on doc.depid=dep.depid inner join( SELECT reg_id,specialist FROM register_category ) as cat on reg.catid=cat.catid GROUP BY reg.docid, cat.specialist


    updatePatientLoginTime (number, time)
    更新编号为number病人的最近登录时间为time
    UPDATE patient SET last_login_datetime=\<time> WHERE pid=\<number>


    updateDoctorLoginTime (number, time)
    更新编号为number医生的最近登录时间为time
    UPDATE doctor SET last_login_datetime=\<time> WHERE docid=\<number>


    tryRegister
    根据所给参数尝试挂号(较为复杂,见下)



    tryRegister方法由于不仅仅是执行一个简单的任务并返回结果而显得较为复杂:这个函数是为了将上层的部分逻辑转移到下层以降低系统耦合度而产生的。其流程图如图 4所示。由于这一方法需要分为多步执行,并且需要考虑并发安全性,因此需要启动一个transaction,在图中流程中任意一个地方发生错误则直接报错并回滚,回滚后不会对数据库造成任何影响。由于需要挂号编号是单调递增且连续的,因此transaction的隔离级别设置为repeatable read(可重复读)。执行挂号的sql语句前执行的判断为当前挂号数是否超过最大挂号数,而执行之后的判断为两次对于是否更新余额的判断:一次是判断是否需要从余额扣款,一次判断是否将找零存入余额。在所有语句得以正确执行后提交transaction以持久化更改。

    由于tryRegister返回的是成功挂号时的号码,占用了错误信息的返回渠道,因此为这一方法定义了一个异常RegisterException类来返回错误信息,其中包含错误代码可以指示出错的原因。
    2.2.3 病人操作界面设计病人操作界面的设计是整个程序中最为复杂的部分,其大部分代码均为界面更改代码。界面包含4个输入/下拉框、一个滑块以及2个复选框,输入框分别用于选择科室名称、医生姓名、号种类别以及号种名称,滑动条用于选择交款金额,两个复选框用于选择是否使用余额付款以及是否将找零存入余额。此外,界面上还包含需要显示的信息,包括应缴金额、找零金额以及挂号号码、还包含挂号按钮和退出按钮用于开始执行挂号逻辑以及退出当前界面。用于搭建界面的逻辑此处不再赘述,下面主要描述核心部分较为重要的复选框过滤方法以及当挂号按钮按下后所执行的逻辑。
    由于程序需要良好的用户体验,下拉框需要同时支持鼠标输入以及键盘输入,而当一个下拉框选择了一项内容后,其余的下拉框也应该更新其可选内容以匹配当前内容,这就涉及到过滤策略的问题:如何进行过滤以及在何时进行过滤。经过一番考虑与实践后决定采用如下的过滤策略:为每一个下拉框定义一个原始列表与一个当前列表,原始列表用于存放此下拉框在没有其它限制的情况下所有的可选值,而当前列表用于存放经过过滤后下拉框的候选值。再为每一个下拉框定义2种操作:更改与提交。鼠标在候选值上滑动、键盘正在进行输入的时候下拉框为更改状态,而鼠标点击后、键盘换行键按下后下拉框显示当前列表中的某一个值的状态为提交状态。
    于是过滤策略为:进行更改操作时仅过滤当前列表并将过滤后的内容加入下拉框的候选值中,而提交操作后对于所有的当前列表重新从原始列表进行按序更新,扩展到一般情况,假设共有N个列表,在提交任意一个下拉框m后对于当前列表1~m-1, m+1~N依次进行更新,而对于列表k的更新过程为:依次使用1 ~ k-1、k+1 ~ N的下拉框的已选择值以及此下拉框选择的内容类型对于第k个列表的语义限制对第k个列表进行更新。这一操作看似是具有时间复杂度O(M^2)的,而实际上进行了一定的优化后可以仅仅在O(M)的时间内完成,其中M为平均原始列表长度(由于过程较为庞杂,此处不便于使用图形进行展示)。
    这样设计的好处在于:能够在较短的时间内对于所有的列表进行动态的过滤而不会带来自定义的更新规则或带优先级的更新规则所带来的混乱,当用户选择下拉框m时m自动成为最高优先级(这也是合理的,因为用户当前的焦点在m处),并结合之前已选的内容对于所有下拉框进行过滤,在删除复选框内容时也不会造成其它更新策略可能带来的列表内容丢失。
    在挂号按钮按下后首先进行挂号前检查:判断必要的下拉框是否已经有选择的值以及余额是否充足,然后调用数据库连接器中的tryRegister函数尝试挂号,并根据返回的结果显示相应的挂号号码或者错误信息。
    2.2.4 医生操作界面设计医生操作界面则较为简单,其以查询、统计功能为主,并且统计功能可以集成在sql语句中完成,因此代码逻辑简短。值得注意的是需要使用TreeTableColumn来存储各列的值, 并使用DateTimeFormatter对于时间格式进行转换,否则列表可能不能正常更新,时间也不能正常被数据库连接器读取。
    医生操作界面界面包含一个挂号列表、一个收入列表,均可以按照任意一栏进行排序,包含2个时间选择器以及2个日期选择器,用于过滤起止日期,在2个表中均能过滤,并有2个复选框能够快捷的选定今天或全部时间,此处的界面更新逻辑为:但两个复选框中的任意一个被按下另一个都会取消选择,并且时间选择器与日期选择器会被禁用。最后,界面上包含2个按钮,分别用于发送数据库请求并更新界面以及退出当前界面。
    三、软件开发本程序以及测试程序的编写均在Arch Linux x64系统下完成,并分别在Linux和Windows下进行了测试,具体开发以及测试环境见表 2。



    项目
    版本




    开发操作系统
    Arch Linux 2018年4月更新


    JDK (Linux)
    OpenJDK 1.8.0_172, Java 8


    集成开发环境
    IntelliJ IDEA 2018.1.3


    测试操作系统1
    Arch Linux 2018年5月更新


    JRE (Linux)
    OpenJDK Runtime Environment 1.8.0_172-b11


    数据库 (Linux)
    MariaDB 10.3.7.r77.gee5124d714e


    测试操作系统2
    Windows 10 Pro x64 Version1803


    JRE (Windows)
    Oracle Java SE Runtime 1.8.0_161-b2


    数据库 (Windows)
    MariaDB 10.3.7 x64



    四、软件测试本软件测试在Arch Linux下进行。经测试,程序在Windows下的行为和运行结果与在Arch Linux下完全相同。
    4.1 登录测试首先不启动数据库,在无数据库连接的情况下启动程序,程序直接报错退出,如图 5所示,这是预期行为,因为不能要求用户手动连接数据库,因此只能报错退出。

    在数据库运作正常的情况下,启动后的界面如图 6所示。

    然后尝试不使用用户名、密码输入错误的用户名、密码登录,程序应该提示对应的错误,知己的程序输出如图 7和图 8所示。可以看出,与预期的结果相同。下方的显示区域分别显示“请输入用户名”、“请输入密码”、“用户不存在”以及“密码错误”。














    在使用用户名003、密码003003并点击病人登录后,成功登录,登录后的界面如 所示。此处登录测试完成,进入病人操作界面测试阶段。
    4.2 病人操作界面测试进入病人操作界面后如图 9所示。其中所有的可交互控件均可同时使用鼠标和键盘进行操作。当号种类别以及号种名称被选择时,应缴金额以及找零金额会自动显示,并且找零金额会随着付款方式以及交款金额的变化而变化。当未满足挂号条件时,挂号按钮为未激活状态。

    接下来尝试进行挂号操作当余额低于应缴金额时,使用余额付款按钮为未激活状态,不可使用余额付款,否则可以选择使用余额付款或使用现金付款,当选择使用余额付款时,交款金额滑块为未激活状态以减少无效操作的可能性。正在使用现金挂号的界面以及挂号成功的界面分别如图 10以及图 11所示,在挂号成功后,底部状态栏显示了挂号成功的信息以及挂号号码。


    为了测试程序的并发安全性,使用IntelliJ IDEA同时打开两个程序进行单步调试,调试结果显示无论两个程序以何种方式穿插挂号的操作都不会发生编号冲突或缺失的情况,但有可能造成某一个客户端不能挂号成功而发生数据库回滚。在这种情况下用户需要重新挂号。而当挂号数量达到限额时也会提示挂号失败,如图 12所示,当多次点击挂号后,此号码达到了人数上限,因此在访问数据库的时候会抛出异常并禁止用户进行进一步的挂号操作。
    此外,本程序还支持将找零存入余额以供下次使用。使用另一个号码进行挂号,并勾选将找零存入余额,挂号成功后余额有所增加,如图 13所示。


    在界面的友好性方面,所有的交互控件均能够同时支持鼠标和键盘操作,一个使用键盘通过输入拼音进行下拉框过滤的例子如图 14所示。

    4.3 医生操作界面测试退出病人操作界面后进入医生操作界面,图 15展示了上面用于演示的008医生登录并查询所有挂号列表的操作界面。界面的下方为过滤器,可以自定义时间过滤,也可以使用“全部时间”和“今天”复选框进行快捷的过滤。使用这两个复选框的效果与手动设置时间的效果相同,但是免去了较为费时的选择操作。

    图 16展示了手动输入时间进行过滤的效果,显示的值为上方病人测试中最后一次挂号的信息。可以看出与病人挂号时选择与返回的信息相同。

    标签栏的另一个标签为收入列表,这一列表的过滤方式与挂号列表的过滤方式相同,只是显示的内容有所差异。

    值得注意的是,无论是挂号列表还是收入列表,各栏的相对位置都可以移动,并且可以按照任意一栏排序。图 17即为将挂号人次移动到第三栏并以之为基准进行排序的结果。

    五、特点与不足5.1 技术特点
    病人操作界面使用了效率较高的过滤算法,以多个维度进行过滤,提高了程序的可用性和便捷性
    采用了移动端的控件风格构造了一个更为人性化的操作界面
    程序考虑到了多种异常以及并发的安全问题,健壮性强,并且程序效率较高
    将程序分解为多个模块,耦合度低、可扩展性强,易于修改
    编译时将所有的依赖打入一个jar包中,不存在依赖问题,全平台通用, 图 19为本程序在Window10下的运行效果。可以看出,除了字体有些微的差别外其余并无不同


    5.2 不足和改进的建议
    由于数据库定义密码长度限制,所有的密码为明文存储,安全性较低。可以考虑将密码改为hash加密后的密码存储
    下拉框过滤算法以及医生界面的更新算法可以改为增量更新,并多加上一级cache,这样可以减少数据库操作,并提升性能
    可以添加更多的统计功能供不同角度的查询使用
    可将sql用户名密码等全局变量提取到配置文件中,避免硬编码

    六、过程和体会6.1 遇到的主要问题和解决方法在医生见面设计中发生了统计表格不能自动更新的问题,通过阅读文档发现TreeTable类需要设置CellValueFactory才能实现自动更新,遂修改程序使之得以正常运行。在病人界面的设计时遇到了下拉框过滤输入时过滤算法失效的问题,经过仔细的检查后、发现原因在于应用过滤的优先级混乱造成了下拉框备选项丢失的错误,经过一番思考后想出了上面详细设计所述的过滤算法,从而从根本上解决了这一问题。此外,IntelliJ IDEA的FXML编辑工具缺失部分功能,使得FXML文件不能指定控制类,并且容易导致IDE崩溃退出,这一问题可以使用独立的SceneBuilder解决。
    6.2 课程设计的体会通过此次课程设计,我最大的收获是学习了如何使用JavaFX搭建一个现代化的、友好的界面,以及如何让Java程序与数据库协作来完成复杂的功能。对于第一次接触JavaFX库的我而言,这是一个挑战,但是经过近一个星期的不懈努力,我深入的了解了JavaFX的运作方式,并使用其构造了一个较大的项目,在这个过程中我学到了不少有用的知识。不仅如此,在这一综合性较强的实验中我还顺带复习了有关Java的语言特性、有关数据库的知识以及许多软件工程的经验与知识,这对于今后的学习生活奠定了不小的基础。
    七、源码和说明7.1 文件清单及其功能说明文件(夹)结构及其说明如下:
    src 项目根目录├── 说明文件.txt 说明文件├── lib 第三方库文件夹│ ├── jfoenix-8.0.1.jar JFoenix第三方库│ └── mysql-connector-java-5.1.46-bin.jar MySQL第三方库├── out 编译输出文件夹│ └── hims.jar 编译输出Jar包├── src 源文件夹│ └── hims 源文件夹(包级)│ ├── Login.fxml 登录界面FXML文件│ ├── Doctor.fxml 医生界面FXML文件│ ├── Patient.fxml 病人界面FXML文件│ ├── LoginController.java 登录界面控制类源码│ ├── PatientController.java 病人界面控制类源码│ ├── DoctorController.java 医生界面控制类源码│ ├── DBConnector.java 数据库连接器源码│ ├── Config.java 配置文件│ ├── Main.java 主函数所在类源码│ └── Main.css 界面使用的层叠样式表文件└── sample_data.sql 数据库样例数据文件7.2 用户使用说明书要使用本程序,须按照下列步骤进行环境搭建:

    前提条件

    操作系统:Windows/Linux/OS XJava 运行环境:Jre 1.8.0_161 或其兼容版本数据库:MariaDB 10.3.7或其兼容版本或MySQL对应的兼容版本
    样例数据导入

    启动数据库服务使用root登录进入数据库,创建名为java_lab2的数据库CREATE DATABASE java_lab2;
    创建名为java的用户,设置其密码为javajava,并授予其在java_lab2上的所有权限GRANT ALL ON java_lab2.* TO 'java'@'localhost' IDENTIFIED BY 'javajava';FLUSH PREVILEGES;
    使用java用户登录java_lab2数据库,并运行source sample_data.sql来初始化数据由于数据库名称、用户名以及密码均硬编码在程序中,因此上述信息不可更改
    使启动数据库服务维持启动状态
    运行out文件夹下的hims.jar,可以在命令行中运行java -jar hims.jar,也可直接在文件管理器中点击(需要java在PATH环境变量中并正确的设置MIME TYPE)
    5 评论 234 下载 2019-03-10 13:54:00 下载需要14点积分
  • 基于WinPcap的网络包截获和分析系统

    前言1,基于WinPcap的网络包截获和分析系统2,需要安装winpcap包,WinPcap中文技术文档 http://www.ferrisxu.com/WinPcap/html/main.html3,配置winpcap编程环境(VC6.0 或者VS2008)可参见 开发文档或者 google “vc++ winpcap配置”4,程序使用的皮肤库为 skin#
    可执行程序Npcas.exe在Release 目录下(程序运行在windows平台下),使用程序前需要安装winpcap。
    摘要​ 随着计算机网络技术的飞速发展,网络为社会经济做出越来越多的贡献,可以说计算机网络的发展已经成为现代社会进步的一个重要标志。但同时,计算机犯罪、黑客攻击、病毒入侵等恶性事件也频频发生。网络数据包捕获、监听与分析技术是网络安全维护的一个基本技术同时也是网络入侵的核心手段。通过基于网络数据包的截获和协议分析,对网络上传输的数据包进行捕获,可以获取网络上传输的非法信息,对维护网络安全起到重大作用。
    ​ 本论文依次介绍了网络包截获和分析的研究背景和意义、WinPcap技术介绍及系统开发坏境搭建、系统开发的理论基础、系统的分析与设计、系统的实现方法、系统的测试等。
    ​ 系统提供了网络数据包的捕获、网络协议分析、包捕获过滤设置等功能模块。最终利用VC++实现了基于WinPcap的包截获和分析系统,该系统可以捕获链路层的数据包如Ethernet II,网络层的数据包如IP、ARP / RARP、ICMP,传输层的数据包如TCP、UDP,应用层的数据包如HTTP、DNS。此外系统可以对网络数据包进行细粒度的分析和原始数据包的再现。
    1 系统分析与设计1.1 系统需求分析
    支持多种网络接口的包捕获(以太网,令牌环网,ATM…);及时有效的捕获网络上传送的数据包,高效的处理捕获到得数据包并友好的显示给用户,让用户捕获到他想要的数据;能够保存实时捕获到的网络数据包,供以后系统进一步参考和考证;可以设置过滤规则,减少捕捉的包的容量;可以捕获多种协议的网络数据包,并且软件系统能够提供和用户交流的平台,达到和用户互动的效果,并能够及时改正软件系统的不足之处;良好的图形界面,让用户用起来方便和良好的视觉体验;
    1.2 包截获与分析的基本过程系统基本流程概括如下:
    ① 选择网络接口,首先获得主机的网络设备列表,然后用户通过列表选择自己需要截获哪个网络接口上的网络数据包。
    ② 编译、设置过滤规则,用户如果想捕获特定的网络数据包,就需要设置过滤规则,过滤规则其实就是一个字符串,在WinPcap中使用函数pcap_compole()和pcap_setfilter()分别编译和设置过滤规则。
    ③ 开始捕获网络数据包,循环捕获网络数据包。
    ④ 保存捕获到的数据,根据用户要求实时保存截获的网络数据包,方便进一步的考证。
    ⑤ 处理捕获到的网络数据包,对捕获的网络数据包进行分解处理,从链路层到应用层,层层分解处理。
    ⑥ 把第5步中层层分解得到的数据包信息显示到软件界面,并且根据用户的一些要求细化所截到数据包信息的显示。
    本系统的包截获和分析的基本过程简化为下图所示。

    1.3 系统功能模块设计​ 此系统主要包括六大模块,如图:

    网络包截获模块:
    ​ 用一个线程来捕获网络数据包和协议分析。此模块主要是完成了网络接口的选择,编译过滤规则并设置过滤规则和网络数据包的实时捕获,并且实时保存截获到的网络数据包(注:此时主要把截获到的网路数据包保存到临时的缓冲文件中temp.pcap)。
    网络数据包分析模块:
    ​ 在这一模块中主要对上一模块中捕获的网络数据包进行协议分析。由于TCP/IP采用分层的结构,这样在传输数据的时候,在网络接收端是分解的过程,而发送端是一个封装的过程。
    要发送一个数据包,数据包必须进行层层封装,最终形成以太网数据最终发送到网络物理介质上进行传送。当接收数据包是与发送相反。通过封装,分解,并根据报文的协议字段或标识字段进行协议的分析,最终能够分析以太网Ⅱ、IP,ARP/RARP、ICMP、TCP、UDP、HTTP、DNS等协议。
    截获的数据包保存、打开模块:
    ​ 在这个模块中主要完成对实时截获到的网络数据包进行保存,其根据用户需要把文件保存到具体什么位置,然后保存此文件。打开文件模块主要是创建一个文件打开线程,对脱机文件进行网络协议的分析,并把分析结果准确的显示给用户。
    过滤规则设置模块:
    ​ 过滤规则设置模块主要在系统工作前的过滤规则设置,此模块可以记录一些常用的过滤规则名和过滤语句,并且应该能够编辑和删除相应的过滤规则。当用户选定某一常用过滤规则或者填写自己的过滤语句后单击”开始”按钮后,系统能及时反映此过滤规则是否符合过滤语句语法。
    网络数据包显示模块:
    ​ 系统截获网络数据包是为了显示截获的数据,此模块能够高效及时显示所截获的网络数据包,并能够通过列表,树图,编辑框等多种形式显示给用户,让用户更直观的了解捕获到的网络数据包。当用户选择其要查看的网络信息的某条信息时能够对词条信息进行详细显示。
    软件界面设计模块:
    ​ 好的软件系统不仅有较好的功能,并且需要有友好的界面,和不同的界面显示给用户,用户根据自己的喜好可以更改自己的软件界面。
    1.4 系统的配置设计由于系统要用到一些动态链接库和皮肤库,和过滤规则库,因此能够设计好良好的系统配置信息是必须的。此系统将把所有需要的动态链接库和配置文件放在config目录下,而所需要的系统皮肤文件放在skin目录下。系统启动之前必须具备这两个目录下的文件。否则将达不到预期的系统效果。
    2 系统实现2.1 模块功能介绍本系统是基于WinPcap的网络包截获与分析系统。根据课题设计需要实现的功能如下:
    ① 网络包截获模块。可以截获经过此主机的网络数据包。
    ② 网络协议分析。此模块可以对不同层的网络协议的分析。
    ③ 过滤规则设置模块。此模块可以设置常用规则并且可以直接设置规则进行相应的包截获过滤语句的设置。
    ④ 网络数据包显示模块。此模块可以有多种方法层次化的显示所截获的网络数据包,包括列表形式的显示,树形显示,编辑框显示等,让用户直观的获得所截获的网络数据包协议信息。
    ⑤ 能够实时保存捕获到的网络数据,并且用户可以选择保存到自己想保存的目录下。此外用户可以根据需要打开以前截获到的.pcap文件,实现数据分析和取证。
    ⑥ 软件界面设计模块,此模块能够满足用户换肤,程序托盘,有一个比较好的图形界面接口呈现给用户。
    2.2 功能实现方法在这一小节中,介绍了系统的主要的功能模块和其实现方法的大致描述,详细内容请见本系统的源代码,系统源代码已经做了比较详细的注解。
    2.2.1 系统基本要素此系统无需安装,只要系统必需要素具备即可运行。其中config目录下有filter.ini配置文件及SkinH.dll动态链接库。skin目录下为系统所需的皮肤文件。
    系统基本要素如图所示。

    2.2.2 系统启动界面系统启动时动画界面显示启动界面。

    实现方法:
    方法其实比较简单,创建一个兼容的内存DC放置位图,把启动画面分成一个大网格,一个20*20的网格,每个网格内存放分割的位图,并用数组记录已显示过的位图。何时显示哪个小网格中的图片则采用随机数(两个1 – 20
    的随机数,这样就可以用二维数组来记录是否显示过此位图),所以感觉是动画的。
    2.2.3 系统换肤和良好的图形界面
    上图 所示这是系统的工作界面。此系统可以根据自己的喜好实现系统皮肤的切换。点击系统右上方的图标即可实现系统的换肤,如下图所示。

    实现方法:
    实现由美丽的程序界面和换肤的功能其实也不是太复杂。主要借助第三方库,此系统借用的是比较好用的skin#库,下载此库的.dll文件和你喜好的皮肤库。
    此系统我们采用程序运行时动态链接的方法加载皮肤库。
    /*宏定义函数指针类型 */typedef int ( WINAPI *SKINH_ATTACHEX)(LPCTSTR strSkinFile,LPCTSTR strPassword);// 取得SKINH_ATTACHEX函数的地址SKINH_ATTACHEX pSkinFun=(SKINH_ATTACHEX)::GetProcAddress(LoadLibrary("config\\SkinH.dll"),"SkinH_AttachEx");
    2.2.4 过滤规则设置模块 在开始捕获数据包前可以根据自己的需要来设置自己想要的数据包过滤语句,单击过滤规则模块如下图所示。可以根据自己的需要添加和删除自己的过滤规则。

    实现方法:
    先定义一个关于过滤规则的结构体如下:
    typedef struct FILETER_ { char FilterName[256]; // 过滤规则名称 char FilterValue[256]; // 过滤规则语句表达式 // 重载赋值运算符 const FILETER_ & operator=( const FILETER_ temp ) { ZeroMemory(FilterName,256); ZeroMemory(FilterValue,256); strcpy(FilterValue,temp.FilterValue); strcpy(FilterName,temp.FilterName); return *this; } }FILETER_DATA ,*PFILETER_DATA;
    然后定义一个此结构体类型的向量typedef vector<FILETER_DATA> Filter_Vector。
    当用户设置过滤规则时,通过LoadUserFilter()函数加载用户自己的过滤规则库,其中此函数为自己实现的,为从一个我们之前定义的配置文件中读取常用规则并写入到向量Filter_Vector 中,并显示在我们的窗口上。用户可以添加和删除常用规则,此时同时反映在Filter_Vector中,当用户退出过滤规则设置时对话框时系统将同时读取到过滤语句,并且通过我们自定义的SaveUserFilter()把我们的过滤规则存回配置文件.ini中。配置文件的功能主要用到win API GetPrivateProfileString 和WritePrivateProfileString。
    帮助模块的功能实现方法比较简单,实现连接到网站的功能主要利用到了Win API ShellExecute函数实现,其它的实现方法不再敷述。
    2.2.5 截获的包保存和文件打开模块此模块能够实时保存捕获到的网络数据,并且用户可以选择保存到自己想保存的目录下。此外用户可以根据需要打开以前截获到的.pcap文件,实现数据分析和取证。
    效果如下图所示。


    实现方法:
    保存文件:首先就是在截获网络数据包时,把截获到的网络数据包实时的保存到临时的缓冲文件temp.pcap中,然后根据用户是否保存文件,若用户选择保存此文件到一个指定的路径下,则系统创建一个CFileDialog文件对话框,获得用户指定的路径及文件名,然后把保存的缓冲临时文件拷贝到用户指定的目录下,这个主要利用函数CopyFile()来实现。这样即可实现文件的保存。
    打开文件:它的实现方法主要是创建一个文件读取线程ThreadReadFile(),这个线程中主要函数为DumpFileOperation(),在这个函数中首先创建一个CFileDialog文件对话框,然后根据用户的选择的文件利用GetFileName()函数获得文件路径及名字,然后就是根据WinPcap的编程模型的一些具体的常见步骤,最后pcap_loop()读取真正的网络数据文件,在这个pcap_loop()函数里我们定义了ProcessProtocolPacketFromDumpFile()这个回调函数。这个回调函数里包括原始的数据包,并且实现了对其的解析。详见下一小节的数据包分析功能模块。
    2.2.6 包截获和分析功能模块系统使用多线程技术,来实现捕获数据包和数据包的显示同步进行,提高系统的性能。
    此模块主要实现了网络数据包的截获和分析。过滤规则设置后,单击系统的开始按钮选择所需监听的网卡。如下图所示,系统把所截获的信息实时的显示给用户,用户可以根据自己的需要查看相应的协议数据信息。

    实现方法:
    数据包的捕获主要采用WinPcap编程技术实现的,然后对捕获到的数据包进行各种协议的分析,把分析的网络信息显示在主程序界面中。本模块主要采用多线程技术,创建一个线程用来捕获数据包和协议分析,然后把协议分析结果传送给网络数据包显示模块。
    ​ 其中文件Protocol.h中主要定义了相关协议的数据结构,文件sniffer.h定义了捕获线程和分析数据包所用到的自定义的原始数据包结构。此功能模块主要的核心功能函数的调用过程如下图所示。

    在网络数据包协议解析和数据包的信息显示中有一个比较重要的数据结构如下:
    //程序内部保存的数据包结构,即原始数据typedef struct{ pcap_pkthdr PktHeader; //包头部信息结构指针 u_char* pPktData; //包数据指针 u_int ip_seq; //网络层截获的包Ip序号 u_int tcpOrUdp_seq;//传输层截获的包序号} RAW_PACKET;
    这个RAW_PACKET结构记录了所捕获的网络数据包的原始数据以及它的网络层、传输层的包序号(即在列表框中已经解析的协议),这样当再次解析这个原始数据包时就可以直接调用之前已经解析过的信息,这样可以达到功能复用的效果,提高程序的效率。
    下面具体介绍包截获和分析线程的具体实现方法:
    首先创建一个包捕获线程:UINT ThreadPacketCapture(LPVOID pParam);
    在这个函数里我们定义一个包捕获函数CapturePacket(),在这个函数里我们按照WinPcap捕获数据包的方法进行编程(详细请见系统源码,在此不再敷述),在CapturePacket()函数中比较实质的网络数据包捕获函数就是pcap_loop()函数,它实现了循环捕获网络数据包。int pcap_loop(pcap_t p, int cnt, pcap_handlercallback, u_char user);
    这个函数参数p表示WinPcap句柄,cnt表示捕获的个数,负数表示无限捕获,参数callback表示回调函数(注意着个函数很重要,稍后再介绍),user表示传递给回调函数的参数,如果无就无NULL,回调函数我们定义为typedefvoid (pcap_func_t)(unsigned char , const struct pcap_pkthdr, const u_char)这样的类型,第一个参数就是从pcap_loop中传进来的用户参数,第二个参数就是捕获到的网络数据包的简单信息,是由WinPcap设定的,有数据结构pcap_pkthdr表示,第三个参数表示捕获到的真正的网络数据包,它存储了网络数据包的原始数据,后面所有的分析其实就是对这个原始数据包进行分析。
    ​ 在包截获函数CapturePacket()中我们定义一个pcap_handler类型的句柄变量Handler,我们把这个变量赋值为我们自定义的pcap_loop()的回调函数的指针并调用它如下代码所示:
    /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ Handler = (pcap_func_t)ProcessProtocolPacket; /* 开始捕获 */ pcap_loop(PcapHandle, number, Handler, (unsigned char *)PcapFile);
    在回调函数ProcessProtocolPacket(unsigned char user,const struct pcap_pkthdr h,const unsigned char* packetdata)中我们把截获到的数据包(即此函数第2、3个参数所包含的数据)保存到在堆中申请的内存中(此时用用到自定义的数据结构RAW_PACKET中),此时我们就有了捕获到的这个数据包的简单信息和存放自定义数据结构信息在堆的指针,然后调用ParseEthernet(packetdata, pRawPacket)函数分析截获到的以太帧数据,在函数ParseEthernet()中可以进行IP,ARP等网络层的协议分析,依次类推我们可以根据本层的协议字段来进一步分析传输层、应用层的协议信息。具体请见sniffer.cpp文件中的线程函数和数据包分析函数。
    4.2.7 数据包分析结果显示功能模块本系统采用三种形式把数据显示给用户,首先用户可以根据自己的需要点击Tab页的网络协议按钮,查看各个网络层次的网络协议。相关的指定的协议信息在列表框中分条显示出来。其次用户可以在列表框框中选择自己需要查看的某条网络数据包的详细信息,鼠标单击此条即可查看此条数据包的相对完整信息,即从链路层的网络信息到你所查看的协议层的信息或者全部的链路层包括所负载的网络信息。用户可以单击右键复制你所需要的某一列的信息。最后用户可以单击左下角树图控件的节点查看比较具体协议信息的解析。用户可以鼠标双击树节点,这样可以在右下角的编辑控件中的十六进制字码中查看原始的网络数据包(分别是相应的十六进制和可显示字符),此时的原始数据包在编辑框中被选中,这样方便用户进行网络协议的分析。如下图所示。

    实现方法:
    在分析网络协议信息时要把协议信息实时的显示到程序的界面。我们用自定义消息来实现,一共定义了有关协议显示的9条消息。当捕获到数据包后就对其进行相应的协议分析,然后通过发送自定义的消息通知程序界面模块显示分析后的协议数据。自定义消息如下:
    #define WM_MY_MESSAGE_COMMON (WM_USER+100) //显示数据包一些常用的协议消息#define WM_MY_MESSAGE_ARP (WM_USER+101) //显示ARP/RARP协议信息消息#define WM_MY_MESSAGE_IP (WM_USER+102) //显示IP协议信息消息#define WM_MY_MESSAGE_TCP (WM_USER+103) //显示TCP协议信息消息#define WM_MY_MESSAGE_UDP (WM_USER+104) //显示UDP协议信息消息#define WM_MY_MESSAGE_ICMP (WM_USER+105) //显示ICMP协议信息消息#define WM_MY_MESSAGE_ETHERNET (WM_USER+106)//显示以太网信息消息#define WM_MY_MESSAGE_HTTP (WM_USER+107) //显示HTTP协议信息消息#define WM_MY_MESSAGE_DNS (WM_USER+108) //显示DNS协议信息消息
    这些自定义的消息有自己自定应的函数,比如WM_MY_MESSAGE_ETHERNET对应的消息自定义函数消息映射ON_MESSAGE(WM_MY_MESSAGE_ETHERNET, OnEthernet)可以知道当操作系统捕获到WM_MY_MESSAGE_ETHERNET消息就会调用OnEthernet函数在对应的Tab页(即以太网信息Tab也)中的列表框中显示详细的协议解析信息。其它的协议显示过程和此同理。
    在协议分析过程中,以太网协议分析是第一步。首先分析以太网协议部分,然后根据以太网中的协议字段再分析其它的协议数据。比如在2.2.6小节中ParseEthernet(packetdata, pRawPacket)函数,首先强制转化指向自定义的数据结构指针的参数pRawPacket,LPARAM lp=(LPARAM)pRawPacket(这样为了PostMessage),此时向系统发送消息,表示截获到以太帧,::PostMessage(g_hWnd, WM_MY_MESSAGE_ETHERNET, 0, lp),其中g_hWnd为系统主界面窗口句柄,他将捕获到的以太消息放入到与主界面线程相联系的消息队列里,用来显示其协议信息。参数lp即是指向堆中的原始网络数据包指针,它在显示函数中会用API SetItemData()添加附加据,即将列表项与存放原始数据包的堆地址指针关联,这样为了方便以后的处理某一列显示在树控件和编辑框中所用。
    ​ 此外当系统分析到网络层、传输层,在显示函数如OnIp()、OnTcp ()、OnUdp(),中修改pRawPacket所指结构RAW_PACKET中的ip_seq,tcpOrUdp_seq的序号(它们即是这个pRawPacket所指原始数据包中对应的数据包的网络层、传输层在对应Tab中的某一行的序号),这样当再使用这个原始数据包时,可以通过对应协议的Tab页中的列表框直接使用已经解析过的数据包结果,可以提高程序的效率达到复用的效果。
    ​ 当用户单击列表框的某一行时,系统就处理相应的NM_CLICK消息事件,在处理鼠标单击事件的函数中调用显示数据包信息的函数比如ShowPacketInfo(RAW_PACKET* pRawPacket)、ShowIpInfo(int nItem)、ShowTcpInfo(int nItem)、ShowUdpInfo(intnItem)等来进一步详细的显示包信息。
    ​ 其中ShowPacketInfo()就是通过我们关联到列表框每一行的原始数据来进行树控件中MAC的显示,以及右下角编辑框中的原始数据的显示,ShowIpInfo()、ShowTcpInfo()、ShowUdpInfo()函数则利用我们定义的RAW_PACKET结构中的ip_seq,tcpOrUdp_seq序号来直接读取列表框中的关于此数据包中网络层或者传输层的协议分析结果,把从列表框中读取的信息显示到左下角的树控件中去,这样做就可以把列表框中某一行的数据包信息更精细的显示到树控件和编辑框中。
    参考文献:http://www.ferrisxu.com/WinPcap/html/main.html
    [1] The WinPcap Team .WinPcap中文技术文档. Torino:CACETechnologies,Translated By CoffeeCat Studio 2007.7
    [2] 郭军. 网络管理(第3版).北京:北京邮电大学出版社,2008.3
    [3] 武孟军、等. Visual C++开发基于SNMP网络管理软件(第2版)..北京:人民邮电出版社,
    2009.4
    [4] 王艳平. Windows 网络与通信程序设计.北京:人民邮电出版社,2009.4
    [5] 侯俊杰. win32多线程编程.武汉. 华中科技大学出版社,2002.1
    [6] 辛长安.VisualC++权威剖析—MFC原理、机制与开发实例.清华大学出版社, 2008-4-25
    [7] 胡滨.基于Windows平台的底层数据包截获技术[J].计算机工程与设计.2005(11):179-180,223
    [8] 谢希仁.计算机网络(第五版).北京:电子工业出版社,2008.1
    1 评论 25 下载 2019-05-17 11:23:47 下载需要9点积分
  • 基于JAVA和SQL SERVER数据库实现的酒店管理系统

    1 设计目标就数据库学习应用于生活,以简单数据库应用为例,本小组以制作“酒店管理系统”为目标。该管理系统已经能实现一些酒店住房管理的一些基本功能,适用于中小型宾馆使用及访问,旨在用计算机系统为操作人员提供更方便的操作,并保持很高的灵活性和易操作性,该软件具备以下特点:

    易学易用,操作简便,它是基于Java的应用程序,操作界面友好直观
    功能完善,本系统包括前台经营和后台管理,功能完善,能够实现酒店的数字化经营
    开放型好,采用标准的开发工具和技术,后台数据库采用微软SQL 2008中文版,可以提供开放的数据接口,可同其他软件交流数据
    较为完善的会员机制
    功能完善,分为 4 个主要模块,分别为:查询房间状态、加入会员、宾客入住、结账

    查询房间状态,该功能可以查询当前的房状态,查询已入住和未入住的信息,并且很好的保存了用户的隐私,实现了连接SQL Server 2008 R2数据库从所建的表中查询功能
    加入会员模块:该功能可以实现连接SQL Server 2008 R2数据库从所建的表中插入、修改功能。并且设计会员打折模块(未实现)
    来宾入住模块:该功能实现了和现实生活一样的模式,登记入住信息,连接SQL Server 2008 R2数据库从所建的表中更新功能,把来宾的个人信息插到用来存放数据看guess表中
    结账模块:该功能实现了从数据库中查询自己的入住信息,和点击结账之后的数据更新功能,结账之后更新数据库的guess表,把入住状态设为未入住等


    2 功能设计
    3 数据库设计关于数据库设计方面,我们做的不是很完善,比如设计的这个数据库,有很多的函数依赖都没有消除,主码外码等定义也不是特别完美。名为Hotel的数据库主要分为三个表,分别是:guess,vip,operater.

    其中,guess主要用来存储来访信息和用户住房登录信息的,它的构造如下图:

    数据库主要的实现查询更新插入等功能便是通过这张表。
    Vip表是用于存储用户加入会员的,而operaer则是用于登录的时候存储管理员的信息的。其中guess表是整个数据库乃至整个程序的核心部分,用的java语言只是用来进行流程控制。整个程序通过JDBC操作也主要是这个。虽然有很多不足的地方,但是我们相信,我们是可以慢慢完善的。
    4 系统实现4.1 模块一登录界面模块:通过使用Java swing组件编写的比较友好的登录界面,同时也包括了通过JDBC连接数据库查询管理员的信息,数据库中的管理员表,正确输入密码和用户名才能尽进行登录。

    4.2 模块二使用主界面,界面美观漂亮,可以通过底部的按钮实现相应的功能。

    4.3 模块三查询模块:这个模块可以查询到当前的入住信息,当前的房间状态,而且很好的保护了客户的隐私,是通过JDBC连接数据库的方式得到数据之后插入一张二维表来显示的。可以一目了然的查看当前的空房,并且在允许的情况下让用户自主选择要入住的房型和房间号.

    4.4 模块四会员模块:这个模块可以让使用者加入会员,实现优惠功能,虽然还未实现打折功能,但是相信,只要时间允许,这个打折功能是可以实现的。这个功能是通过数据库的插入操作实现的。是把所有的方法封装在一个数据访问层DAO中,它封装了所有程序中要通过JDBC操作数据库的方法,包括查询,更新,修改,删除。

    4.5 模块五来宾入住信心录入模块。该模块参照了现实生活中的宾馆入住实例,通过把宾客的信息录入到数据库的指定的表中,并且保存起来,之后还可以通过结账模块的查询功能来表现。也是通过JDBC连接数据库实用”UPDATE 表名 姓名 所选的房间等 ”,通过调用DAO(数据访问层)的更新方法来实现,既可以查询,又可以更新:

    4.6 模块六结账模块。该模块实现了查询和更新一体的操作,是通过注册按钮事件来执行数据访问层DAO里的数据查询和数据更新操作.并弹出对话框”您需要付款¥xxx”,并且在弹出的同时,通过JDBC调用DAO层的更新方法来实现重置房间状态。既是用户活动的结束,也是流程的结束。

    也可以更新:

    结账的时候更新数据,设置房间为空。
    5 分析与结论这一次的课程设计给了我们莫大的好处,让我们更熟悉了数据库和高级语言的关联性,又能做到学以致用。在这个课程设计之前,我们都很迷茫,不会学以致用,只是知道跟着老师的步伐学习。通过这次的课程设计,我们还学会了自主学习的方法,明白了怎么样才是正确的学习方法。而不是一味的乱撞,我们通过做这个项目,还参考了很多课外教科书,查找了很多的资料。通过对一个问题的实际分析,良好的综合的运用了所学的知识,既学到了知识,巩固了基础,让我们更深入的学会如何将课本上所学的知识运用到实际上。
    虽然我们做的这个小小的项目不是很完善,也不是很好,还有很多需要改进的地方,很多的bug需要去修复。在这之前,我们成天都是跟着课本上整天加加减减,没什么感觉。通过这个课程设计,有了一点成就感,也大大加深了我们努力钻研的精神,终于可以学以致用了。我们相信在以后我们会更加努力的去探讨知识,开发出更好的软件!
    参考文献【1】王珊 萨师煊第四版 数据库系统概论
    【2】王珊 萨师煊第五版 数据库系统概论
    【3】java语言程序设计
    【4】java编程思想
    【5】清华大学出版社 主编 徐琳等 java程序设计专家门诊
    【6】UML统一建模教程
    10 评论 570 下载 2018-11-06 14:36:13 下载需要8点积分
  • 基于javaEE实现的在线音乐系统

    1、概述
    开发环境

    Windows10EclipseTomcat 9.0Mysql 8JDK 10
    运行环境

    Tomcat 9.0Mysql 8Chrome 71

    2、系统的需求分析2.1 系统可行性分析本系统是采用Java Web技术的B-S架构的网站,用到的技术有Java EE 、CSS、JavaScript、Ajax。为了使用的效果以及开发的简便,在前端使用了CSS的Bootstrap及其UI组件框架,JS的Jquery库以及Ajax技术。在线音乐是获取的外部站点的API提供查询服务,并且每一次播放在线音乐会将其数据保存到本地数据库。本地音乐是从本地数据库中获取数据展现到用户界面。
    2.2 系统功能描述2.2.1 用户管理
    用户注册:用户可以点击注册按钮进入注册界面,注册属于自己的账号
    用户登录:用户使用自己的账号登录系统
    用户设置:可点击个人设置,修改个人信息,例如昵称,头像等

    2.2.2 播放界面
    音乐播放[播放与暂停]:对在播放器中的音乐进行播放和暂停
    音量调节:对正在播放器中的音乐的音量大小进行调节

    2.2.3 歌单与音乐管理
    歌单管理:对歌单的增删查改
    音乐管理:将音乐添加进歌单,将音乐从歌单中删除

    2.2.4 音乐搜索
    在线搜索:在网络上搜索获取音乐资源
    系统乐库:获取系统所在服务器上的音乐资源

    2.3 系统的数据流图
    2.4 系统UML建模设计
    2.5 系统的状态图登陆模块状态图

    登陆模块状态图

    2.6 系统的UML类图本系统后台总共使用了四个包,其功能与关系如下。

    utils:封装了连接数据库和关闭数据库的操作
    dto:包含封装了某个对象的所有信息的类
    dao**:封装了对数据库的操作
    servlets:包含处理前端发送的各种信息的servlet

    以上四个包都位于cn.edu.whpu.music包下,它们的关系如下图所示。

    3、系统总体设计3.1 系统结构方框图
    3.2 各模块功能用户信息模块
    包含功能有用户的注册,用户的登录,用户个人信息的修改和用户个人信息的展示。
    搜索音乐模块
    包含功能有在线音乐搜索,本地乐库搜索。
    歌单管理模块
    包含功能有添加新的歌单,修改歌单信息,删除歌单。
    收藏歌曲模块
    包括添加音乐到指定歌单,从歌单中删除音乐。
    3.3 详细的UML类图用户信息模块


    DBManager:封装了连接数据库和关闭数据库的操作
    UserDTO:封装了用户个人信息的类
    UserDAO:封装了操作用户个人信息的类
    UserRegistServlet:用户注册的servlet
    UserLoginServlet:用户登录的servlet
    EditUserInfoServlet:修改用户个人信息的servlet

    搜索音乐模块(本地乐库)


    DBManager:封装了连接数据库和关闭数据库的操作
    MusicDTO:封装了音乐信息的类
    MusicDAO:封装了操作音乐信息的类
    LocalMusicServlet:处理点击本地乐库按钮后的请求并返回数据的servlet

    注:在线音乐搜索是用ajax请求的网络上的API接口获取数据并渲染到主页面,与该处的servlet无关。
    歌单管理模块


    DBManager:封装了连接数据库和关闭数据库的操作
    MusicListDTO:封装了歌单信息的类
    MusicListDAO:封装了操作歌单信息的类
    AddMusicListServlet:添加歌单信息的servlet
    InitMusicListServlet:通过用户ID查询并返回其对应的歌单的servlet
    DeleteMusicListServlet:删除歌单的servlet
    UpdateMusicListServlet:更新歌单的servlet
    ShowMusicsFromListServlet:通过用户ID和歌单ID查询并返回其对应的歌单的servlet

    收藏歌曲模块


    DBManager:封装了连接数据库和关闭数据库的操作
    MusicDTO:封装了音乐信息的类
    MusicDAO:封装了操作音乐信息的类
    ListMusicRelationDTO:封装了音乐与歌单对应关系信息的类
    ListMusicRelationDAO:封装了操作音乐与歌单对应关系信息的类
    CollectionMusicServlet:收藏音乐进入指定歌单的servlet
    CancelCollectMusicServlet:从歌单取消某音乐收藏的servlet

    3.4 设计数据管理子系统因为使用java编写的并发量不是特别大的后台程序,所以使用了更为轻量级的mysql 8。其好处有如下几点:

    普及性:MySQL在过去两年已经获得了25%的市场份额。相比其他的开源数据库和闭源数据库,越来越多的开发者将继续选择MySQL。MySQL在业界的流行所带来的另一个好处是,人们总可以很轻松地发现本行业的解决方案
    简单性:对于MySQL数据库,无论是在开发方面,还是支持方面,现在有大量强大的工具可以选择。每一个新手开发者可以轻松地使用MySQL数据库进行开发。甚至一个有经验的Windows管理者也可以轻松部署并开始学习它,不需投入一分钱来了解这个数据库
    低成本:MySQL数据库归MySQL AB公司所有,但是这个软件是开源的,有一个社区版可以免费下载。稍俱常识的新入门者都可以轻松实现在一个常见硬件上安装和配置MySQL。MySQL对硬件的较低要求是其最大的优势之一,不过需要注意的是:内存越多越好,因为所有的重要数据存储都在内存中完成。一个免费的数据库意味着,更多珍贵的资金可以用于其他业务的启动,诸如市场、广告或调研和开发等

    3.5 系统E-R图
    系统E-R图如上图所示。主要逻辑有:

    一个用户在某一时刻只能播放一首歌曲
    一个用户可以创建n个歌单
    一个音乐可以被n个歌单收藏,一个歌单也能收藏n首音乐

    3.6 数据库表
    本系统该次设计了四张表:

    tb_users表记录用户信息
    tb_musiclists表
    tb_list_music表
    tb_musics表

    3.7 数据库表之间的关系关系1
    tb_users表和tb_musiclists表是一对多的关系。其中tb_musiclists中表的list_uid字段必须遵照tb_users表中的user_id。即一个用户可以有多个歌单,而一个歌单只能由一个用户。

    关系2
    tb_musiclists表和tb_musics表是多对多的关系,并且tb_list_music表记录了它们之间的对应关系。tb_list_music表中lid参照tb_musiclists中的list_id字段,tb_list_music表中mid参照tb_musics中的music_id字段。并且lid与mid作为该表的联合主键。即一个歌单可以收藏多首音乐,一个音乐也能被多个歌单收藏。

    3.8 数据库表结构tb_users表

    tb_musics表

    tb_musiclists表

    tb_list_music表

    3.9 设计人机交互子系统3.9.1 登陆界面登录界面引用了jquery.video库,将登录的背景设置为一个动态的循环播放的视频,极具科技感和新鲜感。
    3.9.2 注册界面注册界面沿袭登陆界面的大体设计,并在注册信息上使用了jquery.validate库对于用户输入注册所需要的信息进行了初步的判断,避免录入冗余、错误的信息。
    规则有:

    用户名:必须输入,长度为6-20个字符
    密码:必须输入,长度为8-16个字符
    重复密码:必须输入,必须与密码一致
    用户昵称:必须输入,长度为20字符以内
    性别:必须选择
    头像:必须上传

    3.9.3 主界面整体布局
    使用一个界面避免用户进行跳转。采用bootstrap框架的栅栏式布局。大致布局如下图所示。

    顶部导航栏
    整体采用bootstrap的导航栏样式,如下图所示。左侧依次为Logo区,在线搜索区和本地乐库区。当在在线搜索的输入框中输入内容,并点击搜索按钮后,会在主界面呈现出搜索内容。当点击本地乐库按钮后,会在主界面呈现出系统自带的音乐列表。

    右侧为用户栏,点击歌单按钮会弹出“添加歌单”、“管理歌单”功能按钮。点击“添加歌单”按钮会弹出模态框。点击管理歌单会在歌单栏出现编辑和删除按钮。点击消息按钮会弹出“查看消息”功能按钮。点击用户按钮会弹出“修改信息”和“退出”功能按钮。点击修改信息会弹出修改用户信息的模态框。详情在模态框中介绍。
    主区域

    主界面大致分为左、中、右三个区域。按照1:2:1的分配区域。在点击导航栏中的“管理歌单按钮后”,歌单区域进入编辑模式。点击主区域的播放按钮和收藏按钮,歌单区的编辑和删除,用户区的头像均会弹出模态框。详情在模态框中介绍。
    唱片CD会在歌曲播放的时候旋转如下图所示。

    播放器区域
    播放器方面我们开发小组认为原生的AUDIO标签还有市面上找到的播放器插件不契合我们的样式,于是从5sing音乐网站的播放器部分借鉴了样式,并为静态的样式添加了拖动歌曲进度,实时显示已经播放时间,和调节音量大小的动态效果。其主要布局和功能如下图所示。

    模态框
    为了使界面不发生跳转,我们将主界面中所有的操作运用了bootstrap的模态框UI组件。在点击“歌单➡添加歌单”,“用户➡修改信息”,“歌曲➡收藏按钮”,“用户➡头像”,“歌单列表➡修改”,“歌单列表➡删除”时均会弹出相应的模态框组件。


    整体效果

    4、详细设计4.1 登陆注册模块流程图
    4.2 在线搜索模块流程图
    4.3 本地乐库模块流程图
    4.4 歌单管理模块流程图
    5、总结通过为期五天的课设,我收获颇多。最直观的感受就是在这次在线音乐网站的开发中能够系统性的运用从大一到现在所学到的知识。新学习到的Servlet的知识更是让我明白了为何之前写过的html只能叫做静态页面,还有一个网站的前端是如何与后台进行数据交互。同时这次也让我从平时的文件级别的代码编写上升到了工程级别的项目开发。在其中更好了理解了这个学期学过的关于软件开发的步骤。认识到了文档编写在软件项目开发中不可替代的重要性。我觉得收获最深的地方就是以前只觉得写代码才是最重要的部分,现在明白了项目管理、需求分析、文档编写才是项目开发中更重要的部分。让我把眼界从代码拓宽到了更高的层次。同时还深刻认识到自己在项目开发领域还存在着诸多的不足,明白了以后要学习的地方还有许多。
    当然,在项目开发期间也遇到过许多问题:
    老师教授的是用后台修改动态数据,而我们想将前端和后台完全分离,这就存在了一个如何将后台数据打包成JSON发送到前端的问题。
    解决方案:在老师的指导下使用了阿里巴巴公司的fastjson这个jar包来打包数据。同时在前端使用了axios来实现通信和局部页面刷新。
    因为一开始没有编写规范的文档,导致在开发途中想要增加功能的时候会发现不知道更改项目的哪个地方。他人接手编写的时候还要重新看一遍代码,效率低下。
    解决方案:停下开发工作,转头编写规范的文档,统一了数据库和测试数据。并且集中到一起开发,使得遇到什么问题小组成员之间可以很快的交流并解决。
    每个人有自己的代码编写风格,而为了赶时间没有在文件中书写规范的注释。结果在互相测试小组成员单元时发现看不懂对方的代码,导致浪费极多时间。
    解决方案:就算再赶时间也要书写规范的注释,写明类的用途,写明方法的用途以及接受参数和返回参数的类型、作用,这样在测试或者修改的时候会节省大量时间。
    在开发过程中想要对功能进行增加、删除、修改或者是想要复用一段代码时发现代码编写冗余没有规范。导致代码难以维护、复用程度低,重构成本极高。
    解决方案:在将来的项目开发中一定要灵活运用设计模式,设计模式是前辈留给我们的解耦、复用代码的成功经验,虽然使用设计模式在编写的时候可能会造成困难,但是在维护、更新的时候会体现出设计模式的优越性。
    8 评论 165 下载 2019-04-16 15:00:02 下载需要14点积分
  • 基于JAVA和SQL SERVER数据库实现的火车票预售系统

    1 系统设计1.1 设计目的乘坐火车是我们生活中几乎不可缺少的一件事儿,每天都会有各种各样的火车班次发布与被预定。针对这个火车票预售的环节我设计了一个火车票预售系统,为购票用户与卖票管理人员之间搭建平台。让我们的用户能够通过该软件对管理人员发布的航班进行预购与查询。另一方面也可以加强我们的管理人员对班次信息与乘客的管理与查询。
    本系统的根本目的是让管理人员能够发布与查询班次信息、查询乘客信息等;用户可以通过该系统对班次进行预购、自己购票记录的查询等。
    1.2 需求分析1.2.1 信息要求该系统主要记录用户、班次、火车、银行卡之间的关系

    用户分为管理员与购票用户

    售票管理员信息:管理员编号、管理员名字、管理员电话购票用户信息:身份证号、电话号码、银行卡号
    班次信息

    班次号、火车号、出发地点、目的地、出发时间、到达时间
    火车信息

    火车号、火车节数、座位数、各种座位票价、火车车速
    银行卡信息:

    银行卡号、余额、持有人身份证号
    身份证信息

    身份证号、姓名、性别、所有者
    车票信息

    车票号、班次号、座位号、乘客身份证号、车票价钱、车厢数

    1.2.2 处理要求
    能够正确、高效、迅速地完成所有操作
    一个管理员可以管理多个班次、一个用户可以多次订购不同时间段的车票

    1.2.3 安全性与完整性需求
    安全性

    该系统需要用户进行账号的注册与登陆通过对不同的用户种类的检测来给予不同的权限与界面用户登陆自己账号后只能查询自己用户名下身份证的购票信息与个人信息用户不可对班次、火车等信息进行修改售票员能对班次信息进行修改与查询,对于用户信息只能查询不能修改
    完整性

    实体完整性

    手机号、班次号、火车号、银行卡号、身份证号、车票号分别为用户、班次、火车、银行卡、身份证、车票的主码
    参照完整性

    班次号中的火车号为火车表的主码、银行卡号中所有者号码为用户的主码、车票中的火车号与班次号分别为火车表与班次表的主码。以上外码要么为空要么是参照表中已有数据

    用户定义完整性

    性别只能是男女、车票钱不能为空、车速不能为空、班次目的地与出发地不能为空、用户类型只能是 0 与 1:0 表示普通用户、1 表示管理员用户。用户名字不能为空

    1.3 开发和运行环境选择
    开发工具

    前台开发语言为 Java后台数据库为 SQL SERVER 2017
    运行环境

    Java 1.8 版本 Windows2000 以上

    2 数据库设计2.1 数据库概念设计本系统中包括六个实体:身份证实体、银行卡实体、用户实体、车票实体、火车实体、班次实体,根据需求需求分析的结果以上六个实体对应的 ER 属性图如下图所示。

    系统整体 E-R 图

    2.2 数据库逻辑设计根据 E-R 图向关系模型的转换原则,一个实体型转换为一个关系模式,实体的属性就是关系的属性。因此按照图 2.7 中的整体 E-R 图,本数据库系统中应包括六张表:身份证、银卡、用户、车票、火车、和班次。






    3 火车票预售系统详细设计3.1 功能概述售票管理员系统主要包含如下几个功能:

    发布与删除班次及其相关信息
    查看班次详细信息
    通过班次号对班次进行详细查询
    通过身份证号码查询乘车人信息

    用户系统主要包含如下几个功能:

    通过地点、时间查询班次信息
    通过班次号查询班次详细信息
    添加和删除信用卡
    添加、修改和删除乘车人信息
    查询得到班次后可以为乘车人购买车票
    查看自己用户的购票记录

    整体功能描述图如下图所示:

    3.2 火车票预售系统详细设计3.2.1 界面设计3.2.1.1 管理员界面设计在使用管理员账号登录过后,会出现管理员主界面。在界面最上方有初始地点选择和时间选择控件,可以通过时间和地点的详细选择来针对性地发布班次信息,在点击发布后会弹出火车号填写对话框,在此处填写火车号以完成班次发布的目的,如下图在发布下方有一个查看按钮,通过在左边列表中选择想查看的班次再点击该按钮即可查看到详细的班次信息如下图所示。


    3.2.1.2 用户系统界面设计在使用用户权限帐号登录过后,会进入相应的用户界面,用户界面分为四个 fragment,第一个 fragment 通过站点和时间来查询班次列表,在查询到班次后可以通过购买按钮对车票进行购买,购买时需要选择乘车人、勾选座位信息和选择支付的信用卡,如下图所示。


    在第二个 fragment 里用户可以通过班次号来查询班次信息与购票,如下图所示,在点击查询后按钮会自动变成购票,后续购票操作和第一个 fragment 一样。


    在第三个 fragment 里用户可以查询到自己所有的购票记录,可以通过查看详细按钮进行查看,还可以通过退票按钮进行退票操作,如下图所示。


    最后是用户信息界面,用户可以在此处看到自己用户名以及邮箱号,如下图所示。用户可以在此界面增、删、改常用乘车人信息,如下图所示。也可以添加和删除信用卡,如下图所示。




    3.2.2 功能实现整体功能是通过两个 Activity 与四个 Fragment 来实现,具体实现功能顺序为下图所示。本系统大量使用 Dialog 来对详细信息填写进行管理。
    功能实现过程中使用到 UI 的类有 17 个,其中三个抽象父类用于限制所有界面的业务处理与逻辑执行顺序,其余 14 个子类用于详细界面以及功能的实现,整体界面代码框架如下图所示。
    注:Dialog 是在 Fragment 中使用,Fragment 是放置到底层 Activity 使用。

    4 系统测试发布班次示意图

    发布班次示意图

    到数据库查询如下图所示:

    购票操作示意图 1

    购票操作示意图 2

    到数据库查询如下图所示:

    退票操作示意图

    到数据库查询如下图所示:

    5 总结三个星期的时间非常快就过去了,这三个星期不敢说自己有多大的进步,获得了多少知识,但起码是了解了项目开发的部分过程。虽说上过数据库等相关的课程,但是没有亲身经历过相关的设计工作细节。这次课设证实提供了一个很好的机会。通过这次的系统设计,我在很多方面都有所提高。综合运用所学知识的理论知识实际训练从而培养和提高学生独立工作的能力,巩固所学的知识,掌握系统程序的编排和运行,使自己的独立思考能力有了显著提高。
    从各种文档的阅读到开始的需求分析、概念结构设计、逻辑结构设计、物理结构设计。亲身体验了一回系统的设计开发过程。很多东西书上写的很清楚,貌似看着也很简单,思路非常清晰。但真正需要自己想办法去设计一个系统的时候才发现其中的难度。经常做到后面突然就发现自己一开始的设计有问题,然后又回去翻工,在各种反复中不断完善自己的想法。
    我们学习并应用了SQL 语言,对数据库的创建、修改、删除方法有了一定的了解,通过导入表和删除表、更改表学会了对于表的一些操作,为了建立一个关系数据库信息管理系统,必须得经过系统调研、需求分析、概念设计、逻辑设计、物理设计、系统调试、维护以及系统评价的一般过程,为毕业设计打下基础。
    很多事情不是想象中的那么简单的,它涉及到的各种实体、属性、数据流程、数据处理等等。很多时候感觉后面的设计根本无法继续,感觉像是被前面做的各种图限制了。在做关系模型转换的时候碰到有些实体即可以认为是实体又可以作为属性,为了避免冗余,尽量按照属性处理了。
    不管做什么,我们都要相信自己,不能畏惧,不能怕遇到困难,什么都需要去尝试,有些你开始认为很难的事在你尝试之后你可能会发现原来并没有你以前觉得的那样,自己也是可以的。如果没有自信,没有目标,没有信心就不可能把事情做好,当其他人都在迷茫的时候,自己一定要坚信目标。有一个这样的感觉就是课程设计学到的东西比一个学期都多。
    参考文献[1] CSDN 论坛、简书论坛、知乎论坛
    [2] 毕广吉.Java 程序设计实例教程[M]. 北京:冶金工业出版社,2007 年
    [3] 肖成金,吕冬梅。 Java 程序开发数据库与框架应用[J]. 科技展望,2017,05:19.
    [4] 吕锋,梅细燕,周晓东;基于 JDBC 的数据库管理及其应用[J];武汉理工大学学报;2002 年 10 期
    [5] 姚永一,SQL Server 数据库实用教程,北京:电子工业出版社,2010.
    [6] 高云,崔艳春,SQL Server 2008 数据库技术实用教程,北京:清华大学出版社,2011
    [7] 何玉洁,梁琦,数据库原理与应用(第二版),北京:机械工业出版社,2011
    [8] 壮志剑,数据库原理与 SQL Server,北京:高等教育出版社,2008
    [9] 杜丁超.计算机软件 Java 编程特点及其技术分析
    [10] 萧仁惠,陈锦辉. JDBC 数据库程序设计[J].北京:中国铁道出版社,2004
    5 评论 195 下载 2018-11-20 11:12:53 下载需要17点积分
  • 基于JAVA实现简易版泡泡堂小游戏

    一、简介——童年记忆《泡泡堂》是由韩国游戏公司Nexon开发的一款休闲游戏(Casual Game),于2003年在中国大陆上线,由盛大网络运营。游戏讲述了在哈巴森林的一个村落的村民们利用神奇的水泡来打猎和采集宝石,故事由为拯救村民和夺回被海盗抢去的宝石而展开。
    该游戏设有8位基本角色、2位隐藏角色和在基本角色上进阶的新角色。卡通的人物形象、多种道具、饰品和搞怪表情,是一款适合任何年龄的休闲类网游。
    借JAVA课程大作业的契机,我决定用JAVA来做一个简易版的泡泡堂!

    二、程序设计原理、目的,算法说明2.1 场景布局泡泡堂采用乐高积木的风格,每个单元布局一个道具或者场景元素,因而布局比较规整,用代码实现也比较方便。

    用大小13*15数组的方式存放地图元素信息,每个单元格需要放置什么样元素,repaint回调函数依据大小13*15数组信息进行场景的绘制。

    数组实现方式:
    // 地图信息static int [][]MAP_INFO = { {GRASS,BOXO,BOXR,BOXO,BOXR,BUSH,ROAD,ROAD2,BOX,BUSH,HomeY,BOXR,HomeY,GRASS,HomeY},{GRASS,HomeR,BOX,HomeR,BOX,Tree,BOX,ROAD2,ROAD,Tree,BOXR,BOXO,GRASS,GRASS,GRASS},.......}// 图片读取imgrule=ImageIO.read(new File("image/rule/rule.png"));imgmap[GRASS]=ImageIO.read(new File("image/map/grass.png"));......
    实现结果

    2.2 人物人物的动作需要多帧来完成,多帧的图片也用二维数组来存储。例如:
    BabyMove = ImageIO.read(new File("image/player/Role1.png"));int imgWidth = BabyMove.getWidth();int imgHeight = BabyMove.getHeight();// 读入移动的24帧图像for(int i=0;i<4;i++){ for(int j=0;j<6;j++){ ImageBuffer[i][j] = BabyMove.getSubimage(j*imgWidth/6, i*imgHeight/4, imgWidth/6, imgHeight/4); } }

    实现动画帧的播放,从而获得更加真实的效果。
    2.3 炸弹泡泡角色通过释放炸弹摧毁乐高积木来获取道具以及杀死对手。炸弹释放后不会立即爆炸,而是在释放若干时间后自行爆炸。炸弹的爆炸有连锁反应。

    炸弹的定时效果由线程定时器控制,定时器会在后面介绍。
    炸弹的水波也用贴图动画的形式实现,为了获得的爆炸效果,炸弹的贴图动态程度更高一些。如果仔细看的话,不同点的爆炸纹理是不同的。

    炸弹的算法在这里需要一提。
    炸弹的水波蔓延用宽度搜索的方式进行实现,在宽度搜索的过程中,如果当前距离小于水波长度,依次执行以下内容:

    判定当前区域,如果是房子或者树不可蔓延,并且停止继续蔓延;如果是盒子则进行炸毁,如果是人物则杀死人物
    如果是炮弹的话,都刷新炮弹的时间。从而实现连环爆炸
    根据当前位置进行贴图
    继续蔓延直至超出炸弹能力

    2.4 道具玩家通过拾取不同的道具,从而获得不同能力的增长。例如拾取跑鞋可以加速,拾取炸弹可以增加释放炸弹的数量,拾取药水可以增加炸弹压力等等。拾取宠物可以获得坐骑,坐骑可以抵一条命。

    如果捡到坐骑的话,人物动画切换坐骑动画。在实现过程中,就是repaint函数画人物时,检测Role类成员变量pet的值,根据pet的情况进行绘图。

    2.5 被炸弹炸死当人物被水波炸到时,会被水波困住,然后爆炸死亡,游戏结束。


    爆炸死亡时会有动画,为获得更好的游戏效果。动画同样用播放图片数组来实现帧的效果。
    2.6 线程定时器在本工程中有专门的一个TimerTest的类,内含大量的静态函数继承Timertask的函数,供游戏中需要定时器的功能,例如人物移动动画,开场动画,炸弹定时器,音乐定时器等等,有着广泛的应用。以下是炸弹定时器的一个例子。
    // 炸弹定时器 static class MyTimerBump extends TimerTask { void SetInterLinkBump(int ci,int cj, int init_i, int init_j){ if(cj > init_j) Map.BUMB_INFO[cj][ci] = 4; else if(cj == init_j && ci >= init_i){ Map.BUMB_INFO[cj][ci] = 4; } else Map.BUMB_INFO[cj][ci] = 3; int wavelength = Map.mapcanvas.Role1.getWaveLength(); for(int i=1;i<=wavelength;i++){ if(ci+i<15 && Map.BUMB_INFO[cj][ci+i]>4){ SetInterLinkBump(ci+i,cj,init_i,init_j); } if(ci-i>=0 && Map.BUMB_INFO[cj][ci-i]>4){ SetInterLinkBump(ci-i,cj,init_i,init_j); } if(cj-i>=0 && Map.BUMB_INFO[cj-i][ci]>4){ SetInterLinkBump(ci,cj-i,init_i,init_j); } if(cj+i<13 && Map.BUMB_INFO[cj+i][ci]>4){ SetInterLinkBump(ci,cj+i,init_i,init_j); } } return; }
    2.7 音乐播放背景音乐与特效音乐(鼠标点击、炸弹爆炸),都由线程来负责。
    public class Game extends JFrame{ Game(){ new Timer().schedule(new TimerTest.StartMusic(), 0); Map map=new Map(); }}
    音乐分两种,一种是循环播放(背景音乐),另一种是只播放一次(例如炸弹)。代码只是一个参数的差别。
    // 线程播放炸弹声音static class BumbSound extends TimerTask { public void run() { try { URL MusicURL; File MusicFile = new File("sound/explode.wav"); MusicURL = MusicFile.toURL(); AudioClip clip=(AudioClip) Applet.newAudioClip( MusicURL); clip.play(); }catch(Exception mue){ System.out.println(mue.toString()); } }}
    三、程序流程框图、调用函数关系、文件列表3.1 程序流程框图
    3.2 重要函数与类介绍
    Game类:启动游戏
    Map类:存储地图信息
    //炸弹信息static int [][] BUMB_INFO//炸弹归属信息static int [][] BUMB_ROLE//爆炸水波信息static int [][] EXPLOSION_INFO//道具信息static int [][] GIFT_INFO//初始化static void Init()//构造函数public Map()
    MapCanvas:画布类,图片读取,根据信息画布绘制
    // 角色信息Player Role1 = new Player(1,Role1_x,Role1_y);Player Role2 = new Player(2,Role2_x,Role2_y);// 地图信息static int [][]MAP_INFO// 初始化void Init() // 构造函数public MapCanvas()//回调绘制函数(最重要)public void paint(Graphics g){ drawmap(g);} // 绘制函数public void drawmap(Graphics g)
    Music类:音乐测试
    Player类:玩家信息,玩家基本动作,键盘监听
    // 人物输出的图形BufferedImage outImageBuffer;// 坐骑图像BufferedImage outPETImageBuffer; // 获得炮弹长度信息int getWaveLength()// 初始化void Init(int Rx, int Ry)// 下一帧动画public void NEXT(int DIR, int [][]MAP_INFO)// 放炮void SetBump() // 静止动画public void Stand()// 键盘监听public void keyPressed(KeyEvent e)
    Timer类:后台线程计时器,包括动画计时,炮弹刷新,音乐播放
    //背景音乐计时器static class StartMusic extends TimerTask // 线程播放炸弹声音static class BumbSound extends TimerTask // 线程播放点击鼠标的声音static class MouseClick extends TimerTask// 线程播死亡的声音static class RoleDieMusic extends TimerTask// 线程播放获得道具的声音static class GetGiftSound extends TimerTask// 游戏开始动画static class GameStartAnimation extends TimerTask/*炸弹定时器*/static class MyTimerBump extends TimerTask// 炸毁箱子随机出现道具,箱子分两种int DestroyBox(int boxi,int boxj)// 水平方向障碍物检测boolean HCheck(int Bumbi,int Bumbj, int nowi, int nowj)// 竖直方向障碍物检测boolean VCheck(int Bumbi,int Bumbj, int nowi, int nowj)// 检测人物是否被水波炸到void CheckRole(int wavei, int wavej)// 刷新游戏时钟,总计时器timertest中public void run()

    3.3 文件列表
    四、项目所用到的模式:MVCMVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

    Model(模型):表示应用程序核心(比如数据库记录列表)
    View(视图):显示数据(数据库记录)。
    Controller(控制器):处理输入(写入数据库记录)

    在本工程中,Model模块写于Msp模块中存储数据信息;View模块写于MapCanvas的两个大类,通过回调的方式进行绘制;Control模块写于Player中,用于控制逻辑。
    五、难点、要点、得意点5.1 难点本工程同时运行的线程比较多,时钟刷新,键盘监听,图像绘制,动画绘制(每一次移动都是多帧播放实现动画)。并行性写的非常高,虽然并行程序都是次要的,但是这些并行程序(动画,爆炸音乐)大大提高了游戏的可玩性。
    同时,动画的绘制是另一个难点。如果人物不会进行微小的动作,只会让玩家觉得这是没有生命的刚体,游戏性大打折扣。所以事先游戏人物动画是非常有必要的。事先动画是利用人类的视觉延迟,在较短的时间内播放多帧静态图片,从而化静为动。
    炸弹的算法。其实这个游戏中能用数据结构的地方非常少,炸弹水波的蔓延采取了宽度搜索的计算,按每一次宽度搜索进行逻辑判断,从而判定是停止蔓延还是炸毁箱子还是炸死人还是绘制结束水纹等等。
    5.2 要点充分利用java的一些优势,尽量做到封装。同时充分利用静态static,简化访问增强数据一致性。充分利用线程,并行计算。尽量关注细节,提高游戏还原度。
    5.3 得意点为了还原真实的泡泡堂场景,所有图像、音乐、道具等等均为原版资源,地图布局为原版泡泡堂,具有非常高的还原度。泡泡堂作为十五年前盛极一时的游戏,由韩国游戏公司Nexon开发。我用了2000余行java代码实现了原泡泡堂50%的基础操作。还是很有成就感的。

    5.4 原版泡泡堂与先前做过的一些课程游戏坦克大战,赛车相比,这次的动画,音乐细节做得更为细致。如果有机会,我还想用opengl来实现一下3d版本的泡泡堂。
    游戏中应用了大量的后台线程,充分利用了多核计算机的性能。并行程序对于游戏来说极为重要,大大提高了游戏的真实性和可玩性。游戏中的音乐,操作,监听,动作,动画均由线程来完成。
    结合计算机数据空间局部性和时间局部性,以及操作系统内存管理等知识,我将游戏场景布局切割成小元素模块而非整张大图的读写,这样局部数据可以反复读取,近期操纵的数据在接下来也会反复调用,提高了游戏运行效率,降低了开销。
    算法上利用了最基本但是行之有效的宽度搜索来进行炸弹爆炸的计算,不仅完成了障碍物的判定,人物的识别,还实现了炸弹的连锁爆炸,增加游戏难度和趣味性。
    六、程序使用说明打开MyPPT.jar包即可运行程序。提醒:不可移动jar包的位置
    操作指南

    玩家一:WSAD控制上下左右,J放炮
    玩家二:↑↓←→控制上下左右,L放炮
    按P键初始化程序


    七、结论与展望在实现这次Java泡泡堂的过程中,运用了很多本学期学到的java知识,并且在编写程序的时候考虑了一些硬件上实现的东西,例如线程,内存管理,局部性等等。在实现的过程中遇到了很多bug,花了点时间调了下bug,收获也很多。个人觉得写得这个游戏还是挺漂亮的。不过也有一些不足之处。比如代码尽管大部分用了面向对象的知识,但是不少部分个人觉得函数功能还不够明确,导致看起来像面向过程编程。
    这个迷你泡泡堂和原版泡泡堂其实还有不小的差距,原版游戏中道具的功能更加多样,不同的地图也非常多。如果要进一步拓展的话,可以让这个游戏更加复杂。
    还有一点挺遗憾的是,没有时间继续写连网的机制,其中一个原因是时间不够,另一个则是还没有上过计算机网络这门课,之前也没实现过联网多玩家游戏怎么来写。争取寒假的时候做一下尝试。
    这学期跟着楼老师学java收获良多,楼老师有点我非常喜欢就是上课会打代码,不像别的老师只会念PPT,一点也不讲工程上的东西。给老师打个call。
    2 评论 99 下载 2019-04-02 16:00:24 下载需要20点积分
  • 基于C语言的图书馆管理系统

    一 需求分析主要实现以下功能:

    分管理员和学生两种身份,不同身份操作不同

    管理员

    进购书籍决定某本书是由被借阅查看借阅情况
    学生

    借书,限制每人5本还书图书分类展示搜索不能重复借阅同一本书

    开机动画,功能就是提高逼格(可以考虑加上音效)

    二 程序设计2.1 总体设计程序总体流程如下图所示:

    2.2 管理员模块设计管理员模块运行流程如下图所示:

    2.3 学生模块设计学生模块运行流程如下图所示:

    三 程序实现3.1 系统结构层次程序结构层次如下图所示:

    3.2 储存结构储存文件分为两个:书目(book_list),借阅历史(borrow_history)

    书目只让管理员操作
    借阅历史学生可以操作

    储存文件具体描述如下:
    book_list:

    书籍编号书名分类出版社价格是否借阅余量
    borrow_history:

    历史ID借阅人书名借阅日期应还日期
    3.3 接口文档3.3.1 结构体定义书籍目录
    typedef struct { int book_no; // 三个字符,如:001 char *name; int category; int price; bool can_borrow; int remain; char *press;} book;
    借阅历史
    typedef struct { int year; int month; int day;} my_time;typedef struct { int history_id; char *borrow_by; my_time *borrow_time; my_time *return_time; char *book_name;} history;
    学生表
    typedef struct { int stu_no; char *name; // int class;} student;
    3.3.2 预设函数界面层
    void bootstrap() ;void welcome_admin(); void show_admin_menu();void welcome_student(char *name);void show_student_menu();void show_list(linklist list);void print_*();void see_you();
    IO
    bool io_input_histroy(linklist history); bool io_input_student(linklist students); bool io_input_book_list(linklist book_list) bool io_output_histroy(linklist history); bool io_output_student(linklist students); bool io_output_book_list(linklist book_list)
    数据层
    bool check_stu_no(int stu_no);bool check_admin(char *passwd);bool get_by_category(int category, linklist *get_list);bool borrow_book(char *name);bool return_book(char *name);void search(char *name,linklist list);bool see_self(int stu_no, linklist list);
    3 评论 150 下载 2018-10-24 14:51:56 下载需要5点积分
显示 0 到 15 ,共 15 条
eject