分类

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

资源列表

  • 基于VC++的MFC类库实现的五子棋小游戏

    一、项目基本信息
    操作系统:Windows 10
    开发环境:VS2017 和 MFC 框架
    项目基本信息:通过 VS2017 的 MFC 框架编程编制五子棋程序,棋盘 19*19 大小, 执黑先行,黑白交替,当一方出现五个子连成一条线,即判断胜方,弹出消息框,显示胜方,可重新开始游戏

    二、 项目基本结构2.1 程序流程图
    2.2 数据结构// 棋子逻辑结点struct chesspoint { int chesscolor; // 棋子颜色:1 黑色,-1 白色 bool chessuseflag; // 棋子是否被占用 int chessflag[8]; // 棋子标志,一个棋子周围的 8 个位置,标识当前组成 的线段最大长度,0 号为左上角,顺时针计数 };
    2.3 主要函数// 绘制棋盘背景 void DrawBackground(); // 绘制棋子,绘制成功返回 1,失败返回-1 int DrawChessPoint(); // 规格化落子位置,正常返回 1,子越界返回-1,该位置已落子返回-2 int StandardPointPosition(); // 绘制一个特定颜色的棋子 void DrawPoint(int color); // 添加棋子(logic_x,logic_y)到棋子逻辑数组中,并动态更新棋子连线信息 void AddChessPoint(int logic_x,int logic_y);
    2.4 程序结构程序结构主要分为以下几个部分:
    2.4.1 菜单部分
    游戏(子菜单:19x19)——开始游戏
    重新开始(子菜单:初始化)——初始化游戏(棋盘未绘制)

    2.4.2 绘制棋盘部分(void DrawBackground())
    以(500, 500)为中心绘制 19x19 的棋盘,每个棋盘结点间距为 50,共 361 个结点
    2.4.3 下子部分
    设置 blackplayflag、whiteplayflag 作为交替下子的标志位,一真一假, 完成下子后交替互换
    设置 CPoint 类型的向量 player_black、player_white 存储鼠标左键下子 的坐标值,用 black_pointNum、white_pointNum、chess_pointNum 分别记录黑 子数,白子数,总棋子数

    2.4.4 规格化下子位置部分(int StandardPointPosition())
    下子位置超出棋盘边界:删除添加的相应颜色的棋子,棋子数相应减一, 返回-1
    将棋子位置化成(i*50,j*50)的形式,即下子位置若在某节点为中心, 50 为边长的正方形内时,下子位置更新为该节点位置(将下子位置划归到离散 有限点上),返回 1
    由第二步可获得棋子的逻辑位置(i, j),使用自定义数据结构 chesspoint 的 一个 19x19 的数组 chess_logic 存储棋子的 逻辑信息( 在 void AddChessPoint(int logic_x, int logic_y) 函数中进行修改):
    int chesscolor; // 棋子颜色:1 黑色,-1 白色 bool chessuseflag; // 棋子是否被占用 int chessflag[8]; // 棋子标志
    一个棋子周围的 8 个位置,标识当前组成的线段最大长度,0 号为左上角,顺时针计数。若(i, j)位置的 chessuseflag 标志位为 TRUE 则表明该位置已被占用,删除添加的相应颜色的棋子,棋子数相应减一,返回-2
    添加棋子到棋子逻辑数组中,并动态更新棋子连线信息(void AddChessPoint(int logic_x, int logic_y))
    设置相应 chesspoint 的结点信息:
    int chesscolor; //棋子颜色:1 黑色,-1 白色 bool chessuseflag; //棋子是否被占用 int chessflag[8]; //棋子标志,一个棋子周围的 8 个位置,标识当前 组成的线段最大长度,0 号为左上角,顺时针计数

    动态更新每个逻辑棋子结点的 chessflag[8],用来标识当前连线长度。
    以 0 号标志位(左上角)为例,当前棋子位置为(i, j),若(i-1, j-1)位置棋子的 chesscolor 与( i, j)一致,则 chess_logic[i][j].chessflag[0] = chess_logic[i-1][j-1].chessflag[0]+1(即左上方连线长度加一),否则, 置为 1,表示左上方连线长度为 1,其他方向相同。

    利用 MFC 绘制特定颜色圆的方法绘制棋子
    判断输赢部分;当棋子总数 chess_pointNum 大于 8 时( 即棋盘上至少有五个黑子, 4 个白子时), 开始对最后落下的子进行判断,检查其逻辑棋子中的 chessflag[8]部分,若有 一个大于等于 5,即说明有五个颜色相同的子连成直线,由该子的颜色断定胜利 的是黑方还是白方,弹出消息框

    三、项目演示选择游戏(19x19),开始游戏

    开始下棋,简单演示

    点击确定后

    重新开始

    四、项目心得经过本次项目编写,收获如下:

    对 MFC 的框架编程更加熟练了,自己的编程能力也有了提升
    在添加黑白棋子,绘制棋子的过程中发现了很多逻辑上的问题,通过不断检查调试,发现在重复绘制棋子的过程中绘制完成后没有返回,导致之前 绘制的被覆盖,出现了比较大的问题,通过解决这个问题,对于 MFC 的绘图有了比较深刻的理解
    该项目的核心部分是判断哪一方胜利的算法的设计,最开始使用的是遍历算法,通过遍历每一条线,寻找相邻的五个颜色相同的棋子,发现算法思想简单,但时间空间复杂度较高,于是开始算法的改造,设计了特殊的数 据结构 chesspoint,通过动态更新棋子标志位的方法将棋子的连线信息 存储在标志位中,使得遍历寻找的过程变成了查看当前棋子的标志位是否有等于 5 的判断,使得算法的时间复杂度下降,理解起来也比较容易,这样的思考过程对自己的提升也很大
    1 评论 71 下载 2018-11-05 15:57:15 下载需要6点积分
  • 基于MFC类库实现的飞机大战小游戏

    1 概述1.1 简介本次实训项目是做一个飞机大战的游戏,完成一个界面简洁、操作简单的桌面游戏。该飞机大战项目主要使用的是MFC编程,运用MFC中的类以及自己创建的类,设计好各个类之间的继承关系,实现飞机大战游戏的简单功能。
    1.2 基本功能
    设置一个战机具有一定的速度,通过键盘,方向键可控制战机的位置,空格键发射子弹
    界面中敌机出现的位置,以及敌机炸弹的发射均为随机的,敌机与敌机炸弹均具有一定的速度,且随着关卡难度的增大,数量和速度均增加
    对于随机产生的敌机和敌机炸弹,若超过矩形区域,则释放该对象
    添加爆炸效果,包括战机子弹打中敌机爆炸、敌机炸弹打中战机爆炸、战机与敌机相撞爆炸以及战机子弹与敌机炸弹相撞爆炸四种爆炸效果。且爆炸发生后敌机、子弹、炸弹均消失,战机生命值减一

    1.3 扩展功能为游戏界面添加了背景图片,并在战机击中敌机、敌机击中战机、以及战机敌机相撞时均添加了背景音效。

    为游戏设置了不同的关卡,每个关卡难度不同,敌机与敌机炸弹的速度随着关卡增大而加快,进入第三关以后敌机从上下方均会直接向战机次发射炸弹。战机每打掉一个敌机则增加一分,同时为战机增加一个生命值,当战机得分超过100分则可进入下一关;每进入一关敌机速度都会加快,分别从上下两方飞出,在第四关和第五关有boss,分别以不同的方式发射子弹
    在游戏界面输出当前游戏进行信息,包括当前得分、当前关卡、生命值以及boss生命值
    增加了鼠标控制战机位置这一效果,战绩的位置随着鼠标的移动而移动
    实现了暂停游戏的功能,玩家可通过键盘上的‘Z’键,对游戏进行暂停。‘S’键开始
    实现了设置游戏难度的功能,可以通过菜单栏上的设置难度选项设置难度。同时菜单实现了查看游戏说明和重新开始的功能

    2 相关技术2.1 透明贴图为了解决界面上的图片飞机等闪烁问题,采用了双缓冲技术。先定义一个位图对象和一个设备描述表对象,为设备描述表创建DC,再建立一个与屏幕设备描述表兼容的位图,将位图选入到内存设备描述表中,以后的每个飞机、子弹对象就画在设备描述表中,最后一起画在pDC上,绘图完成后要进行清理。
    绘制透明位图的关键就是创建一个“掩码”位图(mask bitmap),这个“掩码”位图是一个单色位图,它是位图中图像的一个单色剪影。在MFC中,绘图需要使用设备描述表,透明贴图时需要创建两个内存设备描述表,一个是用于存放位图的设备描述表(imgDC),一个是用于存放“掩码”位图的设备描述表(maskDC)。在“掩码”位图设备描述表中制作“掩码”位图的方法是先创建一个单色的bitmap,放入掩码设备描述表(maskDC)中,然后使用拷贝粘贴的方式将存放有位图的设备描述表(imgDC)绘制到掩码设备描述表上,这样,掩码设备描述表显示的位图即是“掩码”位图。
    整个实现过程如下:

    创建一张大小与需要绘制图像相同的位图作为“掩码”位图
    将新创建的“掩码”位图存储至掩码位图的设备描述表中
    把位图设备描述表的背景设置成“透明色”,即不需要显示的颜色
    复制粘贴位图到“掩码”位图的设备描述表中,这个时候“掩码”位图设备描述表中存放的位图与位图设备描述表中的位图一样
    把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上
    把“掩码”位图与这个时候对话框相应区域的背景进行逻辑与的操作
    最后一步重复步骤5的操作,把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上
    删除使用过的GDIObject,释放非空的指针,最后把新建的设备描述表也删除

    2.2 Windows定时器技术由于敌机和敌机的子弹、boss等是随机出现的,所以在new这些对象时要设置定时器,是指不同的定时器,才会产生时差的效果,这样就能实现了随机出现。在MFC的API函数中使用SetTimer()函数设置定时器,设置系统间隔时间,在OnTimer()函数中实现响应定时器的程序。
    Windows定时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。程序将时间间隔告诉Windows,然后Windows给程序发送周期性发生的WM_TIMER消息以表示时间到了。本程序中使用多个定时器,分别控制不同的功能。在MFC的API函数中使用SetTimer()函数设置定时器,设置系统间隔时间,在OnTimer()函数中实现响应定时器的程序。在程序结束时调用OnDestory()函数用KillTimer()函数删除定时器。
    2.3 图像列表位置爆炸效果是连续的显示一系列的图片。如果把每一张图片都显示出来的话,占用的时间是非常多的,必然后导致程序的可行性下降 ,而CImageList是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用来存放爆炸效果的一张图片,使用draw函数来绘制在某拖拉操作中正被拖动的图象,即可连续绘制出多张图片做成的爆炸效果。
    图像列表控制(CImageList)是相同大小图像的一个集合,每个集合中均以0为图像的索引序号基数,图像列表通常由大图标或位图构成,其中包含透明位图模式。可以利用WINDOWS32位应用程序接口函数API来绘制、建立和删除图像,并能实现增加、删除、替换和拖动图像等操作。图像列表控制提供了控制图像列表的基本方法,如CimageList&imageList建立图像控制对象结构,Create 初始化图像列表并绑定对象。
    2.4 获取矩形区域和判断是否相交在产生爆炸效果时,需要获取两个对象所在位置的矩形区域,然后判断两个矩形区域是否相交,相交则产生爆炸,否则不产生爆炸。
    首先,使用CRect定义一个对象,然后使用类的成员函数GetRect()函数,获取对象所在位置的矩形区域。然后使用IntersectRect(&,&))函数来判断两个矩形是否有重合的部分。如果有不为空,则返回非零值;否则,返回0。
    2.5 CObject类和CObList类MFC类库中提供了丰富的CObject类的成员函数,游戏中建立的多个类都是间接继承自CObject类,多个链表都是用到CObList类,此程序主要用到的成员函数如下:

    构造函数,为CObject指针构造一个空的列表
    GetHead(),访问链表首部,返回列表中的首元素(列表不能为空)
    AddTail(),在列表尾增加一个元素或另一个列表的所有元素
    RemoveAll(),删除列表中所有的元素
    GetNext(),返回列表中尾元素的位置
    GetHeadPosition(),返回列表中首元素的位置
    RemoveAt(),从列表中删除指定位置的元素
    GetCount(),返回列表中的元素数

    3 需求分析3.1 功能需求分析战机数量为1,由玩家通过键盘控制(方向键控制位置、空格键发射导弹)战机,导弹释放存在间隔,有一定的运行速度,导弹遇到敌机发生爆炸,敌机被炸毁,导弹消失,玩家得分,由计算机控制敌机自动向战机发动攻击,敌机数量随机,计算机生成敌机时随机选择类别,敌机从游戏区域的上下两端进入,左右位置随机,敌机行驶期间,不左右移动,不反向移动,当敌机到达战机下方时发射炸弹对战机进行攻击,运行线路为直线,方向为从下至上,或从下至上,不可左右移动。纵向由发射位置起至游戏区域结束,炸弹遇到战机时发生爆炸,战机被炸毁,炸弹消失,游戏结束,炸弹运行时有速度限制,爆炸时的声音。
    3.2 数据需求分析游戏中需要记录战机得分情况,所以具体的的分规则是战机击中敌机后得一分,战机被敌机击中则减一分生命值,战机若与敌机相撞则生命值减五分;战机击中boss后得一分,战机被boss击中则减一分生命值,战机若与boss相撞则生命值减五分;当战机生命值减为0时,游戏结束。
    游戏中的数据大部分是随机的例如敌机的个数,发射子弹的个数,出现时间,发射子弹实际都是随机的,所以该游戏对数据的需求不是很明确。
    3.3 行为需求分析每一个函数都有特定的功能,也有一定的联系,大部分的函数是在OnTime函数和OnDraw函数中使用的,例如每个类都有的draw函数和move函数是必须要在这两个函数中使用的,否则每个对象的位置是不会随时间的变化而变化的。
    4 总体设计与详细设计4.1 系统模块划分主要用到的类

    CPlaneObject:基类
    CPlane:战机类
    Cenemy:敌机类
    CBomb:战机子弹类
    Cball:敌机子弹类
    Cexplosion:爆炸类
    Cboss:boss类
    Clevles:设置难度用的事件处理类

    各个类的关系如下

    设计思路
    首先,在OnInitinalUpdate函数里加载需要导入的图片资源文件,同时设置定时器;
    然后在OnTimer函数中创建战机对象以及敌机、子弹、炮弹链表,再在OnDraw函数中把战机用draw函数画出来,依次遍历敌机、子弹、炮弹链表,使这些对象画在界面上;
    然后再次执行OnTimer函数,依次遍历每一个链表,调用每个对象的move函数,使其移动,在移动的过程中要注意边界控制的问题;再次调用OnDraw函数使其改变的位置显示在界面上,当对象的位置在界面以外时,将其从链表中删除;
    在OnTimer函数中判断碰撞问题,取得两个对象所在位置的矩形区域,如果两个区域相交,则产生爆炸,同时将对象从链表中删除,再次用OnDraw函数刷新界面。
    飞机大战活动图

    4.2 主要功能模块4.2.1 敌机和子弹的移动在这个游戏中的基本功能就是敌机从上方和下方两个不同的方向随机出发,然后随机发射子弹,要实现这个功能,就必须设置不同的定时器,让敌机不同时出现,不同时发子弹。首先,在OnTimer()创建一个敌机链表,new出两个不同方向的敌机,并将敌机添加到敌机链表中,再在OnDraw()函数中遍历的将敌机draw出来,再在OnTimer()函数中调用敌机对象中的move函数,使得敌机在游戏界面上移动起来。子弹的设计与敌机相似,但是必须在遍历敌机队列使其移动时,得到一个敌机对象后,就创建一个子弹对象,并将其初始位置设置为敌机的当前位置,然后将其添加到子弹队列中,再在OnDraw()函数中遍历的将子弹draw出来,再在OnTimer()函数中调用子弹对象中的move函数,使得子弹在游戏界面上移动起来。
    4.2.2 添加爆炸效果在战机打中敌机,敌机与战机子相撞,打死boss时都会产生爆炸效果,需要调用每个类的getRect函数取到两个对象的位置,再用IntersectRect函数判断是否有交集,若有则两个对象相撞,产生爆炸效果,若没有交集,则两个对象没有相撞。
    爆炸效果是连续的显示一系列的图片。CImageList是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用CImageList的对象ImageList来存放爆炸效果的一张图片,使用draw函数来绘制在某拖拉操作中正被拖动的图象,即可连续绘制出多张图片做成的爆炸效果。在产生爆炸效果的同时用PlaySound函数添加声音效果。
    4.3 扩展功能设计思路4.3.1 追踪弹在这个游戏中我设计了追踪弹,既有战机追踪敌机的追踪弹,也有敌机和boss追踪战机的追踪弹,在设计战机追中敌机的追踪弹时,采用的是追踪距离战机最近的敌机,但是由于位置始终在变化所以位置总是找不准,因此有时追踪的不是距离最近的敌机,准确率也不是很高,但是也能击中敌机。在设计敌机追踪战机的追踪弹时,由于战机只有一个位置比较好控制,所以准确率较高。
    设计这个追踪弹的主要思路是,在子弹move时,不再是直上直下的飞,而是将当前你要追踪的位置作为参数传给move,move函数计算你的位置和要追踪的位置的斜率,沿着这条直线的方向移动子弹,子弹就实现了追踪的功能。
    4.3.2 战机和boss的多种子弹战机按空格键发射双排子弹,主要是在OnTimer()函数里创建了一个子弹链表,每隔一段时间就将两个子弹对象添加到链表中,这两个子弹的初始位置是在飞机的两个机翼上,然后再在OnDraw()函数中调用draw函数,将子弹画在游戏界面上,然后再在OnTimer()函数嗲有子弹的move函数,使得子弹在界面上移动起来。战机和boss也可以发射三列和五列的散弹,分别朝着三个和五个不同的方向发射子弹,主要涉及是重写了子弹的构造函数,在构造函数中传了一个参数type,如果type是单数则向左偏,type是双数则向右偏,type等于0则沿竖直方向发射,在偏转时向左和右方向偏转的速度与type有关,因此产生了分散发射的效果。
    5 编码实现void CPlaneGameView::OnDraw(CDC* pDC){ CPlaneGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 this->GetClientRect(&lpRect);//取得当前的窗口大小 //加载图片背景 CBitmap bitmap_BackGround; bitmap_BackGround.LoadBitmap(IDB_BACKGROUND); BITMAP bimap2;//位图图像 bitmap_BackGround.GetBitmap(&bimap2); CDC cdc_BackGround;//定义一个兼容的DC cdc_BackGround.CreateCompatibleDC(pDC);//创建DC CBitmap*Old=cdc_BackGround.SelectObject(&bitmap_BackGround); //定义一个位图对象 //双缓冲 CBitmap MemBitmap; CDC MemDC; //这时还不能绘图,因为没有位图的设备描述表是不能绘图的 //只有选入了位图的设备描述表才有地方绘图,画到指定的位图上 MemDC.CreateCompatibleDC(pDC); //下面建立一个与屏幕设备描述表(或者内存设备描述表)兼容的位图 MemBitmap.CreateCompatibleBitmap(pDC,lpRect.right,lpRect.bottom); CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap); //将位图选入到内存设备描述表 MemDC.StretchBlt(0,0,lpRect.Width(),lpRect.Height(),&cdc_BackGround,0,0,bimap2.bmWidth,bimap2.bmHeight,SRCCOPY); //绘图完成后的清理 plane.draw(&MemDC); //画战机的子弹 POSITION pos,position; Cbomb *bombomb1; for(pos=pDoc->bomblist1.GetHeadPosition(); pos!=NULL;) { bombomb1=(Cbomb*)pDoc->bomblist1.GetNext(pos); bombomb1->draw(&MemDC); } //画敌机 Cenemy *enemy1,*enemy2; for(pos=pDoc->enemylist1.GetHeadPosition(); pos!=NULL;) { enemy1=(Cenemy*)pDoc->enemylist1.GetNext(pos); enemy1->draw(&MemDC); } for(pos=pDoc->enemylist2.GetHeadPosition(); pos!=NULL;) { enemy2=(Cenemy*)pDoc->enemylist2.GetNext(pos); enemy2->draw(&MemDC); } //画敌机的子弹 Cball *ball1,*ball2; for(pos=pDoc->balllist1.GetHeadPosition(); pos!=NULL;) { ball1=(Cball*)pDoc->balllist1.GetNext(pos); ball1->draw(&MemDC); } for(pos=pDoc->balllist2.GetHeadPosition(); pos!=NULL;) { ball2=(Cball*)pDoc->balllist2.GetNext(pos); ball2->draw(&MemDC); } //画爆炸 Cexplosion *explosion; for(pos=pDoc->explosionlist.GetHeadPosition(); pos!=NULL;) { position=pos; explosion=(Cexplosion*)pDoc->explosionlist.GetNext(pos); if(explosion->draw(&MemDC)==0){ pDoc->explosionlist.RemoveAt(position); delete explosion; } } //画boss和boss的子弹 if(level>=4){ if(boss!=NULL){ boss->draw(&MemDC); } Cball *balllist3; for(pos=pDoc->balllist3.GetHeadPosition(); pos!=NULL;) { balllist3=(Cball*)pDoc->balllist3.GetNext(pos); balllist3->draw(&MemDC); } } CString t; wchar_t ttt[100],tt[100],tttt[100],ttttt[100]; wsprintf(ttt,L"当前得分:%d",score); wsprintf(tt,L"当前生命值:%d",lifenum); wsprintf(tttt,L"当前关卡:%d",level); wsprintf(ttttt,L"当前boss生命值:%d",bosslife); t.SetString(tttt); SetBkMode(MemDC,TRANSPARENT); SetTextColor(MemDC,RGB(255,255,255)); MemDC.TextOut(0,0,t); t.SetString(tt); MemDC.TextOut(0,20,tt); CPen pen(PS_SOLID,3,RGB(255,0,0)),*pOldPen; CBrush brush(RGB(255,0,0)),*pOldBrush; pOldBrush=(CBrush*)pDC->SelectObject(&brush); pOldPen=(CPen*)pDC->SelectObject(&pen); MemDC.Rectangle(120,20,120+lifenum,40); pDC->SelectObject(pOldPen); pDC->SelectObject(pOldBrush); t.SetString(ttttt); MemDC.TextOut(0,40,t); MemDC.Rectangle(130,40,130+bosslife,60); t.SetString(ttt); MemDC.Rectangle(90,60,90+score,80); MemDC.TextOut(0,60,t); pDC->BitBlt(lpRect.left,lpRect.top,lpRect.right,lpRect.bottom,&MemDC,0,0,SRCCOPY); MemBitmap.DeleteObject(); MemDC.DeleteDC();}void CPlaneGameView::OnTimer(UINT_PTR nIDEvent){ // TODO: 在此添加消息处理程序代码和/或调用默认值 CPlaneGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; if(level>=4&&boss==NULL){ boss = new Cboss; } plane.setSpeed(level*5); if(GetKeyState(VK_LEFT)<0&&isPause==0) { if(plane.getPosition().x>lpRect.left) { plane.move(1); } } if(GetKeyState(VK_RIGHT)<0&&isPause==0) { if(plane.getPosition().x+50<lpRect.right) { plane.move(2); } } if(GetKeyState(VK_UP)<0&&isPause==0) { if(plane.getPosition().y>lpRect.top) { plane.move(3); } } if(GetKeyState(VK_DOWN)<0&&isPause==0) { if(plane.getPosition().y+100<lpRect.bottom) { plane.move(4); } } if(GetKeyState('Z')<0){//暂停 isPause=1; } if(GetKeyState('S')<0){//开始 isPause=0; } if(isPause == 0) { CPoint point =plane.getPosition(); switch(nIDEvent) { case 1: { //战机发射子弹 if(GetKeyState(VK_SPACE)<0) { Cbomb *bomb1 = new Cbomb(); Cbomb *bomb2 = new Cbomb(); bomb1->setPosition(plane.pos.x,plane.pos.y); bomb2->setPosition(plane.pos.x+40,plane.pos.y); pDoc->bomblist1.AddHead(bomb1); pDoc->bomblist1.AddHead(bomb2); } if(GetKeyState('X')<0) { Cbomb *bomb1 = new Cbomb(0,1); bomb1->setPosition(plane.pos.x+25,plane.pos.y-60); pDoc->bomblist1.AddHead(bomb1); } if(GetKeyState(VK_CONTROL)<0&&level>=4) { for(int i=0;i<3;i++){ Cbomb *bomb1 = new Cbomb(i,0); bomb1->setPosition(plane.pos.x+20,plane.pos.y); pDoc->bomblist1.AddHead(bomb1); } } if(GetKeyState(VK_SHIFT)<0&&level==5) { for(int i=0;i<5;i++){ Cbomb *bomb1 = new Cbomb(i,0); bomb1->setPosition(plane.pos.x+20,plane.pos.y); pDoc->bomblist1.AddHead(bomb1); //PlaySound((LPCTSTR)IDR_WAVE3, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); } } //战机子弹移动 POSITION pos,position; Cbomb *bomb1; int i=0; for(pos=pDoc->bomblist1.GetHeadPosition(); pos!=NULL;) { position=pos; bomb1=(Cbomb*)pDoc->bomblist1.GetNext(pos); if((bomb1->getPosition().y)>lpRect.top){ if(GetKeyState('X')<0){ POSITION pos1,tpos,tpos2; double min=999999,d; Cenemy *enemy1=new Cenemy(-1); for(pos1=pDoc->enemylist1.GetHeadPosition();pos1!=NULL;){ tpos=pos1;tpos2=pDoc->enemylist1.GetHeadPosition(); enemy1=(Cenemy*)pDoc->enemylist1.GetNext(pos1); d=(enemy1->getPosition().x-bomb1->getPosition().x)*(enemy1->getPosition().x-bomb1->getPosition().x)+(enemy1->getPosition().y-bomb1->getPosition().y)*(enemy1->getPosition().y-bomb1->getPosition().y) ; d=sqrt(d); if (d<min) { min=d; tpos2=tpos; } bomb1->move(((Cenemy*)pDoc->enemylist1.GetNext(tpos2))->getPosition()); } } else bomb1->move(); } else { if(pDoc->bomblist1.GetHeadPosition()==position){ pDoc->bomblist1.RemoveHead(); delete bomb1; } else if(pDoc->bomblist1.GetTailPosition()==position){ pDoc->bomblist1.RemoveTail(); delete bomb1; } else if(pDoc->bomblist1.GetAt(position)!=NULL) { pDoc->bomblist1.RemoveAt(position); delete bomb1; } } } this->Invalidate(TRUE); break; } case 2: { //产生敌机 POSITION pos1,position1; int direction1=-1,direction2=1; Cenemy *enemy1 = new Cenemy(direction1); Cenemy *enemy2 = new Cenemy(direction2); enemy1->setPosition(rand()%1000,lpRect.top+35); enemy2->setPosition(rand()%1000,lpRect.bottom-35); if(level<=2){ if(pDoc->enemylist1.GetCount()<5*level&&rand()%10==level) { enemy1->setSpeed(1+2*level); pDoc->enemylist1.AddHead(enemy1); } if(pDoc->enemylist2.GetCount()<5*level&&rand()%10==level) { enemy2->setSpeed(1+2*level); pDoc->enemylist2.AddHead(enemy2); } } else{ if(pDoc->enemylist1.GetCount()<10&&rand()%10==level) { enemy1->setSpeed(5); pDoc->enemylist1.AddHead(enemy1); } if(pDoc->enemylist2.GetCount()<10&&rand()%10==level) { enemy2->setSpeed(5); pDoc->enemylist2.AddHead(enemy2); } } //产生敌机的子弹,敌机移动 for(pos1=pDoc->enemylist1.GetHeadPosition(); pos1!=NULL;) { position1=pos1; enemy1=(Cenemy*)pDoc->enemylist1.GetNext(pos1); if(pDoc->balllist1.GetCount()<=5){ Cball *ball1 = new Cball(); ball1->setPosition(enemy1->getPosition().x+20,enemy1->getPosition().y+30); ball1->setSpeed(level*2+30); pDoc->balllist1.AddHead(ball1); } if((enemy1->getPosition().y)<lpRect.bottom) enemy1->move(direction1); else { if(pDoc->enemylist1.GetHeadPosition()==position1){ pDoc->enemylist1.RemoveHead(); delete enemy1; } else if(pDoc->enemylist1.GetTailPosition()==position1){ pDoc->enemylist1.RemoveTail(); delete enemy1; } else if(pDoc->enemylist1.GetAt(position1)!=NULL) { pDoc->enemylist1.RemoveAt(position1); delete enemy1; } } } for(pos1=pDoc->enemylist2.GetHeadPosition(); pos1!=NULL;) { position1=pos1; enemy2=(Cenemy*)pDoc->enemylist2.GetNext(pos1); if(pDoc->balllist2.GetCount()<=5){ Cball *ball2 = new Cball(); ball2->setPosition(enemy2->getPosition().x+20,enemy2->getPosition().y-30); ball2->setSpeed(level*2+30); pDoc->balllist2.AddHead(ball2); } if((enemy2->getPosition().y)>lpRect.top) enemy2->move(direction2); else { if(pDoc->enemylist2.GetHeadPosition()==position1){ pDoc->enemylist2.RemoveHead(); delete enemy2; } else if(pDoc->enemylist2.GetTailPosition()==position1){ pDoc->enemylist2.RemoveTail(); delete enemy2; } else if(pDoc->enemylist2.GetAt(position1)!=NULL) { pDoc->enemylist2.RemoveAt(position1); delete enemy2; } } } //设置boss的子弹 if(level==4&&boss!=NULL){ for(int i=0;i<5;i++){ Cball *ball3 =new Cball(i,0); ball3->setPosition(boss->getPosition().x+45,boss->getPosition().y+125); pDoc->balllist3.AddTail(ball3); } } if(level==5&&boss!=NULL){ for(int i=0;i<5;i++){ Cball *ball3 =new Cball(i,0); ball3->setPosition(boss->getPosition().x+10*i,boss->getPosition().y+125); pDoc->balllist3.AddTail(ball3); } /*for(int i=0;i<=5;i++){ Cball *ball3 = new Cball(); ball3->setPosition(boss->getPosition().x+) }*/ } this->Invalidate(TRUE); break; } case 3: { //敌机子弹的移动 POSITION pos2,position2; CPoint point; Cball *ball1; Cball *ball2; int direction1=-1,direction2=1; for(pos2=pDoc->balllist1.GetHeadPosition(); pos2!=NULL;) { position2=pos2; ball1=(Cball*)pDoc->balllist1.GetNext(pos2); if((ball1->getPosition().y)<lpRect.bottom){ if(level>=3) ball1->move(plane.getPosition()); else ball1->move(direction1); //if(point!=plane.getPosition()) // ball1->type=1; } else { if(pDoc->balllist1.GetHeadPosition()==position2){ pDoc->balllist1.RemoveHead(); delete ball1; } else if(pDoc->balllist1.GetTailPosition()==position2){ pDoc->balllist1.RemoveTail(); delete ball1; } else if(pDoc->balllist1.GetAt(position2)!=NULL) { pDoc->balllist1.RemoveAt(position2); delete ball1; } } } for(pos2=pDoc->balllist2.GetHeadPosition(); pos2!=NULL;) { position2=pos2; ball2=(Cball*)pDoc->balllist2.GetNext(pos2); if((ball2->getPosition().y)>lpRect.top){ if(level>=3) ball2->move(plane.getPosition()); else ball2->move(direction2); //ball2->move(direction2); //if(point!=plane.getPosition()) // ball2->type=1; } else { if(pDoc->balllist2.GetHeadPosition()==position2){ pDoc->balllist2.RemoveHead(); delete ball2; } else if(pDoc->balllist2.GetTailPosition()==position2){ pDoc->balllist2.RemoveTail(); delete ball2; } else if(pDoc->balllist2.GetAt(position2)!=NULL) { pDoc->balllist2.RemoveAt(position2); delete ball2; } } } this->Invalidate(TRUE); break; } case 4:{ //boss子弹移动 if(level>=4){ int i=1; Cball *ball3; POSITION pos2,position2; int direction1=-1; for(pos2=pDoc->balllist3.GetHeadPosition(); pos2!=NULL;) { position2=pos2; ball3=(Cball*)pDoc->balllist2.GetNext(pos2); ball3->setSpeed(40); if((ball3->getPosition().y)<lpRect.bottom) if(level==5) ball3->move(plane.getPosition()); else ball3->move(); else { if(pDoc->balllist3.GetHeadPosition()==position2){ pDoc->balllist3.RemoveHead(); delete ball3; } else if(pDoc->balllist3.GetTailPosition()==position2){ pDoc->balllist3.RemoveTail(); delete ball3; } else if(pDoc->balllist3.GetAt(position2)!=NULL) { pDoc->balllist3.RemoveAt(position2); delete ball3; } } } } //boss移动 if(level>=4&&boss!=NULL){ boss->move(); } this->Invalidate(TRUE); break; } } } shot();//射击 pass();//通关 CView::OnTimer(nIDEvent);}//射击int CPlaneGameView::shot(void){ CPlaneGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return 1; if(isPause == 0){ CRect tempRect; //战机子弹打中向下走的敌机 POSITION pos,position,pos1,position1; Cenemy *enemy1,*enemy2; Cbomb *bomb1; for(pos=pDoc->enemylist1.GetHeadPosition();pos!=NULL;){ position=pos; enemy1=(Cenemy*)pDoc->enemylist1.GetNext(pos); CRect eneRect = enemy1->getRect(); for(pos1=pDoc->bomblist1.GetHeadPosition();pos1!=NULL;){ position1=pos1; bomb1=(Cbomb*)pDoc->bomblist1.GetNext(pos1); CRect bomRect = bomb1->getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&bomRect,eneRect)) { //将爆炸对象添加到爆炸链表中 Cexplosion *explosion=new Cexplosion((enemy1->getPosition().x+17),(enemy1->getPosition().y+17)); pDoc->explosionlist.AddHead(explosion); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); //删除子弹 if(pDoc->bomblist1.GetHeadPosition()==position){ pDoc->bomblist1.RemoveHead(); delete bomb1; } else if(pDoc->bomblist1.GetTailPosition()==position){ pDoc->bomblist1.RemoveTail(); delete bomb1; } else if(pDoc->bomblist1.GetAt(position1)!=NULL){ pDoc->bomblist1.RemoveAt(position1); delete bomb1; } //删除敌机 if(pDoc->enemylist1.GetHeadPosition()==position1){ pDoc->enemylist1.RemoveHead(); delete enemy1; } else if(pDoc->enemylist1.GetTailPosition()==position1){ pDoc->enemylist1.RemoveTail(); delete enemy1; } else if(pDoc->enemylist1.GetAt(position)!=NULL){ pDoc->enemylist1.RemoveAt(position); delete enemy1; } score++; lifenum++; break; } } } //战机子弹打中向上走的敌机 for(pos=pDoc->enemylist2.GetHeadPosition();pos!=NULL;){ position=pos; enemy2=(Cenemy*)pDoc->enemylist2.GetNext(pos); CRect eneRect = enemy2->getRect(); for(pos1=pDoc->bomblist1.GetHeadPosition();pos1!=NULL;){ position1=pos1; bomb1=(Cbomb*)pDoc->bomblist1.GetNext(pos1); CRect bomRect = bomb1->getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&bomRect,eneRect)) { //将爆炸对象添加到爆炸链表中 Cexplosion *explosion=new Cexplosion((enemy2->getPosition().x+17),(enemy2->getPosition().y+17)); pDoc->explosionlist.AddHead(explosion); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); //删除子弹 if(pDoc->bomblist1.GetHeadPosition()==position){ pDoc->bomblist1.RemoveHead(); delete bomb1; } else if(pDoc->bomblist1.GetTailPosition()==position){ pDoc->bomblist1.RemoveTail(); delete bomb1; } else if(pDoc->bomblist1.GetAt(position1)!=NULL){ pDoc->bomblist1.RemoveAt(position1); delete bomb1; } //删除敌机 if(pDoc->enemylist2.GetHeadPosition()==position1){ pDoc->enemylist2.RemoveHead(); delete enemy2; } else if(pDoc->enemylist2.GetTailPosition()==position1){ pDoc->enemylist2.RemoveTail(); delete enemy2; } else if(pDoc->enemylist2.GetAt(position)!=NULL){ pDoc->enemylist2.RemoveAt(position); delete enemy2; } score++; lifenum++; break; } } } Cball *balllist1,*balllist2,*ball3; //敌机向下的子弹打中战机 for(pos=pDoc->balllist1.GetHeadPosition();pos!=NULL;){ position=pos; balllist1=(Cball*)pDoc->balllist1.GetNext(pos); CRect ballRect = balllist1->getRect(); CRect planeRect = plane.getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&ballRect,planeRect)) { if(position==pDoc->balllist1.GetHeadPosition()){ pDoc->balllist1.RemoveHead(); delete balllist1; } else if(position==pDoc->balllist1.GetTailPosition()){ pDoc->balllist1.RemoveTail(); delete balllist1; } else if(pDoc->balllist1.GetAt(position)!=NULL) { pDoc->balllist1.RemoveAt(position); delete balllist1; } lifenum--; break; } } //敌机向上的子弹打中战机 for(pos=pDoc->balllist2.GetHeadPosition();pos!=NULL;){ position=pos; balllist2=(Cball*)pDoc->balllist2.GetNext(pos); CRect ballRect = balllist2->getRect(); CRect planeRect = plane.getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&ballRect,planeRect)) { if(position==pDoc->balllist2.GetHeadPosition()){ pDoc->balllist2.RemoveHead(); delete balllist2; } else if(position==pDoc->balllist2.GetTailPosition()){ pDoc->balllist2.RemoveTail(); delete balllist2; } else if(pDoc->balllist2.GetAt(position)!=NULL){ pDoc->balllist2.RemoveAt(position); delete balllist2; } lifenum--; break; } } //战机与向下走的敌机相撞 for(pos=pDoc->enemylist1.GetHeadPosition();pos!=NULL;){ position=pos; enemy1=(Cenemy*)pDoc->enemylist1.GetNext(pos); CRect eneRect = enemy1->getRect(); CRect planeRect = plane.getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&eneRect,planeRect)) { ////将爆炸对象添加到爆炸链表中 Cexplosion *explosion=new Cexplosion((enemy1->getPosition().x+17),(enemy1->getPosition().y+17)); pDoc->explosionlist.AddHead(explosion); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); if(position==pDoc->enemylist1.GetHeadPosition()){ pDoc->enemylist1.RemoveHead(); delete enemy1; } else if(position==pDoc->enemylist1.GetTailPosition()){ pDoc->enemylist1.RemoveTail(); delete enemy1; } else if(pDoc->enemylist1.GetAt(position)!=NULL) { pDoc->enemylist1.RemoveAt(position); delete enemy1; } lifenum=lifenum-5; break; } } //战机与向上走的敌机相撞 for(pos=pDoc->enemylist2.GetHeadPosition();pos!=NULL;){ position=pos; enemy2=(Cenemy*)pDoc->enemylist2.GetNext(pos); CRect eneRect = enemy2->getRect(); CRect planeRect = plane.getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&eneRect,planeRect)) { ////将爆炸对象添加到爆炸链表中 Cexplosion *explosion=new Cexplosion((enemy2->getPosition().x+17),(enemy2->getPosition().y+17)); pDoc->explosionlist.AddHead(explosion); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); //删除敌机 if(position==pDoc->enemylist2.GetHeadPosition()){ pDoc->enemylist2.RemoveHead(); delete enemy2; } else if(position==pDoc->enemylist2.GetTailPosition()){ pDoc->enemylist2.RemoveTail(); delete enemy2; } else if(pDoc->enemylist2.GetAt(position)!=NULL) { pDoc->enemylist2.RemoveAt(position); delete enemy2; } lifenum=lifenum-5; break; } } if(level>=4){ //boss的子弹打中战机 for(pos=pDoc->balllist3.GetHeadPosition();pos!=NULL;){ position=pos; ball3=(Cball*)pDoc->balllist3.GetNext(pos); CRect ballRect = ball3->getRect(); CRect planeRect = plane.getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&ballRect,planeRect)) { //删除boss子弹 if(position==pDoc->balllist3.GetHeadPosition()){ pDoc->balllist3.RemoveHead(); delete ball3; } else if(position==pDoc->balllist3.GetTailPosition()){ pDoc->balllist3.RemoveTail(); delete ball3; } else if(pDoc->balllist3.GetAt(position)!=NULL){ pDoc->balllist3.RemoveAt(position); delete ball3; } lifenum--; break; } } //boss与战机相撞 if(boss!=NULL){ CRect bossRect = boss->getRect(); CRect planeRect = plane.getRect(); if(tempRect.IntersectRect(&bossRect,planeRect)) { lifenum=lifenum-10; //bosslife=bosslife-5; } //战机子弹打中boss for(pos1=pDoc->bomblist1.GetHeadPosition();pos1!=NULL;){ position1=pos1; bomb1=(Cbomb*)pDoc->bomblist1.GetNext(pos1); CRect bomRect = bomb1->getRect(); //判断两个矩形区域是否有交接 if(tempRect.IntersectRect(&bomRect,bossRect)) { //删除子弹 if(pDoc->bomblist1.GetHeadPosition()==position){ pDoc->bomblist1.RemoveHead(); delete bomb1; } else if(pDoc->bomblist1.GetTailPosition()==position){ pDoc->bomblist1.RemoveTail(); delete bomb1; } else if(pDoc->bomblist1.GetAt(position1)!=NULL){ pDoc->bomblist1.RemoveAt(position1); delete bomb1; } score++; lifenum++; bosslife--; break; } } } } } if(bosslife<=0&&boss!=NULL){ //将爆炸对象添加到爆炸链表中 Cexplosion *explosion=new Cexplosion((boss->getPosition().x+17),(boss->getPosition().y+17)); pDoc->explosionlist.AddHead(explosion); PlaySound((LPCTSTR)IDR_WAVE2, AfxGetInstanceHandle(), SND_RESOURCE |SND_ASYNC); delete boss; boss=NULL; bosslife=0; } return 0;}int CPlaneGameView::pass(void){ CPlaneGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return 1; //过关 if(score>=100&&isPause==0){ isPass = 1; } if(isPass==1){ if(level>=5){ KillTimer(1); KillTimer(2); KillTimer(3); KillTimer(4); if(AfxMessageBox(L"恭喜你已通关!是否重新开始?",MB_YESNO)==6) { pDoc->enemylist1.RemoveAll(); pDoc->enemylist2.RemoveAll(); pDoc->bomblist1.RemoveAll(); pDoc->balllist1.RemoveAll(); pDoc->balllist2.RemoveAll(); pDoc->balllist3.RemoveAll(); score=0; lifenum=100; bosslife=30; isPause=0; isPass=0; level=1; boss=new Cboss; SetTimer(1,100,NULL); SetTimer(2,60,NULL); SetTimer(3,50,NULL); SetTimer(4,500,NULL); } else exit(1); } else if(level<5) { KillTimer(1); KillTimer(2); KillTimer(3); KillTimer(4); if(AfxMessageBox(L"恭喜你已过关!是否进入下一关?",MB_YESNO)==6) { pDoc->enemylist1.RemoveAll(); pDoc->enemylist2.RemoveAll(); pDoc->bomblist1.RemoveAll(); pDoc->balllist1.RemoveAll(); pDoc->balllist2.RemoveAll(); pDoc->balllist3.RemoveAll(); score=0; level++; lifenum=100*level; bosslife=50; isPause=0; isPass=0; boss=new Cboss; SetTimer(1,100,NULL); SetTimer(2,60,NULL); SetTimer(3,50,NULL); SetTimer(4,500,NULL); } else exit(1); } } if(lifenum<=0&&isPass==0&&isPause==0){ KillTimer(1); KillTimer(2); KillTimer(3); KillTimer(4); if(AfxMessageBox(L"GAME OVER!是否重新开始?",MB_YESNO)==6) { pDoc->enemylist1.RemoveAll(); pDoc->enemylist2.RemoveAll(); pDoc->bomblist1.RemoveAll(); pDoc->balllist1.RemoveAll(); pDoc->balllist2.RemoveAll(); pDoc->balllist3.RemoveAll(); score=0; lifenum=100; bosslife=50; isPause=0; isPass=0; level=1; SetTimer(1,100,NULL); SetTimer(2,60,NULL); SetTimer(3,50,NULL); SetTimer(4,500,NULL); } else exit(1); } return 0;}
    6 测试情况说明6.1 主要模块测试情况(白盒)6.1.1 战机模块测试


    上方向键
    战机向上移动
    到达边界时停止




    下方向键
    战机向下移动
    到达边界时停止


    左方向键
    战机向左移动
    到达边界时停止


    右方向键
    战机向右移动
    到达边界时停止


    空格键
    战机双排子弹
    到达边界和击中敌机时消失


    Ctrl键
    战机三列散弹
    到达边界和击中敌机时消失


    Shift键
    战机五列散弹
    到达边界和击中敌机时消失


    X键
    战机追踪弹
    到达边界和击中敌机时消失



    6.1.2 敌机测试模块


    向上飞的敌机
    从下边界出发,到达上边界和被击中时消失




    向下飞的敌机
    从上边界出发,到达下边界和被击中时消失


    向上飞的敌机发射向上的子弹
    从某一敌机出发,到达上边界或已使用消失


    向下飞的敌机发射向下的子弹
    从某一敌机出发,到达下边界或已使用消失



    6.1.3 BOSS测试模块


    随机出现
    在游戏界面某一不确定位置出现




    向下移动
    boss出现后向下移动


    到达一定点后不再移动
    到达中间位置后不再移动


    三列散弹
    发射三列散弹攻击战机


    追踪弹
    追踪战机的位置并向其发射追踪弹



    6.2 主要功能测试情况(黑盒)6.2.1 移动功能测试


    战机
    战机按方向键可以正常上下左右移动




    敌机
    敌机从上下两个方向移动


    战机子弹
    战机子弹沿直线移动


    敌机子弹
    敌机子弹沿竖直方向移动


    boss
    boss上下移动


    boss子弹
    boss的子弹沿直线移动



    6.2.2 爆炸功能测试


    战机与敌机相撞
    敌机爆炸并产生声音效果




    战机子弹击中敌机
    敌机爆炸并产生声音效果


    战机打死boss
    boss爆炸并产生声音效果



    6.2.3 得分功能测试


    战机击中敌机
    得一分




    战机与敌机相撞
    减生命值五分


    战机击中boss
    得一分


    战机与boss相撞
    减生命值十分


    敌机击中战机
    减生命值一分


    Boss击中战机
    减生命值一分



    7 实训中遇到的主要问题及解决方法在刚开始设置难度的时候老是出错,后来重新再次添加了事件处理函数之后就解决了这个问题。
    在第四关时有一个大boss,在将其打死之后,delete boss时出错,后来发现是在删除之后boss就空了,所以在使用boss对象的成员函数时,先判断一下boss是否为空,问题解决。在测试中发现当战机的子弹打中敌机之后没有消失,主要原因是子弹的发射速度太快以及定时器设置的不合理,画面上虽然显示可能是两颗子弹,但是实际上并不只有两颗,所以子弹没有消失,经过修改定时器和子弹发射速度解决了这个问题。
    8 实训体会通过这次的C++实训,深刻体会了MFC的类库以及其消息传递机制,使我的编程能力有了很大的提高,同时更重要的是提高了自学能力即包括资料检索、阅读理解等能力。
    刚开始的时候不是很了解MFC中的类有哪些,对如何添加类也不太会,后来,向同学询问和自己查资料熟悉了一些常用的MFC类,对于一些函数的使用也有了更加深刻的理解。
    通过这次的实训更加加深了我对C++的了解,是我了解到在课堂上学到的C++知识只是小小的一部分,经过这次的实训使我提高了不少,增强了我掌握和利用C++进行程设计的能力, 而且进一步理解和运用结构化程设计的思想和方法。初步掌握了开发系统应用程序的基本方法和步骤。
    在实现飞机大战的过程中,确实感觉到自己知识的薄弱,很多知识不会,例如visual studio2008中的很多功能的作用,这次实习使我对编程有了新的认识和定义,其实程序的编程不是枯燥和乏味的,而是包含着轻松和愉悦,每当自己解决了一个刺手的问题后,自己都有一种成功感。
    9 游戏演示游戏初始弹窗

    游戏说明

    游戏画面1

    游戏画面2

    游戏结束
    1 评论 41 下载 2018-11-05 15:52:29 下载需要9点积分
  • 基于C#和ACCESS数据库实现的水电管理信息系统

    1 需求和规格说明设计一个水电管理信息系统,能够对高校的水电费用进行管理,包括了登记费用,查询费用,以及住户信息管理等。在设计时要考虑到学生和教工在用水电时的不同,学生可以免费使用一定额度的水电,超过这个额度的随便以后必须自费使用,且自费部分水电费的价格标准要高于教工的收费标准(主要是节约资源)。

    实现对用户信息的录入
    实现水电煤气数据的录入
    计算并查询用户应缴费用
    查询未缴纳费用名单

    2 设计2.1 用户登陆
    设定一个唯一的管理员用于管理数据
    密码使用md5加混合方式
    保存于databse/ PWD.mdb

    2.2 用户的录入和查询
    所有用户的信息放置于数据库databse/ data.mdb
    数据库密码zxcasdqwe
    应缴费用等详细显示在主界面
    默认的配置config.ini
    后台服务设置都可以再这里实现
    历史信息以文件的形式放入userdata

    2.3 考虑到用户可能需要远程查询数据,加入网络模块
    考虑到可以让用户用浏览器浏览,所以协议使用tcp/ip
    实际使用方面抛弃了原vb的activeX控件
    使用.net库中的Tcpclienter和Tcplistener

    2.4 设计语言采用c#(有类的概念.类C语言系)2.5 用户信息数据库采用access,oledb方式连接
    保存于databse/ data.mdb
    2.6 附加程序在不使用主程序的时候可使用此程序建立后台服务,供远程用户查询数据。



    类名
    属性/方法
    访问级别
    说明




    sck

    public
    网络通信模块



    listen(int ThreadNum,int port)
    public
    使用多线程.监听本地端口



    ls()
    private
    由线程控制的函数 用于处理连接的quest并返回数据


    server

    public
    结构体



    text
    public
    用于在启动窗口和后台线程间传递服务器状态消息


    ReadFile

    public
    读取和设置文件



    Existen (string path)
    public
    判断文件是否存在



    GetConfig(string path,string name)
    public
    读取配置文件 返回设定的值



    SetPwd (string path,string username,string userpwd)
    public
    设置管理员密码



    AddDetail(string path, string detail)
    public
    添加历史记录



    SetPwd(string path,string username,string userpwd)
    public
    设置用户密码


    Launcher


    启动窗体



    string GetMd6STR(string ConvertString)
    public
    给字符编码(md5+混合…)


    Mainform


    主窗体



    LoadData()
    public
    载入数据



    setdefault()
    public
    默认值的载入



    updatemoney()
    public
    更新应缴金额


    AboutBox


    版权声明窗体



    2.7 系统类图
    3 用户手册
    如果无法启动,请先安装.NET framework 2.0
    默认账户admin,密码icyfire,请妥善保管您的密码
    您可以自由发布此程序,但请注明作者白忠魏(princehaku)
    在未获得作者本人同意下请不要尝试破解,修改此软件
    配置文件在config.ini;默认端口为80,线程数为10
    BackgroundServise使用说明;此附加程序为后台服务程序,主程序已带有此功能
    在不使用主程序的时候可使用此程序建立后台服务,供远程用户查询数据
    程序接口p 端口 t线程数

    例:BackgroundServise.exe /p 80 /t 10->将在本地开启10个线程监听80端口超过此线程用户将无法连接
    访问方式;浏览器输入服务器ip:服务器端口
    例: 服务器::cmd{c:/>ipconfig+---------------------------------------------+IP Address. . . . . . . . . . . . : 125.70.161.182+---------------------------------------------+c:/>BackgroundServise.exe /p 80 /t 10远程用户::打开浏览器.输入地址http://125.70.161.182:80回车即可ps:服务器自己可使用http://localhost:804 调试及测试4.1 本地测试登陆系统

    新增数据

    保存数据

    删除数据

    详细记录

    修改


    查询


    4.2 网络测试浏览器查看

    测试id为123123的用户(不存在此用户)

    命令行结果:
    GET /?id=123&button=%E6%9F%A5%E8%AF%A2 HTTP/1.1./UserData/wegDbfile-123.rdb123无此数据测试id为200403080326的用户(存在此用户)

    命令行结果:
    GET /?id=200403080326&button=%E6%9F%A5%E8%AF%A2 HTTP/1.1200403080326数据发送成功5 关键代码5.1 主界面代码using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Security.Cryptography;using Readfile;namespace My_project{ public partial class MainForm : Form { private bool Edit; Readfile.Readfile rf = new Readfile.Readfile(); public MainForm() { InitializeComponent(); } public struct cell { public static int ColumnIndex; public static int RowIndex; public static string usertype; } private void userBindingNavigatorSaveItem_Click(object sender, EventArgs e) { //保存并重置数据单元 this.Validate(); this.userBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.dataDataSet); this.LoadData(); this.Edit = false; } //数据加密 public static string GetMd6Str(string ConvertString) { MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-", "B"); t2 += BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-", "Z"); t2 += BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-", "W"); return t2; } //填充数据库 public void LoadData() { this.userTableAdapter.Fill (this.dataDataSet.user); } private void 删除ToolStripMenuItem_Click(object sender, EventArgs e) { foreach (DataGridViewCell c in userDataGridView.SelectedCells) { if (c.RowIndex>=0) { this.userDataGridView.Rows.RemoveAt(c.RowIndex); } } } //设定当前单元格 private void userDataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { if (userDataGridView.SelectedCells.Count == 1 & e.ColumnIndex >= 0 & e.RowIndex >= 0 & e.Button==MouseButtons .Right) { userDataGridView.CurrentCell = userDataGridView[e.ColumnIndex, e.RowIndex]; } } private void 关于本程序ToolStripMenuItem_Click(object sender, EventArgs e) { AboutBox newabout= new AboutBox (); newabout.Show(); } private void fwToolStripMenuItem_Click(object sender, EventArgs e) { groupBox3.Visible = true; } private void MainForm_FormClosed(object sender, FormClosedEventArgs e) { if(this.Edit == true) { switch (MessageBox.Show("是否保存您作的修改?", "关闭程序", MessageBoxButtons.YesNo)) { case DialogResult.Yes: this.Validate(); this.userBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.dataDataSet); break; } } //else { //终止所有线程并结束程序 Application.Exit(); } } private void hideall() { //隐藏所有可能出现的窗体 groupBox2.Visible = false; button1.Visible = false; groupBox1.Visible = false; } private void userDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { string[] data=new string[0]; hideall(); this.Edit = true; if (e.RowIndex >= 0) { try { switch (e.ColumnIndex) { //配额 case 5: groupBox1.Text = "用户配额"; //读取数据 data = userDataGridView.CurrentCell.Value.ToString().Split('|'); if (data[0] != "") { water.Text = data[0]; elec.Text = data[1]; gas.Text = data[2]; } else { water.Text = "0"; elec.Text = "0"; gas.Text = "0"; } cell.ColumnIndex = e.ColumnIndex; cell.RowIndex = e.RowIndex; groupBox1.Visible = true; button1.Visible = true; //用户类型 cell.usertype = userDataGridView[e.ColumnIndex - 1, e.RowIndex].Value.ToString(); break; //单价 case 6: groupBox1.Text = "单价"; //读取数据 data = userDataGridView.CurrentCell.Value.ToString().Split('|'); if (data[0] != "") { water.Text = data[0]; elec.Text = data[1]; gas.Text = data[2]; } else { water.Text = "0"; elec.Text = "0"; gas.Text = "0"; } cell.ColumnIndex = e.ColumnIndex; cell.RowIndex = e.RowIndex; groupBox1.Visible = true; button1.Visible = true; //用户类型 cell.usertype = userDataGridView[e.ColumnIndex - 2, e.RowIndex].Value.ToString(); break; //使用 case 7: groupBox1.Text = "已经使用"; //读取数据 data = userDataGridView.CurrentCell.Value.ToString().Split('|'); if (data[0] != "") { water.Text = data[0]; elec.Text = data[1]; gas.Text = data[2]; } else { water.Text = "0"; elec.Text = "0"; gas.Text = "0"; } cell.ColumnIndex = e.ColumnIndex; cell.RowIndex = e.RowIndex; groupBox1.Visible = true; //用户类型 cell.usertype = userDataGridView[e.ColumnIndex - 3, e.RowIndex].Value.ToString(); ; break; //缴费 case 8: recent.Text = userDataGridView[9, e.RowIndex].Value.ToString(); updatemoney(); button1.Visible = true; groupBox2.Visible = true; //应交费的信息 textBox4.Text = userDataGridView[e.ColumnIndex, e.RowIndex].Value.ToString(); ; break; default: this.userDataGridView.BeginEdit(true);break; } handin.Text = ""; } catch { groupBox1.Text = ""; } } } private void 详情ToolStripMenuItem_Click(object sender, EventArgs e) { //详情载入 //string type = userDataGridView.CurrentCellAddress.X(); string userid = "wegDbfile-" + userDataGridView[1, userDataGridView.CurrentCell.RowIndex].Value.ToString(); string path=Application.StartupPath + "/UserData/" + userid + ".rdb"; detail de = new detail(path,userDataGridView[1, userDataGridView.CurrentCell.RowIndex].Value.ToString()); de.Show(); } private void 数据库导出ToolStripMenuItem_Click(object sender, EventArgs e) { saveFileDialog1.ShowDialog(); if (saveFileDialog1.FileName.ToString() != "") { this.dataDataSet.WriteXml(saveFileDialog1.FileName.ToString()); } } private void toolStripSplitButton1_ButtonClick(object sender, EventArgs e) { toolStripSplitButton1.ShowDropDown(); } private void toolStripButton1_Click(object sender, EventArgs e) { //this.Search_col.Text.Trim; //this.Search_con.Text.Trim; if (this.Search_col.Text.ToString() != "" & this.Search_con.Text.ToString() != "") { userTableAdapter.Adapter.SelectCommand.CommandText = "SELECT [user].* FROM [user] WHERE [" + this.Search_col.Text.Trim() + "] like '%" + this.Search_con.Text.Trim() + "%'"; this.userTableAdapter.Fill(this.dataDataSet.user); } } private void toolStripButton2_Click(object sender, EventArgs e) { userTableAdapter.Adapter.SelectCommand.CommandText = "SELECT [user].* FROM [user]"; this.userTableAdapter.Fill(this.dataDataSet.user); } private void MainForm_Load(object sender, EventArgs e) { } //限制输入数字 private void water_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar != 8 && !Char.IsDigit(e.KeyChar)&& !(e.KeyChar == '.')) { e.Handled = true; } } private void elec_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar != 8 && !Char.IsDigit(e.KeyChar) && !(e.KeyChar == '.')) { e.Handled = true; } } private void handin_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar != 8 && !Char.IsDigit(e.KeyChar) && !(e.KeyChar == '.')) { e.Handled = true; } } private void gas_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar != 8 && !Char.IsDigit(e.KeyChar) && !(e.KeyChar == '.')) { e.Handled = true; } } private void accept_Click(object sender, EventArgs e) { userDataGridView[cell.ColumnIndex, cell.RowIndex].Value = water.Text + '|' + elec.Text + "|" + gas.Text; if(groupBox1.Text=="已经使用") { //更新应交金额 updatemoney(); } //加入历史 string userid = "wegDbfile-" + userDataGridView[1, userDataGridView.CurrentCell.RowIndex].Value.ToString(); string detail = DateTime.Now.ToString() + "-----" + groupBox1.Text + "改变为" +getdata() +"<br />"; rf.AddDetail(Application.StartupPath + "/UserData/" + userid + ".rdb", detail); groupBox1.Visible = false; } //得到详细改变的内容 private string getdata() { switch (groupBox1.Text.ToString()) { case "已经使用": return userDataGridView.CurrentCell.Value.ToString(); case "用户配额": return userDataGridView.CurrentCell.Value.ToString(); case "单价": return userDataGridView.CurrentCell.Value.ToString(); } return "未知"; } private void button1_Click(object sender, EventArgs e) { setdefault(); } private void pictureBox1_Click(object sender, EventArgs e) { groupBox1.Visible = false; } //+++++++++++++++++++++ private void setdefault() { //默认值的取得 Readfile.Readfile rf = new Readfile.Readfile(); if (groupBox1.Text == "用户配额") { if (cell.usertype == "教师") { water.Text = "0"; elec.Text = "0"; gas.Text = "0"; } else { water.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "waterlimit"); elec.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "eleclimit"); gas.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "gaslimit"); } } if (groupBox1.Text == "单价") { if (cell.usertype == "教师") { water.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "watertc"); elec.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "electc"); gas.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "gastc"); } else { water.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "waterst"); elec.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "elecst"); gas.Text = rf.GetConfig(Application.StartupPath + "/config.ini", "gasst"); } } //rf.GetConfig(); /*water.Text = data[0]; elec.Text = data[1]; gas.Text = data[2]; * */ } private void pictureBox2_Click(object sender, EventArgs e) { groupBox2.Visible = false; } private void button2_Click(object sender, EventArgs e) { if (handin.Text != "") { //修改余额 userDataGridView[9, userDataGridView.CurrentCell.RowIndex].Value = double.Parse(recent.Text.ToString()) + double.Parse(handin.Text.ToString()) - double.Parse(textBox4.Text.ToString()); recent.Text=userDataGridView[9, userDataGridView.CurrentCell.RowIndex].Value.ToString(); //加入历史 string userid = "wegDbfile-" + userDataGridView[1, userDataGridView.CurrentCell.RowIndex].Value.ToString(); string detail = DateTime.Now.ToString() + "-----应交" + textBox4.Text.ToString() + "元" + "<br />" + "----------------------缴入" + handin.Text.ToString() + "元" + "<br />" + "----------------------剩余" + recent.Text.ToString() + "元" ; rf.AddDetail(Application.StartupPath + "/UserData/" + userid + ".rdb", detail); //清空已经使用 userDataGridView[userDataGridView.CurrentCell.ColumnIndex - 1, userDataGridView.CurrentCell.RowIndex].Value = "0|0|0"; updatemoney(); groupBox2.Visible = false; textBox4.Text = "0"; } } private void updatemoney() { string[] used; string[] limit; string[] per; double money=0.0; double me = 0.0; //已经使用 used=userDataGridView[7, userDataGridView.CurrentCell.RowIndex].Value.ToString().Split('|'); //单价 per = userDataGridView[6, userDataGridView.CurrentCell.RowIndex].Value.ToString().Split('|'); //配额 limit = userDataGridView[5, userDataGridView.CurrentCell.RowIndex].Value.ToString().Split('|'); //用户类型 try { if (cell.usertype == "教师") for (int i = 0; i < 3; i++) { money += double.Parse(used[i]) * double.Parse(per[i]); } else { for (int i = 0; i < 3; i++) { //如果没超过使用限额算0元 if (double.Parse(limit[i]) - double.Parse(used[i]) >= 0) { me = 0; } else { me=(double.Parse(used[i]) - double.Parse(limit[i])) * double.Parse(per[i]); } money += me; } } if (recent.Text=="") { recent.Text = "0"; } //money = money - double.Parse(recent.Text.ToString()); userDataGridView[8, userDataGridView.CurrentCell.RowIndex].Value = money.ToString(); } catch { userDataGridView[8, userDataGridView.CurrentCell.RowIndex].Value = "数据格式有误..请检查"; } } private void pictureBox3_Click(object sender, EventArgs e) { groupBox3.Visible = false; } private void button3_Click(object sender, EventArgs e) { string user = rf.GetConfig(Application.StartupPath + "/Database/PWD.mdb", "username"); string pwd = rf.GetConfig(Application.StartupPath + "/Database/PWD.mdb", "userpassword"); if (curpwd.Text == "" || newpwd.Text == "" || newpwd2.Text == "") { MessageBox.Show("密码不能为空"); return; } if (newpwd.Text != newpwd2.Text) { MessageBox.Show("两次输入不同"); return; } if (GetMd6Str(curpwd.Text) == pwd) { //写入密码 rf.SetPwd(Application.StartupPath + "/Database/PWD.mdb", GetMd6Str("admin"), GetMd6Str(newpwd.Text)); MessageBox.Show("设置成功"); } else { MessageBox.Show("原密码错误"); } //隐藏并清空 groupBox3.Visible = false; curpwd.Text = ""; newpwd.Text = ""; newpwd2.Text = ""; } private void 复制单元格ToolStripMenuItem_Click(object sender, EventArgs e) { Clipboard.SetText(userDataGridView.CurrentCell.Value.ToString()); } private void 粘贴单元格ToolStripMenuItem_Click(object sender, EventArgs e) { userDataGridView.CurrentCell.Value = Clipboard.GetText(); } private void dToolStripMenuItem_Click(object sender, EventArgs e) { } }}
    5.2 网络模块代码using System;using System.Collections.Generic;using System.Text;using System.Windows.Forms;using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;using Readfile;namespace My_project{ class sck { //创建新tcplistenr监听端口 TcpListener listener = new TcpListener(IPAddress.Any, 8080); public void listen(int ThreadNum,int port) { //改变端口 listener = new TcpListener(IPAddress.Any, port); try { listener.Start(); server.text = "后台服务器启动成功"; //MessageBox.Show (listener.LocalEndpoint.ToString()); //创建线程 for (int i = 1; i <= ThreadNum; i++) { Thread thrd = new Thread(new ThreadStart(this.ls)); thrd.IsBackground = true; thrd.Start(); } } catch { server.text = "socket错误,未能启动"; } } private void ls() { string GET,data; string[] result; TcpClient client = listener.AcceptTcpClient(); //创建网络流 NetworkStream netStream = client.GetStream(); StreamReader sr = new StreamReader(client.GetStream()); //读取第一行的东西然后关闭sr GET=sr.ReadLine().ToString(); StreamWriter sw = new StreamWriter(client.GetStream()); Readfile.Readfile rf = new Readfile.Readfile(); result=GET.Split(' '); //sw.Write("HTTP/1.1 200 OK\r\n"); if (result[1] == "") { ; } if (result[1] == "/"|result[1] == "/index.html") { //显示首页 // StreamReader fsr = new StreamReader(Application.StartupPath + "/UserData/default.dba"); while (fsr.EndOfStream == false) { sw.Write(fsr.ReadLine().ToString()); } fsr.Close(); sw.Close(); sr.Close(); netStream.Close(); } else { try {//分离& string[] fs = result[1].Split('&'); //分离= string[] fr = fs[0].Split('='); if (fr[1].ToString() != "") { if (rf.Existen(Application.StartupPath + "/UserData/wegDbfile-" + fr[1].ToString() + ".rdb")) { StreamReader fsr = new StreamReader(Application.StartupPath + "/UserData/wegDbfile-" + fr[1].ToString() + ".rdb"); while (fsr.EndOfStream == false) { sw.Write(fsr.ReadLine().ToString()); } fsr.Close(); sw.Close(); sr.Close(); netStream.Close(); } else { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); sw.Close(); sr.Close(); netStream.Close(); } } else { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); sw.Close(); sr.Close(); netStream.Close(); } } catch { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); sw.Close(); sr.Close(); netStream.Close(); } //MessageBox.Show(data); } //此线程退出时创建新线程 Thread thrd = new Thread(new ThreadStart(this.ls)); thrd.IsBackground = true; thrd.Start(); } }}
    5.3 启动窗体代码using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Security.Cryptography;using Readfile;namespace My_project{ //公共结构..用于传递后台服务器状况 public struct server { public static string text; } public partial class Launcher : Form { //public int i; MainForm mainform = new MainForm(); public Launcher() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { if (progressBar1.Value == 100) { progressBar1.Value = 0; } progressBar1.Value += 1; //如果线程结束则清空资源进入主窗口 switch (progressBar1.Value) { case 1: label3.Text = "载入配置文件"; break; case 20: label3.Text = "载入数据库"; break; case 40: label3.Text = "设置后台线程"; break; case 60: label3.Text = server.text; break; case 80: label3.Text = "载入界面"; break; } if (!backgroundWorker1.IsBusy & progressBar1.Value >= 90) { //释放线程资源 backgroundWorker1.CancelAsync(); backgroundWorker1.Dispose(); this.Hide(); this.ShowInTaskbar = false; mainform.Show(); timer1.Enabled = false; } } private void Launcher_Load(object sender, EventArgs e) { } public static string GetMd6Str(string ConvertString) { MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-", "B"); t2 += BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-","Z"); t2 += BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8).Replace("-", "W"); return t2; } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { //在此载入数据库 mainform.LoadData(); } private void button1_Click(object sender, EventArgs e) { groupBox1.Enabled =false ; //配置文件读取类 Readfile.Readfile cfg = new Readfile.Readfile(); button1.Enabled = false; //检验用户名和密码 string user=cfg.GetConfig(Application.StartupPath + "/Database/PWD.mdb", "username"); string pwd = cfg.GetConfig(Application.StartupPath + "/Database/PWD.mdb", "userpassword"); if (GetMd6Str(textBox1.Text) == user && GetMd6Str(textBox2.Text) == pwd) { this.Focus(); //改变鼠标形状 this.Cursor = System.Windows.Forms.Cursors.WaitCursor; progressBar1.Visible = true; button1.Visible = false; timer1.Enabled = true; backgroundWorker1.RunWorkerAsync(); //启用socket if(cfg.GetConfig(Application.StartupPath + "/config.ini", "enable")=="true") { sck sock = new sck(); //读取线程数量 sock.listen(int.Parse(cfg.GetConfig(Application.StartupPath + "/config.ini", "ThreadNum")), int.Parse(cfg.GetConfig(Application.StartupPath + "/config.ini", "port"))); } else { server.text="未开启后台服务";} } else { label3.Text = "用户名或密码错误"; timer2.Enabled = true; } } private void timer2_Tick(object sender, EventArgs e) { groupBox1.Enabled = true; button1.Enabled = true; label3.Text = ""; timer2.Enabled = false; } private void pictureBox2_Click(object sender, EventArgs e) { Application.Exit(); } private void Launcher_Paint(object sender, PaintEventArgs e) { //设定交点 if (textBox1.Text=="") { textBox1.Focus(); } } }}
    5.4 Readfile代码using System;using System.Collections.Generic;using System.Text;using System.IO;namespace Readfile{ public class Readfile { public string GetConfig(string path,string name) { StreamReader sr = new StreamReader(path); //初始化数组 string[] config = "1=1".ToString().Split('='); //循环读取所需设置 while (config[0] != name & sr.EndOfStream==false ) { config = sr.ReadLine().ToString().Split('='); } sr.Close(); try { return config[1]; } catch { return "error"; } } public bool SetPwd(string path,string username,string userpwd) { FileStream sm = new FileStream(path, FileMode.Create); StreamWriter sw = new StreamWriter(sm); sw.Write("[管理员信息]"); sw.Write("\r\n"); sw.Write("\r\n"); sw.Write("-------请勿改动以下内容------"); sw.Write("\r\n"); sw.Write("username="+username); sw.Write("\r\n"); sw.Write("userpassword="+userpwd); sw.Write("\r\n"); sw.Write("-------请勿改动以上内容------"); sw.Close(); sm.Close(); return true; } //历史记录加入 public void AddDetail(string path, string detail) { //如果文件不存在则新建 FileStream File = new FileStream(path, FileMode.OpenOrCreate); //如果是新建的文件。。。则写入meta属性 if (File.Length == 0) { StreamWriter sw1 = new StreamWriter(File); sw1.Write("<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\"></head><body>"); sw1.Close(); } File.Close(); FileStream append = new FileStream(path, FileMode.Append); StreamWriter sw = new StreamWriter(append); sw.Write(detail); //写入换行符 sw.Write("<br />"); sw.Close(); } public bool Existen(string path) { try { FileStream File = new FileStream(path, FileMode.Open); File.Close(); return true; } catch { return false; } } }}
    5.5 BackgroundServise代码using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;using Readfile;namespace BackgroundServise{ class Program { //创建新tcplistenr监听端口 static TcpListener listener = new TcpListener(IPAddress.Any,0); static void Main(string[] args) { Console.Clear(); int ThreadNum; int port; //初始值 ThreadNum = -1; port = -1; try { //改变端口和线程数 for (int i = 0; i < args.Length; i++) { string[] result = args[i].Split('='); if (result[0] == "p") { port = int.Parse(result[1]); } if (result[0] == "t") { ThreadNum = int.Parse(result[1]); } } if (ThreadNum == -1 || port == -1) { Console.Beep(); Console.Write("用法错误 \r\n程序接口p 端口 t线程数\r\n例 BackgroundServise.exe p=8080 t=10" + "\r\n将在本地开启10个线程监听8080端口\r\n超过此线程用户将无法连接\r\n"); return; } } catch { //错误捕获 Console.Beep(); Console.Write("用法错误 \r\n程序接口p 端口 t线程数\r\n例 BackgroundServise.exe p=8080 t=10" + "\r\n将在本地开启10个线程监听8080端口\r\n超过此线程用户将无法连接\r\n"); return; } Console.Write("水电管理系统后台服务器\r\n版权所有:白忠魏\r\n"); Console.Write("线程数" + ThreadNum.ToString()); Console.Write("\r\n"); Console.Write("端口" + port.ToString()+"\r\n"); //改变端口 listener = new TcpListener(IPAddress.Any, port); try { listener.Start(); Console.Write("开始监听"); //MessageBox.Show (listener.LocalEndpoint.ToString()); //创建线程 for (int i = 1; i <= ThreadNum; i++) { Thread thrd = new Thread(new ThreadStart(ls)); thrd.IsBackground = false; thrd.Start(); } } catch { Console.Beep(); Console.Write("socket错误,启动失败"); return; } } static private void ls() { string GET,data; string[] result; TcpClient client = listener.AcceptTcpClient(); //创建网络流 NetworkStream netStream = client.GetStream(); StreamReader sr = new StreamReader(client.GetStream()); //读取第一行的东西然后关闭sr GET=sr.ReadLine().ToString(); Console.Write("\r\n"); Console.Write(GET); StreamWriter sw = new StreamWriter(client.GetStream()); Readfile.Readfile rf = new Readfile.Readfile(); result=GET.Split(' '); //sw.Write("HTTP/1.1 200 OK\r\n"); if (result[1] == "") { ; } if (result[1] == "/"|result[1] == "/index.html") { //显示首页 // StreamReader fsr = new StreamReader("./UserData/default.dba"); while (fsr.EndOfStream == false) { sw.Write(fsr.ReadLine().ToString()); } fsr.Close(); sw.Close(); sr.Close(); netStream.Close(); } else { try {//分离& string[] fs = result[1].Split('&'); //分离= string[] fr = fs[0].Split('='); if (fr[1].ToString() != "") { if (rf.Existen("/UserData/wegDbfile-" + fr[1].ToString() + ".rdb")) { StreamReader fsr = new StreamReader("./UserData/wegDbfile-" + fr[1].ToString() + ".rdb"); while (fsr.EndOfStream == false) { sw.Write(fsr.ReadLine().ToString()); } Console.Write("\r\n"); Console.Write(fr[1].ToString()+"数据发送成功"); fsr.Close(); sw.Close(); sr.Close(); netStream.Close(); } else { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); Console.Write("\r\n"); Console.Write(fr[1].ToString() + "无此数据"); sw.Close(); sr.Close(); netStream.Close(); } } else { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); Console.Write("\r\n"); Console.Write(fr[1].ToString() + "数据发送失败"); sw.Close(); sr.Close(); netStream.Close(); } } catch { data = "<html><head><meta http-equiv=content-type content=\"text/html; charset=UTF-8\">数据不存在"; sw.Write(data); sw.Close(); sr.Close(); netStream.Close(); } //MessageBox.Show(data); } //此线程退出时创建新线程 Thread thrd = new Thread(new ThreadStart(ls)); thrd.IsBackground = false; thrd.Start(); } }}
    1 评论 12 下载 2018-11-05 15:46:06 下载需要8点积分
  • 基于C#实现的多线程端口扫描器

    1 概述1.1 课程设计目的加深TCP/IP协议的理解,掌握TCP四次握手机制,同时熟悉socket编程。
    1.2 课程设计内容实现一个端口扫描器:

    支持多进程/线程
    能对单个指定主机进行扫描或扫描指定网段内的主机
    能扫描特定的部分端口或对指定的端口段内的端口进行逐个扫描
    能够显示所开放端口的服务名称

    1.3 运行环境
    操作系统:Windows 7及以上版本系统
    开发环境:Microsoft Visual Studio 2013

    2 系统分析与设计2.1 原理概述端口扫描有好几种,但其中TCP connect扫描是最基本的扫描,我们可以利用系统提供的connect()用来与每一个目标计算机的端口进行连接。如果端口处于侦听状态,那connect()就能成功。否则,这个端口即是不可用的,也就是说没有提供服务。这个技术的一个最大的优点是,你不需要任何权限。系统中的任何用户都有权利使用这个调用。另一个好处就是速度,如果对每个目标端口以线性的方式,使用单独的connect()调用,那么将花费相当长的时间,使用者可以通过多线程同时打开多个套接字来加速扫描。使用非阻塞I/O允许设置一个低的时间用尽周期,同时观察多个套接字。但这种方法的缺点是很容易被察觉,并且被防火墙将扫描信息包过滤掉。目标计算机的Logs文件会显示一连串的连接和连接出错消息,并且能很快使它关闭。
    2.2 程序流程图
    说明:扫描器可实现对单个主机单个端口的扫描也可实现对网段内主机和范围内端口进行逐个扫描,因此操作时要注意是单个主机扫描还是多个IP地址扫描,以及扫描端口范围。确定扫描IP和扫描端口后点击开始即可对相应IP下的端口进行侦听,并返回端口状态,如果端口开放则同时返回端口服务,扫描结束后线程停止。
    2.3 主要数据结构private string ipStart; // 起始IP地址private string ipEnd; // 终点IP地址private int portStart; // 开始端口private int portEnd; // 结束端口private int numThread=20; // 分配的线程数,默认最小为20private int overTime; // 超时限制private Thread t; // 定义一个线程private Thread scanthread; // 端口扫描线程private bool[] done = new bool[65536]; List<string> str; // 扫描结果集
    2.4 主要算法设置好IP地址和端口范围后,开辟线程,开始扫描:
    利用PingReply对象试探目标主机,如果超时则表示不可达,否则连通,显示端口扫描状态,如果端口开放,显示服务。
    2.5 主要函数说明// 界面初始化函数private void Form1_Load(object sender, EventArgs e); // 开始按钮函数private void button1_Click(object sender, EventArgs e);// 扫描IP地址线程public void wait();// 判断端口状态函数,返回(open或closed)public string State(int i);// 根据开放端口号返回具体服务public string Service(int i);// 扫描端口线程public void Scan(object Point);// 停止按钮事件函数private void button2_Click(object sender, EventArgs e);// 关于按钮事件函数private void button3_Click(object sender, EventArgs e);// 超时设置函数private void trackBar1_Scroll(object sender, EventArgs e);// 点选按钮函数private void checkBox2_CheckedChanged(object sender, EventArgs e);
    3 用户使用手册端口扫描器主界面

    运行程序系统进入主界面,主界面中主要包括以下布局:IP地址设置、端口范围设置、线程数设置、Ping超时时间限制、扫描进度、显示扫描结果、开始停止和关于按钮。
    扫描单个主机或单个端口
    当需要扫描单个主机或者单个端口是,需要点选“扫描单个主机”、“扫面单个端口”选框,这时IP范围设置和端口范围设置各自的第二个编辑框变为只读属性,无法再填写相应字段。

    设置线程数和超时限制
    由于多线程,可以分配多个线程,但为了避免消耗过多资源导致主机奔溃建议1-30;超时限制可通过TrackBar滑动选择,默认为10-30s,注意下方的编辑框是只读属性。

    开始、停止、关于按钮
    设置好需要扫描的IP和端口后,点击开始按钮,即可进行扫描,点击停止,将线程挂起,扫描结束。点击关于按钮,弹出个人信息及软件反馈地址。

    显示扫描结果
    通过richTextBox控件显示扫描结果找出开放端口,ListBox控件动态显示扫描结果,包括扫描IP地址,端口号,端口状态(closed、open),以及开放端口服务名称。

    4 心得体会与总结4.1 心得体会如图:对主机就127.0.0.1进行测试,符合预期结果。


    根据计算机网络知识,可以看到通过对单个主机和网段内的多个主机进行端口扫描都得到了正确结果。创建一个socket,通过TCP connect()测试该主机的某个端口是否能够连通,获得该端口的状态,如果open则获知它的服务。
    当然由于采用TCP connect()方法,不可避免的有它的缺陷,因为大量无需权限的访问,容易被防火墙过滤掉,因此可以进行这方面的改进。可以采用TCP SYN扫描、TCP FIN扫描、TCP反向ident扫描、FTP返回攻击等。
    4.2 总结通过这次课程设计,一方面我熟悉了C#方面基本的网络编程和socket编程,同时对TCP连接的过程有了更深入的理解,包括封装API的调用,握手机制等。为了解决这一过程中遇到的问题,除了课本还查阅了很多资料,对网络编程也有了不少的心得,总得来说收获满满!
    1 评论 8 下载 2018-11-05 15:37:28 下载需要20点积分
  • 基于JAVA FX实现的酒店预订系统网站

    1 产品概述参考酒店预订系统用例文档和酒店预订系统软件需求规格说明文档中队产品的概括描述。酒店预订系统主要是应用于网上预定远程酒店订单的在线系统,主要功能见用例图如下。

    2 用户界面层的分解根据需求,系统存在30个用户界面:客户界面,酒店工作人员界面,网站管理人员界面,网站营销人员界面,注册会员界面,搜索酒店信息界面,查看酒店详细信息界面,查看预订过的酒店信息界面,浏览订单界面,生成订单界面,评价订单界面,查看信用记录界面,查看基本信息界面,修改基本信息界面,查看信用记录界面,执行正常订单界面,补登记异常订单界面,维护酒店基本信息界面,录入可用客房信息界面,管理客房状态界面,制定酒店促销策略界面,制定网站促销策略界面,管理异常订单执行情况界面,查看未执行订单界面,信用充值界面,酒店管理界面,用户管理界面,管理客户信息界面,管理酒店工作人员信息界面,管理网站营销人员界面。

    3 数据库表


    表名
    内容




    account
    帐号信息


    Client
    用户信息


    Credithistory
    信用修改记录


    Evaluation
    评价信息


    Hotel
    酒店信息


    Hotelmanager
    酒店管理人员信息


    Locations
    地址及商圈


    Orders
    订单信息


    Promotion
    促销策略


    Room
    酒店房间信息


    vipLevel
    Vip等级对应的信用值


    Webbusiness
    网站营销人员信息



    4 依赖视角客户端开发包

    服务端开发包

    5 界面原型图登录界面

    管理酒店信息

    房间信息

    订单列表

    酒店信息

    预定信息
    1 评论 44 下载 2018-11-05 15:32:19 下载需要6点积分
  • 基于VC++的MFC类库的飞机大战游戏的设计与实现

    1 概述1.1 项目简介本次实训项目是做一个飞机大战的游戏,应用MFC编程,完成一个界面简洁流畅、游戏方式简单,玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是MFC编程中的一些函数、链表思想以及贴图技术。
    1.2 实训功能说明1.2.1 基本功能
    通过键盘,方向键和ASWD键可控制战机的位置,空格键和鼠标左键发射子弹
    界面中敌机出现的位置,以及敌机和Boss炸弹的发射均为随机的,敌机与敌机炸弹、Boss炸弹均具有一定的速度,且随着关卡难度的增大,数量和速度均随着关卡数增加而增加
    对于随机产生的敌机和敌机炸弹,若超过矩形区域,则释放该对象
    添加碰撞效果,包括战机子弹打中敌机爆炸、敌机炸弹打中战机爆炸、战机与敌机相撞爆炸、战机子弹与敌机炸弹相撞爆炸、战机子弹打中Boss、战机与Boss碰撞以及战机吃到血包七种碰撞效果。且碰撞发生后子弹、炸弹、血包均消失,战机生命值减一,敌机和Boss生命值减少当前战机炮弹威力的生命值,若敌机或Boss生命值归零,则删除敌机或Boss
    血包:随着关卡游戏进程的进行,会出现一定量的血包供战机补给生命值,血包会在客户区矩形框内运动,10秒后消失;若战机在10秒内吃到血包,则会增加5点生命值知道生命值上限
    每关中战机有三条命,每条命10点生命值,生命使用完后,会进入GameOver界面显示得分数,并提供重新开始游戏和退出功能
    游戏提供10个关卡,每个关卡需要打死相应关卡的敌机数量才能进入Boss模式,打败Boss之后将会进入下一关。10关通关后,显示通关界面,并提供重新开始游戏和退出游戏的功能选项
    暂停功能:游戏进行过程中按下Z键可进入暂停模式,再按Z则返回游戏
    无敌模式:游戏进行过程中按下Y键可进入无敌模式,再按Y则返回正常游戏。该模式下战机生命值不会减少,可供测试使用
    魔法值:游戏进行过程中,战机魔法值会随着时间递增到上限10,魔法值供战机道具功能的使用,过一个关卡魔法值不清零
    战机大招:当战机魔法值为10满状态时,按下X键消耗所有魔法值可发动大招,对屏幕中的敌机进行清屏,Boss扣50点血量
    防护罩:当魔法值不为0时,按下C键可打开防护罩道具,该状态下战机处于无敌状态,不会损失生命值,但魔法值会随着防护罩开启慢慢降低
    战机升级功能:战机子弹单个威力为1,在魔法值不为0时,按下V键开启升级战机模式,战机图标变为动画,子弹威力变成两倍。(若同时开启防护罩和战机升级,则魔法值递减速度翻倍)

    1.2.2 附加功能
    为游戏界面每个关卡添加了滚动背景图片和背景音乐,并在敌机发送炮弹、战机发射子弹、战机击中敌机、敌机击中战机、战机敌机相撞、敌机战机子弹相撞、战机吃到血包、战机大招、战机升级、战机防护罩、游戏结束时均添加了音效
    为美化游戏界面,采用了一部分全民飞机大战图标,并添加了爆炸动画和升级战机动画特效,背景音乐采用微信飞机大战背景音乐和相关特效音效
    为游戏设置了不同的关卡,每个关卡难度不同,敌机与敌机炸弹的速度随着关卡增大而加快,进入第五关以后敌机从上下方均会随机出现,且随机发射炸弹
    前五关卡敌机从上方飞出,速度一定,战机每打掉一架敌机则增加一分,当战机得分超过该关卡所需分数(和关卡数相关)则可进入Boss模式,打败Boss进入下一关;进入第六关以后,敌机分别从上下两方飞出。随着关卡数增加,敌机数量增加,速度增快,敌机炮弹数量和速度也相应增加,进入Boss所需分数增加,Boss生命值和火力也随着关卡数的增加而增加,游戏难度陡然直升
    游戏界面中显示当前状态下的关卡数、当前命数、当前得分、战机血条、战机魔法条、无敌模式提醒和战机道具提醒,Boss模式下还有Boss血条
    增加了鼠标控制战机位置这一效果,战绩的位置随着鼠标的移动而移动,并且点击鼠标左键可使得战机发射子弹
    进入游戏先进入欢迎界面,欢迎界面中显示游戏使用说明,点击鼠标左键和空格键开始游戏。游戏过程中战机命数使用完、通关均有相应界面进行提醒,用户可选择重新开始游戏或退出游戏

    2 相关技术2.1 Windows定时器技术Windows定时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。程序将时间间隔告诉Windows,然后Windows给您的程序发送周期性发生的WM_TIMER消息以表示时间到了。本程序中使用多个定时器,分别控制不同的功能。在MFC的API函数中使用SetTimer()函数设置定时器,设置系统间隔时间,在OnTimer()函数中实现响应定时器的程序。
    2.2 透明贴图实现技术绘制透明位图的关键就是创建一个“掩码”位图(mask bitmap),这个“掩码”位图是一个单色位图,它是位图中图像的一个单色剪影。
    在详细介绍实现过程之前先介绍下所使用的画图函数以及函数参数所代表的功能;整个绘制过程需要使用到BitBlt()函数。整个功能的实现过程如下:

    创建一张大小与需要绘制图像相同的位图作为“掩码”位图
    将新创建的“掩码”位图存储至掩码位图的设备描述表中
    把位图设备描述表的背景设置成“透明色”,不需要显示的颜色
    复制粘贴位图到“掩码”位图的设备描述表中,这个时候“掩码”位图设备描述表中存放的位图与位图设备描述表中的位图一样
    把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上
    把“掩码”位图与这个时候对话框相应区域的背景进行逻辑与的操作
    重复步骤5的操作,把需要透明绘制的位图与对话框绘图相应区域的背景进行逻辑异或操作绘制到对话框上
    最后把系统的画笔还给系统,删除使用过的GDIObject,释放非空的指针,最后把新建的设备描述表也删除

    2.3 CObList链表MFC类库中提供了丰富的CObList类的成员函数,此程序主要用到的成员函数如下:

    构造函数,为CObject指针构造一个空的列表
    GetHead(),访问链表首部,返回列表中的首元素(列表不能为空)
    AddTail(),在列表尾增加一个元素或另一个列表的所有元素
    RemoveAll(),删除列表中所有的元素
    GetNext(),返回列表中尾元素的位置
    GetHeadPosition(),返回列表中首元素的位置
    RemoveAt(),从列表中删除指定位置的元素
    GetCount(),返回列表中的元素数

    在CPlaneGameView.h文件中声明各游戏对象与游戏对象链表:

    滚动背景模块
    CScenescene;//游戏背景对象 CImageList startIMG;欢迎界面图像列表
    各游戏对象
    CMyPlane *myplane; CEnemy *enemy; CBoss *boss; CBomb *bomb; CBall *ball; CExplosion *explosion; CBlood *blood;
    存储游戏对象的对象链表
    CObList enemyList; CObList meList; CObList bombList; CObList ballList; CObList explosionList; CObList bloodList;
    客户区窗口矩形(用来动态获取客户区矩形大小)
    CRect rect;//窗口屏幕矩形
    游戏运行相关参数:
    int speed;//战机的速度,方向键控制 int myLife;//为战机设置生命值 int lifeNum;//战机命条数 int myScore;//战机的得分 int passScore;//当前关卡得分数 int lifeCount;//血包产生控制参数 int magicCount;//魔法值,控制能否发大招 int bossBlood;//Boss血量 int passNum;//记录当前关卡
    游戏运行相关标志位
    BOOL bloodExist;//标记屏幕中是否存在血包 int isPass;//是否通关的标志 int isPause;//是否暂停 BOOL isBoss;//标记是否进入Boss BOOL bossLoaded;//标记Boss出场完成 BOOL isProtect;//标记是否开启防护罩 BOOL isUpdate;//标记战机是否升级 BOOL test;//无敌模式标志位 BOOL isStop;//标记游戏停止 BOOL isStarted;//标记欢迎界面是否加载完成

    2.4 获取矩形区域首先,使用CRect定义一个对象,然后使用GetClientRect(&对象名)函数,获取界面的矩形区域rect.Width() 为矩形区域的宽度,rect.Height()为矩形区域的高度。
    使用IntersectRect(&,&))函数来判断两个源矩形是否有重合的部分。如果有不为空,则返回非零值;否则,返回0。
    2.5内存释放在VC/MFC用CDC绘图时,频繁的刷新,屏幕会出现闪烁的现象,CPU时间占用率相当高,绘图效率极低,很容易出现程序崩溃。及时的释放程序所占用的内存资源是非常重要的。
    在程序中使用到的链表、刷子等占用内存资源的对象都要及时的删除。Delete Brush,List.removeall()等。
    2.6 CImageList处理爆炸效果爆炸效果是连续的显示一系列的图片。如果把每一张图片都显示出来的话,占用的时间是非常多的,必然后导致程序的可行性下降。CImageList是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用来存放爆炸效果的一张图片,使用Draw()函数来绘制在某拖拉操作中正被拖动的图象,即可通过Timer消息连续绘制出多张图片做成的爆炸效果。
    3 总体设计与详细设计3.1 系统模块划分该飞机大战游戏程序分为游戏滚动背景绘制模块、各游戏对象绘制模块、游戏对象之间的碰撞模块、爆炸效果产生模块、游戏界面输出玩家得分关卡信息模块、战机道具技能维护模块、消息处理模块、视图生命周期维护模块。
    其中在游戏对象绘制模块中,战机是唯一对象,在游戏开始时产生该对象,赋予其固定的生命值,当其与敌机对象、敌机炸弹碰撞时使其生命值减一,直至生命值为零,便删除战机对象。敌机对象与敌机炸弹对象的绘制中采用定时器技术,定时产生。爆炸对象初始化为空,当游戏过程中即时发生碰撞时,在碰撞位置产生爆炸对象,添加到爆炸链表中,并根据爆炸素材图像分八帧进行输出,达到动画特效。
    3.2 主要功能模块3.2.1 系统对象类图
    CGameObject是各个游戏对象的抽象父类,继承自CObject类,其他的类:战机类、敌机类、爆炸类、子弹类、炸弹类、血包类、文字类都继承了此类,CBoss类继承敌机类。每个游戏对象类中既继承了来自父类CGameObject的属性,又有自己的特有属性和方法。
    3.2.2 系统主程序活动图
    3.2.3 系统部分流程图飞机大战游戏执行流程图

    定时器产生敌机和炸弹流程图

    血包执行流程图

    4 编码实现4.1 双缓冲 //双缓冲 CDC *pDC = GetDC(); //获得客户区矩形区域 GetClientRect(&rect); //内存缓冲CDC CDC cdc; //内存中承载临时图像的缓冲位图 CBitmap* cacheBitmap = new CBitmap; //用当前设备CDC初始化缓冲CDC cdc.CreateCompatibleDC(pDC); //绑定pDC和缓冲位图的关系,cdc先输出到缓冲位图中,输出完毕之后再一次性将缓冲位图输出到屏幕 cacheBitmap->CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); //替换cdc原本的缓冲区为缓冲位图,这样cdc输出的内容就写到了缓冲位图中 CBitmap *pOldBit = cdc.SelectObject(cacheBitmap); //将二级缓冲cdc中的数据推送到一级级缓冲pDC中,即输出到屏幕中 pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &cdc, 0, 0, SRCCOPY); //释放二级cdc cdc.DeleteDC(); //释放缓冲位图 cacheBitmap->DeleteObject(); //释放一级pDC ReleaseDC(pDC);
    4.2 滚动背景在滚动背景的初始化函数和释放函数添加背景音乐播放和释放。
    class CScene{ //成员变量private: CImage images[8];//滚动背景,0位为开始图片,1-7为七张不同的背景 int beginY;//背景的Y坐标 bool isStart;//是否开始//成员函数public: bool InitScene();//初始化场景 void MoveBg();//移动背景 ////绘制场景(注:这里bufferDC是引用参数) void StickScene(CDC* pDC, int index, CRect rClient);//传入index-1表示输出开始图片 void ReleaseScene();//释放内存资源//构造与析构public: CScene(void); ~CScene(void);};//初始化场景bool CScene::InitScene(){ //加载开始图片 this->images[0].Load(_T("image\\start.bmp")); CString str; //如果加载失败, 返回false for (int i = 1; i <= 7; i++) { str.Format(_T("image\\background%d.bmp"), i); this->images[i].Load(str); if (images[i].IsNull()) return false; } //开始为真, 背景起始坐标为0 this->isStart = true; this->beginY = 0; //播放背景音乐 mciSendString(L"open sound\\background.mp3 alias bgm", NULL, 0, NULL); mciSendString(L"play bgm repeat", NULL, 0, NULL); return true;}//绘制场景void CScene::StickScene(CDC* pDC,int index, CRect rClient){ if (index == -1) index = 0; else index = index % 7 + 1; //设置缩放图片的模式为:COLORONCOLOR, 以消除像素重叠 pDC->SetStretchBltMode(COLORONCOLOR); //如果到了下边界, 回到起点 if (beginY >= rClient.Height()) { beginY = 0; if (isStart) isStart = false; } //客户区高度 int cltHeight = rClient.Height(); rClient.bottom = cltHeight + beginY; rClient.top = beginY; //如果是开始就绘制起始背景 if (isStart) { this->images[index].StretchBlt(*pDC, rClient, SRCCOPY); } //将下一张背景作为起始背景 else { this->images[index].StretchBlt(*pDC, rClient, SRCCOPY); } //绘制下一张背景 rClient.top -= cltHeight; rClient.bottom -= cltHeight; images[index].StretchBlt(*pDC, rClient, SRCCOPY);}//移动背景void CScene::MoveBg(){ //移动背景 beginY += 1;}//释放内存资源void CScene::ReleaseScene(){ for (int i = 0; i <8; i++) if (!images[i].IsNull()) images[i].Destroy(); mciSendString(L"close bgm", NULL, 0, NULL);}
    onTimer中控制背景滚动:
    //输出背景 if (isStarted == FALSE) scene.StickScene(&cdc, -1, rect); else scene.StickScene(&cdc, passNum, rect); if (nIDEvent == 4) { //滚动背景 scene.MoveBg(); }
    4.3 显示战机if (myplane != NULL) { myplane->Draw(&cdc,FALSE,isProtect);}
    4.4 随机产生敌机和敌机炮弹、Boss炮弹 //随机添加敌机,敌机随机发射炸弹,此时敌机速度与数量和关卡有关 if (myplane != NULL && isPause == 0&&isBoss==FALSE) { //敌机产生定时器触发 if (nIDEvent == 2) { //根据关卡数产生敌机 if (passNum <=5) { //前五关只有一个方向的敌机 int direction = 1;//设置敌机的方向,从上方飞出 CEnemy *enemy = new CEnemy(direction, rect.right, rect.bottom); enemyList.AddTail(enemy);//随机产生敌机 } else if (passNum >5) {//第五关之后,两个方向的敌机 int direction1 = 1; //设置敌机的方向,从上方飞出 CEnemy *enemy1 = new CEnemy(direction1,rect.right,rect.bottom); enemy1->SetSpeed(ENEMY_SPEED+(rand() % 2 + passNum-1)); enemyList.AddTail(enemy1);//随机产生敌机 int direction2 = -1;//设置敌机的方向,从下方飞出 CEnemy *enemy2 = new CEnemy(direction2, rect.right, rect.bottom); enemy2->SetSpeed(ENEMY_SPEED + (rand() % 2 + passNum - 1)); enemyList.AddTail(enemy2);//随机产生敌机 } } //超出边界的敌机进行销毁 POSITION stPos = NULL, tPos = NULL; stPos = enemyList.GetHeadPosition(); int direction = 1; while (stPos != NULL) { tPos = stPos; CEnemy *enemy = (CEnemy *)enemyList.GetNext(stPos); //判断敌机是否出界 if (enemy->GetPoint().x<rect.left || enemy->GetPoint().x>rect.right || enemy->GetPoint().y<rect.top || enemy->GetPoint().y>rect.bottom) { enemyList.RemoveAt(tPos); delete enemy; }//if else { //没出界,绘制 enemy->Draw(&cdc,passNum, FALSE); //敌机炸弹产生定时器触发 if (nIDEvent == 3) { //设置定时器产生敌机炸弹 PlaySound((LPCTSTR)IDR_WAV_BALL, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); CBall * ball = new CBall(enemy->GetPoint().x + ENEMY_HEIGHT / 2, enemy->GetPoint().y + ENEMY_HEIGHT, enemy->GetDirection()); ball->SetBallSpeed(BALL_SPEED + (passNum - 1) ); ballList.AddTail(ball); } }//else }//while //判断产生的敌机炸弹是否出界,若已经出界,则删除该敌机炸弹 POSITION stBallPos=NULL, tBallPos=NULL; stBallPos = ballList.GetHeadPosition(); while (stBallPos != NULL) { tBallPos = stBallPos; ball = (CBall *)ballList.GetNext(stBallPos); if (ball->GetPoint().x<rect.left || ball->GetPoint().x>rect.right || ball->GetPoint().y<rect.top || ball->GetPoint().y>rect.bottom) { ballList.RemoveAt(tBallPos); delete ball; }//if else { ball->Draw(&cdc,passNum, FALSE); }//else }//while } //Boss产生炮弹,一次五发炮弹 if (myplane != NULL && isPause == 0 && isBoss == TRUE) { //Boss发射子弹 //敌机炸弹产生定时器触发 if (nIDEvent == 3) { //设置定时器产生敌机炸弹 PlaySound((LPCTSTR)IDR_WAV_BALL, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); CBall * ball1 = new CBall(boss->GetPoint().x + BOSS_WIDTH / 2, boss->GetPoint().y + BOSS_HEIGHT, 1); ball1->SetBallSpeed(BALL_SPEED + (passNum - 1) * 2); ballList.AddTail(ball1); CBall * ball2 = new CBall(boss->GetPoint().x +5, boss->GetPoint().y + BOSS_HEIGHT, 1); ball2->SetBallSpeed(BALL_SPEED + (passNum - 1) * 2); ballList.AddTail(ball2); CBall * ball3 = new CBall(boss->GetPoint().x + BOSS_WIDTH -5, boss->GetPoint().y + BOSS_HEIGHT,1); ball3->SetBallSpeed(BALL_SPEED + (passNum - 1) * 2); ballList.AddTail(ball3); CBall * ball4 = new CBall(boss->GetPoint().x + BOSS_WIDTH / 2+85, boss->GetPoint().y + BOSS_HEIGHT, 1); ball4->SetBallSpeed(BALL_SPEED + (passNum - 1) * 2); ballList.AddTail(ball4); CBall * ball5 = new CBall(boss->GetPoint().x + BOSS_WIDTH / 2-85, boss->GetPoint().y + BOSS_HEIGHT, 1); ball5->SetBallSpeed(BALL_SPEED + (passNum - 1) * 2); ballList.AddTail(ball5); } //显示Boss炸弹 //判断产生的敌机炸弹是否出界,若已经出界,则删除该敌机炸弹 POSITION stBallPos = NULL, tBallPos = NULL; stBallPos = ballList.GetHeadPosition(); while (stBallPos != NULL) { tBallPos = stBallPos; ball = (CBall *)ballList.GetNext(stBallPos); if (ball->GetPoint().x<rect.left || ball->GetPoint().x>rect.right || ball->GetPoint().y<rect.top || ball->GetPoint().y>rect.bottom) { ballList.RemoveAt(tBallPos); delete ball; }//if else { ball->Draw(&cdc, FALSE); }//else }//while }
    4.5 显示战机发射子弹 if (myplane != NULL&& isPause == 0) { //声明战机子弹位置 POSITION posBomb = NULL, tBomb = NULL; posBomb = bombList.GetHeadPosition(); while (posBomb != NULL) { tBomb = posBomb; bomb = (CBomb *)bombList.GetNext(posBomb); if (bomb->GetPoint().x<rect.left || bomb->GetPoint().x>rect.right || bomb->GetPoint().y<rect.top || bomb->GetPoint().y>rect.bottom) { //删除越界的子弹 bombList.RemoveAt(tBomb); delete bomb; } else bomb->Draw(&cdc, FALSE); } }
    4.6 碰撞检测以战机子弹集中敌机为例。
    if (myplane != NULL&& isPause == 0) { //声明战机子弹位置,敌机位置 POSITION bombPos, bombTemp, enemyPos, enemyTemp; for (bombPos = bombList.GetHeadPosition(); (bombTemp = bombPos) != NULL;) { bomb = (CBomb *)bombList.GetNext(bombPos); for (enemyPos = enemyList.GetHeadPosition(); (enemyTemp = enemyPos) != NULL;) { enemy = (CEnemy *)enemyList.GetNext(enemyPos); //获得战机子弹的矩形区域 CRect bombRect = bomb->GetRect(); //获得敌机的矩形区域 CRect enemyRect = enemy->GetRect(); //判断两个矩形区域是否有交接 CRect tempRect; if (tempRect.IntersectRect(&bombRect, enemyRect)) { //将爆炸对象添加到爆炸链表中 CExplosion *explosion = new CExplosion((bomb->GetPoint().x + BOMB_WIDTH / 2 - EXPLOSION_WIDTH / 2), (bomb->GetPoint().y + BOMB_HEIGHT / 2 - EXPLOSION_WIDTH / 2)); explosionList.AddTail(explosion); PlaySound((LPCTSTR)IDR_WAV_EXPLOSION, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); //爆炸后删除子弹 bombList.RemoveAt(bombTemp); delete bomb; //指向下一个 bomb = NULL; //敌机生命值减少 enemy->life -= (1 + isUpdate); if (enemy->life <= 0) { //增加得分 passScore++; //删除敌机 enemyList.RemoveAt(enemyTemp); delete enemy; } //炮弹已删除,直接跳出本循环 break; } } if (isBoss == TRUE&&bomb != NULL) { //获得战机子弹的矩形区域 CRect bombRect = bomb->GetRect(); //获得Boss的矩形区域 CRect bossRect = boss->GetRect(); //判断两个矩形区域是否有交接 CRect tempRect; if (tempRect.IntersectRect(&bombRect, bossRect)) { //将爆炸对象添加到爆炸链表中 CExplosion *explosion = new CExplosion((bomb->GetPoint().x + BOMB_WIDTH / 2 - EXPLOSION_WIDTH / 2), (bomb->GetPoint().y + BOMB_HEIGHT / 2 - EXPLOSION_WIDTH / 2)); explosionList.AddTail(explosion); PlaySound((LPCTSTR)IDR_WAV_EXPLOSION, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); //爆炸后删除子弹 bombList.RemoveAt(bombTemp); delete bomb; bomb = NULL; //是Boss,不删除敌机,只扣血 bossBlood -= (1 + isUpdate); if (bossBlood <= 0) { delete boss; boss = NULL; //过关的标志变量 delete myplane; myplane = NULL; isPass = 1; isBoss = FALSE; } } } } }
    4.7 显示爆炸效果 if (myplane != NULL&&isPause == FALSE) { POSITION explosionPos, explosionTemp; explosionPos = explosionList.GetHeadPosition(); //检索爆炸链表,非空时在所在位置显示 while (explosionPos != NULL) { explosionTemp = explosionPos; explosion = (CExplosion *)explosionList.GetNext(explosionPos); BOOL flag = explosion->Draw(&cdc, FALSE); //爆炸8帧结束,删除爆炸对象 if (flag == EXPLOSION_STATUS_STOP) { explosionList.RemoveAt(explosionTemp); delete explosion; } }//while}
    4.8 血包功能游戏三分之一和三分之二进程时刻出现血包。
    //开启血包 if (myplane != NULL && myLife > 0) { //关卡打了三分之一三分之二处出现血包 if (passScore > (PASS_SCORE + passNum * 5)*lifeCount/3) { //若屏幕中有未吃掉的血包,这次不产生血包 if (bloodExist == FALSE) { lifeCount++; //产生血包 blood = new CBlood(rect.right,rect.bottom); bloodList.AddTail(blood); bloodExist = TRUE; SetTimer(6, 10000, NULL); }else lifeCount++; } } //血包定时器,10秒后血包消失 if (nIDEvent == 6&&isPause==0) { KillTimer(6); bloodExist = FALSE; //声明血包位置 POSITION bloodPos, bloodTemp; for (bloodPos = bloodList.GetHeadPosition(); (bloodTemp = bloodPos) != NULL;) { blood = (CBlood *)bloodList.GetNext(bloodPos); bloodList.RemoveAt(bloodTemp); delete blood; } } //显示血包 if (myplane != NULL&&isPause == FALSE) { POSITION bloodPos; bloodPos = bloodList.GetHeadPosition(); //检索血包链表,非空时在所在位置显示 while (bloodPos != NULL) { blood = (CBlood *)bloodList.GetNext(bloodPos); blood->Draw(&cdc, FALSE); }//while } //血包碰撞检测 if (myplane != NULL&& isPause == 0) { //声明血包位置 POSITION bloodPos, bloodTemp; for (bloodPos = bloodList.GetHeadPosition(); (bloodTemp = bloodPos) != NULL;) { blood = (CBlood *)bloodList.GetNext(bloodPos); //获得血包矩形 CRect bloodbRect = blood->GetRect(); //获得战机矩形 CRect planeRect = myplane->GetRect(); //判断两个矩形区域是否有交接 CRect tempRect; if (tempRect.IntersectRect(&bloodbRect, planeRect)) { //加血效果 myLife += 5; if (myLife > DEFAULT_LIFE) myLife = DEFAULT_LIFE; // TODO 声音 PlaySound((LPCTSTR)IDR_WAV_BLOOD, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); //加血后血包删除 bloodList.RemoveAt(bloodTemp); delete blood; break; } } }
    4.9 通关和死亡消息页面 if (isStop == FLAG_RESTART) { HFONT textFont; textFont = CreateFont(18, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 10, 0); cdc.SelectObject(textFont); //设置透明背景 cdc.SetBkMode(TRANSPARENT); cdc.SetTextColor(RGB(255, 255, 255)); cdc.TextOutW(rect.right/2-150, rect.bottom/2-30, _T("哇,恭喜你已通关!是否重新开始?Y/N")); } else if (isStop == FLAG_STOP) { HFONT textFont; textFont = CreateFont(18 , 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 10, 0); cdc.SelectObject(textFont); //设置透明背景 cdc.SetBkMode(TRANSPARENT); cdc.SetTextColor(RGB(255, 255, 255)); //显示最后结果 CString str; cdc.TextOutW(rect.right / 2 - 100, rect.bottom / 2 - 30, _T("GAME OVER!")); str.Format(_T("您的得分为:%d"), myScore); cdc.TextOutW(rect.right / 2 - 100, rect.bottom / 2 - 10, str); cdc.TextOutW(rect.right / 2 - 100, rect.bottom / 2 +10, _T("COME ON !重新开始?Y/N")); }
    4.10 魔法值控制维护 //控制魔法值 if (nIDEvent == 5) { if (myplane != NULL&&isPause == 0) { //防护罩和战机升级没打开,魔法值递增 if (isProtect==FALSE&&isUpdate==FALSE) { magicCount++; if (magicCount > 10) magicCount = 10; } //判断是否打开防护罩 if (isProtect == TRUE) { //开启防护罩魔法值递减 magicCount --; if (magicCount <= 0) { magicCount = 0; isProtect = FALSE; } } //判断是否升级战机 if (isUpdate == TRUE) { //战机升级,魔法值递减 magicCount--; if (magicCount <= 0) { magicCount = 0; isUpdate = FALSE; myplane->SetIsUpdate(isUpdate); } } } }
    4.11 得分到达关卡需求,进入Boss // 进入Boss int pScore = PASS_SCORE + passNum * 5; // TODO调试条件 //if (myplane != NULL && passScore >= 3 && isPause == 0&&isBoss==FALSE) if (myplane != NULL && passScore >= pScore && isPause == 0 && isBoss == FALSE) { //进入Boss isBoss = TRUE; boss = new CBoss(1, rect.right, rect.bottom); boss->SetSpeed(BOSS_SPEED+passNum-1); boss->life = BOSS_LIFE + passNum * 50;//Boss总血量 bossBlood= BOSS_LIFE + passNum * 50;//当前Boss血量 //Boss出场,暂停游戏 bossLoaded = FALSE; //重新设置Boss的子弹产生频率,增强Boss子弹发射频率 KillTimer(3); SetTimer(3, 2000 - passNum * 180, NULL); } //显示Boss if (myplane != NULL &&boss!=NULL&& isPause == 0 && isBoss == TRUE) { BOOL status = boss->Draw(&cdc,passNum, FALSE); if (status == TRUE) bossLoaded = TRUE; }
    4.12 检测标记位isPass,判断打赢Boss,进入下一关 if (isPass == 1) { isPass = FALSE; if (passNum ==10)//10关 { //重新初始化数据 KillTimer(1); KillTimer(2); KillTimer(3); //KillTimer(4); KillTimer(5); myplane = new CMyPlane(FALSE); isPause = TRUE; isStop = FLAG_RESTART; //清屏 CBitmap* tCache = cacheBitmap; cacheBitmap = new CBitmap; cacheBitmap->CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); cdc.SelectObject(cacheBitmap); delete tCache; }//if else { KillTimer(1); KillTimer(2); KillTimer(3); //KillTimer(4); KillTimer(5); isPause = TRUE; //保存所需数据 int tScore = myScore + passScore; int tPassNum = passNum + 1; int tTest = test; int magic = magicCount; //重新开始游戏 Restart(); myScore = tScore; passNum = tPassNum; magicCount = magic; test = tTest; } }
    4.13 按键消息响应// 方向上键或W键if (myplane != NULL && (GetKeyState(VK_UP) < 0 || GetKeyState('W') < 0) && isPause == 0)// 方向下键或S键if (myplane != NULL && (GetKeyState(VK_DOWN) < 0 || GetKeyState('S') < 0) && isPause == 0)// 方向左键或A键if (myplane != NULL && (GetKeyState(VK_LEFT) < 0 || GetKeyState('A') < 0) && isPause == 0 )// 方向右键或D键if (myplane != NULL && (GetKeyState(VK_RIGHT) < 0 || GetKeyState('D') < 0) && isPause == 0 )// 空格键发射子弹if (myplane != NULL && (GetKeyState(VK_SPACE) < 0) && isPause == 0 && bossLoaded == TRUE) { //空格键发射子弹 CBomb *Bomb1 = new CBomb(myplane->GetPoint().x+20, myplane->GetPoint().y, 1,isUpdate); PlaySound((LPCTSTR)IDR_WAV_BOMB, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); bombList.AddTail(Bomb1); CBomb *Bomb2 = new CBomb(myplane->GetPoint().x + PLANE_WIDTH-50, myplane->GetPoint().y, 1, isUpdate); bombList.AddTail(Bomb2); //子弹自动飞行,在Timer中绘制}// Z键暂停if (myplane != NULL&&GetKeyState('Z') < 0)// X键发大招清屏if (myplane != NULL&&GetKeyState('X') < 0 && isPause == 0 && bossLoaded == TRUE) { //战机发大招 if (magicCount >= 10) { magicCount -= 10; //清空敌机 POSITION enemyPos, enemyTemp; for (enemyPos = enemyList.GetHeadPosition(); (enemyTemp = enemyPos) != NULL;) { enemy = (CEnemy *)enemyList.GetNext(enemyPos); //将爆炸对象添加到爆炸链表中 CExplosion *explosion = new CExplosion((enemy->GetPoint().x + ENEMY_WIDTH / 2), (enemy->GetPoint().y + ENEMY_HEIGHT / 2)); explosionList.AddTail(explosion); PlaySound((LPCTSTR)IDR_WAV_DAZHAO, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); //删除敌机 enemyList.RemoveAt(enemyTemp); delete enemy; //增加得分 passScore++; }//for if(isBoss==TRUE) { //将爆炸对象添加到爆炸链表中 CExplosion *explosion = new CExplosion(boss->GetPoint().x + BOSS_WIDTH / 2, boss->GetPoint().y + BOSS_HEIGHT / 2); explosionList.AddTail(explosion); PlaySound((LPCTSTR)IDR_WAV_DAZHAO, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); bossBlood -= 50; if (bossBlood <= 0) { //boss死,过关 //过关的标志变量 delete boss; boss = NULL; //过关的标志变量 isPause = TRUE; CMyPlane* temp = myplane; myplane = new CMyPlane(FALSE); delete temp; temp = NULL; isPass = 1; isBoss = FALSE; } } //清空敌机炮弹 POSITION ballPos, ballTemp; for (ballPos = ballList.GetHeadPosition(); (ballTemp = ballPos) != NULL;) { ball = (CBall *)ballList.GetNext(ballPos); //删除敌机炮弹 ballList.RemoveAt(ballTemp); delete ball; }//for }}//if// C键开启防护罩if (myplane != NULL&&GetKeyState('C') < 0 && isPause == 0)// V键战机升级if (myplane != NULL&&GetKeyState('V') < 0 && isPause == 0)// Y键开启无敌模式或响应死亡、重开游戏界面操作:if (GetKeyState('Y') < 0 ){ if (isStop == FALSE) { //无敌模式开关 if (test == FALSE) test = TRUE; else test = FALSE; } else { isStop = FALSE; Restart(); }}// N键响应结束、重开游戏界面操作if (GetKeyState('N') < 0) { if (isStop!=FALSE) { MyDialog dialog; dialog.DoModal(); }}// 按空格进入游戏if (isStarted == FALSE && (GetKeyState(VK_SPACE) < 0)) { isStarted = TRUE;}
    4.14 鼠标消息响应// 鼠标移动void CPlaneWarView::OnMouseMove(UINT nFlags, CPoint point){ // TODO: 在此添加消息处理程序代码和/或调用默认值 if (myplane!=NULL && isPause == 0 ) { //绘制新游戏对象 myplane->SetPoint(point.x,point.y); } CView::OnMouseMove(nFlags, point);}// 鼠标左键发射子弹和开始界面进入游戏void CPlaneWarView::OnLButtonDown(UINT nFlags, CPoint point){ // TODO: 在此添加消息处理程序代码和/或调用默认值 if (myplane!=NULL&&isPause == 0 && bossLoaded == TRUE) { CBomb *Bomb1 = new CBomb(myplane->GetPoint().x, myplane->GetPoint().y, 1, isUpdate); PlaySound((LPCTSTR)IDR_WAV_BOMB, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); bombList.AddTail(Bomb1); CBomb *Bomb2 = new CBomb(myplane->GetPoint().x + BOMB_DISTANCE, myplane->GetPoint().y, 1, isUpdate); bombList.AddTail(Bomb2); //子弹自动飞行,在Timer中绘制 } if (isStarted == FALSE) { isStarted = TRUE; } CView::OnLButtonDown(nFlags, point);}
    4.15 生命周期4.15.1 窗口销毁void CPlaneWarView::OnDestroy(){ // 销毁指针资源 if (myplane != NULL) delete myplane; if (boss != NULL) delete boss; //释放内存资源 scene.ReleaseScene(); CView::OnDestroy();}
    4.15.2 游戏重新开始void CPlaneWarView::Restart(){ // TODO: 在此处添加游戏重新开始初始化参数 //战机重新加载 myplane = new CMyPlane(FALSE); //清空敌机链表 if (enemyList.GetCount() > 0) enemyList.RemoveAll(); //清空战机链表 if (meList.GetCount() > 0) meList.RemoveAll(); //清空战机子弹链表 if (bombList.GetCount() > 0) bombList.RemoveAll(); //清空敌机炸弹链表 if (ballList.GetCount() > 0) ballList.RemoveAll(); //清空爆炸链表 if (explosionList.GetCount() > 0) explosionList.RemoveAll(); //清空血包列表 if (bloodList.GetCount() > 0) bloodList.RemoveAll(); //参数重新初始化 myLife = DEFAULT_LIFE; lifeNum = DEFAULT_LIFE_COUNT; myScore = 0; passScore = 0; passNum = DEFAULT_PASS; isPass = 0; isPause = 0; lifeCount = 1; magicCount = 0; bloodExist = FALSE; bossBlood = BOSS_LIFE; isBoss = FALSE; bossLoaded = TRUE; isProtect = FALSE; isUpdate = FALSE; test = FALSE; boss = NULL; SetMyTimer();}
    4.15.3 游戏暂停void CPlaneWarView::Pause(){ // TODO: 在此处添加游戏暂停操作 isPause = TRUE; Sleep(1000);}
    4.15.4 生命值归零,游戏结束void CPlaneWarView::gameOver(CDC* pDC,CDC& cdc,CBitmap* cacheBitmap){ //结束游戏界面 //释放计时器 KillTimer(1); KillTimer(2); KillTimer(3); //KillTimer(4); KillTimer(5); //计算最后得分 myScore += passScore; //播放游戏结束音乐 PlaySound((LPCTSTR)IDR_WAV_GAMEOVER, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC); //清屏 CBitmap* tCache = cacheBitmap; cacheBitmap = new CBitmap; cacheBitmap->CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); cdc.SelectObject(cacheBitmap); //内存中承载临时图像的缓冲位图 delete tCache; isStop = FLAG_STOP;}
    5 游戏演示游戏初始界面

    游戏画面1

    游戏画面2

    游戏画面3

    游戏结束

    6 实训中遇到的主要问题及解决方法6.1 滚动背景实现问题要实现滚动背景,需要在背景图上取出客户区矩形大小的区域,并按照Timer进行递进,并且要在背景图边界处衔接好返回背景图开头位置,实现一张背景图的循环反复,达到滚动背景图的目的,刚开始由于不懂得如何实现开头结尾处的衔接问题,导致滚动背景图实现难度大,后来经由网上查阅的资料得知要把背景图加载两份,第二份背景图开头衔接在第一份结尾处,让客户区矩形在该两份背景图上进行滑动,当滑动到第二份背景图时,再把第一份接到第二份结尾处,从而实现循环反复滚动背景。其中实现的难点在于控制好客户区矩形坐标和背景图上要显示的图片块位于图片上的坐标的对应关系。
    6.2 背景音乐循环播放和游戏音效同时输出问题由于平时接触MFC太少,对MFC操作多媒体文件颇为陌生,刚开始使用的PlaySound可以简单的实现音频的输出,但是该函数只支持单个音轨的播放,不支持背景音乐和游戏操作音效的同时播放,后来经过大量的查找资料和代码样例,终于找到了使用mciSendString进行背景音乐的播放,而使用原来的PlaySound播放操作音效,达到了背景音乐和操作音效的同时播放的目的。
    6.3 多帧位图素材的动画特效实现问题飞机大战中的爆炸显示是通过一张带有八个帧的位图进行连续输出实现爆炸特效,刚开始只是简单的在一个while循环中循环八次,结果不尽如人意,后来联想到帧和时间的对应关系,在每一次onTimer调用时输出一帧,并在爆炸对象中用progress标记位记录播放位置,等到八个帧播放结束时,返回播放结束标记位,在onTimer中检测并删除播放完成的该爆炸对象。
    6.4 游戏结束和游戏重新开始的游戏资源维护问题该游戏由于实现了多个额外功能,且都是带有全局意义的,因此放置了较多标记位来标记每个状态,通过这些标记位来控制游戏进程中的各个状态,因此在游戏结束和重新开始游戏时,各个标记位的正确重置将会决定重新开始游戏之后的游戏状态。还由于这些操作可能会在onTimer函数调用过程中进行中断,这和onTimer中使用的双缓冲的pDC和cdc的申请和释放息息相关,中断操作必须对pDC和cdc进行正确的处理才会防止游戏过程中的意外崩溃出现。
    7 实训体会由于对MFC接触不多,那次本次实训开发飞机大战相当于是零基础,因此在开发过程中走了很多弯路,但是也是这次的实训,让我了解了MFC的事件处理机制,了解了如何在屏幕中绘制需要向用户展示的图形信息,学会了使用基本的操作对屏幕中绘制的图形进行控制,也了解了Windows的屏幕定时刷新的机制,学习了微软封装好的一些C++的类库,学会遇到问题到MSDN中进行API查询,并对VS的使用和编程也进行了提高。
    在开发过程中遇到了大量的异常和中断,通过此次开发,也学会了遇到错误如何慢慢通过IDE的错误信息进行错误查找和代码排错更正,通过添加断点调试程序一步步监视程序运行的具体过程,从而找到程序运行出错的原因。
    通过此次的实训,对MFC入了门,真正实现了图形界面的底层操作,消除了以前对于MFC的一些恐惧心理,让我也对MFC产生了兴趣,在之后的学习中,我会多找机会使用MFC进行一些开发,做一些实用的程序出来。
    1 评论 47 下载 2018-11-05 15:24:49 下载需要4点积分
  • 基于QT实现的字符串管理系统

    第一章 需求分析1.1 设计目的及要求1.1.1 设计目的运用面向对象程序设计知识,利用C++语言设计和实现一个字符串处理系统。
    1.1.2 设计要求要求具备如下主要功能:

    字符串的输入与输出
    求字符串的长度
    判断字符串是否为空
    将字符串置空
    字符串比较
    字符串赋值
    求指定位置的字符
    字符串合并
    字符串截取
    在指定的字符串中1,查找或替换字符串2

    1.2 设计需求分析
    第二章 具体设计2.1 设计思路本字符串管理系统采用基于QT的对话窗口,通过对字符串的输入,储存为QString形式,利用QString的丰富的函数及QT的信号与槽机制,实现所需的功能。
    2.2 概要设计
    2.3 详细设计UI设计

    对象及类关系,每个QLabel对应的类

    类成员函数
    void on_istr_editingFinished();void on_length_clicked();void on_isempty_clicked();void on_setfree_clicked();void on_compare_clicked();void on_indexof_clicked();void on_append_clicked();void on_mid_clicked();void on_find_clicked();void on_replace_clicked();
    函数详细设计
    // 完成字符串输入后,将字符串赋值给tpvoid Dialog::on_istr_editingFinished(){ tp=ui->istr->text();}
    // 求取字符串长度void Dialog::on_length_clicked(){ QString tempStr; ui->olength->setText(tempStr.fromUtf8("字符串长度为:") + tempStr.setNum(tp.length()));}
    // 判断字符串是否为空void Dialog::on_isempty_clicked(){ QString tempStr; if(tp.isEmpty()) ui->oisempty->setText(tempStr.fromUtf8("it's empty!")); else ui->oisempty->setText(tempStr.fromUtf8("it's not empty!"));}
    // 将字符串置空void Dialog::on_setfree_clicked(){ QString tempStr; tp.remove(0,tp.length()); ui->osetfree->setText(tempStr.fromUtf8("it has been set empty!"));}
    // 字符串比较void Dialog::on_compare_clicked(){ QString tempStr; if(tp>ui->icompare->text()) ui->ocompare->setText(tp+tempStr.fromUtf8(" > ")+ui->icompare->text()); if(tp==ui->icompare->text()) ui->ocompare->setText(tp+tempStr.fromUtf8(" = ")+ui->icompare->text()); if(tp<ui->icompare->text()) ui->ocompare->setText(tp+tempStr.fromUtf8(" < ")+ui->icompare->text());}
    // 求字符串指定位置的字符void Dialog::on_indexof_clicked(){ QString tp1; tp1=tp.at(ui->iindexof->text().toInt()); ui->oindexof->setText(tp1);}
    // 合并字符串void Dialog::on_append_clicked(){ //使用合并后的字符串 tp=tp+ui->iappend->text(); ui->oappend->setText(tp);}
    // 字符串截取void Dialog::on_mid_clicked(){ bool ok; //不使用截取后的字符串,使用原来的字符串,输出截取后的字符串 ui->omid->setText(tp.mid(ui->imidbegin->text().toInt(&ok,10),ui->imidlength->text().toInt(&ok,10)));}
    // 字符串查找void Dialog::on_find_clicked(){ QString tempStr; ui->ofind->setText(tempStr.setNum((tp.indexOf(ui->ifind->text(),0))));}
    // 字符串替换void Dialog::on_replace_clicked(){ int position; //使用替换后的字符串 position=tp.indexOf(ui->iprereplace->text(),0); tp.remove(position,ui->iprereplace->text().length()); tp.insert(position,ui->ilastreplace->text()); ui->oreplace->setText(tp);}
    第三章 调试分析3.1 问题1QT的信号与槽机制让字符串置空难以实现
    解决方式
    采用Public类型的QString字符串tp,在完成输入后赋值给 tp,,其他操作皆为对tp的操作,可实现置空。
    3.2 问题2求字符串指定位置的字符,利用const QChar QString::at(int position) const。此函数返回QChar类型,不同于char*类型。难以输出。
    解决方式

    第四章 测试结果


    第五章 结束语在为期两周的课程设计中,我体会颇多,学到很多东西,通过完成C++程序设计的任务,使我熟练和掌握了这学期所学的有关VS中的一些主要知识点和应用点,比如面向对象中的类的定义、类的实现、对象的定义、类的继承、类的多态性等等;还有面向过程的许多基础知识,结构体的基本类型,基本应用、结构体的堆栈基础知识等等。应用程序的设计和创建,经历了平时在课堂中和考试中,决不会有的重重难题和问题,这些问题,并不是我们平时只靠课本,就可以轻易解决的。所以,锻炼了我们面对难题,自己思索,自己探索,自己查资料发现问题、解决问题的独断能力。
    时间总是过得很快,转眼间C++课程设计就要结束了,通过这次课程设计,我们学会了很多,也懂得了很多,知道自己的C++还有有很大的提高空间。感谢老师,是您让我们知道了这些,是您一次次的帮助我们那些被我们收拾的面目全非的程序,让我们的程序起死回生;感谢老师,是您让我们看到自己的差距,看到了自己的基础知识还很薄弱,还有许多的只是我们还不懂,我们需要再接再厉去提高自己;感谢老师,是您让我们懂得了原来C++的许多程序原来是很深奥很深奥的,原来C++的许多程序是可以那样完成的,还有调试还可以是以那种形式去调试的;总之感谢您老师,是我们的C++有了进一步的提高,谢谢您老师,你们辛苦了。
    “实践是检验真理的唯一标准”。没有实践,就不会发现和深刻体会它的真实所在。只有通过检验的真理,在自己的心里,才会认可它的真实性。C++程序设计的完成,使我们懂得了真理的重要性,理论和实际的相结合,才能真正把握所学和所掌握的知识。理论的拥有并不能代表我们的实力和能力,一切的事件和其成功都是理论和实践的结合。总之,我们学习和掌握的知识,只有通过实际应用,才能真正的理解和掌握,才能更好的去应用理论的拥有并不能代表我们的实力和能力,一切的事件和其成功都是理论和实践的结合。我们学习和掌握的知识,只有通过实际应用,才能真正的理解和掌握,才能更好的去应用。
    1 评论 6 下载 2018-11-05 14:56:08 下载需要9点积分
  • 基于VC++和QT实现的图的可视化工具

    一、开发环境
    OS

    Windows 7 Ultimate 64 Bit
    IDE

    Visual Studio 2012 Premium (MSVC++ 11.0)Qt5 Visual Studio Add-in 1.2.3
    External Libraries

    Qt 5.3 32-bit for Desktop (MSVC 2012 OpenGL) [GPL]OGDF v. 2015.05 [GPL]

    Bin目录下是可执行文件。附有所需的MSVC 11.0运行库的安装包,以及Qt所需的一系列dll文件。已用虚拟机测试,在新安装的纯净Win10系统中能够正常运行。
    由于采用标准C++语法、采用的图形框架Qt是跨平台的、采用的外部库OGDF是C++的且是开源的,本软件理论上可跨平台编译运行。
    程序执行时默认从工作目录下的PaperConferenceAuthorGraph、TopicGraph子目录内读取输入数据。
    二、要求完成情况以下是已完成的项目,依大作业要求和评分标准列出。

    基本要求

    使用QT实现2个数据集的不同数据类型、不同Layout方法的绘图,能够在不同方法数据间切换能够展示节点和边的所有属性能够对视图进行放大、缩小(可使用鼠标滚轮)
    进阶要求

    可以用鼠标拖动节点不同Layout之间用动画进行变换鼠标在移到可以点击的部分时变成手的形状可以针对点的属性(连通度)进行过滤可以利用拉索选择一部分点进行高亮,并且拖动这一些点(鼠标右键进入多选模式)
    其他交互

    自定义视图的几何样式:半径、线宽、字号、配色主题为悬停和选中的节点提供视觉提示点击节点后高亮由其相邻节点构成的子图鼠标拖拽可进行视图平移鼠标滚轮可以当前指针位置为中心进行视图缩放
    自己写的Layout算法

    Circular(简单匀布在圆周上。这是一个平凡的算法,库里本来就有)Circular x2(选取一部分点放在中间的小圆圈内)Grid(简单平铺在平面格点上。这是一个平凡的算法,库里本来就有)LZYLayout(所有点分布成LZY字样。这是一个娱乐性的算法)

    水平实在有限,自己写的布局算法都比较水。感觉亮点在于细腻流畅的前端交互。除使用右侧面板中的按钮、滑动条、下拉列表框之外,主要的交互都可以由各种鼠标动作(左键、右键、滚轮,悬停、点击、拖拽)完成。工程总代码量不大(1.6k行,不计generated files),因为用了些面向对象的技巧精简节约代码量。数据结构和逻辑上做了精心的优化。据我自己测试,程序占用内存的峰值不超过12MB,交互和动画毫无卡顿感。
    三、简要操作说明和运行时截图
    上图为点击菜单项“Load>TopicGraph”后从文件加载TopicGraph后默认展示Force-directed布局。

    上图为放大到局部(可用鼠标滚轮和拖拽,也可用右上角导航按钮)。鼠标悬停于节点上时节点会变色,同时指针变为手形,右侧面板下方会分字段显示该节点详细信息;鼠标按下时节点会再次变色,为用户提供充足的视觉提示。点击一个节点后高亮出其所有相邻节点,显示出相关边的信息(权重)。

    使用鼠标拖拽该节点可改变其位置。与其相邻的边亦实时更新。

    用右侧面板可更改几何样式(半径、线宽、字号、配色)。图为半径最小、线宽最小、字号默认、清华紫配色。

    用右侧面板上的Layout下拉列表框可切换布局方案。切换时有动画过渡。图为Circular布局。用右侧Filter滑块可按节点的连通度进行过滤,被过滤的点和边变为浅色。

    矩形选框模式,可多选节点。按住鼠标右键画出矩形即可多选。下方状态栏提供操作指引。

    用矩形框选中多个节点后,用鼠标右键拖拽,可改变它们的位置。

    另一数据集合。图为从文件加载PaperAuthorConferenceGraph后,不同类型的点以不同颜色显示。

    Tools菜单下的文档内容查看器。输入ID可快速查看文档内容。
    四、实现说明4.1 数据的存储4.1.1 类的结构两种类型的图(PaperConferenceAuthorGraph和TopicGraph)都继承自同一个Graph基类;
    共四种类型的点(PaperNode, ConferenceNode, AuthorNode, TopicNode)都继承自同一个Node基类;
    共两种类型的边(DirectedEdge,UndirectedEdge)都继承自同一个Edge基类。
    点类中存有布局信息(坐标)。
    4.1.2 文件读取由于输入文件格式工整,直接一行一行读。
    图类的void importEdgesFromTXT(string fileName),void importNodesFromTXT(string fileName)两个成员函数用于读入顶点集、边集。
    对于DocumentContent.txt,考虑到该文件较大、不宜全部读入内存,我使用std::map<>建立了“文章id->该文章在文件中的位置”的映射(用到std:: ifstream 的ignore(), tellg(), seekg() 等函数进行随机读取),随用随读。
    4.1.3 点和边的关联顶点集、边集分别是图类的成员(std::vector<>)。节点id的统一性保证可根据节点寻找其邻居、根据边寻找其端点。为提高效率,在每个边中增设Node*成员,保存指向其端点的指针,以在绘制边时迅速得到端点坐标。在调用两个importFrom()之后调用bind()成员函数可维护上述结构。
    对于PaperConferenceAuthorGraph,由于其顶点分为三种,建立了三个成员vector<PaperNode> papers; vector<ConferenceNode> conferences; vector<AuthorNode> authors;。考虑到每当需要遍历所有节点时都需要些三个for(){}循环,代码量巨大,另建立了vector<Node\*> nodes;成员,统一保存所有节点的指针(基类型)。基类型有type字段,需要时据其强制转换为派生类指针以读取派生类特有字段。调用makeUnionNodeVector()成员函数维护上述结构。
    calcDegree()成员函数据顶点集、边集计算各顶点的连通度并存入内存,同时返回图中最大的连通度,用以设置界面上degree filter滑块的范围。
    以上列举的图类的诸成员函数存在相互依赖关系,调用时须明确先后。
    4.2 布局算法使用了OGDF提供的1种力导向算法,自己写了4种布局算法。调用图类的doLayout()函数进行布局计算。各种布局结果均直接存入每个节点。考虑到计算布局耗时较长,为避免用户在切换布局时的等待,本程序在载入文件后立即计算全部5种布局并保存在内存中。
    4.3 图形绘制和交互主窗口class GraphVisualizer继承QMainWindow。成员变量除图外,主要是绘图和交互所需的各种参数,如图形的几何参数、用于视图平移缩放的坐标转换的参数、颜色值、鼠标的坐标、与鼠标产生交互的节点的id或下标、动画的开关和相位等。成员函数主要是处理各种用户事件的槽函数。
    绘图纯QPainter,没有用QGraphicsScene。除沿边的方向斜着打印文字外,没有用到QPainter::setTransform,QPainter::translate。视图的平移缩放等全部是自己计算的,自己编写的,自己维护参数。
    鼠标交互全都靠处理mouseMoveEvent,mousePressEvent,mouseReleaseEvent,wheelEvent事件。为此在窗口类中设立了不少布尔型变量,维护这些flags以保证悬停、点击、拖拽等各种复杂鼠标事件的正确响应。遍历节点、寻找与鼠标动作产生交互的节点的操作是在处理鼠标事件时进行的,更新相关数据结构(当前与鼠标产生交互的节点的id或下标或指针(单个或集合),节点显示状态(高亮与否)的flag)以供绘制。
    动画是QTimer逐帧播放的。为播放动画,每个节点增设成员变量记录动画的终点。利用差分方程根据当前坐标、终点坐标和当前的相位计算下一帧时的坐标。
    1 评论 14 下载 2018-11-05 14:33:42 下载需要8点积分
  • QT实现基于栈的网页信息检索

    一、实验目标在完成对铁甲网论坛上发帖信息的提取和分词的基础上,通过建立词库的平衡二叉树和倒排文档实现对关键词的搜索。
    二、实验环境
    操作系统:Windows 7 x64位
    IDE:Qt 5.6.1
    编程语言:C++

    三、抽象数据结构说明3.1 平衡二叉树(m_balanced_binary_tree.h)树的节点为:
    struct node{ m_charstring<wchar_t> value; // 单词内容 int wordID; // 单词ID int Occur; // 单词总出现次数 int index; // 平衡因子 m_file_list file; // 文档链表 node *LChild, *RChild;};
    二叉树的每个节点记录了单词的相关信息。实现的功能有插入(Insert),搜索(Search)和调整二叉树使其平衡(Adjust)。
    3.2 平衡二叉树的优化最长匹配算法的分词特点是:由最长的待搜寻字符串开始,在词库中搜寻,若未找到,则减短待搜寻长度。根据此特点,我发现将同一长度的单词放置在同一平衡二叉树上,可以提高分词效率,避免重复浪费搜索。结构可以由下图表示:

    通过这样的方式,在分词判断不同长度的字符串时,我们可以直接查找相应“特定长度”的词库,而 不用去遍历全部词库,从而可以大大提高运算效率。
    3.3 词库精简和速度的优化通过研究词库以及2000个文本对原始全部词库的分词结果。(下图示例)

    发现分词结果多为2个字,这也是比较好解释的,词典中3个字的词语多为“专有名词”,4个字的词语多为“成语”,5个字及5个字以上的词语多为专有名词。在挖掘机论坛这样的环境下,很少会有3个字及以上的词语出现,通过上面建立的根据词语长度排列的平衡二叉树可以发现,3个字及以上的词语 占了很多存储空间和运算性能,但是并没有对分词结果起到明显效果。考虑到优化计算速度、精简程序体积,我在初始化词典及判断词时只考虑两个字长度的词语。
    3.4 ⽂文档链表(m_file_list.h)struct list_node{ int webID; // 网页ID m_charstring<wchar_t> website; // 网页网址 m_charstring<wchar_t> content; // 文档内容(gui程序中有,query程序中没有) m_charstring<wchar_t> keyword; // 关键词 m_charstring<wchar_t> title; // 标题 m_charstring<wchar_t> author; // 作者 m_charstring<wchar_t> date; // 发帖日期 int times; // 单词在该文件出现次数 list_node *next,*pre;};
    文档链表的每个节点记录了单词出现的次数、单词是什么、网页ID、文档内容等信息。实现了添加文档(add)、搜先文档(search)、编辑文档(edit)、删除文档(Remove)等功能。
    词典二叉树中的节点和文档链表类的关系如下图:

    搜索平衡二叉树的节点,节点中文件链表的节点就是包含这个词的网页,文档链表是双向链表,是为了方便插入和排序。文档链表的节点顺序按照词语出现的次数,出现次数多的在前面,出现次数少的在后面。因此在类函数中有函数sort(),它是用来调整插入节点顺序的,因为是双向链表,顺序调整十分⾼高效。
    3.5 输出哈希表和排序链表(query_web_hash_table.h)用“链式哈希表”和排序链表来整合输出结果,哈希表每个单元表示一个网页,详细的说明在后面介绍输出算法时将详细介绍。
    四、算法说明4.1 输出整合对于一些词语的搜索,调用平衡二叉树的Search函数,将返回相应的树节点,每个树节点的文档链表即使包含该词语的全部网页。对于同一网页,可能含有不同的搜索词,因此搜索后会有不同的文档链表节点,这些节点需要整合起来(记录搜索词总出现次数),并且只输出一次。
    建立如下图结构的哈希图,最上层是数组,每个数组节点链接一个链表,哈希函数网页ID(webID),用来整合。

    当搜索某一词语后,遍历词语树节点的文档链表节点,文档链表中存储了webID,可以根据哈希函数H(x)=webID找到最上层位置,然后将文档链表节点指针记录下来,构成链表,并记录搜索词语 出现的总次数。同时,将出现的webID记录到一个排序链表中,该链表的所有元素不重复,在此使用了“插入降序排列”。
    4.2 输出顺序优先按照搜索词出现次数先按照搜索词出现总次数从高到低进行排列。如果出现总次数一样,则按照词语出现“离散程度”排列,搜索词语离散程度越低(即出现次数越相近),则排列位置越靠前,反之越靠后。如果“离散程度”也一样,则按照所搜索词语的先后次序排序(初始搜索排序)。
    现对“离散程度”进行说明:
    该思想源于“方差”,该值越大,则表明数据越分散,即相互之间差距很大,该值越小,则表明数据之间比较相近。本程序自定义了以下公式来反应“离散程度”:

    其中n是输入搜索词语的个数, 是各搜索词语在网页中出现的次数, 是各搜索词语出现的平均次数。这个公式相比于方差的平方计算比较简单,运算效率更高。对于输出顺序的改变,是通过改变前面所述的记录webID的链表的节点顺序实现的。
    这样的顺序其实也符合我们日常的逻辑,如果搜索多个词语,搜出的结果包含搜索词次数肯定越多越好,同时还应该尽可能包含不同的关键词,这对应于就是“离散程度小。
    优先按照发帖日期生活中存在这样一种情况:我们需要提取出“最新”帖子。因此在这种排序下,发帖日期越晚,排列越靠前。当不同帖子发帖日期一样时,再按照搜索词语出现的总次数和“离散程度”进行排列。
    五、流程概述
    六、输入输出及操作相关说明exe文件夹中含有两个子文件夹,分别为批量查询和gui查询⽂文件,在两个子文件中,可以在input 文件夹中改变url.csv文件,在dictionary文件夹中改变字典文件。对于批量查询,在query.exe同一层级放入query.txt文件即可对其中内容进行查询。字文件夹中含有的其它文件是程序运行所需要链接的库。
    对于批量查询,直接打开query.exe程序,等待进度条读完,会显示”处理完成”。读条过程如下图所示:

    对于gui查询,打开gui.exe,等待初始化完成后,会显示搜索窗口,在输入框中输入关键词(例如 “二手挖机不想睡觉啦啦”),点击“搜索”即可。点击搜索结果中的网页链接,可以直接跳转至原网页。点击不同排序方式的按钮,可以改变搜索结果的排序方式,默认按“搜索词次数”排列。
    初始化及载入时间在Tsinghua 5G下约10分钟。
    七、实验结果经过调试和验证,发现实验结果符合预期。
    八、功能亮点说明
    Qt网络类的使用,大大提高了网页下载速度,其中还使用了异步操作;gui程序使用了多线程,确保了交互界面的及时刷新
    平衡二叉树按词长创建和词库的精简
    使用哈希表快速整合搜索结果,以及排序顺序的确立,不同的排序方式
    下载2000个网页加处理时间约10分钟左右,程序效率较高

    九、实验体会感觉整个实验很有趣也很有用,自学了很多东西,包括网页下载,Qt多线程程序,编码和解码等等。感觉要实现一些实用的功能其实挺复杂的,还需要我不断学习努力。
    1 评论 2 下载 2018-11-05 12:23:49 下载需要4点积分
  • 基于Libpcap实现的局域网嗅探抓包发包解析工具

    第一章 需求分析1.1 设计目的1.1.1 基本要求完成一个基于Libpcap的网络数据包解析软件,具有易用、美观的界面。
    1.1.2 具体要求
    能够解析本地或局域网的数据包,例如TCP包,UDP包,工CMP包等,能分析报头以及数据包内容
    能分析数据包的版本、头长度、服务类型、数据包总长度、数据包标识、分段标志、分段偏移值、生存时间、上层协议类型、头校验和、源’p地址和目的工p地址等内容。并将上述解析结果规范显示
    能解析每个数据包的内容(如果为加密信息不能解析则备注不能解析)
    能在制定时间段内解析数据包,当程序解析停止时,能分类统计在这段时间内解析的各类数据包的数量
    能够图形化显示数据包统计结果

    1.2 功能要求1.2.1 功能概况本次设计参考计算机网络基础知识,设计实现一个能够对本地局域网数据包进行捕获和分析的程序,并利用此程序实现网络监控和其他实用性功能。
    1.2.2 获取数据包功能利用Libcap,扫描设备网卡,选取不同网卡类型(WiFi/以太)进行对局域网数据包的监听和捕获,设计两种模式:混杂模式和非混杂模式,简单来说就是是否要过滤掉不是发送给自己的数据包,如果设置为混杂模式,则会收到整个局域网内的所有数据包并进行分析。
    1.2.3 分析数据包功能程序能够对捕获的数据包进行分类整理,并提取数据包中内容进行分析,包括数据包的版本、头长度、服务类型、数据包总长度、数据包标识、分段标志、分段偏移值、生存时间、上层协议类型、头校验和、源’P地址和目的’P地址等内容,并将上述解析结果规范显示。对于HTTP., ARP等报文,可以对其内容进行解析。
    1.2.4 图形化显示功能利用表格组件,将获取到的数据包信息以表格形式直观地显示出来,并且加以交换牛可以查看深层内容。
    1.2.5 数据包统计分析功能可以对一段时间获取的数据包进行统计整理,并根据数据包类型进行分类,如:数据包版本类型(IPv4/IPv6)、数据包协议类型(TCP/UDP/ARP等),并以饼图直观显示;对于TCP、UDP、ICMP数据包,用直方图统计其最大、最小、平均生存期和数据包大小,可以直观显示。
    1.2.6 数据包清空功能可以清空之前获取的所有数据包。
    1.2.7 Ping功能调用终端相应,实现Ping操作,可以用来测试与目标主机的连通性。
    1.2.8 TraceRoute功能调用终端相应,实现traceroute操作,通过traceroute我们可以知道信息从本计算机到互联网另一端的主机是走的什么路径。
    1.2.9 ARP-Attack功能本程序可实现局域网内ARP攻击,经测试可以对指定’P地址的主机实现断网攻击,基本原理同上,是欺骗目标机器局域网网关地址,使得该机器ARP缓存表中记录的网关IP -MAC对应错误,从而无法把数据包发送给真正的网关,导致网络连接失败,如果将欺骗的MAC地址设置为自己的MAC地址,则会截获目标机器发送的数据包。
    第二章 概要设计2.1 系统分析2.1.1 架构设计本系统采用架构如图2.1所示,主要分为三个模块:用户操作界面、数据处理模块和网卡驱动模块。其中,网卡驱动模块是最底层模块,主要实现从硬件网卡中获取所有的数据包,利用Unix系统下的Libcap工具,采用jni动态链接库调用网络层之下的底层功能,集成jpcap用来实现对数据包的分析处理;数据处理模块主要用于对网卡驱动模块获得的数据包进行分类处理,根据数据包版本和协议不同用不同方式拆解分析数据包,并将所得到的结果整理分析,同时可以实现对数据包的重构和发送操作;用户操作界面为最顶层功能,便于以可视化的图形界面直接显示抓取数据包的内容,以及可视化其他操作所得到的结果,便于使用者更好的操作和使用。

    2.1.2 实体设计2.1.2.1 登陆实体作为程序的进入窗口,用户登陆时,系统会要求用户输入账号和密码,并在数据库中进行匹配分析,若用户名存在且密码正确则通过身份认证,允许使用本软件。
    2.1.2.2 主界面实体登录成功后的功能选择界面,包含数据包抓取、Ping.. TraceRoute.. APR-Attack 四个功能,其中,对于数据包抓取还细分为抓取数据包和分析数据包功能。
    2.1.2.3 数据包抓取实体从主界面中选择进入,负责调用Libpcap抓取被监听网卡的数据包,可选择是否设置为混杂模式(是否过滤),并可以分类解析所有数据包中的内容,并以图形化界面显示。抓取结束后还可以对抓取到的数据包做统计整理,并将结果用图形化界面显示。
    2.1.2.4 Ping功能实体从主界面中选择进入,输入要测试的网站和测试的次数,可以得到访问该网站的往返RTT,在程序运行结束后可以汇总最大、最小、中值RTT,并可以用折线图将所有RTT统计显示。
    2.1.2.5 TraceRoute功能实体从主界面中选择进入,输入要追踪的网站和测试的次数,可以得到访问该网站的路径(’P地址),如果中途站点拒绝相应会显示“*“,在程序运行结束后可以汇总所经过的站点’P地址。
    2.1.2.6 ARP-Attack功能实体从主界面中选择进入,系统首先自动获取网管IP-MAC地址作为最低地址,同时得到广播地址以表示最高地址,并将该局域网内已连接设备的IP-MAC映射表先是在表格中。用户选择网卡类型后,在表格中选择相应的’P地址便可以对该设备实现APR攻击,使该设备无法连接到互联网。

    2.1.3 分层设计2.1.3.1 Jpcap驱动层(数据层)通过Java扩展jar-Jpcap调取Libpcap基本功能,以及jni包,对设备的底层网卡进行监听,将经过网卡的数据包捕获下来,可以应对不同网卡类型,如wifi、以太、网桥等,同时支持设置混杂模式以监控局域网内的所有数据包。
    2.1.3.2 数据处理层(功能层)主要是对接收到的数据包进行分类处理,将数据包分为IP数据包和ARP数据包,再对IP数据包协议进行解析从而分出TCP、UDP、ICMP、IGMP等数据包,同时对不用版本(IPv4、IPv6)的数据包也做区分,将分类结果。
    2.1.3.3 用户界面层(表示层)接受来自数据处理层的处理结果:对于异常,采用消息框提示的方式展示给用户;对于抓取的数据包分析结果,以表格的形式显示在界面上,用户可以点击表格中某一项查看详细信息;对于数据包的统计结果,自动绘制图表显示给用户;其他提示信息同样采用消息框给用户反馈。同时用户可以直接在界面上选择相应功能,底层会自动实现相应操作并反馈给用户界面层。同样,Ping、 TraceRoute、APR-Attack功能也有相应的图形化界面。

    2.1.4 数据库设计本程序中数据库仅做身份认证功能,不需要将统计到的数据包保存在数据库中,一方面是不需要检索和查询等操作,另一方面是数据库的使用并不会带来效率的提升,因此数据库仅仅保存用户名和密码,在登陆时对数据库进行匹配判断是否允许登陆。

    2.2 程序流程2.2.1 程序结构图
    2.2.2 调用关系运行程序后,首先进入用户登陆界面,用户名与密码不匹配则登陆失败,成功则进入用户操作主界面。
    在用户操作主界面中有四个按钮供用户选择,分别是:Packing、Ping、TraceRoute、Hack,分别对应网络数据包抓取分析功能、Ping功能、TraceRoute 功能、ARP攻击功能,点击不同按钮跳转至不同功能实现界面。
    网络数据包抓取分析功能分为数据包抓取、数据包分析和数据包重置功能:数据包抓取模块可以选取计算机指定网卡进行数据包的抓取操作,自动调用数据处理层的方法对数据包进行分类解析,并将解析结果显示在表格上,若用户想查看详细信息,可以点击表格中具体的某一项进行查看;数据包分析功能可以对本段时间抓取的所有数据包进行整理分析,并使用JFreeChart将所有数据可视化显示,直观的表现网络中流量的状态;数据包重置可以将本段时间抓取的数据包清空,便于下一次分析统计。
    Ping操作调用Unix终端的ping指令,用于测试一个站点的往返延迟(RTT), 本程序以图形化界面实现网络命令ping操作,并允许指定ping操作的执行次数,最终可以在程序运行后显示最大最小与均值RTT,点击绘图按钮可以以折线图统计本段时间所有的往返延迟。
    TraceRoute操作调用Unix终端的trace指令,用于追踪访问一个站点所经过的路由器(IP地址),本程序以图形化界面实现网络命令trace操作,并允许指定trace操作的测试包数目,最终可以在程序运行后显示所经过的全部站点。
    ARP攻击操作可以实现ARP模拟攻击,可以搜索本机局域网内的所有设备ip 地址,利用ARP诈骗原理实现对靶机的断网攻击,核心内容是使靶机无法找到网关IP地址所对应的MAC地址,从而使所有数据包无法发出局域网。
    第三章 详细设计3.1 功能类定义与实现3.1.1 常量与参数的定义(Parameter类)JDBC数据库连接所用参数与端口JDBC (Java Data Base Connectivity, java数据库连接)是一种用于执行SQL 语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC为数据库开发人员提供了一个标准的API,据此可以构建更高级的工具和接口,使数据库开发人员能够用纯Java API编写数据库应用程序,并且可跨平台运行,并且不受数据库供应商的限制。
    本系统采用的是MySQL数据库,编程语言为Java语言,编译器为Eclipse Oxygen, Java连接数据库时需要用到JDBC驱动器、数据库URL以及数据库管理员账号密码等。
    根据数据库的URL、用户名和密码,创建一个JDBC Connection对象。如:
    Connection connection=DriverManager.geiConnection(“连接数据库的URL"," 用户名“,“密码”)
    其中,URL=协议名++I[P地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。示例创建MySQL的数据库连接代码如下:
    Connection connectMySQL=DriverManager.geiConnection("jdbc:rnysql://localhost:3306/rnyuser", "root" ,"root");
    对应代码如下:
    public static final String JDBC_DRIVER="com.mysql.jdbc.Driver"; public static final String DB_RL="jdbc:rnysql://localhost:3306/java?useSSL=true&characterEncoding=UTF8";public static final static final String ACC = "root";pub1ic static final static final String PWD = "zxczxc";
    统计部分参数定义由于在统计部分需要用到已经捕获到的数据包的一些数据,例如:不同版本数据包的数量、不同协议数据包的数量、某协议数据包最大最小与均值生存期及数据包长度等,单独建立一个类用来统计所有的信息:
    package Uti1;public class Statistic{//不同协议总数public static int IPv4=0; public static int IPv6=0; public static int OtherVersion=0;//不同类型数据包总数public static int TCP=0; public static int UDP=0; public static int ICMP=0; public static int IGMP=0; public static int ARP=0; public static int Others=0;//数据包长度最大值、最小值、平均值 public static int TCP_maxLen=0; public static int UDP_maxLen=0; public static int ICMP_maxLen=0;(其他变量定义不再举例)
    3.1.2 IP/ARP数据包显示信息的定义IP数据包TCP/IP协议定义了一个在因特网上传输的包,称为ip数据包,而ip数据报(ip Datagram)是个比较抽象的内容,是对数据包的结构进行分析。由首部和数据两部分组成,其格式如图所示。首部的前一部分是固定长度,共20字节,是所有ip数据报必须具有的。在首部的固定部分的后面是一些可选字段,其长度是可变的。首部中的源地址和目的地址都是ip协议地址。
    ip数据包解析后的内容包括:版本、协议、数据包标识、生存时间、头长度、数据包总长度、优先权、服务类型、区分服务(最大吞吐量和最高的可靠性)、是否分段、分段偏移量、源’p地址与目的’p地址、源MAC地址与目的MAC地址、校验和;
    针对UDP、 TCP数据包,还设定了源端口号和目的端口号;
    针对ICMP数据包(v4/v6)还设定了ICMP报文代码和报文类型;
    除此之外可以详细显示DNS、 HTTP等数据包的包内容。
    ARP数据包在网络通讯时,源主机的应用程序知道目的主机的ip地址和端口号,却不知道目的主机的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP协议就起到这个作用。源主机发出ARP请求,询问ip地址是192.168. 0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF: FF: FF: FF: FF: FF表示广播),目的主机接收到广播的ARP请求,发现其中的ip地址与本机相符,则发送一个ARP应答数据包给源主机,将自己的硬件地址填写在应答包中。这就是ARP数据包。
    ARP数据包解析后的内容包括:版本、协议、头长度、数据包总长度、硬件类型、协议类型、硬件地址长度、协议地址长度、操作字段、源’p地址与目的’p地址、源 MAC地址与目的MAC地址;
    除此之外可以详细显示ARP数据包的包内容。
    3.1.3 捕获数据包的实现(HandleJpcap包)使用Jpcap捕获数据包并分类处理Java的java. net包中,给出了传输层协议TCP和UDP有关的API,用户只能操作传输层数据,要想直接操作网络层,比如自己写传输层数据报(自己写传输层包头), 或者自己写好ip数据包向网络中发则是无能为力的。而Jpcap扩展包弥补了这一点,使我们可以支持从网卡中接收ip数据包,或者向网卡中发送ip数据包。
    Jpcap实际上并非一个真正去实现对数据链路层的控制,而是一个中间件,JPCAP 调用wincap/libpcap,而给JAVA语言提供一个公共的接口,从而实现了平台无关性。
    下载libcap (Linux上的Wincap),使用jni连接C语言调用底层网卡功能,再建立Java动态链接库,实现jpcap. jar的生成。
    使用jpcap后,编写程序按要求完整数据包的抓取,可以获得不同类型数据包,并根据数据包类型对不同数据包进行解析,可以在表格上显示不同数据包的类型、数据包标识、版本、源IP地址、目的IP地址以及数据包长度。对表格每一项可以选择查看其具体信息,对不同数据包有不同的描述,IP包为一大类,ARP包为另一类。
    Jpcap包中主要是用到的方法如下:

    JpcapHandler:这个接口用来定义分析被捕获数据包的方法
    ARPPacket:这个类描述了ARP/RARP包,继承了Packet类
    DatalinkPacket:这个抽象类描述了数据链路层
    EthernetPacket:这个类描述了以太帧包,继承DatalinkPacket类
    ICMPPacket:这个类描述了ICMP包,继承了IPPacket类
    IPAddress:这个类描述了IPv4和工Pv6地址,其中也包含了将IP地址转换为域名的方法
    IPPacket:这个类描述了IP包,继承了Packet类,支持IPv4和IPv6
    Jpcap:用来捕获数据包
    JpcapSender:它用来发送一个数据包
    Packet:这个类是所有被捕获的数据包的基类
    TCPPacket:这个类描述TCP包,继承了IPPacket类
    UDPPacket:这个类描述了UDP包,继承了IPPacket类

    抓取数据包过程如下:
    首先,获得所有网卡列表
    NetworkInterface[]devices=JpcapCaptor.getDeviceList();
    通过网卡,打开一个捕捉器
    openDevice(NetworkInterface intrface, int snaplen, boolean promics, int to_ms);JpcapCaptor captor=JpcapCaptor.openDevice (device[index], 65535, false, 20)
    通过捕捉器捕获数据:
    本程序采用主动获取的方法。通过捕获器的getPacket ()可以主动的,一个数据包一个数据包的获取。主动获取,可以每次需要处理数据包的时候,自己去主动获取。然后写出处理逻辑。但是因为不知道何时去产生数据包,而一直去调用 getPacket(),因此需要开辟多线程,让用户自行终止数据包的捕获。
    数据包分类:
    利用packet instanceof XXPacket对捕获到的数据包进行分类,并对不同类型的数据包进行分类拆解。
    Jpcap还可以完成以下功能:

    Jpcap是直接抓取经过数据链路层的数据包。因此可以自己写’p数据包直接发送给数据链路层
    Jpcap会对抓取到的数据包进行一定程序的解析,根据数据包内容,将数据包封装为对应的对象
    根据用户设定的信息,过滤数据包(其实就是在解析的时候,对不需要的数据直接丢弃,不解析)
    Jpcap只是直接从数据链路层上读取数据,并向数据链路层中发送数据,因此,Jpcap并不能操作其他程序从数据链路层中读数据或者向网卡中发送数据

    数据包存储数据结构的定义(Packet类与AllPackets类)为方便整理统计各类数据包,我们需要采用一种数据结构用来存放前一段时间捕获到的所有数据包以便统计,为此单独设置一个Packet类用来描述单个数据包(ARP/IP数据包通用),对所有的数据包,创建一个集合ArrayLi st用来保存所有的Packet, 放入AllPackets类中,可以对这个集合增添数据包,在某些时刻可以清除已有的数据包。
    3.1.4 Ping操作实现部分(HandlePing包)3.1.4 Ping功能作用Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”、不能打开网页时会说“你先ping 网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机,并显示响应情况,这样我们就可以根据它输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过 ping探测到,通过防火墙设置了禁止ping 或者在内核参数中禁止ping,这样就不能通过ping确定该主机是否还处于开启状态。
    linux下的ping和windows下的ping稍有区别,linux下ping不会自动终止,需要按 ctrl+c终止或者用参数一C指定要求完成的回应次数。
    命令格式
    ping [参数] [主机名或IP地址]
    命令功能
    ping命令用于:确定网络和各外部主机的状态;跟踪和隔离硬件和软件问题;测试、评估和管理网络。如果主机正在运行并连在网上,它就对回送信号进行响应。每个回送信号请求包含一个网际协议(IP))和ICMP头,后面紧跟一个tim结构,以及来填写这个信息包的足够的字节。缺省情况是连续发送回送信号请求直到接收到中断信号(Ctrl-C)。
    ping命令每秒发送一个数据报并且为每个接收到的响应打印一行输出。ping命令计算信号往返时间和(信息)包丢失情况的统计信息,并且在完成之后显示一个简要总结。ping命令在程序超时或当接收到SIGINT信号时结束。Host参数或者是一个有效的主机名或者是因特网地址。
    命令参数

    -d:使用Socket的SO_DEBUG功能
    -f:极限检测。大量且快速地送网络封包给一台机器,看它的回应
    -n:只输出数值
    -q:不显示任何传送封包的信息,只显示最后的结果
    -r:忽略普通的Routing Table,直接将数据包送到远端主机上。通常是查看本机的网络接口是否有问题
    -R:记录路由过程
    -v:详细显示指令的执行过程
    <p>-c:数目,在发送指定数目的包后停止
    -i:秒数,设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次
    -I:网络界面,使用指定的网络界面送出数据包
    -l:前置载入,设置在送出要求信息之前,先行发出的数据包
    -p:范本样式,设置填满数据包的范本样式
    -s:字节数,指定发送的数据字节数,预设值是56,加上8字节的ICMP头,一共是 64ICMP数据字节
    -t:存活数值,设置存活数值TTL的大小

    Java调用Unix终端实现Ping操作
    本扩展功能使用Java调用终端Ping指令,在表格上显示Ping的相关信息,并绘制Ping操作往返延迟折线图,从而实现图形化操作的Ping指令。
    利用正则表达式截取从终端返回的信息
    为方便统计所有的ping结果,并获得最大、最小、均值RTT,我们需要对终端返回的信息进行筛选,由于java中已存在封装好的剪切字符串的方法,可以不用自己编写正则表达式。
    经过以上方法,每一次ping操作的往返延迟便被记录下来展现,同时,最大最小与均值RTT也被记录了下来,表现如下:


    3.1.5 TraceRoute操作实现部分(HandleTraceRoute包)3.1.5.1 TraceRoute功能作用通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地 (destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。linux系统中,我们称之为traceroute,在MS Windows中为tracert。
    traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其IP地址。
    在大多数情况下,我们会在linux主机系统下,直接执行命令行:
    traceroute hostname
    命令格式
    traceroute [参数] [主机]
    命令功能
    traceroute指令让你追踪网络数据包的路由途径,预设数据包大小是40Bytes,用户可另行设置。
    具体参数格式
    traceroute [-dFlnrvx] [-f<存活数值习[-g<网关>…][-i< 网络界面>][-m<存活数值>] [-p<通信端口>] [-s<来源地址>]\ [-t<服务类型>] [-w<超时秒数>][主机名称或IP地址][数据包大小]
    命令参数

    -d:使用Socket层级的排错功能-f:设置第一个检测数据包的存活数值TTL的大小-F:设置勿离断位-g:设置来源路由网关,最多可设置8个-i:使用指定的网络界面送出数据包-I:使用ICMP回应取代UDP资料信息-m:设置检测数据包的最大存活数值TTL的大小-n:直接使用ip地址而非主机名称-p:设置UDP传输协议的通信端口-r:忽略普通的Routing Table,直接将数据包送到远端主机上-s:设置本地主机送出数据包的ip地址-t:设置检测数据包的TOS数值-v:详细显示指令的执行过程-w:设置等待远端主机回报的时间-x:开启或关闭数据包的正确性检验
    Java调用Unix终端实现TraceRoute操作
    本扩展功能使用Java调用终端traceroute指令,在表格上显示traceroute的相关信息,并绘制traceroute操作经过的站点,从而实现图形化操作的traceroute指令。
    利用正则表达式截取从终端返回的信息
    为方便统计所有的trace到的站点结果,我们需要对终端返回的信息进行筛选,由于java中已存在封装好的剪切字符串的方法,可以不用自己编写正则表达式。
    经过以上方法,每一次trace的站点便被记录下来,表现如下:

    3.1.6 ARP-Attack操作实现部分(ARPAttack包)3.1.6.1 ARP原理简介每台主机都设有一个ARP高速缓存(ARP cache),里面有本局域网上各主机和路由器的IP地址和硬件地址的映射表,这些都是该主机目前知道的一些地址。
    当主机A要向本局域网上的某个主机B发送ip数据报时,先在其ARP高速缓存中查看是否有主机B的ip地址。如果有,就在ARP高速缓存中查出其对应的硬件地址,再把这个硬件地址写入MAC帧,然后通过局域网把该MAC帧发往此硬件地址。也有可能查不到主机B的ip地址的项目。这可能是主机B才入网,也可能是主机A刚刚加电,其高速缓存还是空的。在这种情况下,主机A就自动运行ARP,然后按以下步骤找到主机B的硬件地址。
    实现地址解析的第一步是产生ARP请求帧。在ARP帧数据部分的相应字段写入本地主机的物理地址、ip地址、待侦测的目的ip地址,在目的物理地址字段写入0, 并在操作类型字段写入1,用以表示本数据帧是一个ARP请求数据帧。
    该ARP请求帧以本地网络适配器的物理地址作为源地址,以物理广播地址(FF-FF-FF-FF-FF-FF)作为目的地址,通过物理层发送出去。
    由于采用了广播地址,因此网段内所有的主机或设备都能够接收到该数据帧。除了目的主机外,所有接收到该ARP请求帧的主机和设备都会丢弃该ARP请求帧,因为目的主机能够识别ARP消息中的ip地址是否与本机相同。
    与目的ip地址匹配的主机构造ARP应答帧。在ARP应答帧中,以请求分组中源物理地址、源ip地址作为其目的物理地址、目的ip地址,并将自身的物理地址、ip地址填入应答帧的源物理地址、源ip地址字段,并在操作字段中写入2,表示本 ARP数据帧是一个应答数据帧。该分组通过数据链路层直接发给源主机。
    源主机接收到ARP应答帧后,获得目的主机的物理地址,并将它作为一条新记录加入到ARP高速缓存表。
    此外,如果源主机没有发送ARP请求而收到其他主机的ARP响应数据帧,源主机也会在本地ARP缓冲区中缓存该主机物理地址和ip地址的对应关系。
    ARP高速缓存是非常有用的。如果不使用ARP高速缓存,那么任何一个主机只要进行一次通信,就必须在网络上用广播的方式发送ARP请求分组,这会使网络上的通信量大大增加。ARP把保存在高速缓存中的每一个映射地址项目都设置生存时间,超过生存时间的项目就从高速缓存中删除掉。
    ARP是解决同一个局域网上的主机或路由器的ip地址和硬件地址的映射问题。
    3.1.6.2 ARP数据包格式在了解ARP原理的基拙上,还必须了解ARP数据包的格式才能实现ARP欺骗。

    注意到源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。硬件类型指链路层网络类型,1为以太网,协议类型指要转换的地址类型,0x0800为ip地址,后面两个地址长度对于以太网地址和’p地址分别为6 和4(字节),op字段为1表示ARP请求,op字段为2表示ARP应答。

    3.1.6.3 ARP欺骗原理一台电脑通过网络访问另一台电脑的时候,在数据链路层需要知道对方的 MAC地址进行真正的物理通信。
    而电脑上的应用程序通常都是根据另一台电脑的’p地址来和对方建立通信,这时候就需要有一个协议将ip地址解析到MAC地址,这就是ARP协议。
    而ARP具体过程就是当需要通过ip获取一个远端的的MAC地址的时候,系统会首先检查ARP表中是否存在对应的ip地址,如果没有,则发送一个ARP广播,当某一个拥有这个MAC地址的节点收到ARP请求的时候,会创建一个ARP reply包,并发送到ARP请求的源节点,ARP Reply包中就包含了目的地节点的 MAC地址,在源节点接受到这个reply后,会将目的地节点的MAC地址保存在ARP 缓存表中,下一次再次请求同一ip地址的时候,系统将会从ARP表中直接获取目的地MAC地址,而不需要再次发送ARP广播。
    用ping命令举例来说,在机器A,比如说ip是192.168.1. 2中发出下面的 ping命令:
    ping 192.168.1. 3,如果192.168.1.3这个MAC地址在机器A的ARP缓存表中不存在,这时候机器A就会发送一个ARP广播,当192.168.1.3接到广播后,会给机器A回一个ARP Reply包,其中包含了192.168.1.3的MAC地址,这是正常的 ARP过程。
    假设IP—MAC的对应关系为(192.168.1.3—OO-1C-23-2E-A7-OE)
    但是局域网内的其他机器也可向机器A发一个假的IP—MAC对应关系ARP Reply包,机器A接收到这个假的包后同样会更新自己的ARP缓存表.假设发送的为(192.168.1.3—OO-1C-23-2E-A7-OA,OA实际上为192.168.1.4的MAC地址)那么机器A再通过IP往192.168.1.3发送数据的时候,实际上却发到了192.168.1.4这台机器.这样就到达了ARP攻击或者叫做ARP欺骗,RP攻击只能发生在局域网内。
    ARP欺骗分为两种,一种是针对路由器进行欺骗,使路由器的ARP缓存中建立错误的IP与MAC地址映射表,结果就是从路由器发送的数据都发给了一个错误的MAC地址,造成主机无法正常接收信息。另一种是对主机中的ARP缓存进行欺骗,伪造主机路由器IP和MAC地址映射表,使主机发送的数据都发送到伪造后的MAC地址对应的主机上,这种情况不仅会使主机不能正常上网,而且还能用来窃取信息。
    3.1.6.4 本程序ARP欺骗方式我们用程序实现第二种ARP欺骗,并对第二种欺骗方式进行举例。
    假设现在主机A和主机B通过路由器C连接在同一个局域网内,主机A是欺骗方,主机B是被欺骗方。主机A在自己的ARP缓存中查到路由器C的IP地址 IP_C, MAC地址MAC_C,查到主机B的IP地址IP_B。接下来,主机A自己生成一个ARP回复,该回复的源IP并不是主机A的IP地址,而是冒充路由器C的IP地址IP_C,同时编造一个不存在的MAC地址作为回复的源MAC地址(可以设置为自己主机的MAC地址用来窃听),目标IP是IP_B,目标MAC地址是MAC一。这个ARP回复以单播的形式发送给主机B之后,主机B并不会验证回复的真实性,而是会把路由器的IP_ C和一个错误的MAC地址写入自己的ARP缓存,此时,所有主机B通过路由器发送给外界的数据,都会因为找不到路由器而发送失败。(在此,路由器也称作为网关)。
    总结一下:主机A冒充路由器C不断向主机B发送一个包含错误的IP和MAC 映射表的ARP回复,使主机B经由路由器发给外界的数据无法到达目的地,从而致使主机B无法正常上网。
    3.1.6.5 ARP攻击实现流程本实验ARP攻击实现如下图所示:

    在本机接入局域网时,首先调取终端功能查询本机IP地址和MAC地址,并获取局域网网关IP地址和MAC地址,同时获取广播地址查看网段大小;
    接入局域网监听一段时间后,可自动获取局域网内所有设备的IP地址与MAC地址,这些信息缓存在系统arp表中,可以将其查询出来:

    然后选择要攻击设备的IP地址,选定伪装的MAC地址(如果设置为本机MAC地址则会截获所有数据包,起窃听作用),设置网卡模式((WiFi/以太),设置攻击间隔(多长时间攻击一次),便可开始ARP攻击。
    3.1.6.6 ARP欺骗注意事项由于存在ARP代理,对于没有配置缺省网关的计算机要和其他网络中的计算机实现通信,网关收到源计算机的ARP请求会使用自己的MAC地址与目标计算机的IP地址对源计算机进行应答。因此不可以随意指定IP地址,因为如果是错误的IP地址,网关会用自己的MAC地址做应答,然而实际无意义,因此需要判断返回的ARP应答对应的MAC地址是否为网关地址。
    需要在进行攻击之前发送心跳包,测试指定的IP地址是否存在,是否是一个真正的设备而不是预留的设备。
    3.2 函数调用关系图
    程序由LoginFrame()进入,登陆成功进入MainFrame( ),Mainframe可以选择进入GrabbingPacketMainFrame、startPingFrame、startTraceRouteFrame和 startARPAttcckFrame,其中GrabbingPacketMainFrame调用startGrabbingFrame和StatisticFrame函数, StatisticFrame调用HandieDate、JFreeChart和JDBC函数完成统计并自动将数据可视化。
    第四章 测试登录界面

    程序主界面
    如果登陆失败,如账号不存在或密码错误,则会给以相应提示,登陆成功则跳转至程序主界面。

    数据包处理界面

    数据包抓取
    点击数据包抓取按钮,显示出一个表格,首先需要选择网卡类型,即现在上网的方式是使用WiFi还是以太网;选择网卡模式后选定是否设置为混杂模式,即是否过滤掉不是发送给自己主机的数据包,如果不过滤的话意味着可以监听整个局域网内的数据包。点击开始自动抓取数据包,点击暂停停止数据包的捕获。






    1 评论 13 下载 2018-11-05 12:08:40 下载需要13点积分
  • 基于TCP SOCKET实现的远程聊天系统

    一、实验目的本次实验需要完成一个实时聊天系统。该系统由聊天客户端和聊天服务器组成。客户端即可以给指定客户端发送信息,也可以把消息广播给所有客户端。通过这个实验,使得学生掌握socket API编程。
    二、实验内容该章节将会详细的列出服务器和客户端的功能要求。
    2.1 服务器的功能
    服务器能够并行处理客户端发送过来的消息或者命令
    服务器把从新连接的客户端中收到的第一条消息作为该客户端的用户名称
    服务器能够处理从客户端发送过来的两种类型的信息:

    广播信息,服务器收到信息后将其广播给其他客户端点对点信息,服务器将消息转发给指定客户端
    服务器需要把某个客户端发送的广播聊天信息,广播给和该客户端在同一个频道的所有客户端(但是不包括该用户端自己)这个广播消息的格式应为[<name>]:<message>。其中<name>为发送聊天信息的客户端名称,<message>为该客户端发送的消息
    当某个客户端离线时,服务器需要广播一个离线消息给所有的客户端
    容错处理。当客户端发送的数据和规定的格式不一致时,服务器能够处理这个错误并且返回错误信息

    2.2 客户端的功能
    从服务器返回的消息需要显示在控制台下,并且需要去除末尾多余的空格
    为了区别于其他人发送的消息,客户端自己发送的消息,需要在控制台中以“[me]:”显示

    三、操作环境
    操作系统:Mac OS
    编写语言:Java
    编译软件:Eclipse

    四、问题分析网络编程中两个主要的问题一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定工nternet上的一台主机。而TCP层则提供面向应用的可靠(TCP)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。
    两类传输协议:TCP和UDP。TCP是Tranfer Control Protocol 的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket 连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
    UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
    经过慎重的选择,本程序基于TCP传输协议,本人独立完成了整体设计,并部分借鉴了部分教材上使用的架构设计,逐项完成了实验要求的功能。

    五、详细设计5.1 总体设计思路单连接、单线程如果仅仅是客户端与服务器进行通信,则设计较为简单,仅需要在服务器开辟端口建立ServerSocket即可,用死等函数.accept()等待客户端Socket连接,一旦连接成功则在服务器建立Socket对象匹配客户端。具体描述如下:

    用指定的端口实例化一个Seve rSocket对象。服务器就可以用这个端口监听从客户端发来的连接请求
    调用ServerSocket的accept()方法,以在等待连接期间造成阻塞,监听连接从端口上发来的连接请求
    利用accept方法返回的客户端的Socket对象,进行读写I0的操作
    关闭打开的流和Socket对象

    其中,I0操作可以利用Socket提供的getlnputStream和getOutputStream方法得到输入和输出流,为方便起见,可以采用 Buffered Reader/PrintStream封装。

    客户端较为简单,只需要匹配到服务器的’P地址和相应的Port 即可。具体描述如下:

    用服务器的’P地址和端口号实例化Socket对象
    调用connect方法,连接到服务器上
    获得Socket上的流,把流封装进Buffered Reader/Pri ntStrea m的实例,以进行读写
    利用Socket提供的getlnputStream和getOutputStrea m方法,通过旧流对象,向服务器发送数据流

    关闭打开的流和Socket。

    单连接、多线程上述方法一个很致命缺点,这个连接没有开辟多线程,也就是说,服务器必须等待客户端发送来消息后才可以给一个回执消息,客户端操作过程也类似,但我们希望的应该是一个全双工的通信,因此必须开辟多线程。
    开辟多线程的方法很简单,只需要继承Runnable接口即可,然后将I0操作添加至run函数中。

    多连接、多线程现在基本的通讯问题己经解决了,服务器和客户端可以像发短信一样互相全双工的发送信息了,但这还远远不够,因为我们不能是针对两个用户的应用,我们的应用应该可以在服务器转发来自不同用户的消息,并且实现私聊和群聊的区别,因此,我们需要做一个概要设计,之后会详细讲述每部分的内容。
    如图,大体的设计架构如下,总共分为三个包:


    Client包:与客户端相关的一切信息,其中Login类实现客户端到服务器的登陆,Register类实现新用户信息的注册,ChatRoom为聊天主界面,ChatFra m为私聊窗口。
    Server包:完成服务器相关功能实现,FileOpen用于识别登录用户的各项信息,由于时间紧迫,暂未连接数据库,采用的是文本文件存储用户内容,Connection类用于开辟端口等待连接,同样采用多线程技术为每个链接进来的用户开辟线程,并保存所有己连接用户的信息,并保存他们的进程,ChatCu stomer类用于保存连接用户的全部信息,并完成登陆、注册消息的处理以及聊天内容、文件等转发操作。
    Util包:为服务器客户端通用内容,Customer保留了每个登陆用户的信息,Message定义了消息的类别、内容、发送方、接收方以及其他信息,Mtype类定义各种用到的常量。

    Util包具体内容Mtype类定义各种常量,主要分两类:消息类别和IP/Port

    Message类
    如图,主要定义了消息类型、消息内容、发送方、接收方以及其他信息(主要用于文件传送),类中用set函数和get函数为消息赋值和得到消息内容。
    Customer类
    同Message类,主要为得到己连接用户的各项基本信息。
    Server包Connection类由基本的ServerSocket功能扩展而来,主要为并行处理客户端发送过来的消息或者命令,并保存各个连接的客户线程。

    Vector可以更好的处理并发功能,在客户端窗口顶端显示在线的人数。

    采用多线程,并行处理多个连接。并可以返回在线用户列表。
    ChartCustomer类该类主要是处理每个连接到服务器的客户端线程。
    根据发送来的消息,可以分为:登陆消息、离线消息、注册消息、普通聊天消息、文件传送消息,以及应对错误错误消息格式的反馈。在此,如果一个线程断开,认为该用户已离线,发出离线消息给所有用户。

    对于每个消息,服务器都会解析消息的具体内容,对于登录的用户,需要判断该账号是否己经在线、用户名密码是否正确,如果成功登陆则为该用户保留线程资源的同时发送该用户上线的消息给所有在线用户;对于注册用户,注册时需要判断用户名是否己存在,存在则返回注册失败的消息,否则保留该用户的资料;处理文件消息和聊天消息在服务器端是相同的,只需要判断是发送给所有用户的还是发送给单独用户得即可,在服务器实现转发功能。

    FileOpen类保存用户资料。

    Client类由于此部分代码多为界面设计,在此不一一罗列。
    Login类登陆界面,输入用户名密码登陆,登录成功会显示聊天主界面,登录失败会返回失败原因
    Register类用户注册界面,注册失败会予以反馈。
    ChartRoom类聊天主界面,可选择在线用户进行私聊或广播消息,同样可以给用户传送文件或给所有用户群发文件。
    在系统消息一栏,用于显示各种系统消息,如:用户上线、下线提醒,新消息通知、文件传送提醒等。
    ChatFrame类类似腾讯00的聊天界面,可以从本地同步聊天记录,并允许发送消息、接收消息。
    六、测试登录界面

    聊天室主界面

    可以实现私聊功能

    可以实现广播功能

    可以实现文件传送

    离线有提醒
    1 评论 16 下载 2018-11-05 11:58:38 下载需要7点积分
  • 利用TCP协议自己编写服务器

    一、 需求分析
    在 socket 客户端实验的基础上,编写自己的服务器
    实现的功能包括:

    客户端上传文件并自动用 RSA 算法加密/服务器接收文件客户端下载文件并自动解密(拥有密钥)/服务器发送文件客户端获得服务器上的文件列表
    本次实验中数据的传输采用 TCP 协议

    服务端 IP 地址为本机 IP 地址 端口为 10086
    考虑服务器并发性,依次尝试阻塞式服务器、并发式服务器、 异步式服务器,并给出特征分析

    二、操作环境
    操作系统:Mac OS
    编写语言:Java
    编译软件:Eclipse

    三 、 概要设计3.1 客户端3.1.1 Client 类的基本操作
    public void put() throws Exception

    操作结果:将本地文件发送到服务器
    public String listAll() throws Exception

    操作结果:返回服务器保存的文件列表,包括可供下载的文件和用户上传的文件
    public int get(String file_name) throws Exception

    传入参数:要下载文件的名称操作结果:得到下载的文件,下载成功返回 1,失败返回 0(文件不存在)
    public String pre_list() throws Exception

    操作结果:为方便得到服务器上传和下载的文件列表,此函数保留以便今后添加新功能
    public Client()

    操作结果:构造函数,连接服务器

    3.1.2 Frame 类主要为显示客户端主界面,包括各种空间和各种事件处理函数,调用 Client 类 的各种方法,实现图形界面
    3.1.3 ListAll 类主要为显示服务器上的文件列表,包括可供下载的文件和用户上传的文件,调用 Client 类的各种方法,实现图形界面
    3.1.4 download 类主要为显示服务器上可供下载的文件,输入相应文件名后可自动下载,调用 Client 类的各种方法,实现图形界面
    3.1.5 Encrypt 类采用静态方法 public static byte[] encrypt(byte[] data, String filename) throws Exception,传入加密文件转换成的 byte 数组和加密文件的文件名,返回得到经 RSA 算法加密得到的 byte 数组(加密后的文件),其中,私钥保存在本地。
    3.1.6 Decrypt 类采用静态方法 public static byte[] decrypt(byte[] data, String filename) throws Exception,传入从服务器收到加密后文件转换成的 byte 数组和加密文件的文件名,返回得到经 RSA 算法解密得到的 byte 数组(解密后的文件),其中,自动提 取本地私钥,若私钥不存在,则无法解密,返回加密文件。
    3.2 服务器3.2.1 Sever 类包括绑定服务器端口,选择服务器保存文件的目录(以供接受客户端文件),依次测试阻塞式服务器、并发式服务器、异步式服务器。
    3.2.2 Socket_connect 类
    public Socket_connect() throws Exception

    操作结果:构造函数,传入连接成功的 socket,进行初始化
    public void choose() throws Exception

    操作结果:根据客户端传来指令的类型,自动选择发送文件、接受文件、 显示客户端文件列表功能
    public void success_message(String str) throws Exception

    传入参数:成功时需要显示的信息操作结果:服务器成功操作后返回给客户端信息
    public void error_message(String str) throws Exception

    传入参数:失败时需要显示的信息操作结果:服务器成功失败后返回给客户端信息
    public void send_list() throws Exception

    操作结果:服务器发送给客户端文件列表
    public void receive_file() throws Exception

    操作结果:服务器接受客户端发送过来的文件(已加密)
    public void send_file() throws Exception

    操作结果:服务器发送给客户端文件,若文件不存在,返回失败信息

    四 、服务器并发性能分析目前常用的服务器模式共分为三种:

    第一种是阻塞式服务器,是最好实现的服务器,也是问题最多的服务器。客户端发送到服务器的请求,服务器会进行排队,依次处理请求。前一个请求没有处理完成,服务器不会处理后面的请求。也就相当于使用一个 while(true)循环,依次调用 seversocket.accept()函数,每次只接受一个客户端访问,知道访问完成后才处理下一个客户端的请求。这种服务器很容易进行攻击,只需要向服务器发送一个处理时间很长的请求,就会将其他的请求堵在门外,导致其他请求无法得到处理,所以,这种服务器更多的是作为理论模型,实际应用并不多。
    第二种是并发式服务器,这种服务器处理请求时,每接收到一个请求,就启动一个线程处理该请求,这种模式的服务器,好处是不会出现阻塞式服务器请求被拥堵的情况,但是也是存在问题的,服务器启动线程是有一定的开销的,请求数量不多的时候,服务器启动线程没 有问题,但是请求过多时,将会导致服务器的资源耗尽。所以,会存在一种方式——建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理,这样,线程池开放多少线程是固定的,不会导致系统资源耗尽,但是依然会有一些问题,当线程池被占用满时,还是 有可能出现请求被阻塞的情况,所以这种方式是一种折中的方式。但是,对于并发请求不是很多的场景来说, 使用这种方式是完全可以的。本次客户端最终版本就是采用这种方法编写的。
    第三种方式是异步服务器,使用该种方式,一般要借助于系统的异步 IO 机制,如 select 或 poll,这种方式,当一个请求到达时,我们可以先将请求注册,当有数据可以读取时,会得到通知,这时候我们处理请求,这样,服务器进程没有必要阻塞处理,也不会存在很大 的系统开销,因此,目前对于并发量要求比较高的服务器,一般都是采用这种方式。

    对于三种服务器,本人都进行了测试:其中最初版本是阻塞服务器,当一个客户端连接请求到达时,其他客户端都无法进行连接,需要依次排队等候,效率比较低;后来我采用多线程方法,实现了并发服务器,然而发现有些进程无法执行完毕,经检查发现是被系统自动回收了,因此创建了 ArryList 来保存每个客户端连接的进程,以防被回收。
    在尝试第三种服务器时,发现使用的方法与前两种大不相同:前两种用的是 java.net 包,主要是 socket 和 seversocket 完成通讯;异步服务 器使用的是 java.nio 包,主要是socketchanel 和 seversocketchanel 完成通讯。有很多差别,因此在实现上无法做到预期的设想,仅仅是做了简单的字符串传送通信,如果想要实现文件上传下载,还需要阅读相关资料深入学习,尤其是 buff 缓冲区的各种操作。所以最终的客户端选用第二种-并发服务器来完成。对其进行改善,建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理,这样不会导 致系统资源耗尽。这些在代码中都已实现。
    对于异步服务器,基本格式如下:
    public class Server{ String IP = "127.0.0.1"; int PORT = 10086; public void startServer(String serverIP, int serverPort) throws IOException{ ServerSocketChannel serverChannel = ServerSocketChannel.open(); InetSocketAddress localAddr = new InetSocketAddress(IP, Port); //服务器绑定地址 serverChannel.bind(localAddr); //设置为非阻塞 erverChannel.configureBlocking(false); //注册到 selector,会调用 ServerSocket 的 accept,用 selector 监听 accept 能否返回 Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { //调用 select,阻塞在这里,直到有注册的 channel 满足条件 selector.select(); //可以通过 selector.selectedKeys().iterator()拿到符合条件的迭代器 Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); //处理满足条件的 keys while (keys.hasNext()) { //取出一个 key 并移除 SelectionKey key = keys.next(); keys.remove(); try { if (key.isAcceptable()) { //取得可以操作的 channel ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel channel = server.accept(); //注册进 selector,当可读或可写时将得到通知,select 返回 channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { //有 channel 可读,取出可读的 channel SocketChannel channel = (SocketChannel)key.channel(); //创建读取缓冲区,一次读取 1024 字节 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); //锁住缓冲区,缓冲区使用的大小将固定 buffer.flip(); //附加上 buffer,供写出使用 key.attach(buffer); key.interestOps(SelectionKey.OP_WRITE); } else if (key.isWritable()) { //有 channel 可写,取出可写的 channel SocketChannel channel = (SocketChannel)key.channel(); //取出可读时设置的缓冲区 ByteBuffer buffer = (ByteBuffer)key.attachment(); //将缓冲区指针移动到缓冲区开始位置 buffer.rewind(); //读取为 String String recv = new String(buffer.array()); //清空缓冲区 buffer.clear(); buffer.flip(); //写回数据 byte[] sendBytes = recv.toUpperCase().getBytes(); channel.write(ByteBuffer.wrap(sendBytes)); } } catch (IOException e) { //当客户端 Socket 关闭时,会走到这里,清理资源 key.cancel(); try { key.channel().close(); } catch (IOException e1) { e1.printStackTrace(); } } } } }}
    此框架的设计经阅读并参考资料编写,对于具体情况需要改变 buff 缓冲区的读 写方式,可以成功发送接受信息,完成通信。异步服务器的好处在于,服务器没 有工作可做的时候,会等在 select 调用上,不会占用系统资源,而当不同的条 件满足时,又可以第一时间被唤醒,执行相应的操作,所以无论从资源的利用上, 还是从响应的及时性上都优于前两种。另外,如果 write 和 read 的时间比较长, 处理也可以放到线程中处理,这样就结合了并发服务器的优势。
    五、程序测试客户端运行时的主界面

    点击“上传文件”,显示文件选择器,选取要上传的文件, 进行上传

    发送后会显示成功上传

    依次上传本地几个文件,生成的私钥会被保存

    此时服务器上的文件均为加密文件,不可查看

    点击“查看列表”,会显示服务器上可供下载的文件和服务 器上已有的文件,可以看到刚刚上传的文件

    点击“下载文件”,会显示服务器上可供下载的文件和服务器上已有的文件,可以看到刚刚上传的文件,输入要下载的文 件的全部名称(复制即可),单击下载

    选择文件目录,保存下载文件。下载成功后返回信息

    本地的文件已经自动解密,可以正常使用

    1 评论 5 下载 2018-11-05 11:37:17 下载需要6点积分
  • 基于C#实现的电影网站数据爬虫和电影网站

    1 简介1.1 背景随着网络的发展,网购也越来越流行,人们可以在去各大电影院的网站方便的购票并选择自己喜欢的时间去影院观看。但电影院网站众多,人们可能为了寻找一个电影而奔波与各大网站,导致浪费大量时间在寻找电影资源上。本网站提供有效的电影信息聚合,方便用户筛选自己喜欢的电影和电影院,节省时间。
    1.2 目标该文档描述好看的电影网的详细功能定义,并对模块划分、业务流程进行了定义。所有设计人员、开发人员、测试人员以及其他团队成员都应该以该文档作为产品的功能定义,并衍生出其他文档。
    2 功能描述我看电影网提供给用户各大电影院信息,新电影详情,并提供收藏与偏好功能,有效的聚合各类电影信息,帮助用户快速选择喜欢的电影并转入专业买票网站购票页。
    整个系统分为两大主要功能模块和后台数据库服务器模块:

    通过B/S(浏览器/服务器)结构实现的Web部分

    Web部分主要供用户使用。普通用户可以通过 Web 浏览自己的基本资料和收藏、查看电影、购票。看过电影的用户也可以对电影和电影院进行评价
    后台管理部分后台添加

    后台提供前台的各种数据支持,响应前台的各种操作请求,保证前台web端的正常健壮运行

    本文档只描述了Web部分的功能规范,其他部分可以参看另外两门课程(《SQL Server 2000 数据库程序设计》和《基于 C# 的 Windows 应用程序设计》或《基于 ASP. NET 的 Windows 应用程序设计 》)的功能规范。
    2.1.1 注册和登录登录界面如下图所示。登录时,需要输入用户名及密码,并单击“登录”按钮,完成登录过程,如果没有账户,可以直接在登录界面里创建账户并登录,点击重置按钮可以直接清除已经输入的信息。
    登录注册页面

    登录出错页面

    功能说明
    登录名/密码

    登录名由用户自己创建
    读取浏览器端的Cookie值,如果员工以前登录过并没有退出,且Cookie在有效期,则自动进入次用户的信息,如果没有登录过,则显示登录界面
    密码长度不得超过30个字符,对使用字符方面没有限制
    密码用掩码•显示,长度根据已设值进行限制(默认为8~20位),不能为空。若为空或是没有按照格式输入,则显示“密码错误,请重试!”

    登录

    如果登录者的输入是正确的,则直接进入“用户信息管理”模块的主界面
    若“登录名”不存在,则要求用户重新输入,并提示用户名不存在
    若“密码”输入有误,则在“登录”按钮下面显示错误提示信息:“密码错误,请重试!”
    点击重置重置所有信息

    注册

    如果登录者的所有输入是正确的,则直接进入“偏好管理页面”选择用户偏好
    若“登录邮箱”不符合格式,则要求用户重新输入,并提示错误
    若“密码”输入不符合格式,则要求重新输入,并提示错误
    两次密码输入不同则要求用户检查并重新输入
    点击点击重置重置所有信息


    控件说明


    控件
    控件类型
    控件的功能描述




    登录名
    文本框
    用户在这里输入登录名;如果是第一次登录则显示空白;默认显示最近一次的登录名


    密码
    文本框
    用户在这里输入登录密码


    登录
    按钮
    用户点击登录按钮,然后才能进入系统


    出错提示
    标签
    用户登录失败时出现此标签,显示失败原因;默认下不显示



    2.1.2 主界面用户在打开网站后会进入主界面,该主界面上面列出了所有功能模块的链接,单击链接即可进入相应模块的主界面。该导航栏方便了用户在不同模块之间的切换,如下图所示。

    功能说明点击导航栏中每个功能模块的链接,都会进入相应模块的主界面。这个主界面默认为该功能模块下属的几个子链接中的第一个链接页。但会根据用户是否登录,如果用户已经登录,会在用户信息直接显示用户昵称,点击后毋须输入帐号密码即可进入个人信息管理。
    控件说明


    控件
    控件类型
    控件的功能描述




    电影信息
    标签
    这是一个超链接;点击后显示电影详情


    影院
    标签
    这是一个超链接;点击后显示影院列表


    圆点
    按钮
    这是一个超链接;点击后显示下一页内容


    搜索
    按钮
    这是一个超链接;点击后根据前面的输入信息显示需要内容


    搜索框
    文本框
    用于输入内容供搜索按钮检索


    上映类型
    Selection
    用于选择内容供搜索按钮检索


    地区
    Selection
    用于选择内容供搜索按钮检索


    影院
    Selection
    用于选择内容供搜索按钮检索


    推荐
    标签
    这是一个超链接;点击后根据评价排名和显示电影;未登录用户不显示此项


    热度排行
    标签
    这是一个超链接;点击后根据评价排名和显示电影;默认根据热度排行


    评价排行
    标签
    这是一个超链接;点击后根据评价排名和显示电影



    2.1.3 各项列表电影院列表用户点击影院后进入此页,该页界面如下图所示。

    功能说明:

    查询的输入框查询时,可输入电影名和影院名,系统自动匹配文本框可以为空,,系统只根据前面三者的选项筛选搜索按钮以表格形式显示查询出的电影,如图 7所示单击电影名称跳转到影院详情单击正在上映的电影数则进入此影院正在上映的电影列表返回按钮点击返回上一页
    控件说明


    控件
    控件类型
    控件的功能描述




    返回图片
    按钮
    这是一个超链接;点击后返回上一页


    影院,位置,正在上映电影数
    标签
    这是一个标签;用来显示所列信息


    影院名
    标签
    这是一个超链接;点击后显示此影院详情


    区名
    标签
    用来显示此列中影院的位置


    正在上映*部电影
    标签
    这是一个超链接;点击后显示此影院上映的电影列表


    搜索
    按钮
    这是一个超链接;点击后根据前面的输入信息显示需要内容


    搜索框
    文本框
    用于输入内容供搜索按钮检索


    上映类型
    Selection
    用于选择内容供搜索按钮检索


    地区
    Selection
    用于选择内容供搜索按钮检索


    影院
    Selection
    用于选择内容供搜索按钮检索


    页数选择器
    标签
    这是一个超链接;点击后进入上一页或者下一页


    页数指示器
    标签
    用于指示当前所在页数



    单影院正在上映的电影列表默认界面如下图所示。

    功能说明
    查询的输入框

    查询时,可输入电影名和影院名,系统自动匹配文本框可以为空,,系统只根据前面三者的选项筛选
    搜索按钮

    以表格形式显示查询出的电影,如图 7所示单击电影名称跳转到电影详情单击点击购买则进入购票网站
    返回按钮

    点击返回上一页

    控件说明


    控件
    控件类型
    控件的功能描述




    返回图片
    按钮
    这是一个超链接;点击后返回上一页


    电影名,价格,点击购买
    标签
    这是一个标签;用来显示所列信息


    影院名
    标签
    这是一个超链接;点击后显示此电影详情


    价格
    标签
    用来显示此列中影院的位置


    点击购票
    标签
    这是一个超链接;点击后进入购票页


    搜索
    按钮
    这是一个超链接;点击后根据前面的输入信息显示需要内容


    搜索框
    文本框
    用于输入内容供搜索按钮检索


    上映类型
    Selection
    用于选择内容供搜索按钮检索


    地区
    Selection
    用于选择内容供搜索按钮检索


    影院
    Selection
    用于选择内容供搜索按钮检索


    页数选择器
    标签
    这是一个超链接;点击后进入上一页或者下一页


    页数指示器
    标签
    用于指示当前所在页数



    单电影上映的影院列表默认界面如下图所示。

    功能说明
    查询的输入框

    查询时,可输入电影名和影院名,系统自动匹配文本框可以为空,,系统只根据前面三者的选项筛选
    搜索按钮

    以表格形式显示查询出的电影,如图 7所示单击电影名称跳转到电影详情单击点击购买则进入购票网站
    返回按钮

    点击返回上一页

    控件说明


    控件
    控件类型
    控件的功能描述




    返回图片
    按钮
    这是一个超链接;点击后返回上一页


    电影名,价格,点击购买
    标签
    这是一个标签;用来显示所列信息


    影院名
    标签
    这是一个超链接;点击后显示此电影详情


    价格
    标签
    用来显示此列中影院的位置


    点击购票
    标签
    这是一个超链接;点击后进入购票页


    搜索
    按钮
    这是一个超链接;点击后根据前面的输入信息显示需要内容


    搜索框
    文本框
    用于输入内容供搜索按钮检索


    上映类型
    Selection
    用于选择内容供搜索按钮检索


    地区
    Selection
    用于选择内容供搜索按钮检索


    影院
    Selection
    用于选择内容供搜索按钮检索


    页数选择器
    标签
    这是一个超链接;点击后进入上一页或者下一页


    页数指示器
    标签
    指示当前所在页数



    2.1.3 详情页员工可通过单击电影名或者影院名进入此页面。
    电影详情该页由点击电影名进入,默认界面如图8所示。

    功能说明
    网页展示该电影的详情信息
    点击”点击购买可进入购票页面,显示该电影在各大影院的上映信息”
    点击我要评论显示个人评价:

    评价内容不能为空可选择影院的评星登录后自动获取用户昵称评价后返回评价列表
    点击返回图片返回上一页
    点击最新评论和最热评论可对评论排行显示
    点击分享按钮可对此信息进行分享

    控件说明


    控件
    控件类型
    控件的功能描述




    电影名
    标签
    显示该电影的名称


    评分
    标签
    显示该电影的评星


    电影详情
    标签
    显示该电影的简介


    电影海报
    图片
    显示该电影的海报


    最新评论,最热评论
    标签
    这是个超链接;默认为最新评论


    评论
    标签
    显示评论内容


    支持,反对
    表情
    用来展示其他人对评论的卡饭;点击后后面识字加1;每个人只能点一次,根据Cookie限制


    支持数,反对数
    标签
    展示支持数和反对数


    页数选择器
    标签
    这是一个超链接;点击后进入上一页或者下一页


    页数指示器
    标签
    指示当前所在页数


    返回图片
    按钮
    这是一个超链接;点击后返回上一页


    1 评论 18 下载 2018-11-05 11:13:06 下载需要9点积分
  • 基于OpenLayer3与云后端的211大学查询系统

    1 需求说明每年高考结束后,毕业生们就面临着志愿填报的难题,学校的地理位置、综合实力,都将成为同学们选择学校的因素。本平台提供了在线查询211大学的功能,可以将各个大学的信息(图片、简介、百科链接等)在地图上展示,方便同学们进行查询和比对。
    2 主要功能211大学地图在线查询,点击显示大学的相关信息,根据鼠标位置实时显示经纬度位置。
    3 数据来源数据全部来源于网络,由8个同学一起收集整理。
    4 总体分析与设计4.1 实现模式本系统采用B/S架构,以OpenLayer3开源地图引擎为支撑,最终以网页方式呈现。采用Bmob云服务作为后端,方便了数据的维护与管理。部分数据从云后端获取,图片数据从本地服务器获取。
    4.2 数据库设计要将各大高校显示在地图上需要各高校的位置信息。各高校的经纬度坐标数据由两个double字段组成。坐标数据和简介、链接等信息存储在云后端,数据字段格式如下图所示。(注:ID为学校在数据库中的编号,Sname为学校名称,Settime为建校时间,Sdistrict为学校所在的城市,X为纬度,Y为经度,Surl为百科链接,State为学校简介。)


    图片数据存储于本地服务器,命名为“高校名称”+.jpg。通过src值获取。

    5 设计亮点界面友好,色彩美观协调,云后端存储数据方便维护,系统稳定性较好,部署简单,对系统性能要求低。
    6 项目部署test.html为网页文件,包含网页的CSS和JavaScript代码。下载bmob-min.js文件,与将211各大学校图片文件夹和test.html放于同一文件夹下。

    Windows系统下打开IIS服务器新建网站,将域名解析到ipv4地址下,默认端口80,同时使用云解析将域名解析到ipv4地址,一段时间后便可以访问站点了。
    7 关键代码大学信息查询与获取:

    地图单击监听事件:

    8 系统使用说明打开桌面浏览器(推荐使用火狐或谷歌),输入解析好的域名,或双击test.html,打开查询页面,页面中央定位为中国地图。拖动鼠标滚轴或点击左上方按钮实现页面缩放。

    页面左下端可以根据鼠标在页面的位置显示其相应的经纬度。

    在页面中央的输入框输入你要搜索的大学名称,如“厦门大学”。

    点击查找按钮,地图上的相应位置便会出现厦门大学的标注。

    点击标注,标注上方弹出气泡框,显示大学名称,图片,位置,链接,简介。

    点击红色的链接,页面跳转至该大学的百度百科主页。

    在搜索框中输入大学名称,若该大学不存在,如”鲁磨路大学“,则出现弹框:查无结果。

    继续在搜索框输入各个211大学的名称,地图上会继续添加相应大学的标注。

    点击各个大学的标注,气泡框中显示相应大学的信息,可以来回查看比对。

    9 小结通过对网络GIS这门课程的学习,我学习了网页编辑的基本技术,了解了建站流程,学会如何将web技术应用到GIS领域。在成功便写了第一个地图页面后,我对前端开发产生了浓厚的兴趣。对API的熟悉程度决定了开发的效率,在今后的学习中,我会注重实践,提高学习的深度和广度。
    2 评论 4 下载 2018-11-05 10:53:34 下载需要10点积分
  • 词法分析之基于Lex实现词法分析

    一、设计目的通过编写并上机调试一个词法分析程序,掌握在对程序设计语言的源程序进行扫描的过程中,将其分解成各类单词的词法分析方法。
    二、设计要求编制一个读单词过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。 并依次输出各个单词的内部编码及单词符号自身值。
    (遇到错误时可显示“Error”,然后跳过错误部分继续显示)
    三、设计说明3.1 需求分析3.1.1 输入及其范围Lex输入文件由3个部分组成:定义集(definition),规则集(rule)和辅助程序集(auxiliary routine)或用户程序集(user routine)。这三个部分由位于新一行第一列的双百分号分开,因此,Lex输入文件的格式如下:
    {definitions}%%{rules}%%{auxiliary routines}
    范围

    识别保留字:const,var,begin,end,read,while,call,writeln等保留字类别码为1
    运算符包括:+、-、*、/、=、>、<、>=、<=、!= ;类别码为2
    界符包括:,、;、{、}、(、); 类别码为3
    常数为无符号整形数;单词类别码为4
    其他的都识别为标识符;单词类别码为5
    错误字符类别码为 6

    3.1.2 输出形式([数字],“单词“)数字代表所识别的单词所属的类型。
    3.1.3 程序功能读取文件中的源程序,然后对源程序分析,输出分析结果。
    3.1.4 测试数据将测试数据写在文本文件中,测试为程序的输入数据。
    3.2 概要设计3.2.1 数据类型的定义%{ #include <stdio.h> #include <stdlib.h> int count = 0;%}delim [" "\n\t]whitespace {delim}+operator \+|-|\*|\/|:=|#|=//operator \+|-|\*|\/|:=|>=|<=|#|=reservedWord [cC][oO][nN][sS][tT]|[vV][aA][rR]|[pP][rR][oO][cC][eE][dD][uU][rR][eE]|[bB][eE][gG][iI][nN]|[eE][nN][dD]|[iI][fF]|[tT][hH][eE][nN]|[wW][hH][iI][lL][eE]|[dD][oO]|[rR][eE][aA][dD]|[cC][aA][lL][lL]|[wW][rR][iI][tT][eE]|[wW][rR][iI][tT][eE][lL][nN]delimiter [,\.;]constant ([0-9])+identfier [A-Za-z]([A-Za-z][0-9])*%%
    3.2.2 主程序流程
    3.2.3 模块间的调用关系
    3.3 详细设计3.3.1 主体代码部分%%{reservedWord} {count++;printf("%d\t(1,‘%s’)\n",count,yytext);}{operator} {count++;printf("%d\t(2,‘%s’)\n",count,yytext);}{delimiter} {count++;printf("%d\t(3,‘%s’)\n",count,yytext);}{constant} {count++;printf("%d\t(4,‘%s’)\n",count,yytext);}{identfier} {count++;printf("%d\t(5,‘%s’)\n",count,yytext);}{whitespace} { /* do nothing*/ }%%void main(){ printf("词法分析器输出类型说明:\n"); printf("1:保留字\n"); printf("2:运算符\n"); printf("3:分界符\n"); printf("4:常 数\n"); printf("5:标识符\n"); printf("\n"); yyin=fopen("example.txt","r"); yylex(); /* start the analysis*/ fclose(yyin); system("PAUSE");/*暂停停, 使DOS窗口停住*/} int yywrap(){ return 1;}

    四、运行结果及分析4.1 测试数据在example.txt文件中写入如下数据。
    const a=10;var b,c;procedure p; begin c:=b+a; end;begin read(b); while b#0 do begin call p; writeln(2*c); read(b); endend.
    4.2 测试输出的结果
    4.3. 设计与思考基于lex的词法分析,一个模块定义好了之后,其他模块也就出来了。难点在于正则表达式的设计,每个模块都有定义集,规则集和辅助程序集。而且第一部分用“%{”和“%}”括起来。第一和第三个部分为C语言的代码和函数定义,第二个部分为一些规则。
    五、总结通过本次实验,学会了基于Lex的词法分析器构造方法。在实验过程中,万事开头难,刚开始不知道怎么做,以及不知道如何使用老师给的软件,在仔细分析了需求之后,查找了一些关于Parser Genarator 2软件的使用方法,但是在使用过程中错误地使用.y后缀名导致Lex编译总是出错,后来看教程带着尝试的心理改成了.l后缀名才终于成功让Lex程序跑起来,正确生成编译器C代码。
    有了编译器C代码之后打算开始用Code Block编译,但是发现其中的yyleh.h类库找不到,后来用VS2015也一直出现问题,是在没办法,只好按照老师给的VC++的教程安装VC++6.0并导入yylex.h类库,最终获得了成功,很是欣喜。
    1 评论 12 下载 2018-11-05 09:50:50 下载需要6点积分
显示 750 到 765 ,共 15 条
eject