元素,并使用css()
方法修改了它们在页面中显示的文字颜色。
替换内容 replaceWith()
和replaceAll()
方法都可以用于替换元素或元素中的内容,但它们调用时,内容和被替换元素所在的位置不同,分别为如下所示: **$(selector).replaceWith(content)**
和**$(content).replaceAll(selector)**
参数selector为被替换的元素,content为替换的内容。 例如,调用replaceWith()
方法将页面中元素替换成一段HTML字符串,如下图所示: 在浏览器中显示的效果: 从图中可以看出,使用replaceWith()
方法替换类别名为“green”的元素,替换之后,旧元素完全由新替换的元素所取代。
使用wrap()和wrapInner()方法包裹元素和内容 wrap()
和wrapInner()
方法都可以进行元素的包裹,但前者用于包裹元素本身,后者则用于包裹元素中的内容,它们的调用格式分别为: **$(selector).wrap(wrapper)**
和**$(selector).wrapInner(wrapper)**
参数selector为被包裹的元素,wrapper参数为包裹元素的格式。 例如,调用wrap()
方法,将用元素包裹起来,如下图所示:
在浏览器中显示的效果:
从图中可以看出,红色区域的
元素被蓝色边框的元素通过
wrap()
方法包裹起来
使用remove()和empty()方法删除元素 remove()
方法删除所选元素本身和子元素,该方法可以通过添加过滤参数指定需要删除的某些元素,而empty()
方法则只删除所选元素的子元素。 例如,调用remove()
方法删除元素中类别名为“red”的,如下图所示: 在浏览器中显示的效果: 从图中可以看出,使用remove(".red")
方法只是把元素中类别名为“red”的这部分元素给删除了。
使用hover()方法切换事件 hover()
方法的功能是当鼠标移到所选元素上时,执行方法中的第一个函数,鼠标移出时,执行方法中的第二个函数,实现事件的切实效果,调用格式如下: **$(selector).hover(over****,****out);**
over参数为移到所选元素上触发的函数,out参数为移出元素时触发的函数。 例如,当鼠标移到
元素上时,元素中的字体变成金黄色,如下图所示:
在浏览器中显示的效果:
从图中可以看出,使用
hover()
方法执行两个函数,当鼠标移在元素上时调用
addClass()
方法增加一个样式,移出时,调用
removeClass()
方法移除该样式。
使用toggle()方法绑定多个函数 toggle()
方法可以在元素的click事件中绑定两个或两个以上的函数,同时,它还可以实现元素的隐藏与显示的切换,绑定多个函数的调用格式如下: **$(selector).toggle(fun1(),fun2(),funN(),...)**
其中,fun1,fun2就是多个函数的名称 例如,使用toggle()
方法,当每次点击
元素时,显示不同内容,如下图所示:
在浏览器中显示的效果:
从图中可以看出,每次点击
元素时,都依次执行
toggle()
方法绑定的函数,当执行到最后一个函数时,再次点击将又返回执行第一个函数。 注意:toggle()方法支持目前主流稳定的jQuery版本1.8.2,在1.9.0之后的版本是不支持的。
使用one()方法绑定元素的一次性事件 one()
方法可以绑定元素任何有效的事件,但这种方法绑定的事件只会触发一次,它的调用格式如下: **$(selector).one(event,[data],fun)**
参数event为事件名称,data为触发事件时携带的数据,fun为触发该事件时执行的函数。 例如,使用one方法绑定
元素的单击事件,在事件执行的函数中,累计执行的次数,并将该次数显示在页面中,如下图所示:
在浏览器中显示的效果:
从图中可以看出,由于使用了
one()
方法绑定
元素的单击事件,因为事件函数只能执行一次,执行完成后,无论如何单击,都不再触发。
调用animate()方法制作移动位置的动画 调用animate()
方法不仅可以制作简单渐渐变大的动画效果,而且还能制作移动位置的动画,在移动位置之前,必须将被移元素的“position”属性值设为“absolute”或“relative”,否则,该元素移动不了。 例如,调用animate()
方法先将图片向右移动90px,然后,再将图片宽度与高度分别增加30px,如下图所示: 在浏览器中显示的效果: 从图中可以看出,图片先向右移动了“90px”,然后,移动成功后,再在原来的基础之上以动画的效果增大30px,增加成功后,显示“执行完成!”的字样。
s
作者
崔庆才
发表于
2016-03-16
阅读次数:
本文字数:
6.2k
阅读时长 ≈
6 分钟
综述
PhpStorm 可以使用 File Watchers 自动编译 Less,有了这个 IDE,妈妈再也不用担心我的 Less 编译了。下面说一下我的配置过程。 下面的例子以 Mac OS X 为例。
配置
1.配置 npm
更多平台安装方式 npm
2.安装 lessc
安装完毕后查看安装路径
Mac OS X 的结果是
3.配置 PhpStorm
打开 PhpStorm,Preferences->Tools->File Watchers 点击加号新增 Less Template,然后点击编辑按钮编辑,页面如下 其中需要配置两个地方
Program:
配置为 lessc 的路径,这边配置为 /usr/local/bin/lessc
Output paths to refresh:
1
$FileParentDir(less)$/css/$ FileDirPathFromParent(less)$/$FileNameWithoutExtension$.css
在这里简单解释下这个路径的意思。
例如项目名为 project,less 文件我们放置在 project/public/less/manage/style.less $FileParentDir(less)$ 是获取 less 目录的路径,也就是 project/public $FileDirPathFromParent(less)$ 是获取 less 文件到 less 目录的路径,也就是 manage $FileNameWithoutExtension$ 是获取 less 文件不带后缀的名字,也就是 style 经过如上拼接,生成的内容为 project/public/css/manage/style.css
所以,不论我们的 less 文件如何放置,都可以生成相对路径的 css 文件。 配置完成之后,我们新建 less 目录,任意编辑一个 less 文件,都会在 css 目录下生成相应的文件。
简单配置
当然,如果你的 less 文件就直接在 less 目录下,可以简单配置以上的 Output Path 如下 ../css/$FileNameWithoutExtension$.css 这也是一种比较常用的配置方法。 如果目录结构简单,可以采取以上方式。
作者
崔庆才
发表于
2016-02-25
阅读次数:
本文字数:
949
阅读时长 ≈
1 分钟
作者
崔庆才
发表于
2016-02-11
阅读次数:
本文字数:
250
阅读时长 ≈
1 分钟
作者
崔庆才
发表于
2016-02-11
阅读次数:
本文字数:
211
阅读时长 ≈
1 分钟
作者
崔庆才
发表于
2016-02-11
阅读次数:
本文字数:
196
阅读时长 ≈
1 分钟
2022 年最新 Python3 网络爬虫教程
大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。
博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。
最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。
教程请移步:
【2022 版】Python3 网络爬虫学习教程
如下为原文。
关于
首先,在此附上项目的地址,以及官方文档 PySpider 官方文档
安装
1. pip
首先确保你已经安装了 pip,若没有安装,请参照 pip 安装
2. phantomjs
PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API。它全面支持 web 而不需浏览器支持,其快速、原生支持各种 Web 标准:DOM 处理、CSS 选择器、JSON、Canvas 和 SVG。 PhantomJS 可以用于页面自动化、网络监测、网页截屏以及无界面测试等。 安装 以上附有官方安装方式,如果你是 Ubuntu 或 Mac OS X 用户,可以直接用命令来安装 Ubuntu:
1
sudo apt-get install phantomjs
Mac OS X:
3. pyspider
直接利用 pip 安装即可
如果你是 Ubuntu 用户,请提前安装好以下支持类库
1
sudo apt-get install python python -dev python -distribute python -pip libcurl4-openssl-dev libxml2-dev libxslt1-dev python -lxml
测试 如果安装过程没有提示任何错误,那就证明一些 OK。 命令行输入
然后浏览器访问 http://localhost:5000 观察一下效果,如果可以正常出现 PySpider 的页面,那证明一切 OK 在此附图一张,这是我写了几个爬虫之后的界面。 好,接下来我会进一步介绍这个框架的使用。
常见错误
我曾遇到过的一个错误: PySpider HTTP 599: SSL certificate problem 错误的解决方法 ,后来在作者那发了 issue 得到了答案,其他的暂时没什么问题。 不过发现有的小伙伴提了各种各样的问题啊,不过我确实都没遇到过,我再 Win10,Linux Ubuntu,Linux CentOS,Mac OS X 都成功运行。不过确实有些奇怪的问题,跑着跑着崩了,一点就崩了我也就比较纳闷了。 如果大家有问题,可以看看作者项目里面有没有类似的 issue,另外也推荐大家直接到作者的 GitHub 上发 issue。 毕竟,这个框架不是我写的。 在此附上 Issue 地址: PySpider Issue
作者
崔庆才
发表于
2016-02-11
阅读次数:
本文字数:
1.3k
阅读时长 ≈
1 分钟
2022 年最新 Python3 网络爬虫教程
大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。
博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。
最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。
教程请移步:
【2022 版】Python3 网络爬虫学习教程
如下为原文。
综述
爬虫入门之后,我们有两条路可以走。 一个是继续深入学习,以及关于设计模式的一些知识,强化Python相关知识,自己动手造轮子,继续为自己的爬虫增加分布式,多线程等功能扩展。另一条路便是学习一些优秀的框架,先把这些框架用熟,可以确保能够应付一些基本的爬虫任务,也就是所谓的解决温饱问题,然后再深入学习它的源码等知识,进一步强化。 就个人而言,前一种方法其实就是自己动手造轮子,前人其实已经有了一些比较好的框架,可以直接拿来用,但是为了自己能够研究得更加深入和对爬虫有更全面的了解,自己动手去多做。后一种方法就是直接拿来前人已经写好的比较优秀的框架,拿来用好,首先确保可以完成你想要完成的任务,然后自己再深入研究学习。第一种而言,自己探索的多,对爬虫的知识掌握会比较透彻。第二种,拿别人的来用,自己方便了,可是可能就会没有了深入研究框架的心情,还有可能思路被束缚。 不过个人而言,我自己偏向后者。造轮子是不错,但是就算你造轮子,你这不也是在基础类库上造轮子么?能拿来用的就拿来用,学了框架的作用是确保自己可以满足一些爬虫需求,这是最基本的温饱问题。倘若你一直在造轮子,到最后都没造出什么来,别人找你写个爬虫研究了这么长时间了都写不出来,岂不是有点得不偿失?所以,进阶爬虫我还是建议学习一下框架,作为自己的几把武器。至少,我们可以做到了,就像你拿了把枪上战场了,至少,你是可以打击敌人的,比你一直在磨刀好的多吧?
框架概述
博主接触了几个爬虫框架,其中比较好用的是 Scrapy 和PySpider。就个人而言,pyspider上手更简单,操作更加简便,因为它增加了 WEB 界面,写爬虫迅速,集成了phantomjs,可以用来抓取js渲染的页面。Scrapy自定义程度高,比 PySpider更底层一些,适合学习研究,需要学习的相关知识多,不过自己拿来研究分布式和多线程等等是非常合适的。 在这里博主会一一把自己的学习经验写出来与大家分享,希望大家可以喜欢,也希望可以给大家一些帮助。
PySpider
PySpider 是binux 做的一个爬虫架构的开源化实现。主要的功能需求是:
抓取、更新调度多站点的特定的页面
需要对页面进行结构化信息提取
灵活可扩展,稳定可监控
而这也是绝大多数python爬虫的需求 —— 定向抓取,结构化化解析。但是面对结构迥异的各种网站,单一的抓取模式并不一定能满足,灵活的抓取控制是必须的。为了达到这个目的,单纯的配置文件往往不够灵活,于是,通过脚本去控制抓取是最后的选择。 而去重调度,队列,抓取,异常处理,监控等功能作为框架,提供给抓取脚本,并保证灵活性。最后加上web的编辑调试环境,以及web任务监控,即成为了这套框架。 pyspider的设计基础是:以python脚本驱动的抓取环模型爬虫
通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性
通过web化的脚本编写、调试环境。web展现调度状态
抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展
pyspider的架构主要分为 scheduler(调度器), fetcher(抓取器), processor(脚本执行):
各个组件间使用消息队列连接,除了scheduler是单点的,fetcher 和 processor 都是可以多实例分布式部署的。 scheduler 负责整体的调度控制
任务由 scheduler 发起调度,fetcher 抓取网页内容, processor 执行预先编写的python脚本,输出结果或产生新的提链任务(发往 scheduler),形成闭环。
每个脚本可以灵活使用各种python库对页面进行解析,使用框架API控制下一步抓取动作,通过设置回调控制解析动作。
Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。 其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试
Scrapy 使用了 Twisted 异步网络库来处理网络通讯。整体架构大致如下
Scrapy主要包括了以下组件:
引擎(Scrapy): 用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler): 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader): 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders): 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline): 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
爬虫中间件(Spider Middlewares): 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
调度中间件(Scheduler Middewares): 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
首先,引擎从调度器中取出一个链接(URL)用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器,下载器把资源下载下来,并封装成应答包(Response)
然后,爬虫解析Response
若是解析出实体(Item),则交给实体管道进行进一步的处理。
若是解析出的是链接(URL),则把URL交给Scheduler等待抓取
结语
对这两个框架进行基本的介绍之后,接下来我会介绍这两个框架的安装以及框架的使用方法,希望对大家有帮助。
作者
崔庆才
发表于
2016-02-11
阅读次数:
本文字数:
3k
阅读时长 ≈
3 分钟
最近博客的多说评论总是抽风,先来吐槽一下。 刚在整理评论的时候吓我一跳,之前我的好多评论都没了,是的,是只有我自己的评论没有了,而其它人的评论还有。摸不着头脑的我打开多说后台管理,哦天,我之前所有的评论和回复全部自动转为垃圾评论了,八百多条啊,废了一会功夫好不容易批量还原了。结果刷新页面一看,咦,还是没有,重新刷一下后台,竟然再次把我的评论设成垃圾评论了,后来又退出重新绑定了其它平台的账号,总算还原回来了。 然后我就继续开始回复大家的评论呀,结果要发布评论的时候,点一下发布,按钮就卡在正在发布这里不动了。打开浏览器看一下 Ajax 出了什么问题,结果出现了一个 create_json 报了个 500 服务器错误,查看页面信息显示参数配置不正确还是怎么了,没错,是显示我的站点多说评论配置不正确。那,其它人怎么评论上来的? 另外,之前,多说崩溃了已经不知道多少次了。 真是不爽的多说。 另外,多说还有一个非常令人发指的行为,会自动同步用户文章,收集用户信息,同步我们社交账号,即使是修改用户信息也要进行备份,这尼玛发展一定程度,多说还真有可能有利用这些用户信息谋利,甚至可能利用各个用户的文章做一个个性化阅读推荐也说不定。当然最开始的时候我只是觉得多说比较火,当时用上了也感觉比较方便,加上最初网站没几个东西,心想同步就同步呗,然后就一直用着了。现在再想想,也是可怕。 果断!弃用!弃用!弃用! 搜索了网上比较热门的评论插件,发现了畅言。使用了 WordPress 插件。 于是用上了,不过还是有一些令我感到不是太友好的地方,简单在此提一下。 嗯,首先我是比较追求美感的,界面问题。首先我会关注有没有个性化主题定制这个功能,畅言还是有的,支持 CSS 自定义。不过这个功能比较蛋疼,如果你不选择已经提供的主题,而是选择自定义 CSS 样式的话,你需要把所有样式重写一遍,它缺省继承了默认主题。比如我如果想在浅色主题红色风格基础上修改几个样式的话,这是办不到的,除非重写所有的红色风格样式,这就鸡肋了。建议可以选择继承某个默认主题的功能,然后自定义的 CSS 是在这个基础上设置的功能。而且我看 WAP 版本并没有自定义 CSS,非常建议增加这个功能。最后我还是选择了红色主题,不过自定义样式就写在了站点全局样式表里面了,以此解决。 其次,同步本地评论功能,由于换了插件,所以评论都留在了本地了。同步完成之后,我并不能在畅言后台管理看到我刚才同步的评论,但是新发的评论是可以看到的,页面也是可以正常显示的。只是不能在线管理早先的原始评论了。 另外,头像问题,其实我个人非常不能忍受一个账号没有头像的行为,简直是大逆不道。畅言有个 QQ 快速登录的功能,然而,登录之后竟然不能获取我的 QQ 头像!不知道是不是我这边的问题,如果大家正常希望可以反馈我一下。另外,QQ 登录之后怎么会给我取了一个奇怪的用户名,叫什么 cmcccc,有点醉。而微博的快速登录的昵称和头像都是正常的,然而每次评论的时候都会默认勾选那个同步到微博的按钮,这个可以默认取消么? 还有,希望可以增加更多的平台的支持,比如微信、GitHub、脸书、推特等平台啦。 最后,有没有发表文章自动分享到各个平台的功能?我暂时没有发现。这点多说还是做得比较好的。 以上。 嗯,总之换上畅言之后用起来还是比较开心的,嘿嘿主要是改好了样式,看起来一阵舒爽。 昂,没错,我就是颜控! 有时候,我会因为一个样式不合我意而执着地去修改,即使要花费几个小时。 有时候,我会因为一个应用的图标(没错,就是说的图标)太丑了而卸载掉,即使是它的功能再怎么好。 有时候,我会因为一个屏幕膜有一点点损伤而去重新买一个新的。额,其实是因为今天给电脑贴膜折角了,我又花了几十块重新买了一个新的。 有时候,我就是一个强迫症,在写上面三句话的时候,第一句原本是在第二行的,然而因为看起来长度参差不齐我就把它移动到了最上面。嗯,这第四句话要写得更长才行。 好啦,貌似跑题了,时候不早啦,大家晚安。 嗯,换上调教好的美美的畅言还是很开心的,文章前后呼应,拜~ 美美哒~
作者
崔庆才
发表于
2016-02-03
阅读次数:
本文字数:
1.7k
阅读时长 ≈
2 分钟
Hello,时隔一个多月,终于再次回到博客啦,首先跟大家说声抱歉,许多评论没有及时回复感到非常抱歉,希望我现在给大家的回复为时不晚。 距离上次在博客上写日记过去了几个月了吧。那时的我刚刚结束大学三年级。而现在,大四上半学期已经过半啦。这半年的时间可以说忙也可以说不忙。不忙是说这半年以来的课程比较轻松,只有四门选修课,学业负担比较轻。忙是说半年以来各种错综复杂的事情,许多事情需要好好安排一下时间才可以好好把握各个“进程”的合理分配。 那么就从我上次日记开始总结一下吧。 当时更新是去年七月十二日,刚大三放假不久。那个暑假前期过得可谓是心惊胆战呀,当时为了保研北京航空航天大学一直在紧张地复习准备。包括复习备考三年的专业课,还有准备C语言的上机考试。七月二十八二十九那两天机考和面试,不负众望,我顺利拿到了北京航空航天大学计算机科学与技术学院的Offer,心里的石头也落了地,当时看到通过面试的消息时真的激动得说不出口,这也算是我人生中为数不多的重大十字路口做出的一个选择吧。 面试结束之后,我便留在了导师那边做项目,一些大数据处理和爬虫的项目。不过当时的项目个人感觉比较简单,所以就在整个八月份找了一份实习,PHP研发工程师的岗位,之前准备保研也一直没有找工作,这也算是我找的第一份工作吧,不求赚神马,只求充实一下我的假期,也学习一些新的东西。 在这里顺便安利一下,公司名称是佳信德润,主打品牌是灵析 ,北京三环。在那边工作真的感觉特别舒心,虽然是创业公司,但是CEO玛丽老板(女哦)还有负责技术的柱子哥真的超级热情,为人特别好,谈吐之间就有一种亲切的感觉,那边的小伙伴十几个,相处地也十分融洽。在那边工作你不会有一种被忽视和指使的感觉,你可以随意向老板们发表自己的看法,甚至你可以与他们探讨代码问题,甚至公司某处的装饰啦,哪里的餐馆不错啦等等。有次我眼睛发炎,玛丽老板和柱子哥还特别关心我,亲自送到我医院,在这里再次说声谢谢,不知道你们是不是可以看到。我还记得小伙伴们一起拍公司写真,真的超赞。当然最主要的还是一起商讨项目进度,代码问题,接触到我之前没有接触过的新知识。在这里你可以负责许多事情,最大限度地发挥你的光和热。不是我打广告,如果有小伙伴们有意向做PHP,非常推荐你们过去,可以去他们官网 发应聘哦。相信我,不会坑你的。 实习时间真的过得很快,转眼一个月过去了,九月份来了,大四开学啦。 因为需要开学以后办一些保研的相关手续,另外重要的事当然是陪妹纸啦嘿嘿,你懂得。 开学之后就开始忙一些项目了。大四的课程不紧,趁着这大四的时光,多学点东西,以后这样的日子真不多了,好好珍惜。 所以大四上学期的基调是,专注陪妹子,业余撸代码,顺便做做外包。请叫我全职男朋友,兼职程序猿。 这半年以来做的项目不算多。首先,学习了Laravel框架,嗯,保证你学完之后就不想用其他的框架了。最开始是帮我的叔叔做一个拍电影的网站,规模还比较大,一直在完善中。另外在暑假实习的过程中发现了公司自己写了一款CMS框架,自己觉得还有许多功能可以添加,一些架构可以继续完善,所以决定自己写一个CMS,不仅算是练手,也算是造轮子吧。毕竟你写好了就是你自己的,用起来都是那么自然,假如来了个外包神马的,轻松应对分分钟的事,神马深度定制都是浮云。当然还有帮爸爸妈妈写的一个微信平台,在微信订餐售卖东西,嗯,这叫做自己动手实现O2O。另外就是一些零零散散的小外包,没事可以赚点外快养活一下自己。 最近刚刚入手了自己人生中的第一台MAC,当然还是要感谢父亲大人的支持,自己的手头还是比较紧的,你懂得。 嗯摒弃Windows,感觉撸代码的快感不止提升了几管气,也希望它可以陪我度过接下来许多年的学习时光。在这里也安利一下一个小店,微博上的小闷小闷 ,水果从她这里买,价格真的是十分公道啊,正品无误了,入手十几天了,感觉逼格提升了,心情也舒畅了,多年的Windows卡顿症也治好了~ 嗯,接下来的时间,爸爸妈妈年前工作辛苦,接下来几天我要去帮他们分担一下工作啦。 最后,恭祝大家小年快乐,工作顺心,别跟钱过不去,开心最重要~ 欢欢喜喜过大年啦~
作者
崔庆才
发表于
2016-02-01
阅读次数:
本文字数:
1.7k
阅读时长 ≈
2 分钟
问题来源
在写代码的时候遇到了如下问题: 使用了 bootstrap 框架来编写了一个页面,其中 input 元素两侧留有空白。然而用 JS 动态添加的同样的元素却不会出现这种情况。具体截图表现如下: 我们可以发现,第一行和而三行的代码是完全一样的,可是呈现的结果是截然不同的。 在线测试样例 大家是不是觉得很奇怪?没错,我也是。中间的那个缝隙是哪里来的呢?
刨根问底
在这里感谢 wonder 同学的大力相助,才得以找到问题的所在。 出现此问题的原因在于:
html 中的内联元素在书写代码时,如果两元素代码之间有换行,浏览器会将其解释为空格。而这个空格是会被当作一个空白节点(nodeType 等于 3 的节点,就是文字节点)
所以,因为代码中我使元素呈现 inline 的属性,然后两个代码之间具有换行,所以二者之间出现了空白。正常情况下,二者之间是不应该出现空白的。然而用 jQuery 生成元素的时候,因为是用的 + 连接符,所以换行符被忽略了。也就是代码是连接起来的,所以二者之间便不会出现空白。 解决方法: 1.将原代码中的 input 写到一行。如下:
1 2 3
<div class ="form-group"> <input class ="form-control inline span3" name ="education[school][]" type ="text" value ="123"><input class ="form-control inline span3" name ="education[date][]" type ="text" value ="456"> </div>
2.或者在 JS 代码中加入换行符。如下:
1 2 3 4 5 6
$('butto n').on('clic k', function() { $('<div class ="form-group" >'+ '<input class ="form-control inline span3" name="education[school][]" type ="text" value="" >\n'+ '<input class ="form-control inline span3" name="education[date][]" type ="text" value="" >'+ '</div>').appendTo($(".content" )); });
以上两种方式,解决方法都比较简单实用。其他的改变 padding 或者 margin 的方法就不推荐了。 好了,那么明白了之后,我们肯定要可以举一反三的,来探究一下如果是块级元素会不会这样呢?
举一反三
好的,让我们试一下块级元素如果设置为 inline 的话会不会也这样。 把 input 标签改成 div,然后给它加上 display: inline 属性,加一下背景颜色区分,观察一下效果。 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
<!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="utf-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <link rel ="stylesheet" href ="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" > </head > <body > <h1 > 点击按钮添加元素</h1 > <div class ="content" > <div class ="form-group" > <div class ="item" > hello1</div > <div class ="item" > hello2</div > </div > </div > <button class ="btn btn-primary" > 添加</button > <script src ="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js" > </script > <script src ="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js" > </script > <style > .item { display: inline; background : #555 ; } </style > </body > </html >
观察一下效果 嗯,果然,它的间距还是出现了。 那么改成 display: inline-block 呢?
1 2 3 4 5
.item { display : inline-block; width : 200px ; background : #555 ; }
可见间距还是有的。 我们把 div 的换行去掉,看一下。 Perfect!它已经消失不见了! 以上,在 chrome,edge,ie11 测试通过。
综述
通过以上研究我们可以得出如下结论: 内联元素,代码中带有换行,会出现空白间距。块级元素,设置了内联样式,且代码中带有换行,也会出现空白间距。 解决方法是删除代码中的换行即可。 以上是在写程序过程中发现的现象,希望能对大家有帮助!
作者
崔庆才
发表于
2016-01-02
阅读次数:
本文字数:
2.4k
阅读时长 ≈
2 分钟
作者
崔庆才
发表于
2015-12-24
阅读次数:
本文字数:
3.8k
阅读时长 ≈
3 分钟
作者
崔庆才
发表于
2015-12-15
阅读次数:
本文字数:
2.1k
阅读时长 ≈
2 分钟
作者
崔庆才
发表于
2015-12-13
阅读次数:
本文字数:
1.8k
阅读时长 ≈
2 分钟
最近在研究信息安全,需要用到 OpenSSL 库,我用到的开发 IDE 是 VS2012,所以,在这里也记录一下我配置 VS2012 的 OpenSSL 库的过程。
下载 OpenSSL 库
OpenSSL 库大家可以自行下载源码然后用 ruby 进行编译,另外我们也可以选择直接下载编译好的类库。 这里我们利用的后者,在此提供一个下载链接。 OpenSSL-Win32 下载完成之后解压,比如我的放到了 D 盘。
新建项目
首先,我们找一段测试代码,在此利用的是 AES 算法的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include <stdio.h> #include <openssl/aes.h> #include <stdlib.h> #include <string.h> int main () { unsigned char key[16 +1 ] = "my-key-i-choosed" ; char pt1[16 +5 +1 ] = "0123456789abcdef12345" ; char ct[16 +5 +1 ]; char pt2[16 +5 +1 ]; AES_KEY k; unsigned char iv1[16 +1 ] = {"1023456789abcdef" }; unsigned char iv2[16 +1 ] = {"1023456789abcdef" }; { AES_set_encrypt_key(key, 16 *8 , &k); AES_encrypt((unsigned char *)pt1, (unsigned char *)ct, &k); AES_set_decrypt_key(key, 16 *8 , &k); AES_decrypt((unsigned char *)ct, (unsigned char *)pt2, &k); if (memcmp (pt1, pt2, 16 )==0 ) puts ("AES block ok" ); else puts ("AES block err" ); } { int num=0 ; AES_set_encrypt_key(key, 16 *8 , &k); AES_cfb128_encrypt((unsigned char *)pt1, (unsigned char *)ct, 16 +5 , &k, (unsigned char *)iv1, &num, AES_ENCRYPT); num=0 ; AES_set_encrypt_key(key, 16 *8 , &k); AES_cfb128_encrypt((unsigned char *)ct, (unsigned char *)pt2, 16 +5 , &k, (unsigned char *)iv2, &num, AES_DECRYPT); if (memcmp (pt1, pt2, 16 +5 )==0 ) puts ("AES CFB mode ok" ); else puts ("AES CFB mode err" ); } system("pause" ); return 0 ; }
接下来新建一个项目,win32 控制台程序,空项目,完成。 新建源文件,我取名叫做 aes.cpp,将代码复制进去,可以看到代码最初是在报错的。 好,接下来我们进行环境配置。
环境配置
右键项目名称,弹出一个菜单,选择属性。 在 VC++目录选项卡中,添加包含目录和库目录。 在这里,我的包含目录就是刚才解压的 OpenSSL 目录的 include 目录,库目录则是 lib 目录。 注意:分号要是英文分号,英文分号! 接下来选择连接器选项卡,输入 libeay.lib 和 ssleay32.lib 两个附加依赖项。 现在右击项目,重新生成。 我们可以看到,程序可以正常生成 exe 了。 但是直接运行的话会报错,是因为缺少 dll 文件。 之后,将项目中的 libeay32.dll 和 ssleay32.dll 文件放入项目的 debug 目录即可。 最后项目的 debug 目录如下 重新运行 exe 程序,发现已经正常运行。 至此,VS 配置 OpenSSL 环境的过程已经全部完成。 其他项目类似,大家可以试着配一下。 如有问题,欢迎留言交流~
作者
崔庆才
发表于
2015-12-07
阅读次数:
本文字数:
1.9k
阅读时长 ≈
2 分钟
最近服务器要过期了,需要进行迁移,新服务器如果上面配置的是 Apache 服务器该怎么办呢? 系统:Ubuntu 14.04
环境配置
首先新主机上配置好 apache 环境,这个就不多说了,直接执行下面的命令即可。
1 2 3 4 5 6 7
sudo apt-get install apache2 sudo apt-get install php5 php5-cgi php5-mysql php5-curl php5-gd php5-idn php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-mhash php5-ming php5-pspell php5-recode php5-snmp php5-tidy php5-xmlrpc php5-sqlite php5-xsl sudo apt-get install mysql-server mysql-client sudo apt-get install libapache2-mod-php5 sudo apt-get install libapache2-mod-auth-mysql sudo apt-get install phpmyadmin sudo ln -s /usr/share/phpmyadmin/ /var/www/html/phpmyadmin
通过以上配置,新主机便可以实现 lamp 环境的配置了。
代码迁移
首先旧主主机上打包一下代码,比如一个文件夹名字叫 wonder
1
tar -zcvf wonder .tar .gz wonder
然后,打包完成之后,便会出现一个名字叫做 wonder.tar.gz 的文件 可以利用 wget 方式直接下载。
下载完成之后,直接解压即可。 这样代码就取到了。
数据库迁移
数据库迁移无非就是在 phpmyadmin 之间导入导出,这个很简单。 但是重要的一点是,需要把 wp-options 表中的两个 URL 配置改掉,比如原来是一个域名链接,现在需要改为 IP+文件名。 否则,浏览器会提示重定向循环的问题。
服务器配置
首先我们需要将域名解析到这个主机。 配置示例域名:wonderlee.me 然后配置一下,vhost,在 apache 下配置是这样的 首先在 /etc/apache2/apache2.conf 中加入如下两行
1 2
# Include all the user configurations: Include httpd.conf
然后我们需要在 httpd.conf 配置一下域名解析 新建一个 /etc/apach2/httpd.conf,加入如下内容
1 2 3 4 5 6 7 8 9 10 11 12
ServerName 115.28 .24 .44 :80 <VirtualHost 115.28 .24 .44 :80 > DocumentRoot /var/www/html ServerName 115.28 .24 .44 </VirtualHost> <VirtualHost 115.28 .24 .44 :80 > DocumentRoot /var/www/html/wonder ServerName wonderlee.me ServerAlias wonderlee.me </VirtualHost>
然后执行服务器重启操作。
1
sudo service apache2 restart
好,这样的话我们的域名配置解析就好了。 输入 wonderlee.me 即可解析到 wonder 文件夹啦。 可以输入你的域名试试看,已经可以了吧。 然后我们需要开启 rewrite 模块。 输入命令
然后修改 /etc/apache2/apache2.conf 文件
1 2 3 4 5
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory>
改为
1 2 3 4 5
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>
即可,结束之后重启一下服务器。
1
sudo service apache2 restart
在项目目录下新建一个文件 .htaccess,来支持重写
1 2 3 4 5 6 7 8 9 10 11
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase /wonder/RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule . /wonder/index.php [L] </IfModule>
好,这样,重写过程就完成啦。
网站配置
最后,需要将网站的配置修改一下,比如固定链接 比如网站的基地址 至此,网站配置工作全部完成,欢乐地上网体验一下吧 如有问题,欢迎留言。
作者
崔庆才
发表于
2015-11-28
阅读次数:
本文字数:
2.2k
阅读时长 ≈
2 分钟
之前一直都用 Apache 服务器,由于网站访问量比较大,另外加上旧服务器快到期了,准备迁移到新的服务器上,所以决定采用 Nginx 服务器。 迁移过程比较心酸,之前一直用 apache,对 nginx 服务器配置不熟悉,踩了很多坑。下面说一下我的网站从旧主机(配有 apache 服务器)迁移到新主机(配有 nginx 服务器)的过程。
代码迁移
这个过程其实也是比较心酸的,查看了一下目录结构占用空间已经足足快 1 个 G 了,可想而知里面占用的大部分空间是上传的图片素材。 不过要是迁移全部图片的话工程量实在是巨大。不过,好消息是我从开始就使用了七牛 CDN 加速,所以,上传的图片会自动存放到七牛,只不过也在主机本地留了备份而已,所以,我可以安心地删掉它们了。 那么对代码进行瘦身之后,这里就有两种方法来迁移了: 1.可以用 git 上传到 github,然后用另一台主机把代码拉下来即可,在此不再赘述。 2.打包上传,然后直接在另一台主机上下载下来,由于我的两台主机在同一局域网内,所以我直接采用了这种方式,传输速度快。
打包
由于代码中含有 .git 目录,所以这部分我们不需要打包,那么压缩时我们就需要排除这个文件夹。 文件夹名叫 cqc,那么我们就打包一下,排除.git 目录,使用如下命令
1
tar -zcvf cqc.tar .gz --exclude=cqc/.git cqc
运行结束后会出现 cqc.tar.gz 文件,这就是目录压缩包。 然后我们只需要在另一台主机上输入
即可完成下载,速度可是嗖嗖的 然后解压即可,代码便完成了迁移。
数据库迁移
数据库用二者的 phpmyadmin 导出和上传即可。我导出 .sql 文件,大小为 9M,而 phpMyAdmin 的上传限制大小是 2M,怎么办?其实我们可以压缩 .sql 文件为 zip 格式,压缩之后就有了 1.4M 了,分分钟完成上传。要知道 phpMyAdmin 可是支持 .sql.zip 文件的。 接下来是一个比较重要的部分,那就是配置一下站点信息。直接修改数据库的两个 URL。 分别是 siteurl 和 home,一定要修改为 http://xxx.xxx.xxx.xxx/cqc 的形式,也就是把原来的域名改成 IP 加目录的形式,要不然网站是无法访问的,会出现多重循环定向的提示。 好,其他的没什么问题,连接数据库错误的话就修改一下目录的 wp-config.php 文件吧,连接数据库的信息修改正确就好了。
配置 vhosts
和 apache 一样,我们多个域名肯定要可以解析到不同的目录吧,nginx 当然也是支持的。 接下来我们需要把新域名解析到 cqc 目录,在 nginx 下怎么做呢?其实还是比较简单的。 在 /etc/nginx 目录下可以新建一个 vhosts 文件夹。在这里我们要解析 cqc 目录,那么我就新建一个 cqc.conf 文件。 现在例如我要把 blog.cuiqingcai.com 解析到 cqc 文件夹,配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
server { listen 80 ; server_name cuiqingcai.com blog.cuiqingcai.com; index index.html index.htm index.php; root /var/www/cqc; location / { if (!-e $request_filename ) { rewrite ^([_0-9a-zA-Z-]+)?(/wp-.*) $2 last ; rewrite ^([_0-9a-zA-Z-]+)?(/.*\.php)$ $2 last ; rewrite ^ /index.php last ; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000 ; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/cqc$fastcgi_script_name ; include fastcgi_params; } }
其中
1 2 3 4 5 6 7 8
location / { if (!-e $request_filename ) { rewrite ^([_0-9a-zA-Z-]+)?(/wp-.*) $2 last ; rewrite ^([_0-9a-zA-Z-]+)?(/.*\.php)$ $2 last ; rewrite ^ /index.php last ; } }
这一部分是伪静态重写,因为我的博客用的是 wordpress,所以伪静态重写是这样的。当然还有其他的重写方式可以尝试。 之后在 /etc/nginx/nginx.conf 中的 http{} 中添加一行
1
include /etc/ nginx/vhosts/ cqc.conf;
则代表引用了这个文件。 注意,还要把 域名设置一下,添加一条 A 记录到主机上。 好了,一切大功告成了。
后记
迁移和配置的过程坑实在是太多了,列列吧,警醒世人呐。 (1)代码迁移过程上传 git,整个项目差不多 1 个 G,由于数据量太大,导致内存不够无法正常上传。后来删除了图片,发现项目还是很大,结果发现是 .git 目录已经占用了上百兆,后来打包排除这个目录迁移的。 (2)数据库迁移的时候由于 phpMyAdmin 上传大小限制,修改了一番上传大小结果发现没生效,还倒腾了一下 php-fpm,后来发现可以直接上传压缩包,那就分分钟完成了。 (3)配置完之后发现网站首页正常访问了,可是其他页面全部出现了 404 错误,后来配置了一番伪静态解析发现配置代码直接写在了 localhost server 里面,后来发现可以直接新写一个 server,然后配置域名 servername,然后配置伪静态重写才成功。 总之,坎坷是多,但是,自己慢慢摸索出来,也是一种不错的体验。 当你成功之后,会觉得世界又是那么美好。
作者
崔庆才
发表于
2015-11-13
阅读次数:
本文字数:
2.5k
阅读时长 ≈
2 分钟
之前是在eclipse上写的,后面换成了android sudio。 2048游戏的UI整体可以采用线性布局,即LinearLayout,其中嵌套一个线性布局和一个GridLayout,内嵌的线性布局填充文本框,以显示分数,GridLayout中填充4x4的继承自FrameLayout的card类作为主要的游戏界面。由于大部分操作都在GridLayout中进行,可以自定义一个继承自GridLayout的类GameView,类中定义判定上下左右滑动的方法和每次滑动后自动添加一个随机数字的方法以及每次滑动后判断游戏是否可以继续进行的方法。 主布局activity_main.xml代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administractor.game2048.MainActivity" android:orientation="vertical" tools:ignore="MergeRootFrame" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Your Score:" /> <TextView android:id="@+id/tvScore" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <com.example .administractor .game2048 .GameView android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:id="@+id/GameView" > </com.example .administractor .game2048 .GameView> </LinearLayout>
GameView.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
package com.example.administrator.game2048; import java.util.ArrayList; import java.util.List; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Point; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.GridLayout; public class GameView extends GridLayout { public GameView(Context context , AttributeSet attrs , int defStyle ) { super(context, attrs, defStyle); InitGameView() ; } public GameView(Context context , AttributeSet attrs ) { super(context, attrs); InitGameView() ; } public GameView(Context context ) { super(context); InitGameView() ; } private void InitGameView() { setColumnCount(4) ; setBackgroundColor(0xffeee4da) ; setOnTouchListener(new OnTouchListener() { private float startx,starty,offsetx,offsety; @Override public boolean onTouch(View v , MotionEvent event ) { switch(event.getAction() ){ case MotionEvent.ACTION_DOWN: startx=event.getX() ; starty=event.getY() ; break; case MotionEvent.ACTION_UP: offsetx=event.getX() -startx; offsety=event.getY() -starty; if (Math . abs(offsetx)>Math . abs(offsety)){ if (offsetx<-5 ){ swipeLeft() ; }else if (offsetx>5 ){ swipeRight() ; } }else { if (offsety<-5 ){ swipeUp() ; }else if (offsetx>3 ){ swipeDown() ; } } break; } return true ; } }); } @Override protected void onSizeChanged(int w , int h , int oldw , int oldh ) { super.onSizeChanged(w , h , oldw , oldh ) ; int cardWidth=(Math . min(h, w))/4 ; addCards(cardWidth ,cardWidth ) ; startGame() ; } public void addCards(int cardwidth ,int cardheight ) { Card c; for (int y = 0 ; y < 4 ; y++) { for (int x = 0 ; x < 4 ; x++) { c=new Card(getContext () ); c.setNum(0) ; addView(c , cardwidth , cardheight ) ; cardmap[x ] [y ] =c; } } } private void startGame() { MainActivity . getMainActivity() .clearScore() ; for (int y = 0 ; y < 4 ; y++) { for (int x = 0 ; x < 4 ; x++) { cardmap[x ] [y ] .setNum(0) ; } } addRandomNum() ; addRandomNum() ; } private void addRandomNum() { emptyPoints.clear() ; for (int y = 0 ; y < 4 ; y++) { for (int x = 0 ; x < 4 ; x++) { if (cardmap[x ] [y ] .getNum() <=0 ){ emptyPoints.add(new Point(x ,y ) ); } } } Point p=emptyPoints.remove((int )(Math . random() *emptyPoints.size() )); cardmap[p .x ] [p .y ] .setNum(Math.random () >0.1 ?2 :4 ); } private void swipeLeft() { boolean merge = false ; for (int y = 0 ; y < 4 ; y++) { for (int x = 0 ; x < 4 ; x++) { for (int x1 = x+1 ; x1 <4 ; x1++) { if (cardmap[x1 ] [y ] .getNum() >0 ){ if (cardmap[x ] [y ] .getNum() <=0 ){ cardmap[x ] [y ] .setNum(cardmap [x1 ][y ].getNum () ); cardmap[x1 ] [y ] .setNum(0) ; merge=true ; x--; }else if (cardmap[x ] [y ] .equal(cardmap[x1 ] [y ] )){ cardmap[x ] [y ] .setNum(cardmap [x ][y ].getNum () *2 ); cardmap[x1 ] [y ] .setNum(0) ; MainActivity . getMainActivity() .addScore(cardmap [x ][y ].getNum () ); merge=true ; } break; } } } } if (merge){ addRandomNum() ; checkComplete() ; } } private void swipeDown() { boolean merge = false ; for (int x = 0 ; x < 4 ; x++) { for (int y = 3 ; y >=0 ; y--) { for (int y1 = y-1 ; y1 >=0 ; y1--) { if (cardmap[x ] [y1 ] .getNum() >0 ) { if (cardmap[x ] [y ] .getNum() <=0 ) { cardmap[x ] [y ] .setNum(cardmap [x ][y1 ].getNum () ); cardmap[x ] [y1 ] .setNum(0) ; y++; merge = true ; }else if (cardmap[x ] [y ] .equal(cardmap[x ] [y1 ] )) { cardmap[x ] [y ] .setNum(cardmap [x ][y ].getNum () *2 ); cardmap[x ] [y1 ] .setNum(0) ; MainActivity . getMainActivity() .addScore(cardmap [x ][y ].getNum () ); merge = true ; } break; } } } } if (merge) { addRandomNum() ; checkComplete() ; } } private void swipeUp() { boolean merge = false ; for (int x = 0 ; x < 4 ; x++) { for (int y = 0 ; y < 4 ; y++) { for (int y1 = y+1 ; y1 < 4 ; y1++) { if (cardmap[x ] [y1 ] .getNum() >0 ) { if (cardmap[x ] [y ] .getNum() <=0 ) { cardmap[x ] [y ] .setNum(cardmap [x ][y1 ].getNum () ); cardmap[x ] [y1 ] .setNum(0) ; y--; merge = true ; }else if (cardmap[x ] [y ] .equal(cardmap[x ] [y1 ] )) { cardmap[x ] [y ] .setNum(cardmap [x ][y ].getNum () *2 ); cardmap[x ] [y1 ] .setNum(0) ; MainActivity . getMainActivity() .addScore(cardmap [x ][y ].getNum () ); merge = true ; } break; } } } } if (merge) { addRandomNum() ; checkComplete() ; } } private void swipeRight() { boolean merge = false ; for (int y = 0 ; y < 4 ; y++) { for (int x = 3 ; x >=0 ; x--) { for (int x1 = x-1 ; x1 >=0 ; x1--) { if (cardmap[x1 ] [y ] .getNum() >0 ){ if (cardmap[x ] [y ] .getNum() <=0 ){ cardmap[x ] [y ] .setNum(cardmap [x1 ][y ].getNum () ); cardmap[x1 ] [y ] .setNum(0) ; x++; merge=true ; }else if (cardmap[x ] [y ] .equal(cardmap[x1 ] [y ] )){ cardmap[x ] [y ] .setNum(cardmap [x ][y ].getNum () *2 ); cardmap[x1 ] [y ] .setNum(0) ; MainActivity . getMainActivity() .addScore(cardmap [x ][y ].getNum () ); merge=true ; } break; } } } } if (merge){ addRandomNum() ; checkComplete() ; } } public void checkComplete() { boolean complete=true ; ALL: for (int y = 0 ; y <4 ; y++) { for (int x = 0 ; x <4 ; x++) { if (cardmap[x ] [y ] .getNum() ==0 || x>0&& cardmap[x][y].equal(cardmap[x-1][y])|| x<3&& cardmap[x][y].equal(cardmap[x+1][y])|| y>0&& cardmap[x][y].equal(cardmap[x][y-1])|| y<3&& cardmap[x][y].equal(cardmap[x][y+1])){ complete=false ; break ALL ; } } } / / 游戏结束弹出alert提示窗口 if (complete){ new AlertDialog .Builder(getContext () ).setTitle("大林哥温馨提示" ) .setMessage("游戏结束" ) .setPositiveButton("重来" ,new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0 , int arg1 ) { startGame() ; } }).show(); } } private Card [][] cardmap=new Card [4][4]; private List <Point > emptyPoints =new ArrayList <Point >(); }
主类MainActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
package com.example.administrator.game2048;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends Activity { public MainActivity () { mainActivity=this ; } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvscore = (TextView) findViewById(R.id.tvScore); } public void clearScore () { score=0 ; showScore(); } public void showScore () { tvscore.setText(score+"" ); } public void addScore (int s) { score+=s; showScore(); } private TextView tvscore; private int score=0 ; public static MainActivity mainActivity=null ; public static MainActivity getMainActivity () { return mainActivity; } }
Card.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
package com.example.administrator.game2048; import android.content.Context;import android.view.Gravity;import android.view.View;import android.widget.FrameLayout;import android.widget.TextView;public class Card extends FrameLayout { public Card(Context context) { super (context); LayoutParams lp = null ; View background = new View(getContext()); lp = new LayoutParams(-1 , -1 ); lp.setMargins(10 , 10 , 0 , 0 ); background.setBackgroundColor(0x33ffffff ); addView(background, lp); label = new TextView(getContext()); label.setTextSize(28 ); label.setGravity(Gravity.CENTER); lp = new LayoutParams(-1 , -1 ); lp.setMargins(10 , 10 , 0 , 0 ); addView(label, lp); setNum(0 ); } private int n=0 ; public int getNum(){ return n; } public void setNum(int n){ this .n=n; if (n<=0 ){ label.setText("" ); }else { label.setText(n+"" ); } switch (n) { case 0 : label.setBackgroundColor(0x00000000 ); break ; case 2 : label.setBackgroundColor(0xffeee4da ); break ; case 4 : label.setBackgroundColor(0xffede0c8 ); break ; case 8 : label.setBackgroundColor(0xfff2b179 ); break ; case 16 : label.setBackgroundColor(0xfff59563 ); break ; case 32 : label.setBackgroundColor(0xfff67c5f ); break ; case 64 : label.setBackgroundColor(0xfff65e3b ); break ; case 128 : label.setBackgroundColor(0xffedcf72 ); break ; case 256 : label.setBackgroundColor(0xffedcc61 ); break ; case 512 : label.setBackgroundColor(0xffedc850 ); break ; case 1024 : label.setBackgroundColor(0xffedc53f ); break ; case 2048 : label.setBackgroundColor(0xffedc22e ); break ; default : label.setBackgroundColor(0xff3c3a32 ); break ; } } public bool ean equal(Card o){ return getNum()==o.getNum(); } private TextView label; }
作者
大龄锅
发表于
2015-11-07
阅读次数:
本文字数:
10k
阅读时长 ≈
9 分钟
openvpn 原理
VPN 直译就是虚拟专用通道,是提供给企业之间或者个人与公司之间安全数据传输的隧道,OpenVPN 无疑是 Linux 下开源 VPN 的先锋,提供了良好的性能和友好的用户 GUI。 它大量使用了 OpenSSL 加密库中的 SSLv3/TLSv1 协议函数库。 目前 OpenVPN 能在 Solaris、Linux、OpenBSD、FreeBSD、NetBSD、Mac OS X 与 Microsoft Windows 以及 Android 和 iOS 上运行,并包含了许多安全性的功能。它并不是一个基于 Web 的 VPN 软件,也不与 IPsec 及其他 VPN 软件包兼容。 openvpn 通过使用公开密钥(非对称密钥,加密解密使用不同的 key,一个称为 Publice key,另外一个是 Private key)对数据进行加密的。这种方式称为 TLS 加密。 openvpn 使用 TLS 加密的工作过程是,首先 VPN Sevrver 端和 VPN Client 端要有相同的 CA 证书,双方通过交换证书验证双方的合法性,用于决定是否建立 VPN 连接。 然后使用对方的 CA 证书,把自己目前使用的数据加密方法加密后发送给对方,由于使用的是对方 CA 证书加密,所以只有对方 CA 证书对应的 Private key 才能解密该数据,这样就保证了此密钥的安全性,并且此密钥是定期改变的,对于窃听者来说,可能还没有破解出此密钥,VPN 通信双方可能就已经更换密钥了。 扩展阅读: openvpn
安装 openvpn
首先,你需要有一台长期运行的服务器,大家可以用自己的闲置的电脑或者买一台阿里云啦。 我的服务器是 Ubuntu 14.04,下面就演示一下我的配置过程。 安装
1
sudo apt-get -y install openvpn libssl-dev openssl
查看下版本并记录下来
在这里我们的版本是 2.3.2
安装 easy-rsa
easy-rsa 是用来制作 openvpn 相关证书的,使用如下命令安装
1
sudo apt-get -y install easy-rsa
好,一切准备就绪后,我们就开始制作证书啦,我们需要制作的有三个证书 CA 证书、Server 端证书、Client 端证书。行动起来。
制作 CA 证书
openvpn 与 easy-rsa 安装完毕后,我们需要在/etc/openvpn/目录下创建 easy-rsa 文件夹,如下
1
sudo mkdir /etc/ openvpn/easy-rsa/
然后把/usr/share/easy-rsa/目录下的所有文件全部复制到/etc/openvpn/easy-rsa/下
1
sudo cp -r /usr/ share/easy-rsa/ * /etc/ openvpn/easy-rsa/
当然,我们也可以直接在/usr/share/easy-rsa/制作相关的证书,但是为了后续的管理证书的方便,我们还是把 easy-rsa 放在了 openvpn 的启动目录下。 注意:由于我们现在使用的是 ubuntu 系统,所以我们必须切换到 root 用户下才能制作相关证书,否则 easy-rsa 会报错。如果是 centos 系统,则不存在此问题。 切换到 root 用户下,使用如下命令:
在开始制作 CA 证书之前,我们还需要编辑 vars 文件,修改如下相关选项内容即可
1
sudo vi /etc/ openvpn/easy-rsa/ vars
1 2 3 4 5 6 7
export KEY_COUNTRY ="CN" export KEY_PROVINCE ="SD" export KEY_CITY ="JiNan" export KEY_ORG ="germy" export KEY_EMAIL ="cqc@cuiqingcai.com" export KEY_OU ="germy" export KEY_NAME ="germy"
如图所示 之后,我们需要利用这个文件来制作我们的证书,保存一下。 然后一个很重要的一步,赋予权限,否则在制作证书的时候,值还是初始化的值。
1
sudo chmod 777 /etc/ openvpn/easy-rsa/ vars
vars 文件主要用于设置证书的相关组织信息,红色部分的内容可以根据自己的实际情况自行修改。 其中 export KEY_NAME=”germy” 这个要记住下,我们下面在制作 Server 端证书时,会使用到。 注意:以上内容,我们也可以使用系统默认的,也就是说不进行修改也是可以使用的。 然后使用 source vars 命令使其生效,如下:
1 2
source vars./clean-all
注意:执行 clean-all 命令会删除,当前目录下的 keys 文件夹。 现在开始正式制作 CA 证书,使用如下命令:
1 2
cd /etc/openvpn/easy-rsa/ ./build-ca
一路回车即可。 制作完成后,我们可以查看 keys 目录里有什么东西。 如果你的目录下出现了 ca.crt 和 ca.key 两个文件,其中 ca.crt 就是我们所说的 CA 证书。如此,CA 证书制作完毕。 现在把该 CA 证书的 ca.crt 文件复制到 openvpn 的启动目录/etc/openvpn 下,如下:
1
cp keys /ca .crt /etc/openvpn/
制作 Server 端证书
CA 证书制作完成后,我们现在开始制作 Server 端证书。如下:
1
./build -key -server germy
上述命令中 germy,就是我们前面 vars 文件中设置的 KEY_NAME 查看 keys 目录 如果可以发现出现了 germy.crt,germy.csr,germy.key 文件,就说明成功了。 现在再为服务器生成加密交换时的 Diffie-Hellman 文件,如下:
你会发现目录下多了一个 dh2048.pem 文件。 以上操作完毕后,把 germy.crt,germy.key,dh2048.pem 复制到 /etc/openvpn/ 目录下,如下:
1 2
cd /etc/ openvpn/easy-rsa/ cp keys/germy.crt keys/g ermy.key keys/dh2048.pem / etc/openvpn/
如此,Server 端证书就制作完毕。
制作 Client 端证书
Server 端证书制作完成后,我们现在开始制作 Client 端证书,如下:
其中上述命令的 cqc 就是客户端证书名称,可以自定义 如果发现 keys 目录已经生成了 cqc.csr、cqc.crt 和 cqc.key 这个三个文件。其中 cqc.crt 和 cqc.key 两个文件是我们要使用的。 如此,Client 端证书就制作完毕。
配置 Server 端
所有证书制作完毕后,我们现在开始配置 Server 端。Server 端的配置文件,我们可以从 openvpn 自带的模版中进行复制。如下:
1 2
cp /usr/ share/doc/ openvpn/examples/ sample-config-files/server.conf.gz / etc/openvpn/ cd /etc/ openvpn/
解压 server.conf.gz 文件,使用如下命令:
注意:上述命令的意思是解压 server.conf.gz 文件后,然后删除原文件。 现在我们来修改 server.conf 文件 一共要修改 3 处文件 (1)修改了 openvpn 运行时使用的协议,由原来的 UDP 协议修改为 TCP 协议。生成环境建议使用 TCP 协议。 (2)修改了 openvpn 服务器的相关证书,由原来的 server.csr、server.key 修改为 germy.crt、germy.key。 (3)修改了 Diffie-Hellman 文件,由原来的 dh1024.pem 修改为 dh2048.pem。 配置文件修改完毕后,我们现在来启动 openvpn,使用如下命令:
1
/etc/i nit.d/openvpn start
至此,服务器端的 VPN 已经配置完毕了。
客户端的配置
服务器端配置好了,我们需要用另一台机器来连接,这里我们的客户端依然是 Ubuntu 14.04 首先我们需要从服务器上取到刚才生成的证书文件,那么我们需要的有什么呢? 首先这三个,ca.crt,cqc.crt,cqc.key 另外是一个模板,它是 /usr/share/doc/openvpn/examples/sample-config-files/client.conf 把这四个文件下载下来,然后放到客户端里。 比如我们保存到客户机的 home/user 文件夹下 把 client.conf 文件重命名为 client.ovpn 然后修改下面 4 处
1 2 3 4 5
proto tcp remote 121.42 .14 .158 1194ca ca .crt cert cqc .crt key cqc .key
其中 remote 就是你的服务器地址 配置好了之后,我们运行
如果最后的结果是 Sequence Completed 那就证明连接成功啦。 输入
你会发现多了一个 tun0 适配器,这就是 openvpn 的适配器。 至此,openvpn 的配置和连接就全部完成啦。
参考来源
参考文献 如有问题,欢迎留言交流。
作者
崔庆才
发表于
2015-10-30
阅读次数:
本文字数:
3.7k
阅读时长 ≈
3 分钟
HTML5中包含一个帮助检测device orientation的特性,使用这个特性可以在移动设备浏览器中判断用户设备的旋转重力方向。
基本知识
Alpha, Beta, Gamma角度旋转。 当用户旋转手机的时候,HTML5中定义了三个轴方向的旋转,如下: 上图可以看考,分别是z,x,y轴,对应分别是:Alpha,Beta,Gamma,下面图将更清楚的展示: 上图是Alpha旋转, 围绕Z轴旋转(绿线旋转方向,水平) 上图是Beta旋转, 围绕X轴旋转(绿线旋转方向,前后) 上图是Beta旋转, 围绕Y轴旋转(绿线旋转方向,左右)
属性
alpha: (float 类型 ) 以z方向为轴心的旋转角度 浮点数类型,只读属性,取值范围为0到360(不等于360)。
beta: (float 类型 ) 以x方向为轴心的旋转角度 浮点数类型,只读属性,取值范围为-180到180(不等于180)。
gamma: (float 类型 ) 以y方向为轴心的旋转角度 浮点数类型,只读属性,取值范围为-180到180(不等于180)。
参考
原文链接
作者
崔庆才
发表于
2015-10-28
阅读次数:
本文字数:
440
阅读时长 ≈
1 分钟
综述
在上一篇文章中,客户机可以借助路由机直接上网,并没有什么登录限制。接下来我们将加入上网登录验证,只有输入了正确的用户名和密码才可以通过验证,然后才可以访问互联网。 接下来,就跟随我用 PHP 来实现登录验证吧。
环境配置
在这之前,你需要配置一下 LAMP 环境,也就是 Apache,MySQL,PHP 开发环境,依次执行如下命令即可。
1 2 3 4 5 6 7
sudo apt-get install apache2 sudo apt-get install php5 php5-cgi php5-mysql php5-curl php5-gd php5-idn php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-mhash php5-ming php5-pspell php5-recode php5-snmp php5-tidy php5-xmlrpc php5-sqlite php5-xsl sudo apt-get install mysql-server mysql-client sudo apt-get install libapache2-mod-php5 sudo apt-get install libapache2-mod-auth-mysql sudo apt-get install phpmyadmin sudo ln -s /usr/share/phpmyadmin/ /var/www/html/phpmyadmin
如果配置出现问题,请查阅相关资料。 apache 默认的目录为 /var/www/html,我们这时访问 localhost 或者 192.168.122.4 ,都可以出现 apache 的欢迎界面,就证明我们配置成功了。
路由初始设置
为了在登录之前限制主机的上网,我们需要利用 iptables 规则来对数据包的转发加以限制。同时,将网页重定向到本机的登录界面。 初始路由设置如下
1 2 3 4 5 6 7 8 9
iptables -F iptables -t nat -F iptables -t mangle -F iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE iptables -t filter -A FORWARD -s 192.168.122.0/24 -o eth0 -j REJECT iptables -t filter -A FORWARD -s 192.168.122.0/24 -d 119.29.29.29/32 -j ACCEPT iptables -t nat -A PREROUTING -s 192.168.122.0/24 -p tcp -j DNAT --to 192.168.122.4
首先清除所有的 iptables 规则,然后设置前一篇我们说的 IP 伪装,这时可以客户机可以通过主机上网。 接下来的一条规则则禁用了来自 192.168.122.0 网段的所有 IP 的数据包转发,然后设置可访问 DNS 服务器,最后一条则设置了所有的 tcp 连接自动跳转到 192.168.122.4,也就是我们刚才配置的服务器。 可以把以上规则保存为脚本,比如叫 init.sh 来运行,也可以添加到 /etc/rc.local 中,开机自动运行。
登录页面
访问到 192.168.122.4 时,我们需要给用户呈现的当然不是刚才显示的 apache 欢迎页面,而是登录的输入框以及登录按钮界面。 所以,登录界面代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
<!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="utf-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <title > Auth Login</title > <link rel ="stylesheet" href ="css/bootstrap.min.css" > </head > <body > <form id ="auth" method ="post" > <div class ="input-group" > <span class ="input-group-addon" id ="basic-addon1" > Username</span > <input type ="text" class ="form-control" placeholder ="Username" aria-describedby ="basic-addon1" name ="username" > </div > <div class ="input-group" > <span class ="input-group-addon" id ="basic-addon1" > Password</span > <input type ="text" class ="form-control" placeholder ="Password" aria-describedby ="basic-addon1" name ="password" > </div > <input type ="button" id ="login" class ="btn btn-primary" value ="Login" > <input type ="button" id ="logout" class ="btn btn-primary" value ="Logout" > </form > <script src ="js/jquery.min.js" > </script > <script src ="js/bootstrap.min.js" > </script > </body > <style > form { max-width :400px ; margin :0 auto ; } .input-group { margin-bottom :20px ; } </style > <script > $(function ( ) { $("#login" ).on("click" , function ( ) { $("#auth" ).attr("action" , "/login.php" ); $("#auth" ).submit(); }); $("#logout" ).on("click" , function ( ) { $("#auth" ).attr("action" , "/logout.php" ); $("#auth" ).submit(); }); }); </script > </html >
其中的 js,jquery 文件请大家自行引入。 预览一下效果 在这里我们设置了两个按钮,一个是登录,一个是下线。
数据库查询验证
接下来我们新建一个数据库,例如我新建了一个数据库叫 auth,然后数据表 user,里面有三个字段。分别是 id,username,password,我插入了一条数据。 接下来我们就尝试一下登录,提交到 login.php 文件验证一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
<?php $mysql_server_name = "localhost" ; $mysql_username = "root" ; $mysql_password = "123456" ; $mysql_database = "auth" ; $username = @$_POST['username' ]; $password = @$_POST['password' ]; $ip=$_SERVER["REMOTE_ADDR" ]; $conn=mysql_connect($mysql_server_name, $mysql_username, $mysql_password); if ($conn) { $sql = "select * from user where username = '" .$username."'" ; $result = mysql_fetch_array(mysql_db_query($mysql_database, $sql, $conn)); if ($result) { if ($result['password' ] == $password) { $status = -1 ; system("sudo ./bash/login.sh $ip" , $status); if ($status == 0 ) { echo "Login Successfully" ; } else { echo "Login Failed" ; } } else { echo "Wrong Password" ; } } else { echo "Not" ; } } else { die ("Could Not Connect" ); } ?>
其中,最重要的部分莫过于
1
system ("sudo ./bash/login.sh $ip" , $status);
这一行代码了,此处便是登录验证用户名和密码之后执行的一个 Linux 脚本命令。 在这里我把要执行的脚本写入了 login.sh 文件中,传入的参数便是 ip 地址。 那么 login.sh 里面发生了什么事情呢,我们来看一下。
1 2 3 4 5 6
iptables -t nat -D PREROUTING -s $1 /32 -j ACCEPT iptables -t nat -D PREROUTING -s $1 /32 -p tcp -j ACCEPT iptables -t filter -D FORWARD -s $1 /32 -o eth0 -j ACCEPT iptables -t nat -I PREROUTING -s $1 /32 -j ACCEPT iptables -t nat -I PREROUTING -s $1 /32 -p tcp -j ACCEPT iptables -t filter -I FORWARD -s $1 /32 -o eth0 -j ACCEPT
$1 的意思就是获取第一个参数,在这里就是 IP 地址,脚本主要做的事情就是放行来自这个 IP 地址的数据包,让其正常访问互联网。 保存脚本后,记得给脚本赋予权限
1
sudo chmod 777 login.sh
-D 的意思就是删除,因为 iptables 是可以添加多次相同的规则的,在添加之前删除一下,以防止多次添加。 在这里
1
sudo ./bash/login.sh $ip
执行命令脚本前,我们加了 sudo,意思就是管理员身份运行,但是仍然可能导致权限问题,因为命令的执行者是 PHP(其实是 www-data),而并不是 root 用户,所以我们需要修改一下执行权限。 首先通过 PHP 文件获取执行该命令的用户是叫什么,比如新建一个 info.php 文件,输入如下内容:
1 2 3
<?php echo shell_exec("id -a" ); ?>
看一下运行结果 嗯,果然,执行用户是 www-data,这样我们只需要给 www-data 添加一个执行权限就好了。 修改 /etc/sudoers 文件 添加一行
1
www-data ALL =(ALL ) NOPASSWD:ALL
意思是 www-data 以 root 身份运行并且不需要密码。 好,保存之后,我们尝试一下,就可以登录啦。
测试登录
在路由主机(Ubuntu Route)里面,初始化一下 iptables 规则,然后查看当前规则。 我们发现当前访问都是被阻止的,而且 tcp 连接会自动跳转到 192.168.122.4 现在我们登录客户机,随机打开一个网址,比如百度,就发现自动跳转到了登录界面 输入用户名密码,尝试登陆,比如之前插入数据库的是 cqc,123456,输入之后登录。 提示登录成功之后,我们便可以欢乐地上网啦。 好,这样我们就完成了验证之后上网啦。
下线操作
同样的,下线操作我们同样写一个 logout.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
<?php $mysql_server_name = "localhost" ; $mysql_username = "root" ; $mysql_password = "123456" ; $mysql_database = "auth" ; $username = @$_POST['username' ]; $password = @$_POST['password' ]; $ip=$_SERVER["REMOTE_ADDR" ]; echo $ip; $conn=mysql_connect($mysql_server_name, $mysql_username, $mysql_password); if ($conn) { $sql = "select * from user where username = '" .$username."'" ; $result = mysql_fetch_array(mysql_db_query($mysql_database, $sql, $conn)); if ($result) { if ($result['password' ] == $password) { $status = -1 ; system("sudo ./bash/logout.sh $ip" , $status); if ($status == 0 ) { echo "Login Successfully" ; } else { echo "Login Failed" ; } } else { echo "Wrong Password" ; } } else { echo "Not" ; } } else { die ("Could Not Connect" ); } ?>
登出的脚本如下,其实就是单纯去除了刚才添加的路由规则
1 2 3
iptables -t nat -D PREROUTING -s $1 /32 -j ACCEPT iptables -t nat -D PREROUTING -s $1 /32 -p tcp -j ACCEPT iptables -t filter -D FORWARD -s $1 /32 -o eth0 -j ACCEPT
配置方式和登录一样,大家可以尝试下。
源代码
在这里提供大家源代码下载 源码下载 如有问题,欢迎交流。
作者
崔庆才
发表于
2015-10-08
阅读次数:
本文字数:
6.3k
阅读时长 ≈
6 分钟
综述
大家好,这次我们需要实现的是实现双网卡主机共享上网,就是一台主机通过连接另一台可以访问外网的双网卡主机来正常上网。所以我们需要两台机器来进行测试,在这里我们用的是两台 Ubuntu 14.04,其中一台是单网卡,一台是双网卡。废话不多说,行动起来吧。
配置系统
博主使用了 Vmware 来安装了两台 Ubuntu 主机,一台当路由机,名称是 Ubuntu Route,另一台是客户机,名称是 Ubuntu Desktop,具体的网络配置如下: Ubuntu Route: 一个网卡 eth0 通过 NAT 方式来与外部主机共享上网,这个网卡也就是 VMnet8 网卡,网段是 192.168.231.0 另一个网卡 eth1 连接了一个自定义的仅主机模式的网卡 VMnet2,网段是 192.168.122.0 网络适配器设置如下,eth0 开启了 DHCP,ech1 没有开启 DHCP Ubuntu Desktop: 一个网卡 eth0 连接刚才那个自定义的仅主机模式的网卡 VMnet2,网段是 192.168.122.0 好了,以上就是基本硬件的配置
设置 IP
接下来我们设置一下 Ubuntu Route 的 IP 地址,修改 /etc/network/interfaces
1 2 3 4 5 6 7 8
auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp auto eth1 iface eth1 inet static address 192.168.122.4 netmask 255.255.255.0
在这里,eth0 因为我们在 VMware 里面设置了 DHCP,所以这里我们设置 dhcp 即可,eth1 需要手动配置一下,我们分配了 192.168.122.4 这个 IP 地址,当然你可以随意指定,子网掩码如上,不需要写网关,因为它本身作为一个路由。 可以通过执行如下命令来使之生效
1
sudo /etc/i nit.d/networking restart
如果上述方法不行,则可以尝试使用关闭网卡和开启网卡的命令。
1 2
sudo ifup eth0 sudo ifdown eth0
eth1 的开启和关闭同上
开启路由转发
修改 /etc/sysctl.conf 文件,将
这一行取消注释,代表开启了路由转发功能。 也可以通过执行
1
echo 1 > /proc/ sys/net/i pv4/ip_forward
命令来实现
设置 iptables 规则
iptables 是非常重要的一个环节,如果大家不熟悉,可以去搜相关资料了解一下。 执行如下命令,来设置一下 iptables 规则,可以直接在命令行逐条执行,也可以写成一个脚本来执行。
1 2 3 4 5 6
iptables -F iptables -t nat -F iptables -t mangle -F iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
其中最后一条是最重要的,代表将数据包通过 eth0 网卡来转发,也是 IP 伪装的一个常用方法,有了这条指令,从 eth1 网卡流经的一些数据包可以通过 eth0 来转发,这样就相当于连通了两个网卡,这样与 eth1 网卡连接的主机便可以上网了。
客户主机设置
因为客户机的 eth0 连接了 VMnet2 网卡,而 VMnet2 网卡又与路由主机的 eth1 连接,我们只需要简单设置一下 IP 地址就好了。 修改 /etc/network/interfaces
1 2 3 4 5 6
auto eth0iface eth0 inet static address 192.168 .122 .5 netmask 255.255 .255 .0 gateway 192.168 .122 .4 dns-nameservers 119.29 .29 .29
这里很重要的一个设置就是网关,设置成路由主机的 IP 地址。 设置完了同样重启一下网卡使其生效。 还可以选择性设置下 DNS 服务器。 至此,所有配置都完成了,测试一下吧。
测试
我们在客户机里打开浏览器,输入随意一个网页测试一下。 嗯,客户机可以正常上网啦,一切都是那么轻松加愉快! 如有问题,欢迎留言交流~
作者
崔庆才
发表于
2015-10-07
阅读次数:
本文字数:
1.7k
阅读时长 ≈
2 分钟
2022 年最新 Python3 网络爬虫教程
大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。
博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。
最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。
教程请移步:
【2022 版】Python3 网络爬虫学习教程
如下为原文。
综述
最近山大软件园校区 QLSC_STU 无线网掉线掉的厉害,连上之后平均十分钟左右掉线一次,很是让人心烦,还能不能愉快地上自习了?能忍吗?反正我是不能忍了,嗯,自己动手,丰衣足食!写个程序解决掉它! 假若你不能连这个无线,那就照照思路啦~
决战前夕
首先我们看一下那个验证页面是咋样滴,上个图先 嘿,这界面还算可以把,需要我们输入的东西就是俩,一个就是学号,另一个是身份证号后六位,然后就可以登录,享受免费的无线网啦。 不过不知道谁设置了个登录时长,一段时间后就会掉线了,于是,自动模拟登陆系统就要应运而生啦。 来,我们先点击一下连接,看一下浏览器怎么工作的。 按下 F12,监听网络,我们点击第一个响应,也就是 login.jsp,看一下。 我们具体看一下 headers,里面 form 提交了什么东西,真的是茫茫多的数据啊。 嗯,一目了然 POST 的数据和提交的地址。 让我们来分析几个数据吧:
ClientIP:当前客户端的 IP 地址,在山大软件园校区这个地址是 211.87 开头的
timeoutvalue:连接等待时间,也就是俗话说的 timeout
StartTime:登录时间,也就是在你登录的那一刻的时间戳,这个时间戳是 13 位的,精确到了毫秒,不过一般是 10 位的,我们加 3 个 0 就好了
shkOvertime:登录持续时间,这个数据默认是 720,也就是 12 分钟之后,登录就失效了,自动掉线,我们可以手动更改
username:学号
password:密码,也就是我们身份证号后六位
我们需要在登录的时候把 form 表单中的所有信息都 POST 一下,然后就可以完成登录啦。 万事俱备,只欠东风,来来来,程序写起来!
一触即发
说走咱就走啊,天上的星星参北斗啊! 登陆地址:Request URL:http://192.168.8.10/portal/login.jsp?Flag=0 首先,我们需要验证一下 IP 地址,先写一个获取 IP 地址的函数,首先判断当前 IP 是不是 211.87 开头的,如果是的话,证明连接的 IP 是有效的。 首先我们写一个获取本机 IP 的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
self.ip_pre = "211.87" def getIP(self): local_iP = socket.gethostbyname(socket.gethostname()) if self.ip_pre in str(local_iP): return str(local_iP) ip_lists = socket.gethostbyname_ex(socket.gethostname()) for ip_list in ip_lists: if isinstance(ip_list, list): for i in ip_list: if self.ip_pre in str(i): return str(i) elif type(ip_list) is types.StringType: if self.ip_pre in ip_list: return ip_list
这个方法利用了 gethostbyname 和 gethostbyname_ex 方法,获取了各个网卡的 IP 地址,遍历一下,找到那个 211.87 开头的 IP,返回 接下来,获取到 IP 之后,我们便可以构建 form,然后进行模拟登陆了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
def login (self ) : print self .getCurrentTime(), u"正在尝试认证QLSC_STU无线网络" ip = self .getIP() data = { "username" : self .username, "password" : self .password, "serverType" : "" , "isSavePass" : "on" , "Submit1" : "" , "Language" : "Chinese" , "ClientIP" : self .getIP(), "timeoutvalue" : 45 , "heartbeat" : 240 , "fastwebornot" : False, "StartTime" : self .getNowTime(), "shkOvertime" : self .overtime, "strOSName" : "" , "iAdptIndex" : "" , "strAdptName" : "" , "strAdptStdName" : "" , "strFileEncoding" : "" , "PhysAddr" : "" , "bDHCPEnabled" : "" , "strIPAddrArray" : "" , "strMaskArray" : "" , "strMask" : "" , "iDHCPDelayTime" : "" , "iDHCPTryTimes" : "" , "strOldPrivateIP" : self .getIP(), "strOldPublicIP" : self .getIP(), "strPrivateIP" : self .getIP(), "PublicIP" : self .getIP(), "iIPCONFIG" : 0 , "sHttpPrefix" : "http://192.168.8.10" , "title" : "CAMS Portal" } headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36' , 'Host' : '192.168.8.10' , 'Origin' : 'http://192.168.8.10' , 'Referer' : 'http://192.168.8.10/portal/index_default.jsp?Language=Chinese' } post_data = urllib.urlencode(data) login_url = "http://192.168.8.10/portal/login.jsp?Flag=0" request = urllib2.Request(login_url, post_data, headers) response = urllib2.urlopen(request) result = response.read().decode('gbk' )
比较多的内容就在于 form 表单的数据内容以及请求头,后来利用 urllib2 的 urlopen 方法实现模拟登陆。 如果大家对此不熟悉,可以参见 Urllib 的基本使用 这样,登录后的结果就会保存在 result 变量中,我们只需要从 result 中提取出我们需要的数据就可以了。
乘胜追击
接下来,我们就分析一下数据啦,结果有这么几种:
1.登录成功 2.已经登录 3.用户不存在 4.密码错误 5.未知错误
好,利用 result 分析一下结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
def getLoginResult (self, result) : if u"用户上线成功" in result: print self.getCurrentTime(),u"用户上线成功,在线时长为" ,self.overtime/60 ,"分钟" elif u"您已经建立了连接" in result: print self.getCurrentTime(),u"您已经建立了连接,无需重复登陆" elif u"用户不存在" in result: print self.getCurrentTime(),u"用户不存在,请检查学号是否正确" elif u"用户密码错误" in result: pattern = re.compile('<td class="tWhite">.*?2553:(.*?)</b>.*?</td>' , re.S) res = re.search(pattern, result) if res: print self.getCurrentTime(),res.group(1 ),u"请重新修改密码" else : print self.getCurrentTime(),u"未知错误,请检查学号密码是否正确"
通过字符串匹配和正则表达式,我们分辨并提取出了上述五种情况。 增加循环检测 既然是检测网络是否断开,那么我们只需要每隔一段时间检测一下就好了,那就 10 秒吧。 因为这个 10 秒是可配置的,为了方便配置,统一配置到init 方法里面。
然后,我们写一个循环来检测一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
while True : nowIP = self.getIP() if not nowIP: print self.getCurrentTime(), u"请检查是否正常连接QLSC_STU无线网络" else : print self.getCurrentTime(),u"成功连接了QLSC_STU网络,本机IP为" ,nowIP self.login() while True : can_connect = self.canConnect() if not can_connect: nowIP = self.getIP() if not nowIP: print self.getCurrentTime(), u"当前已经断线,请确保连接上了QLSC_STU网络" else : print self.getCurrentTime(), u"当前已经断线,正在尝试重新连接" self.login() else : print self.getCurrentTime(), u"当前网络连接正常" time.sleep(self.every) time.sleep(self.every)
其中我们用到了 canConnect 方法,这个就是检测网络是否已经断开的方法,我们可以利用 ping 百度的方法来检测一下。 方法实现如下
1 2 3 4 5 6 7 8 9
def canConnect(self): fnull = open (os.devnull, 'w' ) result = subprocess.call('ping www.baidu.com' , shell = True, stdout = fnull, stderr = fnull) fnull.close () if result : return False else : return True
好啦,所有的要点我们已经逐一击破,等着凯旋吧
收拾战场
好了,所有的代码要点已经被我们攻破了,接下来就整理一下,让他们组合起来,变成一个应用程序吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
__author__ = 'CQC' import urllibimport urllib2import socketimport typesimport timeimport reimport osimport subprocessclass Login : def __init__ (self) : self.username = '201200131012' self.password = 'XXXXXX' self.ip_pre = '211.87' self.overtime = 720 self.every = 10 def login (self) : print self.getCurrentTime(), u"正在尝试认证QLSC_STU无线网络" ip = self.getIP() data = { "username" : self.username, "password" : self.password, "serverType" : "" , "isSavePass" : "on" , "Submit1" : "" , "Language" : "Chinese" , "ClientIP" : self.getIP(), "timeoutvalue" : 45 , "heartbeat" : 240 , "fastwebornot" : False , "StartTime" : self.getNowTime(), "shkOvertime" : self.overtime, "strOSName" : "" , "iAdptIndex" : "" , "strAdptName" : "" , "strAdptStdName" : "" , "strFileEncoding" : "" , "PhysAddr" : "" , "bDHCPEnabled" : "" , "strIPAddrArray" : "" , "strMaskArray" : "" , "strMask" : "" , "iDHCPDelayTime" : "" , "iDHCPTryTimes" : "" , "strOldPrivateIP" : self.getIP(), "strOldPublicIP" : self.getIP(), "strPrivateIP" : self.getIP(), "PublicIP" : self.getIP(), "iIPCONFIG" :0 , "sHttpPrefix" : "http://192.168.8.10" , "title" : "CAMS Portal" } headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36' , 'Host' : '192.168.8.10' , 'Origin' : 'http://192.168.8.10' , 'Referer' : 'http://192.168.8.10/portal/index_default.jsp?Language=Chinese' } post_data = urllib.urlencode(data) login_url = "http://192.168.8.10/portal/login.jsp?Flag=0" request = urllib2.Request(login_url, post_data, headers) response = urllib2.urlopen(request) result = response.read().decode('gbk' ) self.getLoginResult(result) def getLoginResult (self, result) : if u"用户上线成功" in result: print self.getCurrentTime(),u"用户上线成功,在线时长为" ,self.overtime/60 ,"分钟" elif u"您已经建立了连接" in result: print self.getCurrentTime(),u"您已经建立了连接,无需重复登陆" elif u"用户不存在" in result: print self.getCurrentTime(),u"用户不存在,请检查学号是否正确" elif u"用户密码错误" in result: pattern = re.compile('<td class="tWhite">.*?2553:(.*?)</b>.*?</td>' , re.S) res = re.search(pattern, result) if res: print self.getCurrentTime(),res.group(1 ),u"请重新修改密码" else : print self.getCurrentTime(),u"未知错误,请检查学号密码是否正确" def getNowTime (self) : return str(int(time.time()))+"000" def getIP (self) : local_iP = socket.gethostbyname(socket.gethostname()) if self.ip_pre in str(local_iP): return str(local_iP) ip_lists = socket.gethostbyname_ex(socket.gethostname()) for ip_list in ip_lists: if isinstance(ip_list, list): for i in ip_list: if self.ip_pre in str(i): return str(i) elif type(ip_list) is types.StringType: if self.ip_pre in ip_list: return ip_list def canConnect (self) : fnull = open(os.devnull, 'w' ) result = subprocess.call('ping www.baidu.com' , shell = True , stdout = fnull, stderr = fnull) fnull.close() if result: return False else : return True def getCurrentTime (self) : return time.strftime('[%Y-%m-%d %H:%M:%S]' ,time.localtime(time.time())) def main (self) : print self.getCurrentTime(), u"您好,欢迎使用模拟登陆系统" while True : nowIP = self.getIP() if not nowIP: print self.getCurrentTime(), u"请检查是否正常连接QLSC_STU无线网络" else : print self.getCurrentTime(),u"成功连接了QLSC_STU网络,本机IP为" ,nowIP self.login() while True : can_connect = self.canConnect() if not can_connect: nowIP = self.getIP() if not nowIP: print self.getCurrentTime(), u"当前已经掉线,请确保连接上了QLSC_STU网络" else : print self.getCurrentTime(), u"当前已经掉线,正在尝试重新连接" self.login() else : print self.getCurrentTime(), u"当前网络连接正常" time.sleep(self.every) time.sleep(self.every) login = Login() login.main()
来,我们来运行一下,看下效果吧! 执行
当前是可以联网的,我分别在网页上操作执行了断开,操作,程序自动检测到掉线,自动重新连接。 接下来我直接断开了 QLSC_STU 网络的链接,程序同样检测到 QLSC_STU 这个热点没有连接上,提示用户链接。 接下来我重新连接上了这个热点,由于刚才已经登录上线,且持续时间较短,网络自动恢复正常。 下面是运行结果: 嗯,这样我们就是实现了自动掉线的检测和模拟登录。
凯旋而归
咿呀伊尔哟,想约妹子上自习吗?那就赶紧来试试吧!一网在手,天下我有!追男神女神都不再是梦想! 如果有问题,欢迎留言讨论,代码肯定有不完善的地方,仅供参考。
作者
崔庆才
发表于
2015-09-20
阅读次数:
本文字数:
8.7k
阅读时长 ≈
8 分钟
CSRF是什么?
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
CSRF可以做什么?
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。
CSRF漏洞现状
CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI……而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
CSRF的原理
下图简单阐述了CSRF攻击的思想: 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。 2.在不登出A的情况下,访问危险网站B。
看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:
1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。 2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了……) 3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
上面大概地讲了一下CSRF攻击的思想,下面我将用几个例子详细说说具体的CSRF攻击,这里我以一个银行转账的操作作为例子(仅仅是例子,真实的银行网站没这么傻:>)
示例1:
银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000 危险网站B,它里面有一段HTML的代码如下:
1
<img src =http://www.mybank.com/Transfer.php?toBankId =11&money =1000 >
首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块…… 为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的 以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作 ……
示例2:
为了杜绝上面的问题,银行决定改用POST请求完成转账操作。 银行网站A的WEB表单如下:
1 2 3 4 5
<form action ="Transfer.php" method ="POST" > <p > ToBankId: <input type ="text" name ="toBankId" /> </p > <p > Money: <input type ="text" name ="money" /> </p > <p > <input type ="submit" value ="Transfer" /> </p > </form >
后台处理页面Transfer.php如下:
1 2 3 4 5 6 7
<?php session_start(); if (isset ($_REQUEST['toBankId' ] && isset ($_REQUEST['money' ])) { buy_stocks($_REQUEST['toBankId' ], $_REQUEST['money' ]); } ?>
危险网站B,仍然只是包含那句HTML代码:
1
<img src =http://www.mybank.com/Transfer.php?toBankId =11&money =1000 >
和示例1中的操作一样,你首先登录了银行网站A,然后访问危险网站B,结果…..和示例1一样,你再次没了1000块~T_T,这次事故的原因是:银行后台使用了$_REQUEST去获取请求的数据,而$_REQUEST既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用$_GET和$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题。
示例3:
经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下:
1 2 3 4 5 6 7
<?php session_start(); if (isset ($_POST['toBankId' ] && isset ($_POST['money' ])) { buy_stocks($_POST['toBankId' ], $_POST['money' ]); } ?>
然而,危险网站B与时俱进,它改了一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<html > <head > <script type ="text/javascript" > function steal () { iframe = document .frames["steal" ]; iframe.document.Submit("transfer" ); } </script > </head > <body onload ="steal()" > <iframe name ="steal" display ="none" > <form method ="POST" name ="transfer" action ="http://www.myBank.com/Transfer.php" > <input type ="hidden" name ="toBankId" value ="11" > <input type ="hidden" name ="money" value ="1000" > </form > </iframe > </body > </html >
如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块……因为这里危险网站B暗地里发送了POST请求到银行! 总结一下上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,因为触发条件很简单,一个 就可以了,而第3种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重。 理解上面的3种攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!
CSRF的防御
我总结了一下看到的资料,CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。
1.服务端进行CSRF防御
服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。 (1).Cookie Hashing(所有表单都包含同一个伪随机值): 这可能是最简单的解决方案了,因为攻击者不能获得第三方的Cookie(理论上),所以表单中的数据也就构造失败了:>
1 2 3 4 5
<?php $value = “DefenseSCRF”; setcookie(”cookie”, $value, time()+3600 ); ?>
在表单里增加Hash值,以认证这确实是用户发送的请求。
1 2 3 4 5 6 7 8 9
<?php $hash = md5($_COOKIE['cookie']); ?> <form method =”POST” action =”transfer.php” > <input type =”text” name =”toBankId” > <input type =”text” name =”money” > <input type =”hidden” name =”hash” value =” <?=$hash;? > ”> <input type =”submit” name =”submit” value =”Submit” > </form >
然后在服务器端进行Hash值验证
1 2 3 4 5 6 7 8 9 10 11 12
<?php if (isset ($_POST['check' ])) { $hash = md5($_COOKIE['cookie' ]); if ($_POST['check' ] == $hash) { doJob(); } else { } } else { } ?>
这个方法个人觉得已经可以杜绝99%的CSRF攻击了,那还有1%呢….由于用户的Cookie很容易由于网站的XSS漏洞而被盗取,这就另外的1%。一般的攻击者看到有需要算Hash值,基本都会放弃了,某些除外,所以如果需要100%的杜绝,这个不是最好的方法。
2.验证码
这个方案的思路是:每次的用户提交都需要用户在表单中填写一个图片上的随机字符串,厄….这个方案可以完全解决CSRF,但个人觉得在易用性方面似乎不是太好,还有听闻是验证码图片的使用涉及了一个被称为MHTML的Bug,可能在某些版本的微软IE中受影响。
原文链接
在网上发现这篇文章说得很明了,转载啦,非常感谢原作者的辛苦付出。 原文出处
作者
崔庆才
发表于
2015-09-17
阅读次数:
本文字数:
4.4k
阅读时长 ≈
4 分钟
综述
博主没事就喜欢瞎倒腾,今天登录自己的 SAE 一看,很久之前挂的几个应用早已杳无人烟,才消耗几个云豆,真是觉得太浪费了。所以博主就瞎倒腾一下,准备好好利用起自己的云豆,尽可能地为我提供服务。 好了,废话不多说,让我们开始动起来!
新建应用
既然想利用好里面的服务,那么我们就需要新建一个专属应用,取个专属名称,那么我名字叫崔庆才,那就叫 cuiqingcai 好了,幸运的是,并没有同名应用,可以使用,不错不错。
数据库
SAE 上最近出了一种独享数据库,不像之前的数据库一样每建一个应用自动生成一个数据库,这个数据库就像平常我们用到的 phpMyAdmin 一样一样的。可以新建好多个数据库,可以远程连接,可以登录网页设置。 有了这个,如果你没有 VPS,也不需要去买个 RDS 来用啦! 首先建立好应用之后,到应用管理界面去,点击你的 MySQL,然后就会让你选择是哪一种数据库,右边的独享型 MySQL 是 SAE 新出的,功能也非常强,可以建立数据库,可以建用户,授权数据库,监控数据等等。 点击独享型 MySQL,创建属于自己的数据库吧。创建好了,建立自己的账户,登进来,一切都是那么轻松加愉快,以后有什么需要数据库,扔到这里就好了。 好,相信这个一定非常简单,小伙伴们快试一下吧!
Storage
新浪云中提供了 SCS,也是一个云存储服务,但是费用太坑了,而且不是消耗云豆的,然而应用中的 Storage 是使用云豆的,如果不是什么私密存储的话,还是可以利用一下的。 在这里我想让它发挥 SCS 的作用并绑定到我的域名上,这样通过 scs.cuiqingcai.com 便可以访问了。 我们新建一个 Storage Domain 上传一张图片进去,比如 1.jpg,上传后访问一下 恩,轻松加愉快!接下来,我发现这个域名简直太难记了,我要换成自己的域名,比如 scs.cuiqingcai.com 这样,图片就可以通过 scs.cuiqingcai.com/1.jpg 来访问了。那么怎么配置呢?
1.绑定域名
到应用首页里面,点击域名绑定,SAE 会告诉你两条要解析的内容,自己设置一下 接下来我们需要配置一下代码
2.修改 config.yaml
需要在代码管理中,更改一下 config.yaml 文件,让它做一个跳转。 比如我的代码中 config.yaml 文件就更改为
1 2 3 4
name: cuiqingcaiversion: 1 handle: - rewrite: if (path ~ "/(.*)" ) goto "http://cuiqingcai-scs.stor.sinaapp.com/$1"
其实就是加了一条 reweite 语句,把所有的路径定位到新地址的新路径中。 接下来,我访问一下 scs.cuiqingcai.com/1.jpg,嗯,图片出来了!这样,又一个免费的 CDN 建好了! 嗯,如果有什么图片需要存放,它会是你的一个非常好的选择! 上传的话,SAE 提供了上传的客户端,大家可以自行下载尝试。 之后博主将研究一下怎样配置上传的接口,这样我们就可以通过程序来上传了,我也很期待呢!
结语
以上是博主瞎倒腾的,可以为自己提供一些方便,大家尝试下吧,如有问题,欢迎留言交流!
作者
崔庆才
发表于
2015-09-12
阅读次数:
本文字数:
1.3k
阅读时长 ≈
1 分钟
博主之前一直用的是 apache,随着网站负荷量增高,感觉 apache 稍微有点力不从心了。随着 nginx 越来越流行,而且其功能强大,博主准备采用 nginx 作为自己的服务器啦。 每当到了环境配置的时候,博主便会去网上各种搜集资料,然而感觉他们讲的都条理不一,有的地方并不符合自己的配置习惯,所以博主习惯自己配置的同时把配置过程总结一下,方便自己,也方便大家。 好,接下来我们就开始我们的环境配置之旅吧。
Nginx
1.更新源
2.安装 nginx
1
sudo apt-get install nginx
3.检查是否安装成功
输入 localhost 或者远程地址,若出现 Welcome To Nginx 则证明安装成功。 如果没有看到,可以运行
杀掉 apache 进程,因为可能 80 端口被占用了。
4.更改运行目录
默认的 nginx 目录是/usr/share/nginx/html,我们将其修改为/var/www,当然可以根据个人习惯灵活更改。 修改/etc/nginx/sites-available/default 文件
1
root /usr/ share/nginx/ html;
更改为
再将
1
index index .html index .htm;
更改为
1
index index .html index .php index .htm;
重启 nginx
1
sudo service nginx restart
我们在/var/www 目录下新建 index.html 文件,写入一些测试文字。 重新访问 localhost 或者远程地址,可以看到刚才设置的 index.html 文件中的内容,说明目录已经更改成功了。
PHP
安装 PHP 以及相关扩展。
1
sudo apt-get install php5 php5-cgi php5-mysql php5-curl php5-gd php5-idn php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-mhash php5-ming php5-pspell php5-recode php5-snmp php5-tidy php5-xmlrpc php5-sqlite php5-xsl
执行上述指令即可。 此时有的小伙伴想要测试运行 PHP 文件了,然而很悲剧地告诉你是不可以的,因为你还没有配置 fastcgi,继续往下看。
MySQL
执行如下命令,安装 MySQL 服务端和客户端。
1
sudo apt-get install mysql-server mysql-client
在安装时可能提示你输入 root 用户的密码,设置即可。
phpMyAdmin
执行如下命令,安装 phpMyAdmin。
1
sudo apt-get install phpmyadmin
创建软连接
1
sudo ln -s /usr/ share/phpmyadmin/ /var/ www/phpmyadmin
我们将创建一个根目录为/var/www,链接名为 phpmyadmin 的文件,指向/usr/share/phpmyadmin
spawn-fcgi
1.安装 fastcgi
1
sudo apt-get install spawn-fcgi
2.配置 fastcgi
修改 /etc/nginx/fastcgi_params 文件,增加下面一行
1
fastcgi_param SCRIPT_FILENAME $document_root $fastcgi_script_name ;
修改/etc/php5/cgi/php.ini 文件,将下面一行取消注释
这样 php-cgi 方能正常使用 SCRIPT_FILENAME 这个变量 修改/etc/nginx/sites-available/default 文件,我们之前修改的目录地址是/var/www,将
1 2 3 4 5 6 7 8 9 10 11
#location ~ \.php$ { # fastcgi_split_path_info ^(.+\.php)(/.+)$; # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini # # # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php5-fpm: # fastcgi_pass unix:/var/run/php5-fpm.sock; # fastcgi_index index.php; # include fastcgi_params; #}
修改为
1 2 3 4 5 6 7 8 9 10 11 12
location ~ \.php$ { fastcgi_pass 127.0.0.1:9000 ; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name ; include fastcgi_params; }
3.开启 fastcgi 进程
1
sudo /usr/bin/spawn-fcgi -a 127.0 .0 .1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php5-cgi -P /var/run/fastcgi-php.pid
参数含义如下
-f 指定调用 FastCGI 的进程的执行程序位置,根据系统上所装的 PHP 的情况具体设置 -a 绑定到地址 addr -p 绑定到端口 port -s 绑定到 unix socket 的路径 path -C 指定产生的 FastCGI 的进程数,默认为 5(仅用于 PHP) -P 指定产生的进程的 PID 文件路径 * -u 和-g FastCGI 使用什么身份(-u 用户 -g 用户组)运行,Ubuntu 下可以使用 www-data,其他的根据情况配置,如 nobody、apache 等现在可以在 web 根目录下放个探针或 php 文件测试一下了
运行结果类似如下
1
spawn-fcgi: child spawned successfully: PID: 11775
4.设置开机启动 fastcgi
修改/etc/rc.local 文件,添加下面一行
1
/usr/bin/spawn-fcgi -a 127.0 .0 .1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php5-cgi -P /var/run/fastcgi-php.pid
修改完之后,重启 nginx
1
sudo service nginx restart
我们可以在/var/www 目录下新建 index.php 文件测试运行,发现已经可以运行 PHP 文件了。
测试运行
所有配置已经完毕,现在我们输入 localhost 和 localhost/phpmyadmin 便可以轻松加愉快地访问了。 如有问题,欢迎交流。
作者
崔庆才
发表于
2015-09-11
阅读次数:
本文字数:
2.9k
阅读时长 ≈
3 分钟
2022 年最新 Python3 网络爬虫教程
大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。
博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。
最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。
教程请移步:
【2022 版】Python3 网络爬虫学习教程
如下为原文。
大家好,本次为大家带来的是抓取爱问知识人的问题并将问题和答案保存到数据库的方法,涉及的内容包括:
Urllib 的用法及异常处理
Beautiful Soup 的简单应用
MySQLdb 的基础用法
正则表达式的简单应用
环境配置
在这之前,我们需要先配置一下环境,我的 Python 的版本为 2.7,需要额外安装的库有两个,一个是 Beautiful Soup,一个是 MySQLdb,在这里附上两个库的下载地址, Beautiful Soup MySQLdb 大家可以下载之后通过如下命令安装
1
python setup.py install
环境配置好之后,我们便可以开心地撸爬虫了
框架思路
首先我们随便找一个分类地址,外语学习 - 爱问知识人 ,打开之后可以看到一系列的问题列表。 我们在这个页面需要获取的东西有: 总的页码数,每一页的所有问题链接。 接下来我们需要遍历所有的问题,来抓取每一个详情页面,提取问题,问题内容,回答者,回答时间,回答内容。 最后,我们需要把这些内容存储到数据库中。
要点简析
其实大部分内容相信大家会了前面的内容,这里的爬虫思路已经融汇贯通了,这里就说一下一些扩展的功能
1.日志输出
日志输出,我们要输出时间和爬取的状态,比如像下面这样:
[2015-08-10 03:05:20] 113011 号问题存在其他答案 我个人认为应该是樱桃沟很美的 [2015-08-10 03:05:20] 保存到数据库,此问题的 ID 为 113011 [2015-08-10 03:05:20] 当前爬取第 2 的内容,发现一个问题 百度有一个地方,花儿带着芳香,水儿流淌奔腾是什么意思 多多帮忙哦 回答数量 1 [2015-08-10 03:05:19] 保存到数据库,此问题的 ID 为 113010
所以,我们需要引入时间函数,然后写一个获取当前时间的函数
1 2 3 4 5 6 7 8 9
import time #获取当前时间 def getCurrentTime(self): return time .strftime('[%Y-%m-%d %H:%M:%S]' ,time .localtime (time .time())) #获取当前时间 def getCurrentDate(self): return time .strftime('%Y-%m-%d' ,time .localtime (time .time()))
以上分别是获取带具体时间和获取日期的函数,在输出时,我们可以在输出语句的前面调用这函数即可。 然后我们需要将缓冲区设置输出到 log 中,在程序的最前面加上这两句即可
1 2
f_handler =open('out.log' , 'w' )sys.stdout =f_handler
这样,所有的 print 语句输出的内容就会保存到 out.log 文件中了。
2.页码保存
爬虫爬取过程中可能出现各种各样的错误,这样会导致爬虫的中断,如果我们重新运行爬虫,那么就会导致爬虫从头开始运行了,这样显然是不合理的。所以,我们需要把当前爬取的页面保存下来,比如可以保存到文本中,假如爬虫中断了,重新运行爬虫,读取文本文件的内容,接着爬取即可。 大家可以稍微参考一下函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
def main(self): f_handler =open('out.log', 'w' ) sys.stdout =f_handler page = open('page.txt' , 'r' ) content = page.readline() start_page = int(content.strip()) - 1 page.close() print self.getCurrentTime(),"开始页码" ,start_page print self.getCurrentTime(),"爬虫正在启动,开始爬取爱问知识人问题" self.total_num = self.getTotalPageNum() print self.getCurrentTime(),"获取到目录页面个数" ,self.total_num,"个" if not start_page: start_page = self.total_num for x in range(1,start_page): print self.getCurrentTime(),"正在抓取第" ,start_page-x+1,"个页面" try: self.getQuestions(start_page-x+1) except urllib2.URLError, e: if hasattr(e, "reason" ): print self.getCurrentTime(),"某总页面内抓取或提取失败,错误原因" , e.reason except Exception,e: print self.getCurrentTime(),"某总页面内抓取或提取失败,错误原因:" ,e if start_page-x+1 < start_page: f =open('page.txt','w') f.write(str(start_page-x+1)) print self.getCurrentTime(),"写入新页码" ,start_page-x+1 f.close()
这样,不管我们爬虫中途遇到什么错误,妈妈也不会担心了
3.页面处理
页面处理过程中,我们可能遇到各种各样奇葩的 HTML 代码,和上一节一样,我们沿用一个页面处理类即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
import re #处理页面标签类 class Tool: #将超链接广告剔除 removeADLink = re.compile( #去除img标签,1-7位空格, removeImg = re.compile( #删除超链接标签 removeAddr = re.compile( #把换行的标签换为\n replaceLine = re.compile( #将表格制表<td>替换为\t replaceTD= re.compile( #将换行符或双换行符替换为\n replaceBR = re.compile( #将其余标签剔除 removeExtraTag = re.compile( #将多行空行删除 removeNoneLine = re.compile( def replace(self,x): x = re.sub (self.removeADLink,"" ,x) x = re.sub (self.removeImg,"" ,x) x = re.sub (self.removeAddr,"" ,x) x = re.sub (self.replaceLine,"\n" ,x) x = re.sub (self.replaceTD,"\t" ,x) x = re.sub (self.replaceBR,"\n" ,x) x = re.sub (self.removeExtraTag,"" ,x) x = re.sub (self.removeNoneLine,"\n" ,x) #strip()将前后多余内容删除 return x.strip()
我们可以用一段含有 HTML 代码的文字,经过调用 replace 方法之后,各种冗余的 HTML 代码就会处理好了。 比如我们这么一段代码:
1 2 3 4 5 6 7 8 9 10 11 12
<article class ="article-content" > <h2>前言</h2> <p>最近发现MySQL服务隔三差五就会挂掉,导致我的网站和爬虫都无法正常运作。自己的网站是基于MySQL,在做爬虫存取一些资料的时候也是基于MySQL,数据量一大了,MySQL它就有点受不了了,时不时会崩掉,虽然我自己有网站监控和邮件通知,但是好多时候还是需要我来手动连接我的服务器重新启动一下我的MySQL,这样简直太不友好了,所以,我就觉定自己写个脚本,定时监控它,如果发现它挂掉了就重启它。</ p><p>好了,闲言碎语不多讲,开始我们的配置之旅。</p> <p>运行环境:<strong>Ubuntu Linux 14.04</ strong></p> <h2>编写Shell脚本</ h2><p>首先,我们要编写一个shell脚本,脚本主要执行的逻辑如下:</p> <p>显示mysqld进程状态,如果判断进程未在运行,那么输出日志到文件,然后启动mysql服务,如果进程在运行,那么不执行任何操作,可以选择性输出监测结果。</ p><p>可能大家对于shell脚本比较陌生,在这里推荐官方的shell脚本文档来参考一下</p> <p><a href="http:/ /wiki.ubuntu.org.cn/ Shell%E7%BC%96 %E7%A8%8 B%E5%9 F%BA%E7%A1%80 " data-original-title=" " title=" ">Ubuntu Shell 编程基础</a></p> <p>shell脚本的后缀为sh,在任何位置新建一个脚本文件,我选择在 /etc/mysql 目录下新建一个 listen.sh 文件。</p> <p>执行如下命令:</p>
经过处理后便会变成如下的样子:
1 2 3 4 5 6 7 8 9 10 11
前言 最近发现MySQL服务隔三差五就会挂掉,导致我的网站和爬虫都无法正常运作。自己的网站是基于MySQL,在做爬虫存取一些资料的时候也是基于MySQL,数据量一大了,MySQL它就有点受不了了,时不时会崩掉,虽然我自己有网站监控和邮件通知,但是好多时候还是需要我来手动连接我的服务器重新启动一下我的MySQL,这样简直太不友好了,所以,我就觉定自己写个脚本,定时监控它,如果发现它挂掉了就重启它。 好了,闲言碎语不多讲,开始我们的配置之旅。 运行环境:UbuntuLinux14.04 编写Shell 脚本 首先,我们要编写一个shell 脚本,脚本主要执行的逻辑如下: 显示mysqld进程状态,如果判断进程未在运行,那么输出日志到文件,然后启动mysql服务,如果进程在运行,那么不执行任何操作,可以选择性输出监测结果。 可能大家对于shell 脚本比较陌生,在这里推荐官方的shell脚本文档来参考一下 UbuntuShell编程基础 shell 脚本的后缀为sh,在任何位置新建一个脚本文件,我选择在/etc/mysql目录下新建一个listen.sh文件。 执行如下命令:
经过上面的处理,所有乱乱的代码都会被处理好了。
4.保存到数据库
在这里,我们想实现一个通用的方法,就是把存储的一个个内容变成字典的形式,然后执行插入语句的时候,自动构建对应的 sql 语句,插入数据。 比如我们构造如下的字典:
1 2 3 4 5 6 7 8
#构造最佳答案的字典 good_ans_dict = { "text" : good_ans[0 ], "answerer" : good_ans[1 ], "date" : good_ans[2 ], "is_good" : str(good_ans[3 ]), "question_id" : str(insert_id) }
构造 sql 语句并插入到数据库的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
def insertData(self , table, my_dict): try : self .db.set_character_set('utf8' ) cols = ', ' .join(my_dict.keys()) values = '"," ' .join(my_dict.values()) sql = "INSERT INTO %s (%s) VALUES (%s)" % (table, cols, '"' +values+'"' ) try : result = self .cur.execute(sql) insert_id = self .db.insert_id() self .db.commit() if result: return insert_id else : return 0 except MySQLdb.Error,e: self .db.rollback() if "key 'PRIMARY'" in e.args[1 ]: print self .getCurrentTime(),"数据已存在,未插入数据" else : print self .getCurrentTime(),"插入数据失败,原因 %d: %s" % (e.args[0 ], e.args[1 ]) except MySQLdb.Error,e: print self .getCurrentTime(),"数据库错误,原因%d: %s" % (e.args[0 ], e.args[1 ])
这里我们只需要传入那个字典,便会构建出对应字典键值和键名的 sql 语句,完成插入。
5.PHP 读取日志
我们将运行结果输出到了日志里,那么怎么查看日志呢?很简单,在这里提供两种方法 方法一: PHP 倒序输出所有日志内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<html > <head > <meta charset ="utf-8" > <meta http-equiv ="refresh" content = "5" > </head > <body > <?php $fp = file("out.log" ); if ($fp) { for ($i = count($fp) - 1 ;$i >= 0 ; $i --) echo $fp[$i]."<br>" ; } ?> </body > </html >
此方法可以看到所有的输入日志,但是如果日志太大了,那么就会报耗费内存太大,无法输出。为此我们就有了第二种方法,利用 linux 命令,输出后十行内容。 方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<html > <head > <meta charset ="utf-8" > <meta http-equiv ="refresh" content = "5" > </head > <body > <?php $ph = popen('tail -n 100 out.log' ,'r' ); while ($r = fgets($ph)){ echo $r."<br>" ; } pclose($ph); ?> </body > </html >
上面两种方法都是 5 秒刷新一次网页来查看最新的日志。
源代码放送
好了,闲言碎语不多讲,直接上源码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
import urllibimport urllib2import reimport timeimport typesimport pageimport mysqlimport sysfrom bs4 import BeautifulSoupclass Spider : def __init__ (self) : self.page_num = 1 self.total_num = None self.page_spider = page.Page() self.mysql = mysql.Mysql() def getCurrentTime (self) : return time.strftime('[%Y-%m-%d %H:%M:%S]' ,time.localtime(time.time())) def getCurrentDate (self) : return time.strftime('%Y-%m-%d' ,time.localtime(time.time())) def getPageURLByNum (self, page_num) : page_url = "http://iask.sina.com.cn/c/978-all-" + str(page_num) + ".html" return page_url def getPageByNum (self, page_num) : request = urllib2.Request(self.getPageURLByNum(page_num)) try : response = urllib2.urlopen(request) except urllib2.URLError, e: if hasattr(e, "code" ): print self.getCurrentTime(),"获取页面失败,错误代号" , e.code return None if hasattr(e, "reason" ): print self.getCurrentTime(),"获取页面失败,原因" , e.reason return None else : page = response.read().decode("utf-8" ) return page def getTotalPageNum (self) : print self.getCurrentTime(),"正在获取目录页面个数,请稍候" page = self.getPageByNum(1 ) pattern = re.compile(u'<span class="more".*?>.*?<span.*?<a href.*?class="">(.*?)</a>\s*<a.*?\u4e0b\u4e00\u9875</a>' , re.S) match = re.search(pattern, page) if match: return match.group(1 ) else : print self.getCurrentTime(),"获取总页码失败" def getQuestionInfo (self, question) : if not type(question) is types.StringType: question = str(question) pattern = re.compile(u'<span.*?question-face.*?>.*?<img.*?alt="(.*?)".*?</span>.*?<a href="(.*?)".*?>(.*?)</a>.*?answer_num.*?>(\d*).*?</span>.*?answer_time.*?>(.*?)</span>' , re.S) match = re.search(pattern, question) if match: author = match.group(1 ) href = match.group(2 ) text = match.group(3 ) ans_num = match.group(4 ) time = match.group(5 ) time_pattern = re.compile('\d{4}\-\d{2}\-\d{2}' , re.S) time_match = re.search(time_pattern, time) if not time_match: time = self.getCurrentDate() return [author, href, text, ans_num, time] else : return None def getQuestions (self, page_num) : page = self.getPageByNum(page_num) soup = BeautifulSoup(page) questions = soup.select("div.question_list ul li" ) for question in questions: info = self.getQuestionInfo(question) if info: url = "http://iask.sina.com.cn/" + info[1 ] ans = self.page_spider.getAnswer(url) print self.getCurrentTime(),"当前爬取第" ,page_num,"的内容,发现一个问题" ,info[2 ],"回答数量" ,info[3 ] ques_dict = { "text" : info[2 ], "questioner" : info[0 ], "date" : info[4 ], "ans_num" : info[3 ], "url" : url } insert_id = self.mysql.insertData("iask_questions" ,ques_dict) good_ans = ans[0 ] print self.getCurrentTime(),"保存到数据库,此问题的ID为" ,insert_id if good_ans: print self.getCurrentTime(),insert_id,"号问题存在最佳答案" ,good_ans[0 ] good_ans_dict = { "text" : good_ans[0 ], "answerer" : good_ans[1 ], "date" : good_ans[2 ], "is_good" : str(good_ans[3 ]), "question_id" : str(insert_id) } if self.mysql.insertData("iask_answers" ,good_ans_dict): print self.getCurrentTime(),"保存最佳答案成功" else : print self.getCurrentTime(),"保存最佳答案失败" other_anses = ans[1 ] for other_ans in other_anses: if other_ans: print self.getCurrentTime(),insert_id,"号问题存在其他答案" ,other_ans[0 ] other_ans_dict = { "text" : other_ans[0 ], "answerer" : other_ans[1 ], "date" : other_ans[2 ], "is_good" : str(other_ans[3 ]), "question_id" : str(insert_id) } if self.mysql.insertData("iask_answers" ,other_ans_dict): print self.getCurrentTime(),"保存其他答案成功" else : print self.getCurrentTime(),"保存其他答案失败" def main (self) : f_handler=open('out.log' , 'w' ) sys.stdout=f_handler page = open('page.txt' , 'r' ) content = page.readline() start_page = int(content.strip()) - 1 page.close() print self.getCurrentTime(),"开始页码" ,start_page print self.getCurrentTime(),"爬虫正在启动,开始爬取爱问知识人问题" self.total_num = self.getTotalPageNum() print self.getCurrentTime(),"获取到目录页面个数" ,self.total_num,"个" if not start_page: start_page = self.total_num for x in range(1 ,start_page): print self.getCurrentTime(),"正在抓取第" ,start_page-x+1 ,"个页面" try : self.getQuestions(start_page-x+1 ) except urllib2.URLError, e: if hasattr(e, "reason" ): print self.getCurrentTime(),"某总页面内抓取或提取失败,错误原因" , e.reason except Exception,e: print self.getCurrentTime(),"某总页面内抓取或提取失败,错误原因:" ,e if start_page-x+1 < start_page: f=open('page.txt' ,'w' ) f.write(str(start_page-x+1 )) print self.getCurrentTime(),"写入新页码" ,start_page-x+1 f.close() spider = Spider() spider.main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
import urllib import urllib2 import re import time import types import tool from bs4 import BeautifulSoup class Page: def __init__(self): self.tool = tool.Tool() def getCurrentDate(self): return time.strftime('%Y-%m-%d',time.localtime(time.time())) def getCurrentTime(self): return time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time())) def getPageByURL(self, url): try : request = urllib2.Request(url) response = urllib2.urlopen(request) return response.read().decode("utf-8") except urllib2.URLError, e: if hasattr(e, "code"): print self.getCurrentTime(),"获取问题页面失败,错误代号", e.code return None if hasattr(e, "reason"): print self.getCurrentTime(),"获取问题页面失败,原因", e.reason return None def getText(self, html): if not type(html) is types.StringType: html = str(html) pattern = re.compile('<pre.*?>(.*?)</pre>', re.S) match = re.search(pattern, html) if match: return match.group(1) else : return None def getGoodAnswerInfo(self, html): pattern = re.compile('"answer_tip.*?<a.*?>(.*?)</a>.*?<span class="time.*?>.*?\|(.*?)</span>', re.S) match = re.search(pattern, html) if match: time = match.group(2) time_pattern = re.compile('\d{2}\-\d{2}\-\d{2}', re.S) time_match = re.search(time_pattern, time) if not time_match: time = self.getCurrentDate() else : time = "20"+time return [match.group(1),time] else : return [None,None] def getGoodAnswer(self, page): soup = BeautifulSoup(page) text = soup.select("div.good_point div.answer_text pre") if len(text) > 0: ansText = self.getText(str(text[0])) ansText = self.tool.replace(ansText) info = soup.select("div.good_point div.answer_tip") ansInfo = self.getGoodAnswerInfo(str(info[0])) answer = [ansText, ansInfo[0], ansInfo[1],1] return answer else : return None def getOtherAnswerInfo(self, html): if not type(html) is types.StringType: html = str(html) pattern = re.compile('"author_name.*?>(.*?)</a>.*?answer_t">(.*?)</span>', re.S) match = re.search(pattern, html) if match: time = match.group(2) time_pattern = re.compile('\d{2}\-\d{2}\-\d{2}', re.S) time_match = re.search(time_pattern, time) if not time_match: time = self.getCurrentDate() else : time = "20"+time return [match.group(1),time] else : return [None,None] def getOtherAnswers(self, page): soup = BeautifulSoup(page) results = soup.select("div.question_box li.clearfix .answer_info") answers = [] for result in results: ansSoup = BeautifulSoup(str(result)) text = ansSoup.select(".answer_txt span pre") ansText = self.getText(str(text[0])) ansText = self.tool.replace(ansText) info = ansSoup.select(".answer_tj") ansInfo = self.getOtherAnswerInfo(info[0]) answer = [ansText, ansInfo[0], ansInfo[1],0] answers.append(answer) return answers def getAnswer(self, url): if not url: url = "http://iask.sina.com.cn/b/gQiuSNCMV.html" page = self.getPageByURL(url) good_ans = self.getGoodAnswer(page) other_ans = self.getOtherAnswers(page) return [good_ans,other_ans] page = Page() page.getAnswer(None)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#-*- coding:utf-8 -*- import re #处理页面标签类 class Tool: #将超链接广告剔除 removeADLink = re.compile( #去除img标签,1-7位空格, removeImg = re.compile( #删除超链接标签 removeAddr = re.compile( #把换行的标签换为\n replaceLine = re.compile( #将表格制表<td>替换为\t replaceTD= re.compile( #将换行符或双换行符替换为\n replaceBR = re.compile( #将其余标签剔除 removeExtraTag = re.compile( #将多行空行删除 removeNoneLine = re.compile( def replace(self,x): x = re.sub (self.removeADLink,"" ,x) x = re.sub (self.removeImg,"" ,x) x = re.sub (self.removeAddr,"" ,x) x = re.sub (self.replaceLine,"\n" ,x) x = re.sub (self.replaceTD,"\t" ,x) x = re.sub (self.replaceBR,"\n" ,x) x = re.sub (self.removeExtraTag,"" ,x) x = re.sub (self.removeNoneLine,"\n" ,x) #strip()将前后多余内容删除 return x.strip()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
import MySQLdb import time class Mysql : def getCurrentTime (self ) : return time.strftime('[%Y-%m-%d %H:%M:%S]' ,time.localtime(time.time())) def __init__ (self ) : try: self .db = MySQLdb.connect('ip' ,'username' ,'password' ,'db_name' ) self .cur = self .db.cursor() except MySQLdb.Error,e: print self .getCurrentTime(),"连接数据库错误,原因%d: %s" % (e.args[0 ], e.args[1 ]) def insertData (self , table, my_dict) : try: self .db.set_character_set('utf8' ) cols = ', ' .join(my_dict.keys()) values = '"," ' .join(my_dict.values()) sql = "INSERT INTO %s (%s) VALUES (%s)" % (table, cols, '"' +values+'"' ) try: result = self .cur.execute(sql) insert_id = self .db.insert_id() self .db.commit() if result: return insert_id else: return 0 except MySQLdb.Error,e: self .db.rollback() if "key 'PRIMARY'" in e.args[1 ]: print self .getCurrentTime(),"数据已存在,未插入数据" else: print self .getCurrentTime(),"插入数据失败,原因 %d: %s" % (e.args[0 ], e.args[1 ]) except MySQLdb.Error,e: print self .getCurrentTime(),"数据库错误,原因%d: %s" % (e.args[0 ], e.args[1 ])
数据库建表 SQL 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
CREATE TABLE IF NOT EXISTS `iask_answers` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '自增ID' , `text` text NOT NULL COMMENT '回答内容' , `question_id` int (18 ) NOT NULL COMMENT '问题ID' , `answerer` varchar (255 ) NOT NULL COMMENT '回答者' , `date` varchar (255 ) NOT NULL COMMENT '回答时间' , `is_good` int (11 ) NOT NULL COMMENT '是否是最佳答案' , PRIMARY KEY (`id` ) ) ENGINE =InnoDB DEFAULT CHARSET =utf8; CREATE TABLE IF NOT EXISTS `iask_questions` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '问题ID' , `text` text NOT NULL COMMENT '问题内容' , `questioner` varchar (255 ) NOT NULL COMMENT '提问者' , `date` date NOT NULL COMMENT '提问时间' , `ans_num` int (11 ) NOT NULL COMMENT '回答数量' , `url` varchar (255 ) NOT NULL COMMENT '问题链接' , PRIMARY KEY (`id` ) ) ENGINE =InnoDB DEFAULT CHARSET =utf8;
运行的时候执行如下命令即可
1
nohup python spider.py &
代码写的不好,仅供大家学习参考使用,如有问题,欢迎留言交流。
运行结果查看
我们把 PHP 文件和 log 文件放在同一目录下,运行 PHP 文件,便可以看到如下的内容: 小伙伴们赶快试一下吧。
作者
崔庆才
发表于
2015-09-08
阅读次数:
本文字数:
18k
阅读时长 ≈
16 分钟
前言
最近发现 MySQL 服务隔三差五就会挂掉,导致我的网站和爬虫都无法正常运作。自己的网站是基于 MySQL,在做爬虫存取一些资料的时候也是基于 MySQL,数据量一大了,MySQL 它就有点受不了了,时不时会崩掉,虽然我自己有网站监控和邮件通知,但是好多时候还是需要我来手动连接我的服务器重新启动一下我的 MySQL,这样简直太不友好了,所以,我就觉定自己写个脚本,定时监控它,如果发现它挂掉了就重启它。 好了,闲言碎语不多讲,开始我们的配置之旅。 运行环境:Ubuntu Linux 14.04
编写 Shell 脚本
首先,我们要编写一个 shell 脚本,脚本主要执行的逻辑如下: 显示 mysqld 进程状态,如果判断进程未在运行,那么输出日志到文件,然后启动 mysql 服务,如果进程在运行,那么不执行任何操作,可以选择性输出监测结果。 可能大家对于 shell 脚本比较陌生,在这里推荐官方的 shell 脚本文档来参考一下 Ubuntu Shell 编程基础 shell 脚本的后缀为 sh,在任何位置新建一个脚本文件,我选择在 /etc/mysql 目录下新建一个 listen.sh 文件。 执行如下命令:
1 2 3
cd /etc/mysqltouch listen.sh vi listen.sh
进入到 vi 中,我们添加如下脚本内容:
1 2 3 4 5 6 7 8 9
#!/bin/bash pgrep mysqld &> /dev/null if [ $? -gt 0 ]then echo "`date` mysql is stop" service mysql start else echo "`date` mysql running" fi
其中 pgrep mysqld 是监测 mysqld 服务的运行状态,&> /dev/null 是将其结果输出到空文件,也就是不保存输出信息 $? 是拿到上一条命令的运行结果,-gt 0 是判断是否大于 0,后面则是输出时间到日志文件,然后启动 mysql,否则不启动 mysql 保存好了,那么我们执行如下的命令,来测试一下。 贴心的命令文字版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
root@iZ28uogb3laZ :/etc/mysql root@iZ28uogb3laZ :/etc/mysql 3359 root@iZ28uogb3laZ :/etc/mysql root@iZ28uogb3laZ :/etc/mysql Sun Aug 16 16 : 44:58 CST 2015 mysql running root@iZ28uogb3laZ :/etc/mysql mysql stop/waiting root@iZ28uogb3laZ :/etc/mysql Sun Aug 16 16 : 45:17 CST 2015 mysql is stop mysql start/running, process 4084 root@iZ28uogb3laZ :/etc/mysql Sun Aug 16 16 : 45:24 CST 2015 mysql running root@iZ28uogb3laZ :/etc/mysql
嗯,编辑完了.sh 文件之后,我们首先要对其进行授权,增加可执行的权限。
1
sudo chmod 777 listen .sh
然后运行脚本测试一下,显示 mysql 正在运行。把 mysql 关掉,运行脚本,便会检测到 mysql 已关闭,然后重新启动了 mysql,再次运行,便会发现 mysql 正常运行了。 注:这里我比较纳闷 shell 脚本中 pgrep mysqld 的返回结果是什么。为什么它大于 0 便代表 mysql 服务挂掉了呢?感觉逻辑有点是相反的,不过实测可用有效。如果大家明白为什么,非常希望您可以给我一个解释。
修改日志输出
好,接下来我们把输出的内容保存到日志里。修改脚本文件如下
1 2 3 4 5 6 7 8 9
#!/bin/bash pgrep mysqld &> /dev/null if [ $? -gt 0 ]then echo "`date` mysql is stop" >> /var/log /mysql_listen.logservice mysql start else echo "`date` mysql running" >> /var/log /mysql_listen.logfi
这样,每执行一次脚本,输出结果都会被保存到 /var/log/mysql_listen.log 中了。
添加定时任务
好了,脚本可以顺利执行了,那么我们就需要定时调用一下这个脚本来运行了,我们需要用到 cron。 首先我们需要编辑一下 corn 调度表格,命令如下:
如果你是第一次编辑这个,他会让你选择文件打开方式,随便选一个数字就好了。 比如我们用 GNU 打开的,我们就在它的最后一行添加下面的一句话即可。 、 文字版本:
1
*/5 * * * * / etc/mysql/my sql_listen.sh
/5 代表五分钟执行一次,后面的四个点依次代表了,小时,日,月,星期。如果想要时间长一些,比如一小时调度一次,那就设置一下后面第一个*就好了。 好,保存一下,重启 cron 服务。
嗯,调度任务已经添加进去了,这样,每五分钟系统就会调用一下刚才写的那个脚本。 过一段时间,我们来看一下运行效果,嗯,监控跑的很顺利呐。 文字版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Sun Aug 16 15 :39 :12 CST 2015 mysql running Sun Aug 16 15 :40 :01 CST 2015 mysql running Sun Aug 16 15 :45 :02 CST 2015 mysql running Sun Aug 16 15 :50 :01 CST 2015 mysql running Sun Aug 16 15 :55 :01 CST 2015 mysql running Sun Aug 16 16 :00 :01 CST 2015 mysql running Sun Aug 16 16 :05 :01 CST 2015 mysql running Sun Aug 16 16 :10 :01 CST 2015 mysql running Sun Aug 16 16 :15 :01 CST 2015 mysql running Sun Aug 16 16 :20 :01 CST 2015 mysql running Sun Aug 16 16 :25 :01 CST 2015 mysql running Sun Aug 16 16 :30 :01 CST 2015 mysql running Sun Aug 16 16 :35 :01 CST 2015 mysql running Sun Aug 16 16 :40 :01 CST 2015 mysql running Sun Aug 16 16 :51 :04 CST 2015 mysql running
哈哈,是不是五分钟监测了一次呢?大功告成。
后记
这样,我们就实现了五分钟定时检测 MySQL 进程服务,妈妈再也不用担心我的网站会宕掉啦。 如有问题,欢迎留言交流,谢谢啦。
作者
崔庆才
发表于
2015-08-16
阅读次数:
本文字数:
2.9k
阅读时长 ≈
3 分钟
最近在准备C语言的上级考试,之前对C接触不多,在练习过程中把一些小知识点记录下来。
1.字符串的截取
利用strncpy函数,传入三个参数,分别为目标字符串,起始位置,长度。 例如将日期字符串转化为数字,如20120112
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char date1[20 ],date2[20 ]; scanf ("%s" ,&date1); scanf ("%s" ,&date2); char temp[4 ]; int year1 = atoi(strncpy (temp,date1,4 )); int year2 = atoi(strncpy (temp,date2,4 )); printf ("year1:%d\n" ,year1); printf ("year2:%d\n" ,year2); char temp2[2 ]; int month1 = atoi(strncpy (temp2,date1+4 ,2 )); int month2 = atoi(strncpy (temp2,date2+4 ,2 )); printf ("month1:%d\n" ,month1); printf ("month2:%d\n" ,month2); int day1 = atoi(strncpy (temp2,date1+6 ,2 )); int day2 = atoi(strncpy (temp2,date2+6 ,2 )); printf ("day1:%d\n" ,day1); printf ("day2:%d\n" ,day2); return 0 ; }
以上便实现了输入一个日期然后对其进行分割的操作。
2.二维数组的动态声明
利用malloc可以实现数组的动态声明
1 2 3 4 5 6 7 8 9
int **a; a = (int **)malloc(2 *sizeof(int *)); int i,j; for (i = 0 ; i < 2 ; i ++) { a[i] = (int *)malloc(3 *sizeof(int )); for (j = 0 ; j < 3 ; j++) { scanf("%d" ,&a[i][j]); } }
以上便实现了动态数组的分配,利用scanf为数组赋值
3.二维数组的声明和初始化
头文件
初始化和测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int result[2 ][2 ]; for (i = 0 ; i < 2 ; i ++) { for (j = 0 ; j < 2 ; j ++) { printf("%d " ,result[i ][j ]); } printf("\n" ); } memset(result,0 ,sizeof(int)*4 ); for (i = 0 ; i < 2 ; i ++) { for (j = 0 ; j < 2 ; j ++) { printf("%d " ,result[i ][j ]); } printf("\n" ); }
结果
1 2 3 4
4196944 0 4195696 0 0 0 0 0
上述是数组的非动态声明
4.快速排序
假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。一趟快速排序的算法是: 1)设置两个变量I、J,排序开始的时候 I=0,J=N-1; 2)以第一个数组元素作为关键数据,赋值给X,即X=A[0]; 3)从J开始向前搜索,即由后开始向前搜索,找到第一个小于X的值,两者交换; 4)从I开始向后搜索,即由前开始向后搜索,找到第一个大于X的值,两者交换; 5)重复第3、4步,直到I=J; 例如:待排序的数组A的值分别是:(初始关键数据X:=49) A[0] A[1] A[2] A[3] A[4] A[5] A[6] 49 38 65 97 76 13 27 进行第一次交换后: 27 38 65 97 76 13 49 ( 按照算法的第三步从后面开始找 ) 进行第二次交换后: 27 38 49 97 76 13 65 ( 按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时I=3 ) 进行第三次交换后: 27 38 13 97 76 49 65 ( 按照算法的第五步将又一次执行算法的第三步从后开始找) 进行第四次交换后: 27 38 13 49 76 97 65 ( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J=4 ) 此时再执行第三不的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后: 27 38 13 49 76 97 65 即所有大于49的数全部在49的后面,所有小于49的数全部在49的前面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include <stdio.h> void quiksort(int a[],int low,int high){ int i = low; int j = high; int temp = a[i]; if ( low < high) { while (i < j) { while ((a[j] >= temp) && (i < j)) { j--; } a[i] = a[j]; while ((a[i] <= temp) && (i < j)) { i++; } a[j]= a[i]; } a[i] = temp; quiksort(a,low,i-1 ); quiksort(a,j+1 ,high); } else { return ; } } int main(){ int arr[5 ] = {23 ,1 ,21 ,4 ,19 }; quiksort(arr,0 ,4 ); int i; for (i=0 ;i<5 ;i++) { printf("%d " ,arr[i]); } printf("\n" ); return 0 ; }
快速排序代码如上
5.字符串拷贝
小例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char str[20 ] = "hello" ; char *a = "world" ; strcpy (str,a); printf ("%s" ,str); printf ("\n" ); system("pause" ); return 0 ; }
运行结果:world,即把后者完全覆盖前者。
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char *str = new char [6 ]; char *a = "world" ; strcpy (str,a); printf ("%s" , str); printf ("\n" ); system("pause" ); return 0 ; }
运行结果一致 某一长度的字符串截取
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char *str = new char [6 ]; char *a = "world" ; strncpy (str,a+1 ,5 ); printf ("%s" , str); printf ("\n" ); system("pause" ); return 0 ; }
运行结果:orld
6.字符串的拼接
小例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char str[20 ] = "hello " ; char *a = "world" ; char *x = strcat (str,a); printf ("%s\n" , x); printf ("%s\n" , x); printf ("\n" ); system("pause" ); return 0 ; }
运行结果: hello world hello world 此函数既返回结果,又将目标字符串赋值
7.字符串查找匹配
例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char str[20 ] = "hello " ; char *x = strchr (str,'e' ); printf ("%d\n" , x - str); printf ("%s\n" ,x); printf ("\n" ); system("pause" ); return 0 ; }
运行结果: 1 ello
8.字符串比较
例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char str[20 ] = "hello " ; char str2[20 ] = "hello2" ; int x = strcmp (str, str2); printf ("%d\n" ,x); printf ("\n" ); system("pause" ); return 0 ; }
运行结果-1 忽略大小写
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char str[20 ] = "hello" ; char str2[20 ] = "Hello" ; int x = stricmp(str, str2); printf ("%d\n" ,x); printf ("\n" ); system("pause" ); return 0 ; }
运行结果0
9.字符串分割
示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#include <stdio.h> #include <string.h> #include <stdlib.h> int main () { char test1[] = "feng,ke,wei" ; char x[3 ][30 ]; char *p; p = strtok(test1, "," ); int count = 0 ; while (p) { printf ("%s\n" , p); strcpy (x[count],p); count++; p = strtok(NULL , "," ); } for (int i = 0 ; i<count; i ++) { printf ("%s " , x[i]); } system("pause" ); return 0 ; }
运行结果 feng ke wei feng ke wei
10.格式化输出几位小数
例如
则是输出5位小数 又如
1
printf ("%5.1f" ,1.2345 );
则是控制总位数为5,小数点后为1位,不够的在前面补空格
作者
崔庆才
发表于
2015-07-18
阅读次数:
本文字数:
4.8k
阅读时长 ≈
4 分钟
本节任务
本次我们需要完成的任务是 完成两台主机通过中间主机的数据通信(网络层)
其实最主要的就是基于IP地址的转发功能,网络层的封装其实我们在初级功能中就已经做好了。
原理
首先,实验的思路是A通过中间主机B向C发送数据。那么B则作为一个路由器,B要监听两个网卡,一个网卡发来的数据通过另一个网卡发出去。 示意图如下: A————->B1===B2——————>C 从图上可以看出,B主机的两个网卡数据互通,A和B1则处于一个局域网内,B2和C处于另一个局域网内。 就比如这样,现在室友A在用有线上网,我的电脑B也在用有线上网,我们的有线处在同一局域网,我的电脑B同时散着一个无线网,我的手机C又连接到了这个无线上。 那么要实现A到C的数据传送,即模拟室友A要发送数据到我的手机C,那么流程则是这样的: 室友A在有线局域网发送数据到我的网卡B1,B1将数据通过网卡B2转发到无线局域网,通过无线局域网到达我的手机C。 A的发送要构建一个帧,目的MAC地址为B1,目的IP为C。B则要开启两个网卡,B1监听接收数据,B2网卡则要用ARP协议扫描所在无线局域网内的IP和MAC,B获取到了A发来的帧之后,解析它的IP地址和MAC地址,匹配刚才扫描得到的IP和MAC对应表,将源MAC换成B2网卡MAC,目的MAC换成C的MAC,IP不变,数据data不变。构建新帧之后发送出去。 好啦,思路大体就是这样。
实战
需要三个程序,一个是发送,一个路由,一个接收。所以一共三个程序要同时运行起来执行。 以上是我的大体思路,如有错误,还请指正。现已用代码实现完毕。 代码暂不公开,只提供部分重点代码解析:
一、发送端
其实发送端和初级功能的发送差不多 个人编写的交互流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
IP地址:121.250 .216 .221 MAC地址:3 c970e4b56d6con:127 ------------------------------------------- IP地址:121.250 .216 .227 MAC地址:089e01 b948f4con:128 ------------------------------------------- IP地址:121.250 .216 .228 MAC地址:10 bf48705aeecon:129 获取MAC地址完毕,请输入你要发送对方的IP地址: 192.168 .1 .3 请输入你要发送的内容: im cqc 要发送的内容:im cqc
具体代码不再解析,同上一篇初级功能。
二、路由端
首先要开启两个网卡,声明两个网卡对象和处理器
1 2
pcap_if_t *d,*d2; pcap_t *adhandle,*adhandle2;
一个用来接收一个用来发送,这里定义了adhandle是用来发送,adhandle2是用来接收数据。 那么打开适配器就在main方法中,提前打开两个网卡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
int num; printf("请输入你要转发数据的网卡代号:\n" ); scanf_s("%d" ,&num); for (d=alldevs, i=0; i< num-1 ; d=d-> next, i++); if ((adhandle = pcap_open(d->name , 65535 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL, errbuf )) == NULL){ fprintf (stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name ); pcap_freealldevs(alldevs); return -1 ; } int num2; printf("请输入你要接收数据的网卡代号:" ); scanf_s("%d" ,&num2); for (d2=alldevs, i=0; i< num2-1 ; d2=d2-> next, i++); if ((adhandle2 = pcap_open(d2->name , 65535 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL, errbuf )) == NULL){ fprintf (stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d2->name );
接下来用用于发送的handle处理器来扫描它的局域网IP,获得局域网内的MAC地址,记录在一个表中,存放IP和MAC的对应关系。 这个表可以用结构体数组来保存,比如可以这样:
1 2 3 4
struct ip_mac_list{ IpAddress ip; unsigned char mac[6]; };
那么以上便是准备工作,我们完成了两个网卡的打开,发送网卡扫描获取局域网MAC,接下来便是最重要的监听加转发。 那么这个怎办?那就开一个新线程。 让我们声明一个新的路由线程。
1
DWORD WINAPI RouteThread(LPVOID lpParameter)
那么线程要接收进来什么参数呢? 首先必须要的是两个网卡处理器,在main方法中已经做好初始化的adhandle和adhandle2,另外还有alldevs,可以持有这个指针来释放设备列表,出现错误时释放资源并退出。 初级功能中声明过了 struct sparam sp; struct gparam gp; 这两个就是发送ARP线程和接收ARP线程中的两个参数,那么仿照这个功能,我们定义一个新的结构体
1 2 3 4 5
struct rparam { pcap_t *adhandle_rec; pcap_t *adhandle_send; pcap_if_t * alldevs; };
在main方法中把它来初始化赋值
1 2 3
rp.adhandle_send = adhandle rp.adhandle_rec = adhandle2 rp.alldevs = alldevs
当做参数传入这个线程
1 2
routethread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE ) RouteThread, &rp , 0 , NULL)
其中第四个参数就是传递了这个结构体进去。注意这个语句最好不要直接放在main方法中直接调用,可以在全部获取完MAC地址之后再开启这个线程。 那么接下来就说一下这个线程都干了些什么,只简略说一下核心部分。 首先开启了这个线程之后会一直都在执行,那么就可以加入
1
while((res = pcap_next_ex(adhandle2 ,&header ,&pkt_data ))>=0)
这样的while判断语句来一直监听数据包的接收,然后解析数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
ethernet = (EthernetHeader *)(pkt_data); for (int i=0 ;i<6 ;i++){ sou_mac[i] = ethernet->SourMAC[i]; } for (int i=0 ;i<6 ;i++){ des_mac[i] = ethernet->DestMAC[i]; } // 获得IP数据包头部的位置 ip = (IpHeader *) (pkt_data +14 ); // 14 为以太网帧头部长度 //获得TCP头部的位置 ip_len = (ip->Version_HLen & 0xf ) *4 ; tcp = (TcpHeader *)((u_char *)ip+ip_len); data = (char *)((u_char *)tcp+20 ); printf ("data:%s\n" ,data); printf ("ip:" ); printf ("%d.%d.%d.%d -> %d.%d.%d.%d\n" , ip->SourceAddr.byte1, ip->SourceAddr.byte2, ip->SourceAddr.byte3, ip->SourceAddr.byte4, ip->DestinationAddr.byte1, ip->DestinationAddr.byte2, ip->DestinationAddr.byte3, ip->DestinationAddr.byte4); printf ("sou_mac:%02x-%02x-%02x-%02x-%02x-%02x\n" , sou_mac[0 ], sou_mac[1 ], sou_mac[2 ], sou_mac[3 ], sou_mac[4 ], sou_mac[5 ]); printf ("des_mac:%02x-%02x-%02x-%02x-%02x-%02x\n" , des_mac[0 ], des_mac[1 ], des_mac[2 ], des_mac[3 ], des_mac[4 ], des_mac[5 ]);
然后接下来每接收到一个数据,就进行构建新的帧转发出去,目的MAC先匹配list表,如果list没有找到,那么我让他指定了一个mac,比如广播MAC。源MAC地址则赋值网卡的MAC地址。 注意,传统以太网中数据长度为45-1500,那么我在构建前把解析出的data作了下判断长度再构建,因为我已经把sendbuffer声明为一个固定长度了,为了防止越界,我先进行一个长度判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
if(strlen(data)<1500 ){ BYTE send_destmac[6 ]; bool findMac = false; for(int c = 0 ;c<con;c++){ if(ip->DestinationAddr.byte1 == list [c].ip.byte1&& ip->DestinationAddr.byte2 == list [c].ip.byte2&& ip->DestinationAddr.byte3 == list [c].ip.byte3&& ip->DestinationAddr.byte4 == list [c].ip.byte4) { printf("Find its MAC!\n " ); findMac = true; send_destmac[0 ] = list [c].mac[0 ]; send_destmac[1 ] = list [c].mac[1 ]; send_destmac[2 ] = list [c].mac[2 ]; send_destmac[3 ] = list [c].mac[3 ]; send_destmac[4 ] = list [c].mac[4 ]; send_destmac[5 ] = list [c].mac[5 ]; } } if(!findMac){ send_destmac[0 ] = 0xff ; send_destmac[1 ] = 0xff ; send_destmac[2 ] = 0xff ; send_destmac[3 ] = 0xff ; send_destmac[4 ] = 0xff ; send_destmac[5 ] = 0xff ; } printf("destmac:%02x-%02x-%02x-%02x-%02x-%02x\n " , send_destmac[0 ],send_destmac[1 ],send_destmac[2 ], send_destmac[3 ],send_destmac[4 ],send_destmac[5 ] ); memcpy(send_ethernet.DestMAC, send_destmac, 6 ); BYTE send_hostmac[6 ]; send_hostmac[0 ] = local_mac[0 ]; send_hostmac[1 ] = local_mac[1 ]; send_hostmac[2 ] = local_mac[2 ]; send_hostmac[3 ] = local_mac[3 ]; send_hostmac[4 ] = local_mac[4 ]; send_hostmac[5 ] = local_mac[5 ]; memcpy(send_ethernet.SourMAC, send_hostmac, 6 ); send_ethernet.EthType = htons(0x0800 ); memcpy(&SendBuffer, &send_ethernet, sizeof(struct EthernetHeader));
以上只是赋值了帧头,至于IP头,TCP头,数据的赋值就参照初级功能的来赋值吧,不要忘了校验和的检验。好,大体上就是这样,接受来数据包并转发出去的原理就是这样。
三、接收
不用多改,就是初级功能中的接收,在此写一写小小的优化措施,防止接收到过多的数据帧而造成不断乱蹦,导致你看不到接收的东西。 在打印的时候加一个过滤就好了。部分代码如下: 在main方法中提示用户输入要接收的IP地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
printf("请输入要接收的IP地址,输入0.0.0.0代表全部接收,请输入\n" ); bool receiveAll = false ; u_int ip1,ip2,ip3,ip4; bool legal = false ; while (!legal){ scanf_s("%d.%d.%d.%d" ,&ip1,&ip2,&ip3,&ip4); if (ip1==0 &&ip2==0 &&ip3==0 &&ip4==0 ){ receiveAll = true ; legal = true ; break ; } if (ip1<0 ||ip1>255 ||ip2<0 ||ip2>255 ||ip3<0 ||ip3>255 ||ip4<1 ||ip4>254 ){ legal = false ; printf("对不起,IP输入不合法,请重新输入:\n" ); }else { legal = true ; } }
打印时的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
if (receiveAll||(ip->SourceAddr.byte1==ip1&& ip->SourceAddr.byte2==ip2&& ip->SourceAddr.byte3==ip3&& ip->SourceAddr.byte4==ip4)){ printf ("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n" , ip->SourceAddr.byte1, ip->SourceAddr.byte2, ip->SourceAddr.byte3, ip->SourceAddr.byte4, sport, ip->DestinationAddr.byte1, ip->DestinationAddr.byte2, ip->DestinationAddr.byte3, ip->DestinationAddr.byte4, dport); printf ("sou_mac:%02x-%02x-%02x-%02x-%02x-%02x\n" , sou_mac[0 ], sou_mac[1 ], sou_mac[2 ], sou_mac[3 ], sou_mac[4 ], sou_mac[5 ]); printf ("des_mac:%02x-%02x-%02x-%02x-%02x-%02x\n" , des_mac[0 ], des_mac[1 ], des_mac[2 ], des_mac[3 ], des_mac[4 ], des_mac[5 ]); printf ("%s\n" ,data); printf ("-----------------------------------------------------\n" ); }
好,代码就先放送这么多,具体的实现只要有了思路我相信肯定不难,如有问题,欢迎与我交流。
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
7.3k
阅读时长 ≈
7 分钟
学习文档
这里我们直接进入正题吧,关于Winpcap的基础知识讲解这里就不再赘述了。在这里给大家提供一些学习网址 Winpcap网络编程及通信教程 Winpcap中文技术文档
学习内容
获取设备列表
获取已安装设备的高级信息
打开适配器并捕获数据包
不用回调方法捕获数据包
过滤数据包
分析数据包
处理脱机堆文件
发送数据包
收集并统计网络流量
这些内容,在上述两个链接中均已经有了比较详细的讲解,希望对大家有帮助。
两台主机通信实战
完成两台主机之间的数据通信(数据链路层)
仿真ARP协议获得网段内主机的MAC表
使用帧完成两台主机的通信(Hello! I’m …)
首先我们要理解ARP是干嘛的,ARP主要作用就是通过IP地址来获取MAC地址。那么怎样获取呢?本机向局域网内主机发送ARP包,ARP包内包含了目的IP,源IP,目的MAC,源MAC,其中目的MAC地址为广播地址,FF-FF-FF-FF-FF-FF,即向局域网内所有主机发送一个ARP请求,那么其他主机收到这个请求之后则会向请求来源返回一个数据包。在这个返回的数据包中包含了自身的MAC地址。那么本机收到这些返回的数据包进行解析之后便会得到局域网内所有主机的MAC地址了.. 编程开始: 新建一个C++项目,配好环境,引入Winpcap相关的库,这些不再赘述。 头文件引入
1 2 3 4 5
#define HAVE_REMOTE #define WPCAP #include <stdio.h> #include <stdlib.h> #include <pcap.h>
在main函数中首先声明一系列变量如下
1 2 3
char *ip_addr; char *ip_netmask; unsigned char *ip_mac;
为这三个变量分配地址空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
ip_addr = (char *) malloc (sizeof (char ) * 16 ); if (ip_addr == NULL ) { printf ("申请内存存放IP地址失败!\n" ); return -1 ; } ip_netmask = (char *) malloc (sizeof (char ) * 16 ); if (ip_netmask == NULL ) { printf ("申请内存存放NETMASK地址失败!\n" ); return -1 ; } ip_mac = (unsigned char *) malloc (sizeof (unsigned char ) * 6 ); if (ip_mac == NULL ) { printf ("申请内存存放MAC地址失败!\n" ); return -1 ; }
接下来就是烂大街的程序,获取适配器列表并选中相应的适配器,注释已经在代码中了,如果还有不明白的请参照前几次的讲解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1 ){ fprintf(stderr,"Error in pcap_findalldevs_ex:\n" ,errbuf); exit(1 ); } for (d = alldevs;d !=NULL;d = d-> next){ printf ("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name ); if (d-> description){ printf ("description:%s\n",d-> description); }else { printf("description:%s" ,"no description\n" ); } printf ("\tLoopback: %s\n",(d-> flags & PCAP_IF_LOOPBACK)?"yes" :"no" ); pcap_addr_t *a; for (a = d->addresses ;a;a = a-> next){ switch (a->addr -> sa_family) { case AF_INET: printf("Address Family Name:AF_INET\n" ); if (a-> addr){ printf ("Address:%s\n",iptos(((struct sockaddr_in *)a->addr )-> sin_addr.s_addr)); } if (a-> netmask){ printf ("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask )-> sin_addr.s_addr)); } if (a-> broadaddr){ printf ("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr )-> sin_addr.s_addr)); } if (a-> dstaddr){ printf ("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr )-> sin_addr.s_addr)); } break; case AF_INET6: printf("Address Family Name:AF_INET6\n" ); printf("this is an IPV6 address\n" ); break; default: break; } } } if (i == 0 ){ printf("interface not found,please check winpcap installation" ); } int num; printf("Enter the interface number(1-%d):" ,i); scanf_s("%d" ,&num); printf("\n" ); if (num<1 ||num>i){ printf("number out of range\n" ); pcap_freealldevs(alldevs); return -1 ; } for (d=alldevs, i=0; i< num-1 ; d=d-> next, i++); if ((adhandle = pcap_open(d->name , 65535 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL, errbuf )) == NULL){ fprintf (stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name ); pcap_freealldevs(alldevs); return -1 ; }
上述代码中需要另外声明的有:
1 2 3 4 5
pcap_if_t * alldevs; pcap_if_t *d; char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *adhandle; int i = 0 ;
1
char *iptos (u_long in) ;
1 2 3 4 5 6 7 8 9 10 11 12 13
#define IPTOSBUFFERS 12 char *iptos(u_long in ) { static char output[IPTOSBUFFERS][3 *4 +3 +1 ]; static short which; u_char *p; p = (u_char *)&in ; which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1 ); sprintf_s(output[which], "%d.%d.%d.%d" , p[0 ], p[1 ], p[2 ], p[3 ]); return output[which]; }
到此程序应该会编译通过,可以试着编译一下运行。 GO ON… 接下来我们首先要用ifget方法获取自身的IP和子网掩码 函数声明:
1
void ifget (pcap_if_t *d, char *ip_addr, char *ip_netmask) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
//获取IP和子网掩码赋值为ip_addr和ip_netmask void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask) { pcap_addr_t *a; // 遍历所有的地址,a代表一个pcap_addr for (a = d->addresses; a; a = a->next) { switch (a->addr->sa_family) { case AF_INET: //sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式。通常用的都是AF_INET。代表IPV4 if (a->addr) { char *ipstr; // 将地址转化为字符串 ipstr = iptos(((struct sockaddr_in *) a->addr)->sin_addr.s_addr) ; //*ip_addr printf ("ipstr:%s\n" ,ipstr) ; memcpy (ip_addr, ipstr, 16 ) ; } if (a->netmask) { char *netmaskstr ; netmaskstr = iptos (((struct sockaddr_in *) a->netmask)->sin_addr.s_addr) ; printf ("netmask:%s\n" ,netmaskstr) ; memcpy (ip_netmask, netmaskstr, 16 ) ; } case AF_INET6 : break ; } } }
main函数继续写,如下调用,之前声明的ip_addr和ip_netmask就已经被赋值了
到现在我们已经获取到了本机的IP和子网掩码,下一步发送一个ARP请求来获取自身的MAC地址 这个ARP请求的源IP地址就随便指定了,就相当于你构造了一个外来的ARP请求,本机捕获到了请求,然后发送回应给对方的数据包也被本机捕获到了并解析出来了。解析了自己发出去的数据包而已。 那么我们就声明一个函数并实现:
1
int GetSelfMac(pcap_t * adhandle , const char * ip_addr , unsigned char * ip_mac ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
int GetSelfMac (pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac) { unsigned char sendbuf[42 ]; int i = -1 ; int res; EthernetHeader eh; Arpheader ah; struct pcap_pkthdr * pkt_header ; const u_char * pkt_data; memset (eh.DestMAC, 0xff , 6 ); memset (eh.SourMAC, 0x0f , 6 ); memset (ah.DestMacAdd, 0x0f , 6 ); memset (ah.SourceMacAdd, 0x00 , 6 ); eh.EthType = htons(ETH_ARP); ah.HardwareType= htons(ARP_HARDWARE); ah.ProtocolType = htons(ETH_IP); ah.HardwareAddLen = 6 ; ah.ProtocolAddLen = 4 ; ah.SourceIpAdd = inet_addr("100.100.100.100" ); ah.OperationField = htons(ARP_REQUEST); ah.DestIpAdd = inet_addr(ip_addr); memset (sendbuf, 0 , sizeof (sendbuf)); memcpy (sendbuf, &eh, sizeof (eh)); memcpy (sendbuf + sizeof (eh), &ah, sizeof (ah)); printf ("%s" ,sendbuf); if (pcap_sendpacket(adhandle, sendbuf, 42 ) == 0 ) { printf ("\nPacketSend succeed\n" ); } else { printf ("PacketSendPacket in getmine Error: %d\n" , GetLastError()); return 0 ; } while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0 ) { if (*(unsigned short *) (pkt_data + 12 ) == htons(ETH_ARP) && *(unsigned short*) (pkt_data + 20 ) == htons(ARP_REPLY) && *(unsigned long *) (pkt_data + 38 ) == inet_addr("100.100.100.100" )) { for (i = 0 ; i < 6 ; i++) { ip_mac[i] = *(unsigned char *) (pkt_data + 22 + i); } printf ("获取自己主机的MAC地址成功!\n" ); break ; } } if (i == 6 ) { return 1 ; } else { return 0 ; } }
其中我们需要定义一下常量如下
1 2 3 4 5 6
#define ETH_ARP 0x0806 #define ARP_HARDWARE 1 #define ETH_IP 0x0800 #define ARP_REQUEST 1 #define ARP_REPLY 2 #define HOSTNUM 255
另外发送ARP请求少不了帧头和ARP头的结构,我们需要声明出来,另外我们构建发送包需要再声明两个结构体sparam和gparam
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
struct EthernetHeader { u_char DestMAC[6 ]; u_char SourMAC[6 ]; u_short EthType; }; struct Arpheader { unsigned short HardwareType; unsigned short ProtocolType; unsigned char HardwareAddLen; unsigned char ProtocolAddLen; unsigned short OperationField; unsigned char SourceMacAdd[6 ]; unsigned long SourceIpAdd; unsigned char DestMacAdd[6 ]; unsigned long DestIpAdd; }; struct ArpPacket { EthernetHeader ed; Arpheader ah; }; struct sparam { pcap_t *adhandle; char *ip; unsigned char *mac; char *netmask; }; struct gparam { pcap_t *adhandle; }; struct sparam sp ;struct gparam gp ;
到现在代码也是完整可以运行的,如果有问题请检查上述代码完整性和位置。 可能出现的BUG: 只显示ARP发送成功,没有接受到并解析打印。可能的原因是帧构造有问题,字节没有对齐,有偏差,像#define一样 写入如下代码:
GO ON.. 获取到了自身的MAC地址之后,就可以在本机上构建ARP广播请求,向局域网内的所有主机发送ARP请求,得到回应之后解析回应的数据包并进行解析,得到对方的MAC地址。在这里我们需要开启两个线程,一个用来发送一个用来接收。好,我们继续.. 先声明两个线程
1 2
HANDLE sendthread; HANDLE recvthread;
在main方法中继续写,对sp和gp两个ARP请求所需要的结构体进行赋值。赋值什么?就是你之前用ifget获取来的IP地址和子网掩码以及用getSelfMac获取来的MAC地址。
1 2 3 4 5
sp.adhandle = adhandle sp.ip = ip_addr sp.mac = ip_mac sp.netmask = ip_netmask gp.adhandle = adhandle
接下来直接创建两个线程,一个是发送一个接受,分别调用两个方法。
1 2 3 4 5
sendthread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE ) SendArpPacket, &sp , 0 , NULL) recvthread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE ) GetLivePC, &gp , 0 , NULL) printf("\nlistening on 网卡%d ...\n" , i)
那么发送数据包的方法和接收解析数据包的方法怎样实现呢? 发送数据包,发送数据包先对结构体数据进行赋值,就像getSelfMac方法一样,然后声明了一个buffer用来存储每一个字节内容。 利用memset方法对buffer进行赋值。再利用一个for循环对255个主机进行发送,指定他们的IP地址。另外定义了一个flag,当发送成功之后将flag设置为1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
DWORD WINAPI SendArpPacket (LPVOID lpParameter) { sparam *spara = (sparam *) lpParameter; pcap_t *adhandle = spara->adhandle; char *ip = spara->ip; unsigned char *mac = spara->mac; char *netmask = spara->netmask; printf ("ip_mac:%02x-%02x-%02x-%02x-%02x-%02x\n" , mac[0 ], mac[1 ], mac[2 ], mac[3 ], mac[4 ], mac[5 ]); printf ("自身的IP地址为:%s\n" , ip); printf ("地址掩码NETMASK为:%s\n" , netmask); printf ("\n" ); unsigned char sendbuf[42 ]; EthernetHeader eh; Arpheader ah; memset (eh.DestMAC, 0xff , 6 ); memcpy (eh.SourMAC, mac, 6 ); memcpy (ah.SourceMacAdd, mac, 6 ); memset (ah.DestMacAdd, 0x00 , 6 ); eh.EthType = htons(ETH_ARP); ah.HardwareType = htons(ARP_HARDWARE); ah.ProtocolType = htons(ETH_IP); ah.HardwareAddLen = 6 ; ah.ProtocolAddLen = 4 ; ah.SourceIpAdd = inet_addr(ip); ah.OperationField = htons(ARP_REQUEST); unsigned long myip = inet_addr(ip); unsigned long mynetmask = inet_addr(netmask); unsigned long hisip = htonl((myip & mynetmask)); for (int i = 0 ; i < HOSTNUM; i++) { ah.DestIpAdd = htonl(hisip + i); memset (sendbuf, 0 , sizeof (sendbuf)); memcpy (sendbuf, &eh, sizeof (eh)); memcpy (sendbuf + sizeof (eh), &ah, sizeof (ah)); if (pcap_sendpacket(adhandle, sendbuf, 42 ) == 0 ) { } else { printf ("PacketSendPacket in getmine Error: %d\n" , GetLastError()); } Sleep(50 ); } Sleep(1000 ); flag = TRUE; return 0 ; }
注: 此函数和flag变量在前面别忘了声明一下… 然后是接收数据包并打印MAC地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
DWORD WINAPI GetLivePC (LPVOID lpParameter) { gparam *gpara = (gparam *) lpParameter; pcap_t *adhandle = gpara->adhandle; int res; unsigned char Mac[6 ]; struct pcap_pkthdr * pkt_header ; const u_char * pkt_data; while (true ) { if (flag) { printf ("获取MAC地址完毕,请输入你要发送对方的IP地址:\n" ); break ; } if ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0 ) { if (*(unsigned short *) (pkt_data + 12 ) == htons(ETH_ARP)) { ArpPacket *recv = (ArpPacket *) pkt_data; if (*(unsigned short *) (pkt_data + 20 ) == htons(ARP_REPLY)) { printf ("-------------------------------------------\n" ); printf ("IP地址:%d.%d.%d.%d MAC地址:" , recv->ah.SourceIpAdd & 255 , recv->ah.SourceIpAdd >> 8 & 255 , recv->ah.SourceIpAdd >> 16 & 255 , recv->ah.SourceIpAdd >> 24 & 255 ); for (int i = 0 ; i < 6 ; i++) { Mac[i] = *(unsigned char *) (pkt_data + 22 + i); printf ("%02x" , Mac[i]); } printf ("\n" ); } } } Sleep(10 ); } return 0 ; }
以上暂告一段落,通过整合以上代码,我们可以得到如下运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
-------------------------------------------------- number:1 name:rpcap://\Device\NPF_{5AC72F8D-019C-4003-B51B- description:Network adapter 'Microsoft' on local h Loopback: no Address Family Name:AF_ INET6this is an IPV6 address Address Family Name:AF_INET6 this is an IPV6 address -------------------------------------------------- number:2 name:rpcap://\Device\NPF_ {C17EB3F6-1E86-40E5-8790-description:Network adapter 'Microsoft' on local h Loopback: no Address Family Name:AF_INET6 this is an IPV6 address Address Family Name:AF_ INETAddress:192.168.95.1 Netmask: 255.255.255.0 Broadcast Address: 255.255.255.255 -------------------------------------------------- number:3 name:rpcap://\Device\NPF_{33E23A2F-F791-409B-8452- description:Network adapter 'Qualcomm Atheros Ar81 oller' on local host Loopback: no Address Family Name:AF_ INET6this is an IPV6 address Address Family Name:AF_INET6 this is an IPV6 address Address Family Name:AF_ INET6this is an IPV6 address Address Family Name:AF_INET Address:121.250.216.237 Netmask: 255.255.255.0 Broadcast Address: 255.255.255.255 -------------------------------------------------- number:4 name:rpcap://\Device\NPF_ {DCCF036F-A9A8-4225-B980-description:Network adapter 'Microsoft' on local h Loopback: no Address Family Name:AF_INET6 this is an IPV6 address Address Family Name:AF_ INET6this is an IPV6 address -------------------------------------------------- number:5 name:rpcap://\Device\NPF_{D62A0060-F424-46FC-83A5- description:Network adapter 'Microsoft' on local h Loopback: no Address Family Name:AF_ INET6this is an IPV6 address Address Family Name:AF_INET Address:192.168.191.1 Netmask: 255.255.255.0 Broadcast Address: 255.255.255.255 -------------------------------------------------- number:6 name:rpcap://\Device\NPF_ {B5224A53-8450-4537-AB3B-description:Network adapter 'Microsoft' on local h Loopback: no Address Family Name:AF_INET6 this is an IPV6 address Address Family Name:AF_ INETAddress:192.168.191.2 Netmask: 255.255.255.0 Broadcast Address: 255.255.255.255 Enter the interface number(1-6):3 ipstr:121.250.216.237 netmask:255.255.255.0 PacketSend succeed 获取自己主机的MAC地址成功! listening on 网卡2 ... ip_mac:dc-0e-a1-ec-53-c3 自身的IP地址为:121.250.216.237 地址掩码NETMASK为:255.255.255.0 请按任意键继续. . . ------------------------------ IP地址:121.250.216.1 MAC地址:000fe28e6100 ------------------------------------------- IP地址:121.250.216.3 MAC地址:089e012d20d5 ------------------------------------------- IP地址:121.250.216.5 MAC地址:5404a6af5f2d ------------------------------------------- IP地址:121.250.216.6 MAC地址:28d244248d81 ------------------------------------------- IP地址:121.250.216.7 MAC地址:80fa5b0283f3 ------------------------------------------- IP地址:121.250.216.8 MAC地址:14dae9005b9e ------------------------------------------- IP地址:121.250.216.9 MAC地址:b82a72bf8bce ------------------------------------------- IP地址:121.250.216.12 MAC地址:84c9b2fefeed ------------------------------------------- IP地址:121.250.216.15 MAC地址:28d2440b4b1b ------------------------------------------- IP地址:121.250.216.16 MAC地址:bcee7b969beb ------------------------------------------- ........此处省略一万字....
接下来我们让用户输入要发送的IP地址和要发送的数据
1 2 3 4 5 6
u_int ip1,ip2,ip3,ip4; scanf_s("%d.%d.%d.%d" ,&ip1,&ip2,&ip3,&ip4); printf ("请输入你要发送的内容:\n" ); getchar(); gets_s(TcpData); printf ("要发送的内容:%s\n" ,TcpData);
声明一下TcpData
接下来就是重头戏了,需要声明各种结构体,我们发送的是TCP数据,这样,TCP的TcpData 就作为真正的内容,然后在前面加上TCP头,IP头,帧头,还有校验和要正确。 最后构成一个完整的帧,那么另外声明的结构体如下,前面代码声明过的帧头部结构体就去掉了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
struct IpAddress{ u_char byte1; u_char byte2; u_char byte3; u_char byte4; }; struct EthernetHeader{ u_char DestMAC[6 ]; u_char SourMAC[6 ]; u_short EthType; }; struct IpHeader{ unsigned char Version_HLen; unsigned char TOS; short Length; short Ident; short Flags_Offset; unsigned char TTL; unsigned char Protocol; short Checksum; IpAddress SourceAddr; IpAddress DestinationAddr; }; struct TcpHeader{ unsigned short SrcPort; unsigned short DstPort; unsigned int SequenceNum; unsigned int Acknowledgment; unsigned char HdrLen; unsigned char Flags; unsigned short AdvertisedWindow; unsigned short Checksum; unsigned short UrgPtr; }; struct PsdTcpHeader{ IpAddress SourceAddr; IpAddress DestinationAddr; char Zero; char Protcol; unsigned short TcpLen; };
继续main函数中对各种结构体的数据进行初始化赋值,并计算校验和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
memset (ðernet, 0 , sizeof (ethernet)); BYTE destmac[8 ]; destmac[0 ] = 0x00 ; destmac[1 ] = 0x11 ; destmac[2 ] = 0x22 ; destmac[3 ] = 0x33 ; destmac[4 ] = 0x44 ; destmac[5 ] = 0x55 ; memcpy (ethernet.DestMAC, destmac, 6 ); BYTE hostmac[8 ]; hostmac[0 ] = 0x00 ; hostmac[1 ] = 0x1a ; hostmac[2 ] = 0x4d ; hostmac[3 ] = 0x70 ; hostmac[4 ] = 0xa3 ; hostmac[5 ] = 0x89 ; memcpy (ethernet.SourMAC, hostmac, 6 ); ethernet.EthType = htons(0x0800 ); memcpy (&SendBuffer, ðernet, sizeof (struct EthernetHeader)); ip.Version_HLen = 0x45 ; ip.TOS = 0 ; ip.Length = htons(sizeof (struct IpHeader) + sizeof (struct TcpHeader) + strlen (TcpData)); ip.Ident = htons(1 ); ip.Flags_Offset = 0 ; ip.TTL = 128 ; ip.Protocol = 6 ; ip.Checksum = 0 ; ip.SourceAddr.byte1 = 127 ; ip.SourceAddr.byte2 = 0 ; ip.SourceAddr.byte3 = 0 ; ip.SourceAddr.byte4 = 1 ; ip.DestinationAddr.byte1 = ip1; ip.DestinationAddr.byte2 = ip2; ip.DestinationAddr.byte3 = ip3; ip.DestinationAddr.byte4 = ip4; memcpy (&SendBuffer[sizeof (struct EthernetHeader)], &ip, 20 ); tcp.DstPort = htons(102 ); tcp.SrcPort = htons(1000 ); tcp.SequenceNum = htonl(11 ); tcp.Acknowledgment = 0 ; tcp.HdrLen = 0x50 ; tcp.Flags = 0x18 ; tcp.AdvertisedWindow = htons(512 ); tcp.UrgPtr = 0 ; tcp.Checksum = 0 ; memcpy (&SendBuffer[sizeof (struct EthernetHeader) + 20 ], &tcp, 20 ); ptcp.SourceAddr = ip.SourceAddr; ptcp.DestinationAddr = ip.DestinationAddr; ptcp.Zero = 0 ; ptcp.Protcol = 6 ; ptcp.TcpLen = htons(sizeof (struct TcpHeader) + strlen (TcpData)); char TempBuffer[65535 ]; memcpy (TempBuffer, &ptcp, sizeof (struct PsdTcpHeader)); memcpy (TempBuffer + sizeof (struct PsdTcpHeader), &tcp, sizeof (struct TcpHeader)); memcpy (TempBuffer + sizeof (struct PsdTcpHeader) + sizeof (struct TcpHeader), TcpData, strlen (TcpData)); tcp.Checksum = checksum((USHORT*)(TempBuffer), sizeof (struct PsdTcpHeader) + sizeof (struct TcpHeader) + strlen (TcpData)); memcpy (SendBuffer + sizeof (struct EthernetHeader) + sizeof (struct IpHeader), &tcp, sizeof (struct TcpHeader)); memcpy (SendBuffer + sizeof (struct EthernetHeader) + sizeof (struct IpHeader) + sizeof (struct TcpHeader), TcpData, strlen (TcpData)); memset (TempBuffer, 0 , sizeof (TempBuffer)); memcpy (TempBuffer, &ip, sizeof (struct IpHeader)); ip.Checksum = checksum((USHORT*)(TempBuffer), sizeof (struct IpHeader)); memcpy (SendBuffer + sizeof (struct EthernetHeader), &ip, sizeof (struct IpHeader)); int size = sizeof (struct EthernetHeader) + sizeof (struct IpHeader) + sizeof (struct TcpHeader) + strlen (TcpData); int result = pcap_sendpacket(adhandle, SendBuffer,size ); if (result != 0 ) { printf ("Send Error!\n" ); } else { printf ("Send TCP Packet.\n" ); printf ("Dstination Port:%d\n" , ntohs(tcp.DstPort)); printf ("Source Port:%d\n" , ntohs(tcp.SrcPort)); printf ("Sequence:%d\n" , ntohl(tcp.SequenceNum)); printf ("Acknowledgment:%d\n" , ntohl(tcp.Acknowledgment)); printf ("Header Length:%d*4\n" , tcp.HdrLen >> 4 ); printf ("Flags:0x%0x\n" , tcp.Flags); printf ("AdvertiseWindow:%d\n" , ntohs(tcp.AdvertisedWindow)); printf ("UrgPtr:%d\n" , ntohs(tcp.UrgPtr)); printf ("Checksum:%u\n" , ntohs(tcp.Checksum)); printf ("Send Successfully!\n" ); }
校验和方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
unsigned short checksum(unsigned short *data, int length){ unsigned long temp = 0 ; while (length > 1 ) { temp += *data++; length -= sizeof (unsigned short ); } if (length) { temp += *(unsigned short *)data; } temp = (temp >> 16 ) + (temp &0xffff ); temp += (temp >> 16 ); return (unsigned short )(~temp); }
记得在声明一下这个方法。如果放在main函数前当然就不用声明啦。 另外需要声明的变量有
1 2 3 4
struct EthernetHeader ethernet ; struct IpHeader ip ; struct TcpHeader tcp ; struct PsdTcpHeader ptcp ;
1
unsigned char SendBuffer[200 ];
接下来的运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
获取MAC地址完毕,请输 121.250 .216 .112 请输入你要发送的内容 what is tcp 要发送的内容:what i Send TCP Packet. Dstination Port:102 Source Port:1000 Sequence:11 Acknowledgment:0 Header Length:5 *4 Flags:0x18 AdvertiseWindow:512 UrgPtr:0 Checksum:17149 Send Successfully!
截图如下: 好啦,发送帧到此就告一段落啦!如果有疑问请留言。 帧的接收很简单,直接贴源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
#include <stdio.h> #include <stdlib.h> #include <pcap.h> char *iptos (u_long in) ; void packet_handler (u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) ;struct IpAddress { u_char byte1; u_char byte2; u_char byte3; u_char byte4; }; struct EthernetHeader { u_char DestMAC[6 ]; u_char SourMAC[6 ]; u_short EthType; }; struct IpHeader { unsigned char Version_HLen; unsigned char TOS; short Length; short Ident; short Flags_Offset; unsigned char TTL; unsigned char Protocol; short Checksum; IpAddress SourceAddr; IpAddress DestinationAddr; }; struct TcpHeader { unsigned short SrcPort; unsigned short DstPort; unsigned int SequenceNum; unsigned int Acknowledgment; unsigned char HdrLen; unsigned char Flags; unsigned short AdvertisedWindow; unsigned short Checksum; unsigned short UrgPtr; }; struct PsdTcpHeader { unsigned long SourceAddr; unsigned long DestinationAddr; char Zero; char Protcol; unsigned short TcpLen; }; int main () { EthernetHeader *ethernet; IpHeader *ip; TcpHeader *tcp; PsdTcpHeader *ptcp; pcap_if_t * alldevs; pcap_if_t *d; char errbuf[PCAP_ERRBUF_SIZE]; char source[PCAP_ERRBUF_SIZE]; pcap_t *adhandle; int i = 0 ; struct pcap_pkthdr *header ; const u_char *pkt_data; int res; u_int netmask; char packet_filter[] = "tcp" ; struct bpf_program fcode ; u_int ip_len; u_short sport,dport; u_char packet[100 ]; pcap_dumper_t *dumpfile; if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL ,&alldevs,errbuf) == -1 ){ fprintf (stderr ,"Error in pcap_findalldevs_ex:\n" ,errbuf); exit (1 ); } for (d = alldevs;d !=NULL ;d = d->next){ printf ("-----------------------------------------------------------------\nnumber:%d\nname:%s\n" ,++i,d->name); if (d->description){ printf ("description:%s\n" ,d->description); }else { printf ("description:%s" ,"no description\n" ); } printf ("\tLoopback: %s\n" ,(d->flags & PCAP_IF_LOOPBACK)?"yes" :"no" ); pcap_addr_t *a; for (a = d->addresses;a;a = a->next){ switch (a->addr->sa_family) { case AF_INET: printf ("Address Family Name:AF_INET\n" ); if (a->addr){ printf ("Address:%s\n" ,iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); } if (a->netmask){ printf ("\tNetmask: %s\n" ,iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); } if (a->broadaddr){ printf ("\tBroadcast Address: %s\n" ,iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); } if (a->dstaddr){ printf ("\tDestination Address: %s\n" ,iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); } break ; case AF_INET6: printf ("Address Family Name:AF_INET6\n" ); printf ("this is an IPV6 address\n" ); break ; default : break ; } } } if (i == 0 ){ printf ("interface not found,please check winpcap installation" ); } int num; printf ("Enter the interface number(1-%d):" ,i); scanf_s("%d" ,&num); printf ("\n" ); if (num<1 ||num>i){ printf ("number out of range\n" ); pcap_freealldevs(alldevs); return -1 ; } for (d=alldevs, i=0 ; i< num-1 ; d=d->next, i++); if ((adhandle = pcap_open(d->name, 65535 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL , errbuf )) == NULL ){ fprintf (stderr ,"\nUnable to open the adapter. %s is not supported by WinPcap\n" , d->name); pcap_freealldevs(alldevs); return -1 ; } printf ("\nlistening on %s...\n" , d->description); if (pcap_datalink(adhandle) != DLT_EN10MB) { fprintf (stderr ,"\nThis program works only on Ethernet networks.\n" ); pcap_freealldevs(alldevs); return -1 ; } if (d->addresses != NULL ) netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; else netmask=0xffffff ; if (pcap_compile(adhandle, &fcode, packet_filter, 1 , netmask )<0 ) { fprintf (stderr ,"\nUnable to compile the packet filter. Check the syntax.\n" ); pcap_freealldevs(alldevs); return -1 ; } if (pcap_setfilter(adhandle, &fcode)<0 ) { fprintf (stderr ,"\nError setting the filter.\n" ); pcap_freealldevs(alldevs); return -1 ; } while ((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0 ) { if (res ==0 ){ continue ; }else { printf ("%.6ld len:%d " , header->ts.tv_usec, header->len); ip = (IpHeader *) (pkt_data +14 ); ip_len = (ip->Version_HLen & 0xf ) *4 ; printf ("ip_length:%d " ,ip_len); tcp = (TcpHeader *)((u_char *)ip+ip_len); char * data; data = (char *)((u_char *)tcp+20 ); sport = ntohs( tcp->SrcPort ); dport = ntohs( tcp->DstPort ); printf ("srcport:%d desport:%d\n" ,sport,dport); printf ("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n" , ip->SourceAddr.byte1, ip->SourceAddr.byte2, ip->SourceAddr.byte3, ip->SourceAddr.byte4, sport, ip->DestinationAddr.byte1, ip->DestinationAddr.byte2, ip->DestinationAddr.byte3, ip->DestinationAddr.byte4, dport); printf ("%s\n" ,data); } } pcap_freealldevs(alldevs); int inum; scanf_s("%d" , &inum); return 0 ; } #define IPTOSBUFFERS 12 char *iptos (u_long in) { static char output[IPTOSBUFFERS][3 *4 +3 +1 ]; static short which; u_char *p; p = (u_char *)∈ which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1 ); sprintf_s(output[which], "%d.%d.%d.%d" , p[0 ], p[1 ], p[2 ], p[3 ]); return output[which]; }
运行截图如下 Thank You 如有问题,欢迎留言~
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
28k
阅读时长 ≈
26 分钟
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
662
阅读时长 ≈
1 分钟
大家好,本节为大家带来在Eclipse下配置Winpcap环境,欢迎大家收看。 本文原文地址来自: 我的CSDN博客 首先,配置Winpcap环境的前提是你必须配置好了Eclipse下的C/C++环境。如果你还没有配置,欢迎大家收看上节内容进行配置。 链接地址: Eclipse配置C/C++环境 若链接失效,请自行查看上一篇文章或者百度其他文章。 废话不多说啦,开始我们的Winpcap的配置。
Winpcap下载
Winpcap官网: Winpcap官网 Winpcap目前最新版为4.1.3,首先你要下载exe文件并安装,直接双击运行安装即可。 下载地址: Winpcap4.1.3.exe 然后你需要下载开发包,首先必须注意的是,目前最新版本是没有开发包的,最新的开发包为4.1.2,先见下图 下面的红色框说明了目前没有提供Winpcap4.1.3的开发包,最新版本的开发包是4.1.2,他可以与4.1.3的Winpcap配套使用。 所以下载4.1.2的开发包。 下载地址: Winpcap Developer’s Pack 4.1.2
Eclipse相关配置
首先新建一个C 的项目,具体的建立过程可以参见上一节的内容。 我们加入一个测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#define WIN32 #define HAVE_REMOTE #include <stdio.h> #include "pcap.h" #include "Win32-Extensions.h" void gen_packet (unsigned char *buf,int len) ;void gen_packet (unsigned char *buf,int len) { int i=0 ; for (i=0 ;i<6 ;i++) { buf[i]=0x01 ; } for (i=6 ;i<12 ;i++) { buf[i]=0x02 ; } buf[12 ]=0xc ; buf[13 ]=0xd ; for (i=14 ;i<len;i++) { buf[i]=i-14 ; } } int main () { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0 ; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; int ret=-1 ; if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL , &alldevs, errbuf) == -1 ) { fprintf (stderr ,"Error in pcap_findalldevs: %s\n" , errbuf); exit (1 ); } for (d=alldevs; d; d=d->next) { printf ("%d. %s" , ++i, d->name); if (d->description) printf (" (%s)\n" , d->description); else printf (" (No description available)\n" ); } if (i==0 ) { printf ("\nNo interfaces found! Make sure WinPcap is installed.\n" ); return -1 ; } printf ("Enter the interface number (1-%d):" ,i); scanf ("%d" , &inum); if (inum < 1 || inum > i) { printf ("\nInterface number out of range.\n" ); pcap_freealldevs(alldevs); return -1 ; } for (d=alldevs, i=0 ; i< inum-1 ;d=d->next, i++); if ( (adhandle= pcap_open(d->name, 65536 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL , errbuf ) ) == NULL ) { fprintf (stderr ,"\nUnable to open the adapter. %s is not supported by WinPcap\n" , d->name); pcap_freealldevs(alldevs); return -1 ; } printf ("\nsending on %s...\n" , d->description); int packetlen=100 ; unsigned char *buf= (unsigned char *)malloc (packetlen); memset (buf,0x0 ,packetlen); gen_packet(buf,packetlen); if ( (ret=pcap_sendpacket(adhandle,buf,packetlen))==-1 ) { printf ("发送失败\n" ); free (buf); pcap_close(adhandle); pcap_freealldevs(alldevs); return -1 ; } free (buf); pcap_close(adhandle); pcap_freealldevs(alldevs); return 0 ; }
把代码拷贝到你的项目程序里面,可以发现现在是编译错误,有些对象根本无法识别,截图如下: 接下来就需要我们对类库进行配置啦。 首先解压你下载的开发包,随便放硬盘的某个位置,我放在了D盘的eclipse_plugins文件夹中,当然你可以随便放哪里都行。 接下来配置Eclipse,右键项目->属性->C/C++常规->项目和符号。 首先添加你的include库,在包含这个选项卡中添加你的库,点击添加->选择文件系统->选择你刚才的开发库的include文件夹,按照图中的顺序来 点击确定添加,同理在库路径选项卡中进行库路径的配置,这次添加的是lib文件夹。按照图片中的顺序来做 点击确定添加。 然后在库的选项卡中添加wpcap和Packet两个库,注意这次不能选择文件系统了,因为你指定了库路径之后它会自动搜索路径中库的名字,这次你只需要指定库的名字就好了。我之前添加的是文件系统,然后它总是提示找不到这个库,所以一定要直接填写这两个库的名字。如图所示: 添加完毕之后,出现这个样子: 好啦,点击确定,全部配置已经完毕啦。 重新构建项目,运行即可。 注意,构建过程可能出现如下问题:
error C2065: “PCAP_SRC_IF_STRING”: 未声明的标识符
error C3861: “pcap_findalldevs_ex”: 找不到标识符
error C2065: “PCAP_OPENFLAG_PROMISCUOUS”: 未声明的标识符
error C3861: “pcap_open”: 找不到标识符
因为新的版本里WinPcap支持远程数据包获取,所以还应当添加一个头文件remote-ext.h ,即#include “remote-ext.h”(记住这条语句要放在#include “pcap.h”之后,否则会出错!) 好了,一切问题都解决了,运行成功啦! 运行结果如下: 再附测试代码一例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#define WIN32 #define HAVE_REMOTE #include <stdio.h> #include "pcap.h" #include "Win32-Extensions.h" void gen_packet (unsigned char *buf,int len) ;void gen_packet (unsigned char *buf,int len) { int i=0 ; for (i=0 ;i<6 ;i++) { buf[i]=0x01 ; } for (i=6 ;i<12 ;i++) { buf[i]=0x02 ; } buf[12 ]=0xc ; buf[13 ]=0xd ; for (i=14 ;i<len;i++) { buf[i]=i-14 ; } } int main () { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0 ; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; int ret=-1 ; if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL , &alldevs, errbuf) == -1 ) { fprintf (stderr ,"Error in pcap_findalldevs: %s\n" , errbuf); exit (1 ); } for (d=alldevs; d; d=d->next) { printf ("%d. %s" , ++i, d->name); if (d->description) printf (" (%s)\n" , d->description); else printf (" (No description available)\n" ); } if (i==0 ) { printf ("\nNo interfaces found! Make sure WinPcap is installed.\n" ); return -1 ; } printf ("Enter the interface number (1-%d):" ,i); scanf ("%d" , &inum); if (inum < 1 || inum > i) { printf ("\nInterface number out of range.\n" ); pcap_freealldevs(alldevs); return -1 ; } for (d=alldevs, i=0 ; i< inum-1 ;d=d->next, i++); if ( (adhandle= pcap_open(d->name, 65536 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL , errbuf ) ) == NULL ) { fprintf (stderr ,"\nUnable to open the adapter. %s is not supported by WinPcap\n" , d->name); pcap_freealldevs(alldevs); return -1 ; } printf ("\nsending on %s...\n" , d->description); int packetlen=100 ; unsigned char *buf= (unsigned char *)malloc (packetlen); memset (buf,0x0 ,packetlen); gen_packet(buf,packetlen); if ( (ret=pcap_sendpacket(adhandle,buf,packetlen))==-1 ) { printf ("发送失败\n" ); free (buf); pcap_close(adhandle); pcap_freealldevs(alldevs); return -1 ; } free (buf); pcap_close(adhandle); pcap_freealldevs(alldevs); return 0 ; }
运行结果:
1 2 3 4 5
1 . rpcap://\Device\NPF_{5AC72F8D-019C -4003 -B51B-7ABB67AF392A} (Network adapter 'Microsoft' on local host)2 . rpcap://\Device\NPF_{33E23A2F-F791-409B-8452 -A3FB5A78B73E} (Network adapter 'Qualcomm Atheros Ar81xx series PCI-E Ethernet Controller' on local host)3 . rpcap://\Device\NPF_{DCCF036F-A9A8-4225 -B980-D3A3F0575F5B} (Network adapter 'Microsoft' on local host)4 . rpcap://\Device\NPF_{D62A0060-F424-46FC-83A5-3394081685FD} (Network adapter 'Microsoft' on local host)5 . rpcap://\Device\NPF_{B5224A53-8450 -4537 -AB3B-9869158121CD} (Network adapter 'Microsoft' on local host)
那么环境配好了,我们接下来就要进行计算机网络编程学习阶段了,后期的文章将会记录我计算机网络编程学习的一些内容,欢迎大家收看。
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
6.5k
阅读时长 ≈
6 分钟
本次为大家带来的是用Eclipse配置C/C++环境的具体步骤,希望对大家有帮助哦。 声明:如果你希望用Eclipse开发,可以参考本文章配置环境,如若想用VS开发,请看后面的文章。 本文原文地址来自 我的CSDN博客 Eclipse 配置C/C++开发环境讲解
JDK
如果没有安装JDK环境的小伙伴请自行去官网下载安装啦,安装之后的就可以跳过此步. 下载地址: JDK下载 JDK环境配置
Eclipse
当然,下载JDK的目的是在Eclipse下先运行起Java程序,这样才能方便我们下一步的操作,没有Eclipse的也去下载。 下载地址: Eclipse下载 上面两步我想难度都很小,而且大部分同学已经配置了,我就不一一讲解了。
CDT
CDT是什么?它就是Eclipse的一个插件,我们需要安装了之后才可以新建C/C++项目。 注意:下载的CDT必须和Eclipse的版本对应。 下载地址: CDT下载 上面下载的Eclipse是3.7版本,在此给出适配CDT 8.0的下载链接。 下载地址: CDT 8.0下载 如果上述链接已失效,请自行百度适配你Eclipse版本的CDT进行下载。 下载之后它是一个压缩包zip格式。 不要解压直接安装即可,安装讲解如下: 打开Eclipse,进入菜单Help,选择Install New Software…,点击右边Add按钮,在Add Repository对话框中点击右下角的Archive…,浏览到你之前下载保存路径,选中cdt-master-8.0.0.zip并双击,勾选所有CDT部件,然后点击Next>,继续Next>,选中“I accept the terms of the license agreement – Finish”,点击Finish开始安装CDT。 安装完CDT后重启Eclipse。 这样我们的Eclipse的CDT插件就安装好啦,这时你会发现可以新建C/C++项目了,但是还是不能编译运行,为什么?因为没有安装编译器。接下来我们需要下载mingw编译器。
MinGW
这是一个配套Eclipse的C/C++编译器,首先我们下载下来。 官网下载地址: mingw官网下载 现在发现最新版本下载是: mingw-get-setup.exe 双击运行,选择安装路径,我的路径为:D:\mingw,安装完成之后你会发现出现一个下载库的窗口,叫Mingw Installation Manager 注意: 1. 我的类库已经安装完毕,所以每个类库前面都显示为绿色的方框,没安装之前是白色的,你需要都勾选上,然后下载安装。在每个类库上邮件单击选择make for installation即可勾选。再点击菜单中的Installation中的Update Catalogue即可安装你所勾选的类库了。 2. 安装下载可能花费不少时间,请耐心等待。 大家可以发现最左边有Basic Steup和All Packages两个分类。 第一个Basic Steup即为必须要安装的编译类库,All Packages即为所有的类库。为了保险起见,我把所有的类库都安装了。全部安装完之后即会显示绿色。 安装完之后的目录结构如下:
环境变量配置
环境变量配置: 计算机– 属性 – 高级系统设置 – 环境变量 在上方的用户变量中进行如下操作: 注意:mingw的位置不同,环境变量的配置不同,可以看最底层目录比对着来配置。我的环境变量配置如下:
(1)编辑PATH变量,在最后面加入 D:\mingw\bin D:\mingw\msys\1.0\bin D:\mingw\mingw32\bin (2)添加LIBRARY_PATH变量,内容为: D:\mingw\lib (3)添加C_INCLUDE_PATH变量,内容为: D:\mingw\include (4)添加CPLUS_INCLUDE_PATH变量,内容为: D:\mingw\lib\gcc\mingw32\4.8.1\include\c++
修改:
进入D:\mingw\bin下将mingw32-make.exe复制成make.exe。因为Eclipse使用时预设是用系统里的”make”这个文件名,而不是”mingw32-make”。
Eclipse中的配置: 窗口 -> 首选项 -> C/C++ -> New CDT Project Wizard 在右边的首选工具链的右边,工具链栏目内选择MinGW GCC,确定,如图所示: 还是在原来的 New CDT Project Wizard选项卡中,展开,Makefile项目,二进制解析器,勾选PE windows解析器,确定,如图所示。 好啦,配置工作完成,我们来测试一下新C语言项目。
测试项目
右键新建->项目->C项目 下一步,选择可执行文件,Hello World ANCI C Project 编译器选择 MinGw GCC 完成,如图所示: 完成之后的界面应该是如下所示,点击构建项目,图中的红色圆圈,构建完成之后,右键运行程序 运行结果: 欢呼吧小伙伴们 ! Eclipse 配置C/C++环境到此完全结束,如果有问题欢迎交流。
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
2.1k
阅读时长 ≈
2 分钟
Hi,大家好,现在计算机网络课程设计开始啦,本次为大家带来计算机网络实验的系列讲解,希望对于小伙伴们的计网课设有一定帮助哦。
写在前面
首先,博主来自山东大学,这次计网课程设计的实验学校为山东大学,年级为2012级,请对号入座。 另外,对于上学期学的计算机网络课程知识,可以说我就是为了应付考试最后狂背下来的,感觉对于计算机网络里的一些原理真的没有理解。纯粹看一些概念性的东西真的是太抽象啦,这次计网课设的目的就是让我们用编程实现网络分析技术和网络功能仿真,来深入了解计算机网络知识。可以说这些才是学习计算机网络最重要的一环,大家加油。
实验要求
1、基本任务(达标任务) 完成两台主机之间的数据通信(数据链路层)
仿真ARP协议获得网段内主机的MAC表
使用帧完成两台主机的通信(Hello! I’m …)
2、高端任务(优秀任务) 完成两台主机通过中间主机的数据通信(网络层)
本次任务呢分为基本任务和高端任务,当然我们的目标当然不仅限于基本任务咯。
实验概述
一句话,我们需要利用winpcap这个库用C语言编程来实现以上的任务。
winpcap讲解
那么winpcap是什么? 大多数网络应用程序通过被广泛使用的操作系统原件来访问网络,如socket。由于操作系统已经处理了底层的细节问题(如协议的处理、数据包的封装等),并提供了与读写文件类似的函数接口,因此使用该方法可以很容易的访问网络中的数据 然而有些时候,这种简单的方式并不能满足任务要求,有些应用程序需要直接访问网络中的数据包,也就是说,需要访问哪些没有被操作系统处理过的数据包 WinPcap就是为Windows平台的应用程序提供这种访问方式,提供下列功能:
捕获原始数据包。无论是发送到运行WinPcap的机器上的数据包,还是在其它主机(存在网络共享介质上的主机)上进行交换的数据包,都可以被捕获
在数据包传递给应用程序之前,根据用户指定的规则过滤数据包
将原始数据包发送到网络上
收集网络流量与网络状态的统计信息
它提供给我们什么呢?
它包含一个内核空间数据包过滤器(Netgroup Packet Filter, NPF),一个底层动态链接库(Packet.dll)和一个高层独立于操作系统的动态链接库(wpcap.dll)。各部分关系见下图
NPF:为了能够访问网络上传输的原始数据,数据包捕获系统需要绕过操作系统的协议栈。这就需要有一部分程序运行在操作系统的内核中,只有这样才能与网络接口驱动直接交互。在WinPcap中,与操作系统密切相关的是一个名为NPF的设备驱动程序,同时对不同版本的操作系统提供了不同版本的驱动程序。这些驱动程序提供了数据包捕获与发送的基本功能,同时提供了一个可编程的数据包过滤系统、一个网络监视引擎等高级功能。 动态链接库:为了让应用程序能够使用内核驱动提供的功能,数据包捕获系统必须导出相关的接口。对此,WinPcap提供两个不同层次的动态链接库:Packet.dll和wpcap.dll。 Packet.dll提供底层的API,用来直接访问驱动程序的函数,用来提供独立于微软公司不同操作系统的编程接口。 wpcap.dll库导出了更强大、更高层的捕获函数接口,具有与UNIX捕获库libpcap的兼容性。这两个库可使数据包的捕获独立于底层的网络硬件与操作系统。
实验流程
了解了winpcap之后,我们要做的就是利用它提供的类库来实现网络传输,网络解析等一系列的功能。
需要我们做好的准备有:
C语言的相关基础
计算机网络的基础知识
对开发环境的相关了解
如果对于上述中任何一个不熟悉,请先去补习一下基础知识吧。
参考书目
吴功宜等,《计算机网络高级软件编程技术》(第2版),清华大学出版社
徐恪等, 《高级计算机网络》,清华大学出版社
A.S. Tannenbaum,《计算机网络》(第5版),清华大学出版社
吕雪峰等, 《网络分析技术揭秘(原理、实践与WinPcap深入解析》,机械工业出版社
结语
下一节,我们将讲解在Eclipse环境下搭建winpcap环境的具体步骤。本次讲解到此结束,谢谢大家。
作者
崔庆才
发表于
2015-07-13
阅读次数:
本文字数:
1.7k
阅读时长 ≈
2 分钟