2014年11月30日星期日

ModernUI教程:目录 - 牧 童

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
ModernUI教程:目录 - 牧 童  阅读原文»

入门

提示 & 技巧

外观

BBCode

目前左侧链接为原文链接,在下载Live Writer,英文好的可以自行去看原文。


本文链接:ModernUI教程:目录,转载请注明。

『重构--改善既有代码的设计』读书笔记----代码坏味道【4】 - Ricky.K  阅读原文»

转眼之间已经出到坏味道【4】了,有朋友跟我说很喜欢前面出的坏味道系列,感觉很有用,因为重构本身就是可以立即进行。他们不像设计模式那样需要有一个准备和改变的过程,重构本身和开发就是可以做到同步的。作者也提到,设计模式是重构的目标。重构就是一个将代码改优雅让你后期添加行为或者重新理解更加方便的过程。所以你应该将重构放到工作的各个位置,而不是当设计模式一样,当成工具书去查看。下面进入正题:

【13】Speculative Generality(夸夸其谈未来性)

Brain Foote介绍了这个坏味道。其实我的理解就是4个字过度设计,很多时候当我们要着手进行开发的时候都会去考虑未来性,这里需不需要一个抽象类或者这个函数需要不需要设置为虚,这里要不要放一个钩子来为以后使用。总之你会使用各种各样的防卫手段来过度开发,想减少以后程序本身应对变化的改变。但你要知道,需求永远是下一秒,你想都想不到。这种过度开发的设计往往让系统变得难以理解和维护。当然,如果你确定这些以后都用的到,那你大胆放心去做。如果你不能确定用不用的到,那就不值得,所以,删掉它吧。

如果你的某个抽象类没有太多实际的作用,那么可以运用Collapse Hierarchy来打破这层继承关系。如果类和类之间的委托你觉得没有必要,你可以用Inline Class来将类内联。如果函数的参数有部分没有用到,请运用Remove Parameter把参数删掉。如果你本身函数名称在起名字的时候就带有抽象含义,那么你可以运用Rename Method把他的语义层含义表达的更具体点。如果你发现只有测试用例用到了这个函数或者类,除非他们是用来帮助测试用例检查正当功能,不然,你就应该把测试用类和它们一起删掉。

【14】Temporary Field(令人迷惑的暂时字段)

有时候你会看到某个类中的某些变量并不被类时时刻刻使用,只有当某个具体算法或者具体函数的特殊情况下才会去用到这个变量。往往这种不被一直使用的变量会让代码阅读者产生疑惑,因为通常对象应该在所有时候都需要它的所有变量。我们很难在变量未被使用的情况下,去猜测它设置的目的。这个时候你需要用Extract Class将这个变量和和这个变量相关的函数都移到这个新类当中去。如果你发现会有判断变量合不合法的情况,你可能需要运用Introduce Null Object来进行重构从而避免写出条件式代码。

如果类中有一个复杂的算法,实现者在实现这个算法的时候不希望这个算法的参数列表过于冗长,所以他会把这些特殊变量都放到类的字段当中去从而产生坏味道。这个时候你应该运用Extract Class将这些变量和相关函数移到一个新类中,这个类就是我们一直提到的“函数对象”。

【15】Message Chains(过度耦合的消息链)

有些时候我们在调用某个函数的时候往往会形成A->B->C->D.test(),这个时候如果你在A端,你就产生了Message Chains的坏味道。这种状态使的你客户代码在A中变成getB()->getC()->getD()->test(),这会使得客户代码在以后这些类关系发生变化的时候变得极易修改与相当不稳定。这个时候你应该运用Hide Delegate来打破这消息链,理论上你可以重构消息链上的任一对象。但这样做往往会让这些小对象都变成Middle Man(中间人),通常更好的做法是:查看消息链最终对象是用来干什么的,看看是否能用Extract Method把他提炼到一个独立的函数中去,再运用Move Method把这个函数推入消息链,然后查看是否有多个客户访问某个对象以此来航行余下部分的,然后增加函数来做这件事。当然,并不是说消息链就是坏东西,任何事情都有两面性,我们需要自己做出判断。

【16】Middle Man(中间人)

对象的基本特征之一就是封装,就是对外部隐藏其实现的细节。比如我们问服务员,这道菜做好没,我们只需要去询问服务员,服务员具体去问厨师还是去问别的伙计这一点我们不用在意。但是很多时候我们可能会过度委托,有些时候某个类的接口有一半的函数都委托给其他类。这样就是过度使用Middle Man,这个时候你应该使用Remove Middle Man来进行重构,如果这种委托函数只有少数几个,你可以简简单单用Inline Method来处理原来函数。如果Middle Man除了委托还有其他行为,你可以运用Replace Delegation with Inheritance把它变成调用类的子类,这样你可以少写委托动作的同时,又可以扩展原对象的行为。

【17】Inappropriate Intimacy(狎昵关系)

在面向对象的时间里,两个类之间不能出现“情人”关系。也就是彼此的private部分一直互相去探究去查看,要记住在对象时间里,互相依赖是噩梦。你可以运用Move Mothed和Move Field帮他们划清界限。你也可以看看是否可以用Change Bidirectional Association to Uniderctional让其中一个类斩断对另一个类的情丝。如果这两个类实在情投意合,你可以尝试使用Extract Class把两者共同点提炼到一个独立类中,然后让这两个类去访问他,从而改变从双向依赖A->B,B->A变成A->C,B->C。或者你也可以尝试用Hide Delegate让另一个类来为他们传递相思情。

很多时候,委托要比继承来的实用。因为后者往往造成过度亲密过度依赖。如果你觉得可以让这个类独立生活,你可以使用Replace Inheritance widh Delegation来让他离开继承体系。


本文链接:『重构--改善既有代码的设计』读书笔记----代码坏味道【4】,转载请注明。

阅读更多内容

2014年11月28日星期五

python redis链接建立实现分析

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
python redis链接建立实现分析  阅读原文»

python redis链接建立实现分析

今天在写zabbix storm job监控脚本的时候用到了python的redis模块,之前也有用过,但是没有过多的了解,今天看了下相关的api和源码,看到有ConnectionPool的实现,这里简单说下。
在ConnectionPool之前,如果需要连接redis,我都是用StrictRedis这个类,在源码中可以看到这个类的具体解释:

  redis.StrictRedis Implementation of the Redis protocol.This abstract class provides a Python interface to all Redis commands and an  implementation of the Redis protocol.Connection and Pipeline derive from this, implementing how the commands are sent and received to the Redis server  

使用的方法:

   r=redis.StrictRedis(host=xxxx, port=xxxx, db=xxxx)   r.xxxx()  

有了ConnectionPool这个类之后,可以使用如下方法

  pool = redis.ConnectionPool(host=xxx, port=xxx, db=xxxx)  r = redis.Redis(connection_pool=pool)  

这里Redis是StrictRedis的子类
简单分析如下:
在StrictRedis类的__init__方法中,可以初始化connection_pool这个参数,其对应的是一个ConnectionPool的对象:

  class StrictRedis(object):  ........      def __init__(self, host='localhost', port=6379,                   db=0, password=None, socket_timeout=None,                   socket_connect_timeout=None,                   socket_keepalive=None, socket_keepalive_options=None,                   connection_pool=None, unix_socket_path=None,                   encoding='utf-8', encoding_errors='strict',                   charset=None, errors=None,                   decode_responses=False, retry_on_timeout=False,                   ssl=False, ssl_keyfile=None, ssl_certfile=None,                   ssl_cert_reqs=None, ssl_ca_certs=None):           if not connection_pool:               ..........                connection_pool = ConnectionPool(**kwargs)           self.connection_pool = connection_pool  

在StrictRedis的实例执行具体的命令时会调用execute_command方法,这里可以看到具体实现是从连接池中获取一个具体的连接,然后执行命令,完成后释放连接:

     # COMMAND EXECUTION AND PROTOCOL PARSING      def execute_command(self, *args, **options):          "Execute a command and return a parsed response"          pool = self.connection_pool          command_name = args[0]          connection = pool.get_connection(command_name, **options)  #调用ConnectionPool.get_connection方法获取一个连接          try:              connection.send_command(*args)  #命令执行,这里为Connection.send_command              return self.parse_response(connection, command_name, **options)          except (ConnectionError, TimeoutError) as e:              connection.disconnect()              if not connection.retry_on_timeout and isinstance(e, TimeoutError):                  raise              connection.send_command(*args)              return self.parse_response(connection, command_name, **options)          finally:              pool.release(connection)  #调用ConnectionPool.release释放连接  

在来看看ConnectionPool类:

       class ConnectionPool(object):         ...........      def __init__(self, connection_class=Connection, max_connections=None,                   **connection_kwargs):   #类初始化时调用构造函数          max_connections = max_connections or 2 ** 31          if not isinstance(max_connections, (int, long)) or max_connections < 0:  #判断输入的max_connections是否合法              raise ValueError('"max_connections" must be a positive integer')          self.connection_class = connection_class  #设置对应的参数          self.connection_kwargs = connection_kwargs          self.max_connections = max_connections          self.reset()  #初始化ConnectionPool 时的reset操作      def reset(self):          self.pid = os.getpid()          self._created_connections = 0  #已经创建的连接的计数器          self._available_connections = []   #声明一个空的数组,用来存放可用的连接          self._in_use_connections = set()  #声明一个空的集合,用来存放已经在用的连接          self._check_lock = threading.Lock()  .......      def get_connection(self, command_name, *keys, **options):  #在连接池中获取连接的方法          "Get a connection from the pool"          self._checkpid()          try:              connection = self._available_connections.pop()  #获取并删除代表连接的元素,在第一次获取connectiong时,因为_available_connections是一个空的数组,              会直接调用make_connection方法          except IndexError:              connection = self.make_connection()          self._in_use_connections.add(connection)   #向代表正在使用的连接的集合中添加元素          return connection      def make_connection(self): #在_available_connections数组为空时获取连接调用的方法          "Create a new connection"          if self._created_connections >= self.max_connections:   #判断创建的连接是否已经达到最大限制,max_connections可以通过参数初始化              raise ConnectionError("Too many connections")          self._created_connections += 1   #把代表已经创建的连接的数值+1          return self.connection_class(**self.connection_kwargs)     #返回有效的连接,默认为Connection(**self.connection_kwargs)      def release(self, connection):  #释放连接,链接并没有断开,只是存在链接池中          "Releases the connection back to the pool"          self._checkpid()          if connection.pid != self.pid:              return          self._in_use_connections.remove(connection)   #从集合中删除元素          self._available_connections.append(connection) #并添加到_available_connections 的数组中      def disconnect(self): #断开所有连接池中的链接          "Disconnects all connections in the pool"          all_conns = chain(self._available_connections,                            self._in_use_connections)          for connection in all_conns:              connection.disconnect()  

execute_command最终调用的是Connection.send_command方法,关闭链接为 Connection.disconnect方法,而Connection类的实现:

  class Connection(object):      "Manages TCP communication to and from a Redis server"      def __del__(self):   #对象删除时的操作,调用disconnect释放连接          try:              self.disconnect()          except Exception:              pass  

核心的链接建立方法是通过socket模块实现:

      def _connect(self):          err = None          for res in socket.getaddrinfo(self.host, self.port, 0,                                        socket.SOCK_STREAM):              family, socktype, proto, canonname, socket_address = res              sock = None              try:                  sock = socket.socket(family, socktype, proto)                  # TCP_NODELAY                  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)                  # TCP_KEEPALIVE                  if self.socket_keepalive:   #构造函数中默认 socket_keepalive=False,因此这里默认为短连接                      sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)                      for k, v in iteritems(self.socket_keepalive_options):                          sock.setsockopt(socket.SOL_TCP, k, v)                  # set the socket_connect_timeout before we connect                  sock.settimeout(self.socket_connect_timeout)  #构造函数中默认socket_connect_timeout=None,即连接为blocking的模式                  # connect                  sock.connect(socket_address)                  # set the socket_timeout now that we're connected                  sock.settimeout(self.socket_timeout)  #构造函数中默认socket_timeout=None                  return sock              except socket.error as _:                  err = _                  if sock is not None:                      sock.close()  .....  

关闭链接的方法:

      def disconnect(self):          "Disconnects from the Redis server"          self._parser.on_disconnect()          if self._sock is None:              return          try:              self._sock.shutdown(socket.SHUT_RDWR)  #先shutdown再close              self._sock.close()          except socket.error:              pass          self._sock = None  


可以小结如下
1)默认情况下每创建一个Redis实例都会构造出一个ConnectionPool实例,每一次访问redis都会从这个连接池得到一个连接,操作完成后会把该连接放回连接池(连接并没有释放),可以构造一个统一的ConnectionPool,在创建Redis实例时,可以将该ConnectionPool传入,那么后续的操作会从给定的ConnectionPool获得连接,不会再重复创建ConnectionPool。
2)默认情况下没有设置keepalive和timeout,建立的连接是blocking模式的短连接。
3)不考虑底层tcp的情况下,连接池中的连接会在ConnectionPool.disconnect中统一销毁。

本文出自 "菜光光的博客" 博客,请务必保留此出处http://caiguangguang.blog.51cto.com/1652935/1583541

基于Windows Server Backup高级应用之多副本备份  阅读原文»

【需求说明】

对Windows Server 2008 R2系统状态进行备份,且需要保留三天的系统状态备份数据,但是我们会发现Windows Server Backup并不能够创建多个备份计划,不能设置备份副本的保存数量等,这时候怎么处理了?

【解决方法】

如果大家用过Windows Server Backup,那么大家一定知道在Windows Server Backup的界面上,只能建一个备份计划(我也常识找过但未找到可以配置多个备份计划的地方),而且备份选项中只有每天一次和每天多次的备份选项,根本没有几天一次的选项,而Windows Server Backup对于其备份副本的保存有两种机制:在本地目录或直连磁盘上,它会保留每一个备份副本,直到备份空间占满才会删除第一个备份副本来储存下一个新的备份副本,而且每一个备份副本之间是叠加关系,不能单独分离出来使用;而在远程目录上,它只会保留唯一一个最新的备份副本,每次均为覆盖备份。

其实只要好好研究一下,这个功能是可以实现的,只是操作相对复杂一点,通过备份计划我们可以看到它实际是通过计划任务的方式,还进行调度备份任务的,如果你仔细去查看会发现计划任务里面会有它的跟踪,所以我们可以通过计划任务来进行处理此需求。

【实验步骤】

如果你是存储在共享磁盘中可以采用以下方式操作

1、在共享目录中新建三个目录(满足保留三天备份的需求);

clip_image002[4]

2、在Windows Server Backup中创建备份计划

clip_image004[4]

3、点击"下一步"

clip_image006[4]

4、选择"自定义"点击"下一步"

clip_image008[4]

5、"添加项"选择"系统状态",点击"下一步"

clip_image010[4]

6、选择"每一次"点击"下一步"

clip_image012[4]

7、备份到共享网络文件夹,点击"下一步"

clip_image014[4]

8、这里我们选择第一个目录,即backup01,点击"下一步"

clip_image016[4]

9、配置具有执行任务计划的用户名密码,点击"确定"

clip_image018[4]

10、点击完成

clip_image020[4]

11、这时候我们在任务计划库里面就可以看到对应的任务计划

clip_image022[4]

12、然后我们双击对应的计划任务,点击"编辑触发器",这里我们设置计划任务时间为每天,然后每隔两天备份一次;

clip_image024[4]

13、然后我们导出备份配置,然后删除对应的任务计划;

clip_image026[4]

14、这时候我们再在Windows Server Backup中新建备份计划,这次我们设置备份位置为backup02目录;

clip_image028[4]

15、同样的我们修改对应的备份计划任务的备份时间为每天,每隔两天备份一次,这里有一个地方需要注意,就是开始计划任务时间,因为我们前面一个已经设置了2014年11月25日,所以我们这个计划任务要设置后一天即2014年11月26日,这样子才能够实现三个备份日期顺序性;

clip_image030[4]

16、同样的我们再导出计划任务保存起来,以备后面所需;

clip_image032[4]

17、然后我们再在Windows Server Backup中设置第三个备份计划,这此设置位置放在backup03目录中;

clip_image034[4]

18、同样的我们这里需要设置它的计划任务时间为每隔两天备份一次,但开始时间要在2014年11月26日的时间上再加一天,即2014年11月27日;

clip_image036[9]

19、这里要注意不要再删除现在的备份任务计划了,不然后面Windows Server Backup备份计划会不成功的,这时候我们点击"导入任务",

阅读更多内容

数据结构基本概念和算法分析 - yly123

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
数据结构基本概念和算法分析 - yly123  阅读原文»

一、数据结构基本概念

1. 数据:数据是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。

2. 数据元素:数据元素是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。一个数据元素可由若干个数据项组成。数据项是数据的不可分割的最小单位

3. 数据对象:数据对象是性质相同的数据元素的集合,是数据的一个子集,如整型数据对象。

4. 数据结构:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。根据数据元素之间关系的不同特性,通常有下列4类基本结构:(1)集合;(2)线性结构;(3)树形结构;(4)图状结构或网状结构。

数据结构的形式定义为:数据结构是一个二元组

Data_Structure = (D, S)

其中:D是数据元素的有限集,S是D上关系的有限集。

5. 逻辑结构:上述数据结构的定义是对操作对象的一种数学描述。结构定义中的“关系”描述的是数据元素之间的逻辑关系,因此又称为逻辑结构。

6. 物理结构:数据结构在计算机中的表示(又称映像)称为数据的物理结构,又称存储结构。

数据元素之间的关系在计算机中有两种不同的表示方法:顺序映像和非顺序映像,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。

7. 抽象数据类型:抽象数据类型(ADT)是指一个数学模型以及定义在该模型上的一组操作。可细分为3种类型:原子类型,固定聚合类型,可变聚合类型。

二、算法和算法分析

1. 算法:算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作;此外,一个算法还具有下列5个重要特性:

(1)有穷性。一个算法必须总是在执行有穷步之后结束,且每一步都可在有穷时间内完成。

(2)确定性。算法中每一条指令必须有确切的含义读者理解时不会产生二义性。并且,在任何条件下,算法只有唯一的一条执行路径,即对于相同的输入只能得出相同的输出。

(3)可行性。一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。

(4)输入。零个或多个的输入。

(5)输出。一个或多个的输出。

2. 算法设计的要求

通常设计一个“好”的算法应考虑达到以下目标:

(1)正确性。

(2)可读性。

(3)健壮性。

(4)效率与低存储量需求。

3. 算法效率的度量

度量时间复杂度的两种方法:

(1)事后统计的方法。

(2)事前分析估算的方法。一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:

①依据的算法选用何种策略;

②问题的规模;

③书写程序的语言,对于同一个算法,实现语言的级别越高,执行效率就越低;

④编译程序所产生的机器代码的质量;

⑤机器执行指令的速度。

空间复杂度。


本文链接:数据结构基本概念和算法分析,转载请注明。

WPF和Expression Blend开发实例:充分利用Blend实现一个探照灯的效果 - youngytj  阅读原文»

本篇文章阅读的基础是在读者对于WPF有一定的了解并且有WPF相关的编码经验,对于Blend的界面布局有基础的知识.文章中对于相应的在Blend中的操作进行演示,并不会进行细致到每个属性的介绍.同时,本篇文章所用的Blend版本是5.0.40218.0,即VS2012对应的版本,对于其他版本的操作区别,请读者自行研究.Ok,我们现在开始,本篇文章最终的效果如下图所示:

好,我们开始分步介绍过程,除了最后设计的一个按钮的后台代码需要使用到代码之外,其余的操作我们都使用图形操作.

1.新建项目

打开Blend,新建一个WPF应用程序,命名为WPF_SearchLight,确定完成,具体如下:

2.单击项目自动生成的MainWindow,调整属性:

  WindowStyle设置为None;

  Background设置为纯黑色;

  Height设置为200;

  Width设置为600;

  WindowsStartupLocation设置为CenterScreen.

最终效果图如下:

3.接下来我们需要在窗口中添加一个TextBlock,设置Text为任意的文字,Background设置为白色,具体如下:

4.添加一个按钮,用于退出程序,将它放置在TextBlock的右端,并在后台代码中添加处理事件.

添加一个按钮,用于退出程序,将它放置在TextBlock的右端,并在后台代码中添加处理事件.

5.接下来我们需要设计一个圆和一个矩形.画一个矩形,遮盖住TextBlock,它的长度必须为TextBlock的两倍以上,并且Background 设置为纯黑色,同时A的值设置为80%.如下图:

接下来画一个圆,圆的直径与TextBlock的高度相等,效果如图:

然后在在对象和时间线窗口中同时选中(按住Shift),右击,选中合并→相减,会生成一个Path路径.(此处应注意先按矩形,再按圆形即矩形-圆形)

6. 接下来就要设计动画了,在对象和时间线中点击Path,然后点击"+",新建一个名为"SearchLight"的动画如图:

这时候会出现时间面板,如图:

 点击时间左边的按钮,记录此时的Path位置;

 将时间轴拖动到6处,然后水平移动Path至退出按钮处,点击时间左边的按钮,记录此时的Path位置;

将时间轴拖动到7处,不移动Path,点击时间左边的按钮,记录此时的Path位置.

动画设定完成,此时可以点击播放按钮查看效果.

7. 还有一些其他的操作, 选定SearchLight,设置动画的一些属性,如AutoReverse勾选,RepeatBehavior选择为Forever,如图:

在触发器的窗口中,可以选择触发动画的时机,如图:

最终实现的效果如一开始所示的图片一样.

源代码下载:

http://files.cnblogs.com/youngytj/WPF_SearchLight.rar


本文链接:WPF和Expression Blend开发实例:充分利用Blend实现一个探照灯的效果,转载请注明。

阅读更多内容

2014年11月27日星期四

Linux下进程管理工具之(三):glances

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Linux下进程管理工具之(三):glances  阅读原文»

Linux下进程管理工具之(三):glances

实验环境:

CentOS release 6.6 (Final) 一台

IP地址:172.16.249.230

glances是一款用于 Linux、BSD 的开源命令行系统监视工具,它使用 Python 语言开发,能够监视 CPU、负载、内存、磁盘 I/O、网络流量、文件系统、系统温度等信息。本文介绍glances的使用方法和技巧,帮助 Linux 系统管理员了解掌握服务器性能。

.glances简介

glances 可以为 Unix Linux 性能专家提供监视和分析性能数据的功能,其中包括:

CPU 使用率

内存使用情况

内核统计信息和运行队列信息

磁盘 I/O 速度、传输和读/写比率

文件系统中的可用空间

磁盘适配器

网络 I/O 速度、传输和读/写比率

页面空间和页面速度

消耗资源最多的进程

计算机信息和系统资源

glances 工具可以在用户的终端上实时显示重要的系统信息,并动态地对其进行更新。这个高效的工具可以工作于任何终端屏幕。另外它并不会消耗大量的 CPU 资源,通常低于百分之二。glances 在屏幕上对数据进行显示,并且每隔两秒钟对其进行更新。您也可以自己将这个时间间隔更改为更长或更短的数值。glances 工具还可以将相同的数据捕获到一个文件,便于以后对报告进行分析和绘制图形。输出文件可以是电子表格的格式 (.csv) 或者 html 格式。

.glances安装

两种方法安装 glances

通常可以有两种方法安装 glances

第一种是通过编译源代码的方式,这种方法比较复杂另外可能会遇到软件包依赖性问题。

第二种是使用特定的软件包管理工具来安装 glances,这种方法比较简单。

本文使用后者,需要说明的是在 CentOS 特定的软件包管理工具来安装。glances 要首先配置 EPEL repo,然后安装 glances

如果既想获得 RHEL 的高质量、高性能、高可靠性,又需要方便易用(关键是免费)的软件包更新功能,那么 Fedora Project 推出的 EPELExtra Packages for Enterprise Linux http://fedoraproject.org/wiki/EPEL)正好适合你。它是由 Fedora 社区打造,为 RHEL 及衍生发行版如 CentOSScientific Linux 等提供高质量软件包的项目。装上了 EPEL,就像在 Fedora 上一样,可以通过 yum install package-name,随意安装软件。安装使用 EPEL 非常简单:

首先找出离你最近的镜像源网站:

http://mirrors.fedoraproject.org/publiclist/EPEL/6/x86_64/

wKioL1R1vyDR9VUVAAJrkesPqK4032.jpg

选择北京的那个哦!

  #wgethttp://mirrors.yun-idc.com/epel/RPM-GPG-KEY-EPEL-6  #rpm --importRPM-GPG-KEY-EPEL-6  #rm -fRPM-GPG-KEY-EPEL-6  #vi/etc/yum.repos.d/epel.repo  [epel]  name=EPEL RPMRepository for Red Hat Enterprise Linux  baseurl=http://mirrors.yun-idc.com/epel/6/$basearch/  gpgcheck=1  enabled=0  

.glances的使用方法

glances 是一个命令行工具包括如下命令选项:

  -b:显示网络连接速度 Byte/ 秒   -B @IP|host :绑定服务器端 IP 地址或者主机名称   -c @IP|host:连接 glances 服务器端   -C file:设置配置文件默认是 /etc/glances/glances.conf   -d:关闭磁盘 I/O 模块   -e:显示传感器温度   -f file:设置输出文件(格式是 HTML 或者 CSV)   -m:关闭挂载的磁盘模块   -n:关闭网络模块   -p PORT:设置运行端口默认是 61209   -P password:设置客户端 / 服务器密码   -s:设置 glances 运行模式为服务器   -t sec:设置屏幕刷新的时间间隔,单位为秒,默认值为 2 秒,数值许可范围:1~32767   -h : 显示帮助信息   -v : 显示版本信息  

glances 工作界面如下图:

wKioL1R1v1nQddA4AASAcnx6v2E580.jpg

glances 工作界面的说明:

上图的上部是 CPU Load(负载)、Mem(内存使用)、 Swap(交换分区)的使用情况。在上图的中上部是网络接口

右边大部分是Processes(进程)的使用情况。通常包括如下字段:

   VIRT: 虚拟内存大小   RES: 进程占用的物理内存值   %CPU:该进程占用的 CPU 使用率   %MEM:该进程占用的物理内存和总内存的百分比   PID: 进程 ID 号   USER: 进程所有者的用户名  NI: 进程优先级   S: 进程状态,其中 S 表示休眠,R 表示正在运行,Z 表示僵死状态。   TIME+: 该进程启动后占用的总的 CPU 时间   IO_R 和 IO_W: 进程的读写 I/O 速率  NAME: 进程名称  

图的下部是磁盘 I/O 的使用情况和磁盘挂载分区的使用状况。

另外 glances 可以使用交互式的方式运行该工具,用户可以使用如下快捷键:

  h :显示帮助信息   q :离开程序退出   c :按照 CPU 实时负载对系统进程进行排序   m :按照内存使用状况对系统进程排序   i:按照 I/O 使用状况对系统进程排序   p:按照进程名称排序   d :显示磁盘读写状况   w :删除日志文件   l :显示日志   s:显示传感器信息   f :显示系统信息   1 :轮流显示每个 CPU 内核的使用情况(次选项仅仅使用在多核 CPU 系统)  

.glances的高级应用

glances 的结果输出方法

glances 输出HTML 格式文件,需要一个HTTP服务器。

首先安装相关软件包

[root@LinuxHosthtml]# yum install python-jinja2

[root@LinuxHost~]# glances -f /usr/local/nginx/html/ -o html

下面可以使用 Chrom 浏览器输入网址: http://localhost/usr/local/nginx/html/glances.html,结果如图

wKioL1R1v6CRWHd1AAGIbupUNjk481.jpg

继iptables之后的新一代包过滤框架是nftables  阅读原文»

继iptables之后的新一代包过滤框架是nftables

夜已深然未央,准备接着讲述有关Netfilter的故事,行文有点松散,由于未打草稿,有点随意识而流,一气呵成不知是自夸还是自嘲,权当小时候写的日 记吧,自幼喜欢每天写日记,中学时更是以退士为名折腾了几箱子抄本,前几年由于喝酒就改为周记了,现在意识到了生命短暂,时间甚是不够用,不能在迷迷糊糊 中得过且过,就准备把自己知道的关于Linux网络的东西一点一滴记录下来,本来想继续行文于纸上,然而发现在个人电脑智能手机时代,很多字早就不会写 了...上回没有说完关于iptables的故事,本文继续...

一.nftables前传-iptables之弊端

iptables几乎是无人不知无人不晓,人们被圈入了框框也就觉得任何事情都是理所当然,但我例外,和其他很多事情一样,在这个领域,我依然做并且乐于做那个"被排除的人"。
iptables的诸多弊端已经不能再视而不见,然而只有很少的人看到了这些,大多数的人作为使用者,仅仅是使用罢了。在此我不会吐嘈很多了。以下的弊端来自nftables的宣传文档,但是即使是在国外,也引发了超级多的争论:
1.iptables框架在内核态知道的太多,以至于产生了大量的代码冗余。
这一点是显而易见的,比如对于TCP和UDP而言,取sport,dport没有什么不同,但是iptables却使用了两套代码,这只是一个例子,类似的还有很多。
2.iptables的rule结构设计不合理。
这是要着重说明的。

1.iptables的结构

iptables由表,链,规则组成,其中规则又由match,target组成。如下面的结构所示:
Table{
Chain[
Rule
(match,match,match,...)
->target,
Rule
(match,match,match,...)
->target,
...
],
Chain[
...
],
...
}

2.iptables的规则匹配执行流程

iptables的规则是按照配置顺序顺序匹配的,在每一张表的每一个链上依次匹配每一条规则,在每一条规则依次匹配每一个match,全部匹配的match执行该规则的target,由target决定:a.继续匹配下一条规则
b.对数据包做一些修改
c.跳转到其它的链(即开始从该链依次匹配该链上的每一条规则)
d.返回引发跳转的链(即继续匹配跳转前的链的下一条规则)
e.丢弃数据包
f.接收数据包(即不再继续往下匹配,直接返回)
g.记录日志
h....

整个iptables框架执行的流程如下:

  循环1:          static breakrule = 0;          遍历一个chain的每一条rule {          nomatch = 0;          循环2:遍历一条rule的每一个match {                          result = rule->match[curr](skb, info);                          if(result != MATCH) {                                  nomatch = 1;                                  break;                          }          }          if (nomatch == 1) {                  continue该chain的下一条rule;          }          result = rule->target(skb, info);          if (result == DROP) {                  break丢弃数据包          } else if (result == ACCEPT) {                  break接受数据包          } else if (result == GOTO) {                  breakrule = rule;                  跳转到相应的chain,执行循环1          } else if (result == RETURN) {                  break返回调用chain,执行其breakrule的下一条rule          } ...  }  

看了上述的代码就基本知道了iptables的命令实现,程序员能做的就是扩展iptables的功能,具体的做法有两个:写一个match以及写一个target。除此之外,程序员就没辙了,剩下的就看使用者的想象力了...
通过上面的流程,可以发现,包过滤的流程最终要落实到规则匹配,而过滤的动作最终落实到了该规则的target,前面的所有的match匹配返回结果就是0或者非0表示是否匹配,只有所有的match均匹配,才会执行target。这就决定了下面几件事:
a.如果你想实现多个target,就不得不写多条规则
比如实现log和drop,那么就要写两条规则,或者扩展一个LOG_and_DROP target,前者影响效率,后者需要编程。你很在乎性能,同时你又不是程序员不懂编程,你就抓狂了...
b.你可以写一个match,在里面偷偷摸摸做一点事情,但是外部不知道
这一切太不正规了,你可以在一个match里面把一个数据包的校验码改掉,也可以在里面做log,做NAT什么的,但是iptables的框架的本意虽不允许你这么做但是又没有阻止你的行为。
我们可以在iptables执行流的一个细节(上述的流程中未画出)中看到另一个细节,即iptables在match中仅仅确定"是否匹配"真的已经很 不够,就连代码都设计得很勉强。如果你看ipt_do_table这个核心函数,会发现一个控制变量名叫hotdrop,这个变量是干什么的呢?按照注释 的意思是:

  @hotdrop:    drop packet if we had inspection problems  

这 个hotdrop作为传出参数传入每一个match回调函数,用于在match内部指示将一个数据包丢弃。这就暴露出了设计的不足,丢弃一个数据包不是 target要做的吗?一个match的职责是抉择该数据包是否匹配,干嘛要指示丢弃它呢?这不是越级么?这只是一个细节,你可以说出一千个理由表明它是 合理的,但是它却是丑陋的!

弄清楚历史总是能明白更多,这绝对是一句真话,但是恰恰是专业化阻止了大多数的程序员去读历史,哪怕是IT的历史。最好的历史资料就是原著,Netfilter的历史不长,从Linux 2.3.15内核版本被引入至今,不会像老子庄子那样被篡改地体无完肤。
我们当然要看iptables被引入的那段历史。
iptables被引入旨在替掉ipchains,因为当时ipchains的维护者Rusty Russell认识到它拥有诸多的弊端。总的说来,弊端有两个,其它的都是由这两个而发:
a.内核的firewall框架仅仅设置了3个检查点,即input,forward,output,对于环回包以及indev,outdev的控制力很弱;
b.代码写死,匹配项固定,没有可扩展性。
问 题就在这里的b。针对问题a,Rusty Russell提出了Netfilter的设计,精心设计了5个HOOK点,解决了几乎所有的控制点的问题,特别是OUTPUT点的设计顶级绝妙,它被安 放在路由之后,原因在于Linux协议栈的路由操作之后才会给出完整的过滤匹配项,比如源IP地址,出口设备等,路由之后的OUTPUT同时给了调用者再 次路由的权限。FORWARD和INPUT作为路由的二分,同时保持了无用功最少化,因为如果你没有打开ip_forward选项,即便不是INPUT的 数据包也不会进入FORWARD,如果根本就没有找到路由,则既不会到达INPUT,也不会到达FORWARD。对于PREROUTING而言,它可以通 过conntrack区分本地环回流量和网卡进入流量...不管怎么说,这是内核的工作,这个Netfilter的设计十分完美,至今依然被使用。
对于问题b,Rusty Russell提出了iptables,它是一个高度可扩展的框架,也就是从此时起,iptables拥有了match/target配对的扩展方式,每 当需要扩展的时候,每一个match/target除了有用户态的lib之外,还有用内核态的支持,它将ipchains时代的固定匹配模式变成了可以自 己编程扩展的了。
针对ipchains的弊端,Rusty Russell可谓是给出了完美的解决方案,然而仅此而已!任何一个来自同一作者的新的框架几乎均是为了解决上一个框架的弊端的,iptables作为一 个新秀,在获得欢呼的时候,不会有人去考虑它的弊端,任何事情都是这样,不是吗?
iptables的弊端是被逐步发现的,Rusty Russell作为ipchains和iptables的共同作者,它对待后者取代前者的态度永远都是保守的,一个全新的框架需要另一个人或者团队来提 出,而不可能出现在Rusty Russell本人手里以及iptables团队的内部。
针对Netfilter,Rusty写了大量的文档,均在Netfilter网站上可以找到:http://people.netfilter.org/rusty/unreliable-guides/不可否认,这些都是珍贵的第一手资料,对于我们理解Netfilter,可能没有比这些更好的了。任何人都可以从这些原著中找到"XX为何会这样"这种问题的蛛丝马迹,同时它们也是指导你如何改进现有框架的明灯。

三.nftables登场

鉴于iptables的诸多缺点(其实并不是缺点,但是match/target配对的扩展方式导致了开发者延伸了劣势,最终将其确定为缺点),nftables旨在一个个地改进它们。首先是两个问题:a.如何使用一种统一的方式来解析数据包
在这个问题的解决上,u32 match给了作者思路
b.如何执行多个action
事 实上,是iptables的matches/target配对的方式限制了开发者的思路。为何非要区分match和target呢?iptables框架 的执行流程限制了match的结果就是个布尔型,所有的动作都在target中执行,如果去掉了这个限制,将整个流程都开放给开发者,那就灵活多了。 nftables就在这样的背景下登场了。事实证明,nftables做的比修正弊端更多,走的更远。
首先nftables采用了"虚拟机解释字节码"的方式,使一条rule真的成了"为一个数据包做一些事情"这样灵活的命令,而去掉了"匹配所有的 match之后执行一个target"这样的限制。虚拟机执行字节码的方式早就被BPF采用了,我们熟知的tcpdump抓包程序就是利用的它来过滤的数 据包。我们来看一下nftables框架的执行流程:

  循环1:          static breakrule = 0;          遍历一个chain的每一条rule {          nomatch = 0;          reg[MAX]          循环2:遍历一条rule的每一个expression {                          void rule->expression[curr]->operations->expr(skb, info, reg)                          if(reg[VERDICT] != CONTINUE) {                                  break;                          }          }          if (reg[VERDICT] == CONTINUE) {                  continue该chain的下一条rule;          } else if (reg[VERDICT] == DROP) {                  break丢弃数据包          } else if (reg[VERDICT] == ACCEPT) {                  break接受数据包          } else if (reg[VERDICT] == GOTO) {                  breakrule = rule;  

阅读更多内容

Linux线程编程之信号处理 - clover_toeic

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Linux线程编程之信号处理 - clover_toeic  阅读原文»

前言

Linux多线程环境中的信号处理不同于进程的信号处理。一方面线程间信号处理函数的共享性使得信号处理更为复杂,另一方面普通异步信号又可转换为同步方式来简化处理。

本文首先介绍信号处理在进程中和线程间的不同,然后描述相应的线程库函数,在此基础上给出一组示例代码,以讨论线程编程中信号处理的细节和注意事项。文中涉及的代码运行环境如下:

本文通过sigwait()调用来“等待”信号,而通过signal()/sigaction()注册的信号处理函数来“捕获”信号,以体现其同步和异步的区别。

一 概念

1.1 进程与信号

信号是向进程异步发送的软件通知,通知进程有事件发生。事件可为硬件异常(如除0)、软件条件(如闹钟超时)、控制终端发出的信号或调用kill()/raise()函数产生的用户逻辑信号。

当信号产生时,内核通常在进程表中设置一个某种形式的标志,即向进程递送一个信号。在信号产生(generation)和递送(delivery)之间(可能相当长)的时间间隔内,该信号处于未决(pending)状态。已经生成但未递送的信号称为挂起(suspending)的信号。

进程可选择阻塞(block)某个信号,此时若对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程(a)对此信号解除阻塞,或者(b)将对此信号的动作更改为忽略。内核为每个进程维护一个未决(未处理的)信号队列,信号产生时无论是否被阻塞,首先放入未决队列里。当时间片调度到当前进程时,内核检查未决队列中是否存在信号。若有信号且未被阻塞,则执行相应的操作并从队列中删除该信号;否则仍保留该信号。因此,进程在信号递送给它之前仍可改变对该信号的动作。进程调用sigpending()函数判定哪些信号设置为阻塞并处于未决状态。

若在进程解除对某信号的阻塞之前,该信号发生多次,则未决队列仅保留相同不可靠信号中的一个,而可靠信号(实时扩展)会保留并递送多次,称为按顺序排队。

每个进程都有一个信号屏蔽字(signal mask),规定当前要阻塞递送到该进程的信号集。对于每个可能的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则该信号当前被阻塞。

应用程序处理信号前,需要注册信号处理函数(signal handler)。当信号异步发生时,会调用处理函数来处理信号。因为无法预料信号会在进程的哪个执行点到来,故信号处理函数中只能简单设置一个外部变量或调用异步信号安全(async-signal-safe)的函数。此外,某些库函数(如read)可被信号中断,调用时必须考虑中断后出错恢复处理。这使得基于进程的信号处理变得复杂和困难。

1.2 线程与信号

内核也为每个线程维护未决信号队列。当调用sigpending()时,返回整个进程未决信号队列与调用线程未决信号队列的并集。进程内创建线程时,新线程将继承进程(主线程)的信号屏蔽字,但新线程的未决信号集被清空(以防同一信号被多个线程处理)。线程的信号屏蔽字是私有的(定义当前线程要求阻塞的信号集),即线程可独立地屏蔽某些信号。这样,应用程序可控制哪些线程响应哪些信号。

信号处理函数由进程内所有线程共享。这意味着尽管单个线程可阻止某些信号,但当线程修改某信号相关的处理行为后,所有线程都共享该处理行为的改变。这样,若某线程选择忽略某信号,而其他线程可恢复信号的默认处理行为或为信号设置新的处理函数,从而撤销原先的忽略行为。即对某个信号处理函数,以最后一次注册的处理函数为准,从而保证同一信号被任意线程处理时行为相同。此外,若某信号的默认动作是停止或终止,则不管该信号发往哪个线程,整个进程都会停止或终止。

若信号与硬件故障(如SIGBUS/SIGFPE/SIGILL/SIGSEGV)或定时器超时相关,该信号会发往引起该事件的线程。其它信号除非显式指定目标线程,否则通常发往主线程(哪怕信号处理函数由其他线程注册),仅当主线程屏蔽该信号时才发往某个具有处理能力的线程。

Linux系统C标准库提供两种线程实现,即LinuxThreads(已过时)和NPTL(Native POSIX Threads Library)。NPTL线程库依赖Linux 2.6内核,更加(但不完全)符合POSIX.1 threads(Pthreads)规范。两者的详细区别可以通过man 7 pthreads命令查看。

NPTL线程库中每个线程拥有自己独立的线程号,并共享同一进程号,故应用程序可调用kill(getpid(), signo)将信号发送到整个进程;而LinuxThreads线程库中每个线程拥有自己独立的进程号,不同线程调用getpid()会得到不同的进程号,故应用程序无法通过调用kill()将信号发送到整个进程,而只会将信号发送到主线程中去。

多线程中信号处理函数的共享性使得异步处理更为复杂,但通常可简化为同步处理。即创建一个专用线程来“同步等待”信号的到来,而其它线程则完全不会被该信号中断。这样就可确知信号的到来时机,必然是在专用线程中的那个等待点。

注意,线程库函数不是异步信号安全的,故信号处理函数中不应使用pthread相关函数。

二 接口

2.1 pthread_sigmask

线程可调用pthread_sigmask()设置本线程的信号屏蔽字,以屏蔽该线程对某些信号的响应处理。

#include <signal.h>

int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

该函数检查和(或)更改本线程的信号屏蔽字。若参数oset为非空指针,则该指针返回调用前本线程的信号屏蔽字。若参数set为非空指针,则参数how指示如何修改当前信号屏蔽字;否则不改变本线程信号屏蔽字,并忽略how值。该函数执行成功时返回0,否则返回错误编号(errno)。

下表给出参数how可选用的值。其中,SIG_ BLOCK为“或”操作,而SIG_SETMASK为赋值操作。

参数how

描述

SIG_BLOCK

将set中包含的信号加入本线程的当前信号屏蔽字

SIG_UNBLOCK

从本线程的当前信号屏蔽字中移除set中包含的信号(哪怕该信号并未被阻塞)

SIG_SETMASK

将set指向的信号集设置为本线程的信号屏蔽字

主线程调用pthread_sigmask()设置信号屏蔽字后,其创建的新线程将继承主线程的信号屏蔽字。然而,新线程对信号屏蔽字的更改不会影响创建者和其他线程。

通常,被阻塞的信号将不能中断本线程的执行,除非该信号指示致命的程序错误(如SIGSEGV)。此外,不能被忽略处理的信号(SIGKILL 和SIGSTOP )无法被阻塞。

注意,pthread_sigmask()与sigprocmask()函数功能类似。两者的区别在于,pthread_sigmask()是线程库函数,用于多线程进程,且失败时返回errno;而sigprocmask()针对单线程的进程,其行为在多线程的进程中没有定义,且失败时设置errno并返回-1。

2.2 sigwait

线程可通过调用sigwait()函数等待一个或多个信号发生。

#include <signal.h>

int sigwait(const sigset_t *restrict sigset, int *restrict signop);

参数sigset指定线程等待的信号集,signop指向的整数表明接收到的信号值。该函数将调用线程挂起,直到信号集中的任何一个信号被递送。该函数接收递送的信号后,将其从未决队列中移除(以防返回时信号被signal/sigaction安装的处理函数捕获),然后唤醒线程并返回。该函数执行成功时返回0,并将接收到的信号值存入signop所指向的内存空间;失败时返回错误编号(errno)。失败原因通常为EINVAL(指定信号无效或不支持),但并不返回EINTR错误。

给定线程的未决信号集是整个进程未决信号集与该线程未决信号集的并集。若等待信号集中某个信号在sigwait()调用时处于未决状态,则该函数将无阻塞地返回。若同时有多个等待中的信号处于未决状态,则对这些信号的选择规则和顺序未定义。在返回之前,sigwait()将从进程中原子性地移除所选定的未决信号。

若已阻塞等待信号集中的信号,则sigwait()会自动解除信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait()将恢复线程的信号屏蔽字。因此,sigwait()并不改变信号的阻塞状态。可见,sigwait()的这种“解阻-等待-阻塞”特性,与条件变量非常相似。

为避免错误发生,调用sigwait()前必须阻塞那些它正在等待的信号。在单线程环境中,调用程序首先调用sigprocmask()阻塞等待信号集中的信号,以防这些信号在连续的sigwait()调用之间进入未决状态,从而触发默认动作或信号处理函数。在多线程程序中,所有线程(包括调用线程)都必须阻塞等待信号集中的信号,否则信号可能被递送到调用线程之外的其他线程。建议在创建线程前调用pthread_sigmask()阻塞这些信号(新线程继承信号屏蔽字),然后绝不显式解除阻塞(sigwait会自动解除信号集的阻塞状态)。

若多个线程调用sigwait()等待同一信号,只有一个(但不确定哪个)线程可从sigwait()中返回。若信号被捕获(通过sigaction安装信号处理函数),且线程正在sigwait()调用中等待同一信号,则由系统实现来决定以何种方?div style="border-bottom:1px solid #aaa;margin-bottom:25px">网络采集软件核心技术剖析系列(4)---使用C#语言如何将html网页转换成pdf(html2pdf) - 际为软件事务所  阅读原文»

一 本系列随笔概览及产生的背景

本系列开篇受到大家的热烈欢迎,这对博主是莫大的鼓励,此为本系列第四篇,希望大家继续支持,为我继续写作提供动力。

自己开发的豆约翰博客备份专家软件工具问世3年多以来,深受广大博客写作和阅读爱好者的喜爱。同时也不乏一些技术爱好者咨询我,这个软件里面各种实用的功能是如何实现的。

该软件使用.NET技术开发,为回馈社区,现将该软件中用到的核心技术,开辟一个专栏,写一个系列文章,以飨广大技术爱好者。

本系列文章除了讲解网络采编发用到的各种重要技术之外,也提供了不少问题的解决思路和界面开发的编程经验,非常适合.NET开发的初级,中级读者,希望大家多多支持。

很多初学者常有此类困惑,“为什么我书也看了,C#相关的各个方面的知识都有所了解,但就是没法写出一个像样的应用呢?”

这其实还是没有学会综合运用所学知识,锻炼出编程思维,建立起学习兴趣,我想该系列文章也许会帮到您,但愿如此。

开发环境:VS2008

本节源码位置:https://github.com/songboriceboy/csharphtml2pdf

源码下载办法:安装SVN客户端(本文最后提供下载地址),然后checkout以下的地址:https://github.com/songboriceboy/csharphtml2pdf

系列文章提纲拟定如下:

1.如何使用C#语言获取博客园某个博主的全部随笔链接及标题;
2.如何使用C#语言获得博文的正文及标题;
3.使用C#语言如何将html网页转换成pdf(html2pdf)
4.如何使用C#语言下载博文中的全部图片到本地并可以离线浏览
5.如何使用C#语言合成多个单个的pdf文件到一个pdf中,并生成目录
6.网易博客的链接如何使用C#语言获取到,网易博客的特殊性;
7.微信公众号文章如何使用C#语言下载;
8.如何获取任意一篇文章的全部图文
9.如何使用C#语言去掉html中的全部标签获取纯文本(html2txt)
10.如何使用C#语言将多个html文件编译成chm(html2chm)
11.如何使用C#语言远程发布文章到新浪博客
12.如何使用C#语言开发静态站点生成器
13.如何使用C#语言搭建程序框架(经典Winform界面,顶部菜单栏,工具栏,左边树形列表,右边多Tab界面)
14.如何使用C#语言实现网页编辑器(Winform)
......

二 第四节主要内容简介(使用C#语言如何将html网页转换成pdf)

本节示例代码的运行界面如下图所示。点击生成PDF按钮后,程序做了3件事情:

(1)下载网页地址中博文的正文;

(2)下载博文中的全部图片到本地;

(3)将文字和图片用后面所说的工具生成为PDF文档。

点击生成PDF按钮后,该程序后自动下载网址中对应的网页,并在可执行程序所在目录生成一个以博文标题命名的文件夹,如下图所示:

该文件夹中包含了网页正文的html文档(index.html文件),正文中的全部图片,以及最后生成的pdf文档。如下图所示:

生成的pdf文档效果如下图所示:

将html文件转换成pdf文件所需要的技术非常高,我们需要写一个既能解析html文档(类似浏览器功能),又能生成pdf文档(需要掌握pdf文档结构细节)的工具,幸运的是一个有一个好用免费的现成的工具,那就是wkhtmltopdf(http://www.wkhtmltopdf.org/)。

首先,我们来看一下如何通过这个工具来转html文档到pdf文档。下载对应平台的可执行文件(windows平台的可以在我上面的github地址中下载到,在PDFLIB文件夹中)。文件夹中有4个文件,如下图所示:

进入DOS界面,执行命令wkhtmltopdf.exe www.cnblogs.com cnblogs.pdf,第一个参数是要转化的网页地址(www.cnblogs.com),

第二个参数是要保存的pdf文件名称(cnblogs.pdf),这个命令执行成功之后会发现在当前路径下生成了一个cnblogs.pdf文件(我这里的路径是d:/PDFLIB/)。

生成的PDF文件如下:

接下来我们需要做的是将这个生成过程整合到我们自己开发的软件中。

由上面的做法可以很容易得出,我们需要使用C#语言来实现进程间通信,即在我们自己的代码中启动wkhtmltopdf.exe进程,并传递给wkhtmltopdf.exe其所需要的参数。

C#中启动一个进程可以利用Process类,下面我们就来看一下具体做法,核心代码如下:

public bool _html2pdf(string fileName)
{
string strPdfSavedPath = m_strPath;
if (!Directory.Exists(strPdfSavedPath))//判断是否存在
{
Directory.CreateDirectory(strPdfSavedPath);
//创建新路径
}

if (!File.Exists(strPdfSavedPath + fileName + ".pdf"))
{

string strHtmlSavedPath = m_strPath;
string file_flvbind = Application.StartupPath + @"\PDFLIB\wkhtmltopdf.exe";
//MoveFolderTo(fileName, Application.StartupPath + @"\PDFLIB\");
//生成ProcessStartInfo
ProcessStartInfo pinfo = new ProcessStartInfo(file_flvbind);
//pinfo.WorkingDirectory = Application.StartupPath + @"\PDFLIB\";
pinfo.WorkingDirectory = strHtmlSavedPath;
//设置参数
StringBuilder sb = new StringBuilder();
sb.Append(
"--footer-line ");
sb.Append(
"--footer-center \"powered by 际为软件事务所(http://www.cnblogs.com/ice-river)\" ");
sb.Append(
"\"" + "index.html\"");

sb.Append(
" \"" + strPdfSavedPath + fileName + ".pdf" + "\"");

pinfo.Arguments
= sb.ToString();
//隐藏窗口
pinfo.WindowStyle = ProcessWindowStyle.Hidden;
//启动程序

Process p = Process.Start(pinfo);
p.WaitForExit();
//DeleteFiles(Application.StartupPath + @"\PDFLIB\");
if (p.ExitCode == 0)
{
DelegatePara dp
= new DelegatePara();
dp.strLog
= "生成 [" + fileName + "

阅读更多内容