分类

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

技术文章列表

  • 在DLL中创建共享内存

    背景我们都知道在 32 位系统上,每个进程都有自己 4GB 大小的独立空间,互不影响。当然,对内核了解的同学则会质疑说,4GB 大小的内容可以大致分为两部分,低 2 GB内存空间是用户地址空间,高 2 GB是内核地址空间,而内核地址空间是共享的,并非独立。是的,这没错。所以,本文中所指的独立空间是用户地址空间。
    举个例子来说,进程1 的 0x400000 内存地址和 进程2 中 0x400000 内存地址是没有任何关联。任意修改其中一个内存里的数据,是不影响另一个的。这边是进程独立性。
    但是,本文要介绍的这个知识点,就是要突破这个独立性的显示,创建进程共享内存。确切的说是在DLL中创建共享内存,就是在DLL中创建一个变量,然后DLL被加载到多个进程空间,只要其中一个进程修改了该变量的值,其他进程DLL中的这个值也会改变,就相当于多个进程共享一块内存。
    实现原理实现原理比较简单,就是先为DLL创建一个数据段,然后再对程序的链接器进行设置,使其在程序编译完毕开始链接的时候,根据设置的链接选项,把指定的数据段链接为共享数据段。这样,就可以创建共享内存了。
    #pragma data_seg("MySeg") char g_szText[256] = {0};#pragma data_seg()#pragma comment(linker, "/section:MySeg,RWS")
    例如,在上面的代码中,我们使用 #pragma data_seg 创建了一个名为 MySeg 的数据段,接着使用 /section:MySeg,RWS 把 MySeg 数据段设置为可读、可写、可共享的共享数据段。
    程序测试我们开发一个程序加载这个创建了共享内存的DLL,然后调用它的导出函数 Change 来修改共享内存变量的值,调用 Show 来显示共享内存变量的值。发现,只要有一个进程修改了这个变量,其他所有进程里的变量也跟着改变。

    总结这个实现上比较简单,难点就是在思想上的转换,要理解什么是共享内存的思想。
    参考参考自《Windows黑客编程技术详解》一书
    3 留言 2018-11-07 11:28:40 奖励5点积分
  • 跨域

    一、什么是跨域?1.1 什么是同源策略及其限制内容?同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个IP地址,也非同源。
    同源策略限制内容有:

    Cookie、LocalStorage、IndexedDB 等存储性内容
    DOM 节点
    AJAX 请求发送后,结果被浏览器拦截了

    但是有三个标签是允许跨域加载资源:

    <imgsrc=XXX>

    <linkhref=XXX>

    <scriptsrc=XXX>



    1.2 常见跨域场景当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
    特别说明两点:

    第一:如果是协议和端口造成的跨域问题“前台”是无能为力的
    第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

    这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?
    跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
    你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?
    因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。
    同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
    1.3 跨域解决方案1.3.1 jsonpJSONP原理
    利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
    JSONP和AJAX对比
    JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
    JSONP优缺点
    JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
    JSONP的实现流程

    声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
    创建一个 <script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)
    服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是 show(‘我不爱你’)
    最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作

    JSONP都是GET和异步请求的,不存在其他的请求方式和同步请求,且jQuery默认就会给JSONP的请求清除缓存。
    1.3.2 corsCORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
    浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
    服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
    虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
    简单请求
    只要同时满足以下两大条件,就属于简单请求

    条件1:使用下列方法之一:

    GETHEADPOST
    条件2:Content-Type 的值仅限于下列三者之一:

    text/plainmultipart/form-dataapplication/x-www-form-urlencoded

    请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
    复杂请求
    不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
    1.3.3 postMessagepostMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

    页面和其打开的新窗口的数据传递
    多窗口之间消息传递
    页面与嵌套的iframe消息传递
    上面三个场景的跨域数据传递

    postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
    otherWindow.postMessage(message, targetOrigin, [transfer]);
    message: 将要发送到其他 window的数据。
    targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串”*”(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送
    transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

    1.3.4 websocketWebsocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。
    但是WebSocket是一种双向通信协议,在建立连接之后,WebSocket的server与 client都能主动向对方发送或接收数据。
    同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
    原生WebSocket API使用起来不太方便,我们使用 Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
    1.3.5 Node中间件代理(两次跨域)实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。代理服务器,需要做以下几个步骤:

    接受客户端请求
    将 请求 转发给服务器
    拿到服务器 响应 数据
    将 响应 转发给客户端

    1.3.6 nginx反向代理实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
    使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
    实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
    1.4 总结
    CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
    JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
    不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
    日常工作中,用得比较多的跨域方案是cors和nginx反向代理

    参考文章
    跨域资源共享 CORS 详解
    前端面试之道
    window.postMessage
    前端常见跨域解决方案(全)
    深入跨域问题(4) - 利用代理解决跨域
    3 留言 2019-04-08 11:06:29 奖励15点积分
  • 基于Python使用词云图

    1 引言词语图,也叫文字云,是对文本出现频率较高的“关键词”予以视觉化的展现,词云图过滤掉大量的低频低质的文本信息,使得浏览者一下就可以知道文章的主旨。
    2 模块准备import jieba # 分词模块import matplotlib.pyplot as plt # 画图模块from wordcloud import WordCloud # 文字云模块from scipy.misc import imread # 处理图像的函数,用于读取并处理背景图片
    3 实现的思路准备一份需要分析的文本材料,这里选用的是 19 年两会政府工作报告,首先用 jieba 模块对文本材料进行分词处理(即识别出一个个有意义的词语),然后对处理后的材料使用WordCloud 文字云模块生成相应的词云图片即可of course,你也可以选择一张背景图片,以此为背景生成特定的云图。
    4 代码实现def wordcloud(): """ 背景图片为自定义的一个矩阵 :return: 词云图 """ # 读取词源文件 二进制的形式 with open("./govreport.txt", "rb") as f: t = f.read() # 保存为str类型 ls = jieba.lcut(t) # 进行分词 txt = " ".join(ls) # 把分词用空格连起来 # 设置词云的参数 w = WordCloud( font_path="msyh.ttc", # 设置字体 width=1000, # 设置输出的图片宽度 height=700, # 设置输出的图片的高度 background_color="white", # 设置输出图片的背景色 ) w.generate(txt) # 生成词云 w.to_file("./wordColud.png") # 将图片保存 return Nonedef wordcloud2(): """ 用指定的图片生成词云图 :return: 词云图 """ # 词源的文本文件 wf = "./govreport.txt" word_content = open(wf, "r", encoding="utf-8").read().replace("\n", "") # 设置背景图片 img_file = "./map.jpg" # 解析背景图片 mask_img = imread(img_file) # 进行分词 word_cut = jieba.lcut(word_content) # 把分词用空格连起来 word_cut_join = " ".join(word_cut) # 设置词云参数 wc = WordCloud( font_path="SIMYOU.TTF", # 设置字体 max_words=2000, # 允许最大的词汇量 max_font_size=90, # 设置最大号字体的大小 mask=mask_img, # 设置使用的背景图片,这个参数不为空时,width和height会被忽略 background_color="white", # 设置输出的图片背景色 ) # 生成词云 wc.generate(word_cut_join) # 用于显示图片,需要配合plt.show()一起使用 plt.imshow(wc) plt.axis("off") # 去掉坐标轴 plt.savefig("./wordcloudWithMap.png") plt.show() return None
    5 效果展示不带背景图片的词云图

    带有中国地图的词云图
    1 留言 2019-04-21 10:18:25 奖励12点积分
  • 使用VS2013实现修改其他程序的图标

    背景之前,写了一个程序,程序中有一个功能就是获取一个EXE程序的图标,然后,把这个图标更改为另一个EXE程序的图标。在网上搜索了很久,都没有找到相关的例子。找到的大都是修改自己程序的图标,或者是使用一个 .ico 的图标文件,去更改指定程序的图标。虽然,这些功能和自己想要开发的功能有些不同,但,也还是具有一定的参考价值的。
    现在,我就对修改指定程序图标的方式进行总结。总结出 3 种修改方式:

    替换的图标存储在自己程序的资源中
    替换的图标以 .ico 图标文件形式提供
    替换的图标是其他一个EXE程序的图标

    现把程序实现的原理以及实现的过程,写成文档分享给大家,方便大家的参考。
    函数介绍FindResource介绍
    函数声明
    HRSRC FindResource( HMODULE hModule, LPCWSTR lpName,LPCWSTR lpType );
    参数

    hModule:处理包含资源的可执行文件的模块。NULL值则指定模块句柄指向操作系统通常情况下创建最近过程的相关位图文件。lpName:指定资源名称。若想了解更多的信息,请参见注意部分。lpType:指定资源类型。若想了解更多的信息,请参见注意部分。作为标准资源类型。这个参数的含义同EnumResLangProc\lpType。
    返回值

    如果函数运行成功,那么返回值为指向被指定资源信息块的句柄。为了获得这些资源,将这个句柄传递给LoadResource函数。如果函数运行失败,则返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

    SizeofResource 介绍
    函数声明
    DWORD SizeofResource( HMODULE hModule, // module handle HRSRC hResInfo // resource handle );
    参数

    hModule:包合资源的可执行文件模块的句柄。hReslnfo:资源句柄。此句柄必须由函数FindResource或FindResourceEx来创建。
    返回值

    如果函数运行成功,返回值资源的字节数。如果函数运行失败,返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

    LoadResource 介绍
    函数声明
    HGLOBAL LoadResource( HMODULE hModule, // module handle HRSRC hResInfo // resource handle );
    参数

    hModule:处理包合资源的可执行文件的模块句柄。若hModule为NULL,系统从当前过程中的模块中装载资源。hReslnfo:将被装载资源的句柄。它必须由函数FindResource或FindResourceEx创建。
    返回值

    如果函数运行成功,返回值是相关资源的数据的句柄。如果函数运行失败,返回值为NULL。若想获得更多的错误信息,请调用GetLastError函数。

    LockResource 介绍
    函数声明
    LPVOID LockResource( HGLOBAL hResData // handle to resource );
    参数

    hResDate:被装载的资源的句柄。函数LoadResource可以返回这个句柄。
    返回值

    如果被装载的资源被锁住了,返回值是资源第一个字节的指针;否则为NULL。

    BeginUpdateResource 介绍
    该函数返回一个可被UpdateResource函数使用的句柄以便在一个可执行文件中增加、删除或替换资源。
    函数声明
    HANDLE BeginUpdateResource( LPCTSTR pFileName, // executable file name BOOL bDeleteExistingResources // deletion option );
    参数

    pFileName:指向一个表示结束的空字符串指针,它是用来指定用以更新资源的基于32-位可执行文件的文件名。应用程序必须获得访问这个文件的可写权限,并且此文件在当前状态下不能被执行。如果pFileName未被指定完全路径,系统将在当前路径下搜寻此文件。bDeleteExistingResources:说明是否删除pFileName参数指定的现有资源。如果这个参数为TRUE则现有的资源将被删除,而更新可执行文件只包括由UpdateResource函数增加的资源。如果这个参数为FALSE,则更新的可执行文件包括现有的全部资源,除非通过UpdateResource特别说明被删除或是替换的。
    返回值

    如果此函数运行成功,其值将通过使用UpdateResource和EndUpdateResource函数返回一个句柄。如果被指定的文件不是一个可执行文件,或者可执行文件已被装载,或者文件不存在,或是文件不能被打开写入时,则返回值为空。若想获得更多的错误信息,请调用GetLastError函数。

    UpdateResource 介绍
    增加 删除 或替文件中的资源。
    函数声明
    BOOL UpdateResource( HANDLE hUpdate, // update-file handle LPCTSTR lpType, // resource type LPCTSTR lpName, // resource name WORD wLanguage, // language identifier LPVOID lpData, // resource data DWORD cbData // length of resource data );
    参数

    hUpdate:指定更新文件句柄。此句柄由BeginUpdateResource函数返回。lpType:指向说明将被更新的资源类型的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKENTRESOURCE传递的整数值,含义参见EnumResLangProc\lpType。lpName:指向说明待被更新的资源名称的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKEINTRESOURCE传递的整数值。wLanguage:指定将被更新资源的语言标识。要了解基本的语言标识符以及由这些标识符组成的字语言标识符的列表,可参见宏MAKELANGID。lpData:指向被插入可执行文件的资源数据的指针。如果资源是预定义类型值之一,那么数据必须是有效且适当排列的。注意这是存储在可执行文件中原始的一进制数据,而不是由Loadlcon,LoadString或其他装载特殊资源函数提供的数据。所有包含字符串、文本的数据必须是Unicode格式;IpData不能指向ANSI数据。如果lpData为NULL,所指定的资源将从可执行文件中被删除。cbData:指定lpData中的资源数据数据大小,以字节计数。
    返回值

    如果函数运行成功,返回值为非零;如果函数运行失败,返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

    EndUpdateResource 介绍
    终止在可执行文件中的资源更新。
    函数声明
    BOOL EndUpdateResource( HANDLE hUpdate, // update-file handle BOOL fDiscard // write option );
    参数

    hUpdate:用于资源更新的句柄。此句柄通过BeginUpdateResource函数返回。fDiscard:用来说明是否向可执行文件中写入资源更新内容。如果此参数为TRUE,则在可执行文件中无变化;如果此参数为FALSE,则在可执行文件中写入变化。
    返回值

    如果函数运行成功,并且通过调用UpdateResource函数指定的不断积聚的资源修正内容被写入指定的可执行文件,那么其返回值为非零。如果函数运行失败,其返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

    程序实现原理有一个知识点大家需要了解的就是:
    EXE程序会默认把资源ID号最小的ICON类型的图标资源作为自己的程序图标。
    也就是说,要想更改EXE程序的图标,只需要把一个图标创建或替换程序中ICON类型的资源ID最小的图标就可以了。这样,程序就会默认把该图标资源作为自己的程序图标,显示出来。
    那么,图标修改的大致原理就是:

    获取替换图标的图标数据的存储地址
    使用 UpdateResource 函数定位出程序 RT_ICON 类型、资源ID为 1 的图标资源,并使用上述的替换图标数据进行创建或者替换

    这样,就成功替换或者创建了一个资源类型是 RT_ICON,而且资源ID为 1 的最小的图标资源。要注意的是,最小的资源ID号是以 1 开始计数,而不是以 0 开始的,这个需要注意。
    那么,对于上面提到的 3 种方式,主要是获取图标数据的方式上的区别而已。
    针对第 1 种方式:替换的图标存储在自己程序的资源中。获取图标数据的原理就是:

    首先,我们先定位到我们程序里的资源,主要是根据“资源类型”和“资源名称”进行定位,获取资源信息块的句柄
    然后,根据上面获取的资源信息块的句柄,把资源加载到我们程序的内存中
    最后,锁定加载到内存中的资源的内存,防止程序的其他操作影响到这块内存,获取图标数据的首地址

    针对第 2 种方式:替换的图标以 .ico 图标文件形式提供。获取图标数据的原理就是:

    首先,以读方式打开文件,并获取文件的大小
    然后,将文件数据内容全部都出去出来,这样便获取到了图标数据

    针对第 3 种方式:替换的图标是其他一个EXE程序的图标。获取图标数据的方法和第 1 种方式的方法类似,原理是:

    首先,我们先把想要提取图标的EXE将在到我们的程序中来,并获取加载句柄
    然后,根据程序加载句柄,定位到程序里的资源,主要是根据“资源类型”和“资源名称”进行定位,获取资源信息块的句柄
    接着,根据上面获取的资源信息块的句柄,把资源加载到我们程序的内存中
    最后,锁定加载到内存中的资源的内存,防止程序的其他操作影响到这块内存,获取图标数据的首地址

    编码实现在这 3 种方式的图标数据获取中,最麻烦,而且最能体现技术含量的就是第 3 中形式。所以,我们以第 3 种实现方式为例,写出实现的代码。对于其余两种,网上有很多相关的例子和参考资料,相信大家根据原理部分应该自己也可以独立写出来,在此就不给出代码了。
    BOOL ChangeIcon(char *pszChangedIconExeFileName, char *pszSrcIconExeFileName){ // 将在其他程序,并获取程序模块句柄 HMODULE hEXE = ::LoadLibrary(pszSrcIconExeFileName); if (NULL == hEXE) { FreeRes_ShowError("LoadLibrary"); return FALSE; } // 获取其他EXE程序图标资源数据 HRSRC hRsrc = ::FindResource(hEXE, (LPCSTR)1, RT_ICON); if (NULL == hRsrc) { FreeRes_ShowError("FindResource"); return FALSE; } // 获取资源大小 DWORD dwSize = ::SizeofResource(hEXE, hRsrc); if (0 >= dwSize) { FreeRes_ShowError("SizeofResource"); return FALSE; } // 加载资源到程序内存 HGLOBAL hGlobal = ::LoadResource(hEXE, hRsrc); if (NULL == hGlobal) { FreeRes_ShowError("LoadResource"); return FALSE; } // 锁定资源内存 LPVOID lpVoid = ::LockResource(hGlobal); if (NULL == lpVoid) { FreeRes_ShowError("LockResource"); return FALSE; } // 开始修改图标 HANDLE hUpdate = ::BeginUpdateResource(pszChangedIconExeFileName, FALSE); if (NULL == hUpdate) { FreeRes_ShowError("BeginUpdateResource"); return FALSE; } // 如果资源ID存在, 则替换资源; 否则创建资源 // 程序把ICON的最小的资源ID作为程序图标, 所以从1开始, 1最小 BOOL bRet = ::UpdateResource(hUpdate, RT_ICON, (LPCSTR)1, LANG_NEUTRAL, lpVoid, dwSize); if (FALSE == bRet) { FreeRes_ShowError("UpdateResource"); return FALSE; } ::EndUpdateResource(hUpdate, FALSE); // 释放模块 ::FreeLibrary(hEXE); return TRUE;}
    程序测试现在,我们在 main 函数中,调用上述封装好的函数进行测试,把 “520.exe” 程序的图标改成 “360.exe” 程序的图标。
    main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // 把 520.exe 的图标更改为 360.exe 的图标 if (FALSE == ChangeIcon("C:\\Users\\DemonGan\\Desktop\\520.exe", "C:\\Users\\DemonGan\\Desktop\\360.exe")) { printf("Change Icon Error!\n"); } else { printf("Change Icon OK!\n"); } system("pause"); return 0;}
    在测试之前,“360.exe” 程序和 “520.exe” 程序图标如下图所示:

    运行修改图标的程序,提示执行成功:

    然后,再次查看 “360.exe” 程序和 “520.exe” 程序,发现 “520.exe” 图标成功更改了:

    由此可见,程序测试成功。
    总结改程序的核心原理就是:EXE程序会默认把资源ID号最小的ICON类型的图标资源作为自己的程序图标。
    其中,测试的时候,需要注意的一个问题就是,图标更改成功后,可能会看到图标仍然没有更改。那可能是因为系统缓存了原来程序的图标,并没有更新图标缓存的缘故。最简单的方法就是,把修改图标后的程序,拷贝到其他目录下进行查看,这样操作比较简便些。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-11-19 09:19:19 奖励8点积分
  • C语言-基于Huffman编码原理的译码解压缩程序

    C语言实现基于Huffman编码原理的译码解压缩程序
    huffman编码原理这里不做介绍,梳理一下解码的代码实现。
    解码解压缩部分输入已编码文本和编码表,输出解码文本。

    1.获取已编码文本void readfile(int op){ FILE* fp; int text_i = 0; char tempc; fp = fopen(".\\outfile.txt", "r"); if (fp != NULL) { while (fscanf(fp, "%c", &tempc) != EOF) { de_str_text[text_i] = tempc; text_i++; } de_str_text[text_i] = '\0'; fclose(fp); } else { printf("文件读取失败!"); getchar(); exit(0); }}2.获取码表并生成huffman树void bulid_detree(){ FILE *fp; de_root = (Hufftree *)malloc(sizeof(Hufftree)); Hufftree* p = de_root; Hufftree* t; int tag = 0; de_root->lchild = de_root->rchild = NULL; int i = 0, j = 1, k = 0; fp = fopen(".\\mabiao.txt", "r"); if (fp) { for (i = 0; i < charmaxsize; i++) { if ((fgets(mabiao[i], codesize, fp)) == NULL) { break; } else if (tag) { for (k = 0; k < codesize - 1; k++) { mabiao[i][codesize - 1 - k] = mabiao[i][codesize - 2 - k]; } mabiao[i][0] = mabiao[i - 1][0]; tag = 0; } if (mabiao[i][1] == '\0') { tag = 1; } } fclose(fp); } else { printf("码表读取失败"); return; } i = 0; while (mabiao[i][0] != '\0') { p = de_root; while (mabiao[i][j] != '\0') { if (mabiao[i][j] == '1') { if (p->rchild == NULL) { t = (Hufftree*)malloc(sizeof(Hufftree)); t->lchild = t->rchild = NULL; p->rchild = t; p = p->rchild; } else { p = p->rchild; } } if (mabiao[i][j] == '0') { if (p->lchild == NULL) { t = (Hufftree*)malloc(sizeof(Hufftree)); t->lchild = t->rchild = NULL; p->lchild = t; p = p->lchild; } else { p = p->lchild; } } j++; } p->character = mabiao[i][0]; j = 1; i++; } }3.已编码文本转化成伪二进制流void ConvertoBit(){ int bit_i = 0, str_i = 0,i = 0; char temp[7]; int asc; Hufftree *seek; //转化为伪二进制数组 for (str_i = 0; str_i < strmaxsize; str_i++) { if (de_str_text[str_i] != NULL) { asc = (int)de_str_text[str_i]-31; for (i = 0; i < 6; i++) { if (asc % 2 == 0) { temp[5 - i] = '0'; } else { temp[5 - i] = '1'; } asc = asc / 2; } temp[6] = '\0'; strcat(de_strbit_temp, temp); } else { break; } } puts(de_strbit_temp);}4.根据伪二进制流查找huffman树输出字符void decode(){//按照码表转换成原文本 seek = Root; str_i = 0; while (de_strbit_temp[bit_i] != NULL) { if (seek->character != NULL) { de_strcode[str_i] = seek->character; str_i++; seek = Root; } else if (de_strbit_temp[bit_i] == '1') { seek = seek->rchild; bit_i++; } else { seek = seek->lchild; bit_i++; } } de_strcode[str_i] ='\0';}输出解码文档void outfile(int op){ int i = 0; FILE* fp; fp = fopen(".\\decodefile.txt", "w"); fputs(de_strcode, fp); fclose(fp);}至此完成解码部分tip由于解码时,伪二进制流不足6位时会补零可能会导致出现多余字符。
    附:结构体定义//字符编码信息typedef struct Huff_char { char character = 0; int count = 0; char code[20];}Huff_char;//字符队列typedef struct Huffchar { char character = 0; int count = 0;//统计词频 struct Hufftree* self = NULL;}Huffchar;//哈夫曼树节点typedef struct Hufftree { char character;//节点字符信息 int power;//权值 struct Hufftree* self, *rchild, *lchild;//自身地址,左右孩子}Hufftree;
    1 留言 2018-11-09 21:04:01 奖励5点积分
  • 上传资源,获取积分 精华

    上传资源,获取积分“WRITE-BUG技术共享平台”是一个专注于校园计算机技术交流共享的平台,面向的主要目标群体是我们计算机相关专业的大学生。在平台上,大家既可以交流学校课内学习的心得体会,也可以分享自己课外积累的技术经验。
    为了充实平台的资源库,更好地服务于各位同学,平台决定推出“众源计划”,有偿征集同学们自己计算机专业的作业、课程设计或是毕业设计等资源。“众源计划”的主要目的是创建一个具有一定规模的“技术资源库”,资源库里的每一份资源,都必须有详细的开发文档和可编译的源代码。
    作业、课程设计或是毕业设计等资源是同学们自己辛苦付出的成果,也是自己技术进步的见证。这部分资源通常都有详细的开发文档和完整的程序源代码,能够帮助其他初学者更好地消化和吸收将要学习的技术,降低学习门槛。所以,平台决定积分奖励征集这些资源。
    具体要求活动对象
    在校或者已毕业的计算机相关专业大学生,院校不限
    奖励方式
    资源上传并审核通过后,根据资源质量,奖励每贴 10 - 100 点积分
    上传流程
    会员登录自己的账号上传资源
    资源上传后,管理员会在 24 小时之内审核资源
    审核通过后,管理员会立即发放奖励积分至所对应账户

    审核重点
    重点审核资源是否具有详细的文档和完整的源代码
    审查资源是否原创,切勿重复提交

    资源要求“众源计划”仅对两类资源进行积分奖励征集,分别是“课内资源”和“课外资源”,各类资源具体要求如下所示。

    课内资源

    内容范围:计算机相关专业课内的毕业设计、课程设计、小学期、大作业等课程内开发的程序,程序包括游戏、PC程序、APP、网站或者其他软件形式
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告
    具体“课内资源”征集程序列表见附录一

    课外资源

    内容范围:计算机相关专业的课外自己主导研究游戏、项目、竞赛、个人研究等,区别于课程设计和毕业设计等课内资源
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告
    具体“课外资源”征集程序列表见附录二


    附录一注意:“众源计划”的题目范围包括且不限于以下题目

    汇编语言课程设计题目列表

    屏幕保护程序分类统计字符个数计算机钢琴程序字符图形程序音乐盒程序电子闹钟程序俄罗斯方块打字游戏图形变换程序吃豆子程序其他
    C语言课程设计题目列表

    学生成绩管理系统图书信息管理系统设计销售管理管理系统飞机订票管理系统教师评价系统学校运动会管理系统文本文件加密技术英语字典电话簿管理系统流星雨的实现其他
    C++语言课程设计题目列表

    学生学籍管理系统高校人员信息管理系统学生成绩管理系统车辆管理系统职工工作量统计系统学生考勤管理系统单项选择题标准化考试系统图书管理系统超市商品管理系统模拟ATM机存取款管理系统其他
    JAVA语言课程设计题目列表

    简单投票管理系统数学练习题目自动生成系统华容道小游戏电子英汉词典加密与解密标准化考试系统排球比赛计分系统学籍管理系统绘图系统图书信息管理系统其他
    C#语言课程设计题目列表

    学生信息管理系统学生综合测评系统图书管理系统学校运动会管理系统个人通讯录管理系统教师工资管理系统教师工作量管理系统趣味小游戏物资库存管理系统图形图像处理系统其他
    JSP语言课程设计题目列表

    微博系统基于web的学生信息管理系统在线计算机等级考试报名系统在线问卷调查系统网上销售系统论坛系统图书借阅管理系统网上购物系统工资管理系统酒店管理系统其他
    数据结构与算法课程设计题目列表

    设计哈希表实现电话号码查询系统电报压缩/解压缩系统电费核算系统机房计费管理系统公交线路查询系统用二叉平衡树实现图书管理系统运动会赛事安排动态表达式求值用线性结构实现学生成绩管理求解迷宫问题其他
    编译原理课程设计题目列表

    First集和Follow集生成算法模拟LL(1)分析过程模拟FirstVT集和LastVT集生成算法模拟算符优先分析表生成模拟算符优先分析过程模拟LR分析过程模拟PL/0语言的词法分析程序C语言的预处理程序自动机的状态转换图表示数组越界检查工具其他
    操作系统课程设计题目列表

    动态分区分配方式的模拟进程调度模拟算法请求调页存储管理方式的模拟P、V操作及进程同步的实现银行家算法SPOOLING假脱机输出的模拟程序文件系统设计动态不等长存储资源分配算法磁盘调度算法处理机调度算法模拟其他
    数据库课程设计题目列表

    高校学籍管理系统在线投稿审稿管理系统产品销售管理系统高校人力资源管理系统高校课程管理系统酒店客房管理系统报刊订阅管理系统医药销售管理系统学生学籍管理系统餐饮管理系统其他
    计算机网络课程设计题目列表

    TCP通信功能实现网络游戏的开发基于UDP协议网上聊天程序Ping 程序的实现数据包的捕获与分析FTP客户端设计包过滤防火墙的设计与实现简单的端口扫描器简单Web服务器的设计与实现HTTP客户端的设计与实现其他
    软件工程课程设计题目列表

    学校教材订购系统网上选课管理系统简易办公系统图书馆管理系统校园交流论坛网站超市收银系统ATM柜员机模拟程序企业办公自动化管理系统学生成绩管理系统进销存管理系统其他
    VC++程序设计课程设计题目列表

    模拟时钟程序单向链表的操作演示程序电影院售票系统俄罗斯方块五子棋24点游戏背单词软件的设计与实现酒店管理系统餐厅就餐管理系统吹泡泡游戏其他
    其他课程设计

    PHP语言课程设计PYTHON语言课程设计计算机图形学课程设计机器学习课程设计密码学课程设计其他

    附录二注意:“众源计划”的题目范围包括且不限于以下题目

    人脸识别系统车牌识别系统旅游自助APP疲劳驾驶识别检测系统考试管理系统WINDOWS驱动级安全防御系统WINDOWS平台逆向调试器坦克大战小游戏情感分析系统人机博弈的国际象棋游戏其他
    最终解释权归 WRITE-BUG技术共享平台 所有
    11 留言 2018-12-20 10:09:45 奖励100点积分
  • 基于Python使用TensorBoard可视化工具

    首先展示一下代码:
    writer = tf.summary.FileWriter("./summary/linear-regression-0/", sess.graph)
    我们将数据流图的事件保存到当前目录下的/summary/linear-regression-0/中,记得后面关闭一下 writer.close(),我们来看一下运行后的结果。

    那么我们现在需要如何显示呢?写这个目的就是告诉到家是如何使用 TensorBoard 可视化的,步骤如下:

    Windows+R 打开 Terminal
    cd 到 summary 目录(注意不是 cd 到 linear-regression-0 目录)
    使用 TensorBoard —logdir=linear-regression-0

    我们来看一下图应该就能明白:

    如果打不开,中间的 DESKTOP-A0JMDP5 换成 localhost 试试!好了!现在让我们输入网址试试:
    1 留言 2019-04-20 12:26:23 奖励12点积分
  • 线程同步之临界区

    背景在学校学习《操作系统》这门课程的时候,进程线程的调度同步都是比较重要的知识点。关于线程的同步方法有很多种,例如互斥量、信号量、事件、临界区等。
    本文就从编程实现的角度谈谈编程中临界区在多线程同步中的使用方式,现在,把实现的思路和过程写成文档,分享给大家。
    实现原理有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
    临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用WIN32函数EnterCriticalSection 和 LeaveCriticalSection 去进入和离开一个临界区。
    所用到的CRITICAL_SECTION结构对象必须要事先经过 InitializeCriticalSection 的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
    临界区使用完毕之后,要使用 DeleteCriticalSection 函数删除临界区,释放资源。
    编码实现为了更好理解临界区的概念,我们开发了这样一个程序:开启 3 个线程,每个线程都循环打印显示同一个整型全局变量,并将它自增1。要求,每个数字只显示一遍,而且是按 1 递增显示。
    如果我们什么操作不加的话,直接创建 3 个多线程去打印显示的话,代码如下:
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { printf("%d\n", g_iCount); g_iCount++; if (g_iCount > iStop) { break; } } return 0;}BOOL CriticalSectionTest(){ // 多线程1 HANDLE hThread1 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程2 HANDLE hThread2 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程3 HANDLE hThread3 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 等待线程结束 ::WaitForSingleObject(hThread1, INFINITE); ::WaitForSingleObject(hThread2, INFINITE); ::WaitForSingleObject(hThread3, INFINITE); // 关闭句柄 ::CloseHandle(hThread1); ::CloseHandle(hThread2); ::CloseHandle(hThread3); return TRUE;}
    那么,上面程序的显示结果如下图所示:

    可以看到,输出的每个数字并不是都唯一,有些都重复输出,而且并不是按递增 1 的规律输出的。原因细想一下应该就明白了,创建了 3 个多线程,有可能会出现当线程1执行完打印输出的时候,线程2也执行到打印输出,这是全局变量g_iCount值还是0,所以便会重复输出。而它们也会同时执行g_iCount++,那么之后的输出就不会按递增1进行输出了。
    所以,为了每次输出和全局变量递增的时候,只能有一个线程去的独占执行,这时就需要临界区的帮助了。
    加入临界区后的代码就变成下面这样子:
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { // 进入临界区 ::EnterCriticalSection(&g_cs); printf("%d\n", g_iCount); g_iCount++; if (g_iCount > iStop) { break; } // 离开临界区 ::LeaveCriticalSection(&g_cs); } return 0;}BOOL CriticalSectionTest(){ // 初始化临界区 ::InitializeCriticalSection(&g_cs); // 多线程1 HANDLE hThread1 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程2 HANDLE hThread2 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程3 HANDLE hThread3 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 等待线程结束 ::WaitForSingleObject(hThread1, INFINITE); ::WaitForSingleObject(hThread2, INFINITE); ::WaitForSingleObject(hThread3, INFINITE); // 关闭句柄 ::CloseHandle(hThread1); ::CloseHandle(hThread2); ::CloseHandle(hThread3); // 删除临界区 ::DeleteCriticalSection(&g_cs); return TRUE;}
    代码的执行效果如下所示:

    这时,输出满足要求了,每个数字只显示 1 遍,而且是递增 1 输出。也就说明临界区起到了同步的效果,每次显示和递增,都保证了只能一个线程执行这部分的代码。
    总结在编程开发中,使用临界区之前一定记得要进行初始化操作,使用完毕后,要删除临界区。
    2 留言 2018-11-07 10:59:10 奖励5点积分
  • 使用VS2013编译Crypto++加密库

    背景近期写了一个关于AES和RSA加解密的小程序,其中加解密模块使用开源的Crypto++加密库写的。在程序使用Crypto++加密库之前,需要下载Crytpo++加密库的源码到本地,自己编译,得到库文件。
    当时的最新版Crypto++版本是5.6.5版本,支持到VS2010开发环境编译,所以,我使用VS2013去编译的话,还需要项目进行一些编译的设置。
    现把当时编译的过程详细描述出来,形成文档,方便有需要的人们参考。
    准备工作首先,你要有个VS2013开发环境。因为本文讲解的是使用VS2013开发环境进行编译的,理论上VS2010、VS2012、VS2015、VS2017等应该也适用的。
    其次,需要到Crypto++官网 (https://www.cryptopp.com) 下载Crypto++库的源码。本文以“Crypto++ 5.6.5”版本为例进行讲解。
    编译过程1. 升级解决方案将“Crypto++ 5.6.5”版本Crypto++库源码下载下来后,进行解压缩。使用VS2013开发环境打开“cryptest.sln”解决方案文件。VS2013提示说“升级VC++编译器和库”,这时我们点击“确定”。因为Crypto++项目工程原来使用VS2010开发的,现在我们使用VS2013重新进行编译,所以要对项目进行升级。

    升级完成后,我们可以在左侧看到项目列表,其中“cryptlib”项目工程就是我们将要进行编译的项目工程。

    2. 更改项目工程平台工具集选中要编译的“crytplib”项目工程,鼠标右击,选中“属性”,打开“属性页”。然后,在“平台工具集”的选项中,选择“Visual Studio 2013 - Windows XP (v120_xp)”,表示兼容XP平台,也就是在XP系统下,调用此Crypto++库文件,也能正常运行。

    3. 重新生成在设置完成之后,右击“cryptlib”项目工程,选择“重新生成(E)”。

    这样,就可以在生成目录下找到生成的库文件了。

    我们可以选择不同的编译模式:Debug模式或是Release模式,选择不同的位数:Win32或是x64,来生成我们所需的库文件。
    4. 加载到程序中我们编译得到库文件“cryptlib.lib”库文件之后,然后,把程序所用加密算法的头文件如:AES加密算法所需的头文件“aes.h”、RSA加密算法所需的头文件“rsa.h”、“randpool.h”、“hex.h”、“files.h”;连同库文件“cryptlib.lib”文件一起拷贝到我们自己的程序目录下,并导入到程序中。这样,我们就可以直接调用Crypto++加密库的函数了。

    总结步骤不是很复杂,要注意一点的是,注意编译的时候,“运行库”对应的设置,这样在自己程序调用编译好的Crypto++库的时候,也要选择相应的模式,进行相应的“运行库”设置。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-11-06 22:04:18 奖励3点积分
  • ring0下通过OUT指令实现强制关机和重启

    背景通常情况下,我们可以通过调用 NtShutdownSystem 未导出的内核 API 函数,来在内核层下实现计算机的关机和重启等操作。 这种实现方式虽然在内核层下实现,但是相对来说,还是太过于表层了。
    本文要介绍一种在内核层下实现的强制关机以及重启的方法,即通过汇编 OUT 指令,控制端口输出,从而实现计算机的关机和重启。这种方式,较为低层,而且更难防御和监控。现在,我就把程序实现过程和原理,整理成文档,分享给大家。
    实现原理我们通过汇编 OUT 指令,控制端口的输出,从而实现控制计算机关机或者重启。
    对于实现关机操作,则可以向 1004H 端口,写入 2001H,汇编代码为:
    mov ax, 2001h mov dx, 1004h out dx,ax ret
    则,对应的机器码为:
    {0x66,0xB8,0x01,0x20,0x66,0xBA,0x04,0x10,0x66,0xEF,0xC3}
    对于实现重启操作,则可以向 64H 端口,写入 0FEH,汇编代码为:
    mov al, 0FEh out 64h, al ret
    则,对应的机器码为:
    {0xB0,0xFE,0xE6,0x64,0xC3}
    所以,我们可以通过申请一块非分页内存,写入 Shellcode 数据,并声明函数指针,调用 Shellcode 数据并执行,从而实现相应的关机或者重启操作。当然,我们也可以直接写成汇编代码,但是,要注意 64 位系统下汇编代码要保存为 .asm 汇编文件,添加到工程中。
    编码实现强制关机// 强制关机BOOLEAN ShutdownForce(){// {0x66, 0xB8, 0x01, 0x20, 0x66, 0xBA, 0x04, 0x10, 0x66, 0xEF, 0xC3} UCHAR ShellCode[11] = { 0x66, 0xB8, 0x01, 0x20, 0x66, 0xBA, 0x04, 0x10, 0x66, 0xEF, 0xC3 }; ULONG ulShellcodeSize = 11;#ifdef _WIN64 // 64 位 typedef VOID(__fastcall *typedef_SHUTDOWNFUNC)();#else // 32 位 typedef VOID(__stdcall *typedef_SHUTDOWNFUNC)();#endif // 申请内存 typedef_SHUTDOWNFUNC ShutdownFunc = (typedef_SHUTDOWNFUNC)ExAllocatePool(NonPagedPool, ulShellcodeSize); if (NULL == ShutdownFunc) { DbgPrint("ExAllocatePool Error!\n"); return FALSE; } // 写入数据 RtlCopyMemory(ShutdownFunc, ShellCode, ulShellcodeSize); // 执行Shellcode代码实现关机 ShutdownFunc(); // 释放内存 ExFreePool(ShutdownFunc); return TRUE;}
    强制重启// 强制重启BOOLEAN RebootForce(){ // {0xB0, 0xFE, 0xE6, 0x64, 0xC3} UCHAR ShellCode[11] = { 0xB0, 0xFE, 0xE6, 0x64, 0xC3 }; ULONG ulShellcodeSize = 5;#ifdef _WIN64 // 64 位 typedef VOID(__fastcall *typedef_REBOOTFUNC)();#else // 32 位 typedef VOID(__stdcall *typedef_REBOOTFUNC)();#endif // 申请内存 typedef_REBOOTFUNC RebootFunc = (typedef_REBOOTFUNC)ExAllocatePool(NonPagedPool, ulShellcodeSize); if (NULL == RebootFunc) { DbgPrint("ExAllocatePool Error!\n"); return FALSE; } // 写入数据 RtlCopyMemory(RebootFunc, ShellCode, ulShellcodeSize); // 执行Shellcode代码实现重启 RebootFunc(); // 释放内存 ExFreePool(RebootFunc); return TRUE;}
    程序测试在 Win7 32 位系统下,驱动程序正常运行;在 Win10 64 位系统下,驱动程序正常运行。
    总结对于这个层序,虽然编码较为简单,但是要想兼容 32 位和 64 位计算机,则需要特别注意一个问题:
    在声明函数指针的时候,对于 32 位函数,调用约定为 __stdcall;对于 64 位函数,调用约定为 __fastcall。
    所以,一定要注意不同系统位数下,函数的调用约定声明。否则,会出现错误。
    1 留言 2019-04-15 16:24:29
  • 《Windows黑客编程技术详解》勘误——13.3 文件管理之NTFS解析

    在内核篇第13章的第3小节“文件管理之NTFS解析”一文中,由于有网友测试反馈说该小结代码偶尔会出现定位不到存在的文件问题!经过我的排查,是由于配套代码中,多个Data Run的处理逻辑有问题!
    现在为了方便大家理解修改后的源代码,我在此补充下多个Data Run的处理说明。
    在NTFS文件系统中,如果数据很大的话,通常会使用Data Run来记录开辟的新空间,而且这些数据可能会不连续,所以就会出现多个Data Run的情况。
    Data Run的含义分析如下:

    Data Run的第一个字节分高4位和低4位。其中,高4位表示文件内容的起始簇号在Data Run List中占用的字节数。低4位表示文件内容簇数在Data Run List中占用的字节数。
    Data Run的第二个字节开始表示文件内容的簇数,接着表示文件内容的起始簇号。
    Data Run可以指示空间的大小以及偏移位置,例如上述中给的例子,起始簇号为:A2 59 00(10639616),数据大小为:C0 14(49172)。
    现在需要补充的知识点是针对多个Data Run的,如下所示!!!
    对于多个Data Run的情况,第一个Data Run的起始簇号是一个正整数,而第二个Data Run开始,起始簇号偏移是分正负的。可以根据起始簇号偏移的最高位是否来判断,若为1,则是负整数(补码表示);否则,是正整数。而且,从第二个Data Run开始,起始簇号偏移都是相对于上一个Data Run的起始簇号来说的。下面举个例子,方便大家理解。
    例如,有这么一个Data Run如下所示:
    31 01 FD 0A 28 21 01 AB FA 21 01 4A F5 21 01 91 C1 00我们可以看到上面一共有4个Data Run,分别如下:
    第1个Data Run

    31 01 FD 0A 28
    正整数:第一个Data Run的起始簇号都是正整数起始簇号:28 0A FD(2624253)

    第2个Data Run

    21 01 AB FA
    负整数:起始簇号偏移FA AB的最高位是1,所以是负整数(补码),所以FA AB(-1365)起始簇号:相对于上一个Data Run的偏移,所以为:2624253-1365=2622888

    第3个Data Run

    21 01 4A F5
    负整数:起始簇号偏移F5 4A的最高位是1,所以是负整数(补码),所以F5 4A(-2742)起始簇号:相对于上一个Data Run的偏移,所以为:2622888-2742=2620146

    第4个Data Run

    21 01 91 C1
    负整数:起始簇号偏移C1 91的最高位是1,所以是负整数(补码),所以C1 91(-15983)起始簇号:相对于上一个Data Run的偏移,所以为:2620146-15983=2604163

    配套代码修正对该小节配套代码中的HandleAttribute_A0函数和FileContentOffset函数的Data Run处理修改为上述补充的多个Data Run处理:
    计算Data Run的其实簇号代码修改为如下所示:
    if (0 == llClusterOffet){ // 第一个Data Run for (DWORD i = bHi; i > 0; i--) { liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]; }}else{ // 第二个及多个Data Run // 判断正负 if (0 != (0x80 & lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + bHi])) { // 负整数 for (DWORD i = bHi; i > 0; i--) { // 补码的原码=反码+1 liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | (BYTE)(~lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]); } liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + 1; liDataRunOffset.QuadPart = 0 - liDataRunOffset.QuadPart; } else { // 正整数 for (DWORD i = bHi; i > 0; i--) { liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]; } }}// 注意加上上一个Data Run的逻辑簇号(第二个Data Run可能是正整数、也可能是负整数(补码表示), 可以根据最高位是否为1来判断, 若为1, 则是负整数, 否则是正整数)liDataRunOffset.QuadPart = llClusterOffet + liDataRunOffset.QuadPart;llClusterOffet = liDataRunOffset.QuadPart;
    修改后的源码,重新上传在下面的附件中了!!!若对书中内容有疑惑或者发现错误,可以直接戳下面的勘误收集链接哦
    https://www.write-bug.com/article/1966.html
    1 留言 2019-01-21 13:13:32
  • 基于WinPcap实现的UDP发包程序 精华

    背景一天,一位同学打电话给我说,让我帮忙开发一个基于WinPcap工具的UDP发包工具,还特地叮嘱是基于WinPcap,不要原始套接字Raw Socket。而且,时间只有一个白天,它晚上就要,而打电话给我的时候,已经临近中午了。我一听,同学一场,那就举手之劳吧。
    之前,自己就是用WinPcap开发过一些小程序,例如网卡遍历程序、Arp欺骗攻击程序等,所以,对WinPcap还算是熟悉。做这样的UDP发包程序,应该倒也不是那没事。
    现在,为了配合这篇文章的讲解,我特地对这个程序重新开发。把程序的实现原理和过程整理成文档,分享给大家。
    使用VS2013配置WinPcap开发环境程序是使用VS2013开发的,程序中要使用WinPcap工具提供的功能函数的话,就需要将对VS2013进行配置,将WinPcap库导入到程序中,现在,就先介绍WinPcap环境的配置过程:
    1.下载并安装WinPcap运行库http://www.winpcap.org/install/default.htm 。一些捕包软件会捆绑安装WinPcap,MentoHust也会附带WinPcap,这种情况下一般可以跳过此步。
    2.下载WinPcap开发包http://www.winpcap.org/devel.htm ,解压到纯英文路径。
    3.打开VS工程项目,在VS工程项目的 属性 —> VC++目录 中,包含目录 选项添加WpdPack\Include 目录,在 库目录 选项中添加 WpdPack\Lib 目录。
    4.在 属性 —> C/C++ —> 预处理器 中,添加 WPCAP 和 HAVE_REMOTE 这两个宏定义。
    5.在VS工程项目的 属性 —> 链接器 —> 输入 中,添加 wpcap.lib 和 ws2_32.lib 两个库。
    这样,就可以将WinPcap所需的库文件包含到工程项目中了。接下来,我们就开始讲解UDP发包程序的实现过程。
    实现过程1. 获取网卡设备列表首先,我们调用WinPcap函数pcap_findalldevs_ex获取设备列表信息,函数会将计算机上所有网卡设备信息返回到指向pcap_if_t结构体指针里。我们只需要对这个结构体指针进行遍历,就可以获取每个设备的详细信息。
    // 获取网卡设备列表 if (-1 == pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, szErr)) { ShowError("pcap_findalldevs_ex"); return; }
    程序将每个设备的信息显示在界面上,供用户选中使用哪个网卡进行操作。
    2. 设置网卡信息并打开网卡我们使用 pcap_open 函数来设置并打开网卡,函数中第 1 个参数stAdapterInfo. szAdapterName,表示网卡的名称;第 2 个参数 65535,表示设置保留数据包的长度,65535即每个数据包的前65535字节长度的数据被保留在缓冲区中;第 3 个参数 PCAP_OPENFLAG_DATATX_UDP 表示使用UDP协议来处理数据传输;第 4 个参数 1 ,表示以毫秒为单位的读取超时时间。读取超时用来处理,捕获一个数据包后,读操作并不必需要立即返回的情况。但这可能等待一些时间以允许捕获更多的数据包,这样用户层的一次读操作就可从操作系统的内核中读取更多的数据包。第 5 个参数为 NULL;第 6 个参数 errbuf,可以获取返回的出错信息。
    // 打开网卡 m_adhandle = pcap_open(stAdapterInfo.szAdapterName, 65535, PCAP_OPENFLAG_DATATX_UDP, 1, NULL, errbuf); if (NULL == m_adhandle) { ShowError("pcap_open"); return; }
    3. 构造UDP数据包我们根据源MAC地址、目的MAC地址、源IP地址、目的IP地址、源端口、目的端口、数据内容以及数据内容长度,构造UDP数据包。具体构造过程如下:

    首先,构造以太网帧头。
    memcpy((void*)FinalPacket, (void*)DestinationMAC, 6); memcpy((void*)(FinalPacket + 6), (void*)SourceMAC, 6); USHORT TmpType = 8; memcpy((void*)(FinalPacket + 12), (void*)&TmpType, 2);
    然后,构造IP头。
    memcpy((void*)(FinalPacket + 14), (void*)"\x45", 1); memcpy((void*)(FinalPacket + 15), (void*)"\x00", 1); TmpType = htons(TotalLen); memcpy((void*)(FinalPacket + 16), (void*)&TmpType, 2); TmpType = htons(0x1337); memcpy((void*)(FinalPacket + 18), (void*)&TmpType, 2); memcpy((void*)(FinalPacket + 20), (void*)"\x00", 1); memcpy((void*)(FinalPacket + 21), (void*)"\x00", 1); memcpy((void*)(FinalPacket + 22), (void*)"\x80", 1); memcpy((void*)(FinalPacket + 23), (void*)"\x11", 1); memcpy((void*)(FinalPacket + 24), (void*)"\x00\x00", 2); memcpy((void*)(FinalPacket + 26), (void*)&SourceIP, 4); memcpy((void*)(FinalPacket + 30), (void*)&DestIP, 4);
    接着,构造UDP头。
    TmpType = htons(SourcePort); memcpy((void*)(FinalPacket + 34), (void*)&TmpType, 2); TmpType = htons(DestinationPort); memcpy((void*)(FinalPacket + 36), (void*)&TmpType, 2); USHORT UDPTotalLen = htons(UserDataLen + 8); memcpy((void*)(FinalPacket + 38), (void*)&UDPTotalLen, 2); memcpy((void*)(FinalPacket + 42), (void*)UserData, UserDataLen);

    要注意的是,IP校验和以及UDP的校验和计算。这样,我们就可以成功构造UDP的数据包,接下来,就可以对数据包进行发送。
    4. 发送UDP数据包我们使用 pcap_sendpacket 函数发送单个数据包,第 1 个参数表示打开网卡的时候获取的句柄;第 2 个参数就是发送的数据内容;第 3 个参数表示发送数据内容的长度。注意,缓冲数据将直接发送到网络,而不会进行任何加工和处理。这就意味着应用程序需要创建一个正确的协议首部,来使这个数据包更有意义。
    // 发送数据包 if (0 != pcap_sendpacket(m_adhandle, FinalPacket, (UserDataLen + 42))) { char *szErr = pcap_geterr(m_adhandle); ShowError(szErr); return; }
    经过上面 4 步操作,便可以实现使用 WinPcap 发送 UDP 数据包了。要注意IP校验和以及UDP校验和的计算,这两个的值注意不要算错。
    程序测试我们以管理员权限运行程序,并对一个UDP程序发包,观察UDP程序能否接收到我们发包程序发送的UDP数据包。其中,UDP程序使用的是《Socket通信之UDP通信小程序》这篇文章中实现的UDP程序。
    经过测试结果,UDP程序成功接收到UDP数据包。

    总结这个程序是基于WinPcap实现的,所以,可能有很多人之前还没有接触过WinPcap方面的知识,所以,一下子使用和理解起来就比较困难。关键是耐下心来,把程序中调用到的不理解的WinPcap函数,在网上查找说明,对函数理解清晰。
    其中,要注意的是,使用WinPcap工具,需要有管理员权限。
    参考参考自《Windows黑客编程技术详解》一书
    3 留言 2018-11-28 11:22:48 奖励40点积分
  • 大数据 Spark平台5-3、spark-sql

    前文链接:https://write-bug.com/article/2361.html
    Spark-Sql官网:http://spark.apache.org/docs/latest/sql-getting-started.html
    这里对Spark家族进一步介绍,偏入门实践,优化概念会少一些。
    我们在学习Hive时,本质上是把sql语句转换成MR job,节省工作时间,提升效率,其在存储数据时,分为这几个层次:table / partition / bucket / hdfs
    spark sql同样也处理结构化数据,把数据源传来的数据用表格的形式解析并维护起来,与此同时也可和Hive结合使用(数据存储在Hive中)
    在spark streaming中我们通常开发一个模板——Dstream, SparkStreamingContext
    spark SQL同样也有类似的概念——DataFrame, 当成一个table(关系型表格)

    外部数据源(SQLContext):HDFS、网络接口、Mysql等
    Hive数据源(HiveContext):Hive

    两者关系:HiveContext继承自SQLContext,HiveContext只能支持HQL,SQLContext支持语法更多
    DataFrame(由很多RDD组成)让数据处理更为简单,是一个分布式的Table

    与RDD区别:传统RDD以行为单位读数据,DataFrame基于列的内部优化
    与RDD相同点:都具备懒惰机制(基于RDD的抽象)


    Spark SQL处理核心:Catalyst工作流程(本质:把sql、dataframe相结合,以树tree的形式来存储、优化)

    把sql语句和Dataframe输入形成未解析的逻辑计划,加上元数据Catalog的支持,形成逻辑计划,再经过优化,形成物理计划,最后再通过代价模型转化成可执行代码。
    优化点

    基于规则

    一种经验式、启发式优化思路(如sql语句优化)join算子——两张表做join
    外排
    大循环外排:A、B,两张表都很大,O(M*N)——不用游标式外排:归并排序(指针滑动比大小)
    内排:小表放内存,大表做遍历(hive中的mapside join)

    基于代价

    评估每种策略选择的代价,根据代价估算,确定代价最小的方案代价估算模型——调整join的顺序,减少中间shuffle数据的规模

    catalyst工作流程

    parser:针对sql解析
    词法分析:讲输入的sql语句串解析为一个一个的token
    语法分析:再词法分析基础上,将单词序列组合成各类语法短语,组成各个LogicPlan

    SELECT sum(v) FROM( SELECT score.id, 100+80+score.math_score+score.english_score AS vFROM people JOIN scoreWHERE people.id=score.id AND people.age>10 ) a

    analyzer:借助元数据(catalog)解析根据元数据表解析为包含必要列的表,并且相应字段解析为相应的数据类型,相应的计算逻辑解析为对应的函数


    optimizer:基于规则的优化策略经典规则:谓词下推(Predicate Pushdown)、常量累加(Constant Folding)和列值裁剪(Column Pruning)谓词下推:把过滤条件放在join之前执行

    常量累加(180)、列值裁剪(提前过滤掉不用的列值字段):


    物理计划:基于代价的优化策略用物理操作算子产生一个或者多个物理计划。然后用cost模型选择一个物理计划。目前基于cost-based的优化仅仅用 于选择join算法:对已知的很小的relations,sparksql会选择使用spark的提供的点对点的广播功能实现Broadcast join
    执行计划查看方式:

    Spark网页sql
    sql语句后面追加 . queryExecution方法查看

    官方:catalyst优化引擎,执行时间减少75%
    内存管理:Tungsten 内存管理器—— off-heap(堆外内存)

    本质:突破JVM内存管理限制,分配堆外内存(GC、与磁盘做交换dump)
    JVM:GC带来时间开销,可能出现“假死”情况

    实践
    1、基本demo:
    读数据:
    1)从hdfs的原始text中读数据:sqlTest
    //建立学生表Schemeval StudentSchema: StructType = StructType(mutable.ArraySeq( StructField("Sno", StringType, nullable = false), StructField("Sname", StringType, nullable = false), StructField("Ssex", StringType, nullable = false), StructField("Sbirthday", StringType, nullable = true), StructField("SClass", StringType, nullable = true) ))val sparkConf = new SparkConf().setMaster("local[2]").setAppName("sqltest") val sc = new SparkContext(sparkConf)//sqlContext入口 val sqlContext = new org.apache.spark.sql.SQLContext(sc)//RDD导入并解析数据 val StudentData = sc.textFile("hdfs://master:9000/sql_stu.data").map{ lines => val line = lines.split(",") Row(line(0),line(1),line(2),line(3),line(4)) }//把RDD数据和Schema通过sqlContext维护起来形成DataFrame val StudentTable = sqlContext.createDataFrame(StudentData, StudentSchema) StudentTable.registerTempTable("Student")//表名//sql语句使用 sqlContext.sql("SELECT Sname, Ssex, SClass FROM Student").show()2)从hdfs的原始text中读数据(json串):sqlJsonText
    val personInfo = sqlContext.read.json("hdfs://master:9000/person_info.json")//json串中的数据都是维护好的数据,不需要schema personInfo.registerTempTable("personInfo") sqlContext.sql("SELECT id, name, age FROM personInfo").show() println(personInfo.schema)3)从hive中读数据:sqlHiveTest
    启动mysql:]# systemctl start mariadb(hive中元数据)
    终端submit需jar包:lib/mysql-connector-java-5.1.41-bin.jar
    val hiveContext = new HiveContext(sc) hiveContext.table("movie_table").registerTempTable("movie_table")//可对现有表直接进行操作 hiveContext.sql("SELECT movieid, title FROM movie_table").show()2、UDF相关操作:
    1)udf:单条记录处理(map):sqlUdf
    sqlContext.udf.register("strlen", (input: String) => input.length)//函数名字注册,及简单实现功能 val personInfo = sqlContext.read.json("hdfs://master:9000/person_info.json") personInfo.registerTempTable("personInfo") sqlContext.sql("SELECT id, name, strlen(name), age FROM personInfo").show()//字段套用函数2)udaf:聚合场景(groupby)
    例子:每一个打分背后,有多少人参与
    class WordcountUdaf extends UserDefinedAggregateFunction { // 该方法指定具体输入数据的类型 override def inputSchema: StructType = StructType(Array(StructField("input", StringType, true))) //在进行聚合操作的时候所要处理的数据的结果的类型 override def bufferSchema: StructType = StructType(Array(StructField("count", IntegerType, true))) //指定UDAF函数计算后返回的结果类型 override def dataType: DataType = IntegerType // 确保一致性 一般用true override def deterministic: Boolean = true //在Aggregate之前每组数据的初始化结果 override def initialize(buffer: MutableAggregationBuffer): Unit = {buffer(0) =0} // 在进行聚合的时候,每当有新的值进来,对分组后的聚合如何进行计算 // 本地的聚合操作,相当于Hadoop MapReduce模型中的Combiner override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { buffer(0) = buffer.getAs[Int](0) + 1 } //最后在分布式节点进行Local Reduce完成后需要进行全局级别的Merge操作 override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1(0) = buffer1.getAs[Int](0) + buffer2.getAs[Int](0) } //返回UDAF最后的计算结果 override def evaluate(buffer: Row): Any = buffer.getAs[Int](0)}val hiveContext = new HiveContext(sc) hiveContext.table("rating_table").registerTempTable("rating_table") hiveContext.udf.register("strlen", (input: String) => input.length) hiveContext.udf.register("wordCount", new WordcountUdaf)//注册 hiveContext.sql("select rating, wordCount(rating) as count, strlen(rating) as length" + " from rating_table group by rating").show()//这里两函数可做个对比3、终端
    /usr/local/src/spark-2.0.2-bin-hadoop2.6/bin/spark-sql \--master local[2] \--jars /usr/local/src/spark-2.0.2-bin-hadoop2.6/lib/mysql-connector-java-5.1.41-bin.jar测试与hive数据:
    select rating, count(*) from rating_table_ex group by rating limit 100;4、streaming+sql:sqlAndStreamingWC
    nc -l 9999//单例模式object SQLContextSingleton { @transient private var instance: SQLContext = _ def getInstance(sparkContext: SparkContext): SQLContext = { if (instance == null) { instance = new SQLContext(sparkContext) } instance }}if (args.length < 2) { System.err.println("Usage: NetworkWordCount <hostname> <port>") System.exit(1) } val sparkConf = new SparkConf().setMaster("local[2]").setAppName("sqlAndStreamingWC") val sc = new SparkContext(sparkConf) val ssc = new StreamingContext(sc, Seconds(30)) val lines = ssc.socketTextStream(args(0), args(1).toInt, StorageLevel.MEMORY_AND_DISK_SER) val words = lines.flatMap(_.split(" ")) words.foreachRDD((rdd: RDD[String], time: Time) => { val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext) import sqlContext.implicits._//每条数据都用一个对象操作内存,复用性 val wordsDataFrame = rdd.map(w => Record(w)).toDF() wordsDataFrame.registerTempTable("words") val wordCountsDataFrame = sqlContext.sql("select word, count(*) as total from words group by word") println(s"========= $time =========") wordCountsDataFrame.show() }) ssc.start() ssc.awaitTermination()5、streaming+sql + hbase:streamSqlHbase
    nc -l 9999object HbaseHandler { def insert(row: String, column: String, value: String) { // Hbase配置 val tableName = "sparkstream_kafkahbase_table" // 定义表名 val hbaseConf = HBaseConfiguration.create() hbaseConf.set("hbase.zookeeper.quorum", "master,slave1,slave2") hbaseConf.set("hbase.zookeeper.property.clientPort", "2181") hbaseConf.set("hbase.defaults.for.version.skip", "true") val hTable = new HTable(hbaseConf, tableName) val thePut = new Put(row.getBytes) thePut.add("info".getBytes,column.getBytes,value.getBytes) hTable.setAutoFlush(false, false) // 写入数据缓存 hTable.setWriteBufferSize(3*1024*1024) hTable.put(thePut) // 提交 hTable.flushCommits() }}//从套接字中读取到的信息遍历解析lines.foreachRDD((rdd: RDD[String], time: Time) => { val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext) import sqlContext.implicits._ val wordsDataFrame = rdd.map{ x=> (x.split(" ")(0),x.split(" ")(1),x.split(" ")(2)) }.map(w => (w._1, w._2, w._3)).toDF("key", "col", "val") wordsDataFrame.registerTempTable("words") val wordCountsDataFrame = sqlContext.sql("select key, col, val from words") println(s"========= $time =========") wordCountsDataFrame.show() //对dataframe行遍历插入 wordCountsDataFrame.foreach(x => HbaseHandler.insert( x.getAs[String]("key"), x.getAs[String]("col"), x.getAs[String]("val"))) })
    2 留言 2019-04-10 15:19:41 奖励10点积分
  • 基于WinInet的FTP文件下载实现 精华

    背景对于在网络之间的文件传输,我们通常使用FTP传输协议。因为,FTP就是专门为了文件传输而生的,传输效率高,稳定性好。所以,FTP文件传输协议,是我们网络传输中常用的协议。
    为了学习这方面的开发知识,自己专门写了个使用Windows提供的WinInet库实现了FTP的文件传输的基本功能。现在,我就把基于WinInet库实现的FTP文件下载和FTP文件上传分成两个文档分别进行解析。本文介绍的是基于WinInet的FTP文件下载的实现。
    函数介绍介绍FTP下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen 介绍
    初始化一个应用程序,以使用 WinINet 函数。
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect 介绍
    建立 Internet 的连接。
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. FtpOpenFile介绍
    启动访问FTP服务器上的远程文件以进行读取或写入。
    函数声明
    HINTERNET FtpOpenFile( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszFileName, _In_ DWORD dwAccess, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数

    hConnect [in]处理FTP会话。
    lpszFileName [in]指向包含要访问的文件名称的以NULL结尾的字符串。
    dwAccess [in]文件访问。 该参数可以是GENERIC_READ或GENERIC_WRITE,但不能同时使用。
    dwFlags [in]转移发生的条件。 应用程序应选择一种传输类型,以及指示文件缓存如何被控制的任何标志。传输类型可以是以下值之一。




    VALUE
    MEANING




    FTP_TRANSFER_TYPE_ASCII
    使用FTP的ASCII(类型A)传输方法传输文件。 控制和格式化信息被转换为本地等价物。


    FTP_TRANSFER_TYPE_BINARY
    使用FTP的图像(类型I)传输方法传输文件。 文件完全按照存在的方式进行传输,没有任何变化。 这是默认的传输方式。


    FTP_TRANSFER_TYPE_UNKNOWN
    默认为FTP_TRANSFER_TYPE_BINARY。


    INTERNET_FLAG_TRANSFER_ASCII
    以ASCII格式传输文件。


    INTERNET_FLAG_TRANSFER_BINARY
    将文件作为二进制文件传输。



    以下值用于控制文件的缓存。 应用程序可以使用这些值中的一个或多个。



    VALUE
    MEANING




    INTERNET_FLAG_HYPERLINK
    在确定是否从网络重新加载项目时,如果没有到期时间并且没有LastModified时间从服务器返回,则强制重新加载。


    INTERNET_FLAG_NEED_FILE
    如果无法缓存文件,则导致创建临时文件。


    INTERNET_FLAG_RELOAD
    强制从源服务器下载所请求的文件,对象或目录列表,而不是从缓存中下载。


    INTERNET_FLAG_RESYNCHRONIZE
    如果资源自上次下载以来已被修改,请重新加载HTTP资源。 所有FTP资源都被重新加载。




    dwContext [in]指向包含将此搜索与任何应用程序数据相关联的应用程序定义值的变量。 这仅在应用程序已经调用InternetSetStatusCallback来设置状态回调函数时才会使用。
    返回值

    如果成功则返回一个句柄,否则返回NULL。 要检索特定的错误消息,请调用GetLastError。

    4. InternetReadFile 介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    实现原理首先,我们先介绍下FTP的URL格式:
    FTP://账号:密码@主机/子目录或文件例如:ftp://admin:123456@192.168.0.1/mycode/520.zip
    其中,“FTP”就表示使用FTP传输数据;“账号”即登录FTP服务器的用户名;“密码”即登录FTP服务器用户名对应的密码;“主机”表示服务器的IP地址;“子目录或文件”即要进行操作的文件或目录的路径。
    在,WinInet库中,提供了InternetCrackUrl这个函数专门用于URL的分解,在我写的《URL分解之InternetCrackUrl》则篇文章中有详细介绍和使用方法。
    那么,基于WinInet库的FTP文件下载的原理如下所示:

    首先,我们先调用对URL进行分解,从URL中获取后续操作需要的信息。
    然后,使用InternetOpen初始化WinInet库,建立网络会话。
    接着,使用InternetConnect与服务器建立连接,并设置FTP数据传输方式。
    之后,我们就可以调用FtpOpenFile函数,根据文件路径,以GENERIC_READ的方式,打开文件并获取服务器上文件的句柄。
    接着,根据文件句柄,调用FtpGetFileSize获取文件的大小,并根据文件大小在程序申请一块动态内存,以便存储下载的数据。
    然后,就可以调用InternetReadFile从服务器上下载文件数据,并将下载的数据存放在上述申请的动态内存中。
    最后,关闭上述打开的句柄,进行清理工作。

    这样,就可以成功实现FTP文件下载的功能了。与服务器建立FTP连接后,我们使用WinInet库中FTP函数对服务器上文件的操作就如果使用Win32 API函数对本地文件操作一样方便。
    编码实现导入WinInet库文件#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    FTP文件下载// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL FTPDownload(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; ::RtlZeroMemory(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Ftp_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hFTPFile = NULL; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwBufferSize = 4096; BYTE *pBuf = NULL; DWORD dwBytesReturn = 0; DWORD dwOffset = 0; BOOL bRet = FALSE; do { // 建立会话 hInternet = ::InternetOpen("WinInet Ftp Download V1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Ftp_ShowError("InternetOpen"); break; } // 建立连接 hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_INVALID_PORT_NUMBER, szUserName, szPassword, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0); if (NULL == hConnect) { Ftp_ShowError("InternetConnect"); break; } // 打开FTP文件, 文件操作和本地操作相似 hFTPFile = ::FtpOpenFile(hConnect, szUrlPath, GENERIC_READ, FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RELOAD, NULL); if (NULL == hFTPFile) { Ftp_ShowError("FtpOpenFile"); break;; } // 获取文件大小 dwDownloadDataSize = ::FtpGetFileSize(hFTPFile, NULL); // 申请动态内存 pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); pBuf = new BYTE[dwBufferSize]; if (NULL == pBuf) { break; } ::RtlZeroMemory(pBuf, dwBufferSize); // 接收数据 do { bRet = ::InternetReadFile(hFTPFile, pBuf, dwBufferSize, &dwBytesReturn); if (FALSE == bRet) { Ftp_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwBytesReturn); dwOffset = dwOffset + dwBytesReturn; } while (dwDownloadDataSize > dwOffset); } while (FALSE); // 返回数据 if (FALSE == bRet) { delete[]pDownloadData; pDownloadData = NULL; dwDownloadDataSize = 0; } *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; // 释放内存并关闭句柄 if (NULL != pBuf) { delete []pBuf; pBuf = NULL; } if (NULL != hFTPFile) { ::InternetCloseHandle(hFTPFile); } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); } return bRet;}
    程序测试在 main 函数中调用上述封装好的函数,进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; // 下载 if (FALSE == FTPDownload("ftp://admin:123456789@192.168.0.1/Flower520.zip", &pDownloadData, &dwDownloadDataSize)) { printf("FTP Download Error!\n"); } // 将数据保存为文件 Ftp_SaveToFile("myftpdownloadtest.zip", pDownloadData, dwDownloadDataSize); // 释放内存 delete []pDownloadData; pDownloadData = NULL; printf("FTP Download OK.\n"); system("pause"); return 0;}
    测试结果
    运行程序,程序提示下载成功。然后,打开目下查看下载文件,成功下载文件。

    总结在打开Internet会话并和服务器建立连接后,接下来使用WinInet库中的FTP函数对服务器上的文件操作,就如同在自己的计算机上使用Win32 API函数操作一样。都是打开或者创建文件,获取文件句柄,然后根据文件句柄,调用函数对文件进行读写操作,最后,关闭文件句柄。
    所以,大家注意和本地的文件操作进行类比下,就很容易理解了。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-12-09 14:46:21 奖励18点积分
  • 基于Crypto++计算数据或文件的SHA256值

    背景写了一个基于Crypto++加密库中实现计算文件和数据的SHA256值的一个小程序,Crypto++加密库就不详细介绍了,这个库提供了很多知名的加解密算法,直接调用就好了,使用起来还是比较方便的。
    写这篇文章,就是分享自己的学习心得。自己的密码学部分的知识学得不怎么好,还好有Crypto++开源库可以使用,弥补了对加解密部分的不足。现在,向大家分享使用Crypto++中的SHA256模块实现文件和数据的SHA256值计算方法。
    程序编译设置注意事项首先,先要下载Crypto++库的开源代码,然后,自己编译得到Crypto++的库文件。下载链接还有具体的编译步骤,可以参考分享文章 “使用VS2013编译Crypto++加密库“,里面有详细介绍。
    在导入Crypto++的库文件到自己的工程项目的时候,要对自己的工程项目进行编译设置。主要一点就是:项目工程的属性中的“运行库”设置,要与编译Crypto++的库文件时设置的“运行库”选项要对应一致,否则程序会编译不过的。也就是要检查LIB库工程和本测试工程的:属性 —> C/C++ —> 代码生成 —> 运行库 是否统一。
    如果编译出错,报告XX重复定义等错误,同样,要检查LIB库工程和本测试工程的:属性 —> C/C++ —> 代码生成 —> 运行库 是否统一。
    实现原理计算文件和数据的SHA256值,它们的实现核心代码为:
    FileSource(pszFileName, true, new HashFilter(sha256, new HexEncoder(new StringSink(value))));
    StringSource(pData, dwDataSize, true, new HashFilter(sha256, new HexEncoder(new StringSink(value))));
    这两行代码总共用了4个类 StringSink、HexEncoder、HashFilter、FileSource 和 StringSource。而且,这两行代码的主要区别就是 FileSource 和 StringSource 的区别。FileSource 的第一个参数只需要传入文件路径名称即可,而 StringSource 第一第二个参数分别表示数据首地址指针和数据大小。
    其它参数表示的意义都是相同的,首先用类 StringSink 添加到一个 string 对象缓冲区,接着用类 HexEncoder 把这个缓冲区转换为 16 进制。其中,计算 Hash 值主要用到类 HashFilter。FileSource 类是把要计算 Hash 值的文件 filename 进行一定的转换放入临时缓冲区,然后调用实例化的 HashFilter 对其进行计算相应 Hash 函数的 Hash值,并把 Hash 值返回到缓冲区中。而 StringSource 类是把要计算 Hash 值的数据直接传递给 HashFilter,然后调用实例化的 HashFilter 对其进行计算相应 Hash 函数的Hash值,并把 Hash 值返回到缓冲区中。
    编码实现计算文件的SHA256// 计算文件的 SHA256 值string CalSHA256_ByFile(char *pszFileName){ string value; SHA256 sha256; FileSource(pszFileName, true, new HashFilter(sha256, new HexEncoder(new StringSink(value)))); return value;}
    计算数据的SHA256// 计算数据的 SHA256 值string CalSHA256_ByMem(PBYTE pData, DWORD dwDataSize){ string value; SHA256 sha256; StringSource(pData, dwDataSize, true, new HashFilter(sha256, new HexEncoder(new StringSink(value)))); return value;}
    程序测试我们运行程序,分别使用文件和数据两种方式来计算 520.exe 的 SHA256 值,结果两个值都相同:

    总结这个程序不是很复杂,使用起来比较简单。大家在使用的过程中,注意区分下文件的计算和数据的计算之间的差别即可。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2019-01-14 17:16:44 奖励10点积分
显示 60 到 75 ,共 15 条
eject