bash 判断

运算符 描述 示例
文件比较运算符
-e filename 如果 filename存在,则为真 [ -e /var/log/syslog ]
-d filename 如果 filename为目录,则为真 [ -d /tmp/mydir ]
-f filename 如果 filename为常规文件,则为真 [ -f /usr/bin/grep ]
-L filename 如果 filename为符号链接,则为真 [ -L /usr/bin/grep ]
-r filename 如果 filename可读,则为真 [ -r /var/log/syslog ]
-w filename 如果 filename可写,则为真 [ -w /var/mytmp.txt ]
-x filename 如果 filename可执行,则为真 [ -L /usr/bin/grep ]
filename1-nt filename2 如果 filename1 filename2新,则为真 [ /tmp/install/etc/services -nt /etc/services ]
filename1-ot filename2 如果 filename1 filename2旧,则为真 [ /boot/bzImage -ot arch/i386/boot/bzImage ]
字符串比较运算符 (请注意引号的使用,这是防止空格扰乱代码的好方法)
-z string 如果 string长度为零,则为真 [ -z "$myvar" ]
-n string 如果 string长度非零,则为真 [ -n "$myvar" ]
string1= string2 如果 string1 string2相同,则为真 [ "$myvar" = "one two three" ]
string1!= string2 如果 string1 string2不同,则为真 [ "$myvar" != "one two three" ]
算术比较运算符
num1-eq num2 等于 [ 3 -eq $mynum ]
num1-ne num2 不等于 [ 3 -ne $mynum ]
num1-lt num2 小于 [ 3 -lt $mynum ]
num1-le num2 小于或等于 [ 3 -le $mynum ]
num1-gt num2 大于 [ 3 -gt $mynum ]
num1-ge num2 大于或等于 [ 3 -ge $mynum ]



if 语句语法:


if [ condition ]
then 
    action
fi


只有当 condition 为真时,该语句才执行操作,否则不执行操作,并继续执行 "fi" 之后的任何行。

if [ condition ]
then 
    action
elif [ condition2 ]
then 
    action2
.
.
.
elif [ condition3 ]
then 
else
    actionx
fi



以上 "elif" 形式将连续测试每个条件

发表在 linux | 标签为 | bash 判断已关闭评论

sh & bash 资料

一 简单过程
1)可以使用任意一种文字编辑器,比如nedit、kedit、emacs、vi等来编写shell脚本。ubuntu中可以使用gedit,notpad++也不错啊。
2)文件必须以#!/bin/sh开始。符号#!用来告诉系统那个shell来执行该脚本的程序,系统中可以有多个shell,例如使用/bin/sh,/bin/bash。
3)编辑结束并保存后,如果要执行该脚本,必须先使其可执行,使用命令chmod +x filename。
4)此后在该脚本所在目录下,输入 ./filename 即可执行该脚本。
5)最简单的调试方法当然是使用echo命令。你可以在任何怀疑出错的地方用echo打印变量值。
6)可以用sh -x strangescript来调试。
7)可以使用sh -n your_script来检查语法,不执行。
8)以# 开始的行表示注释,直到该行的结束。

二 命令
可以使用所有的Unux的命令。

三 变量
Shell编程中,使用变量无需事先声明,同时变量名的命名须遵循如下规则:1. 首个字符必须为字母(a-z,A-Z)2. 中间不能有空格,可以使用下划线(_)3. 不能使用标点符号 4. 不能使用bash里的关键字(可用help命令查看保留关键字)。
要给变量赋值时,可以这么写:变量名=值, 等号两边均不能有空格存在。为了避免混淆,可以使用{}给变量,如${num}。

四 管道/重定向
*  管道 (|) :将一个命令的输出作为另外一个命令的输入 :grep "hello" file.txt | wc -l 。 上述命令会在file.txt中搜索包含有”hello”的行并计算行数,这里grep命令的输出成了wc命令的
输入。
* 重定向:将命令的结果输出到文件,而不是标准输出(屏幕) > 写入文件并复盖旧文件,>> 加到文件的尾部,保留旧文件内容。
* 反短斜线:反短斜线可以将一个命令的输出作为其它命令的命令行参数。find . -mtime -1 -type f -print。上述命令可以查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本: 
#!/bin/sh
# The ticks are backticks (`) not normal quotes  ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
* 单引号‘’:功能则最强。当你把字符串用单引号括起来时,外壳将忽视所有单引号中的特殊字符。
* 双引号“”:双引号的功能最弱。当你把字符串用双引号括起来时,外壳将忽略字符串中的空格,但其他的字符都将继续起作用。双引号在将多于一个单词的字符串赋给一个变量时尤其有用。
* 反斜杠\:反斜杠的功能和单引号一样,只是反斜杠每次只能使一个字符发生转义,而不是使整个字符串发生转义。

五 特殊字符
• 有些变量在启动外壳时就已经存在于系统中,你可以使用这些系统变量,并且可以赋予
新值:
$HOME 用户自己的目录。
$ PATH 执行命令时所搜寻的目录。
$TZ 时区。
$MAILCHECK 每隔多少秒检查是否有新的邮件。
$ P S 1 在外壳命令行的提示符。
$ P S 2 当命令尚未打完时,外壳要求再输入时的提示符。
$ M A N PATHman 指令的搜寻路径。
• 有些变量在执行外壳程序时系统就设置好了,并且你不能加以修改:
$ # 存储外壳程序中命令行参数的个数。
$ ? 存储上一个执行命令的返回值。
$ 0 存储外壳程序的程序名。
$ * 存储外壳程序的所有参数。
$ @ 存储所有命令行输入的参数,分别表示为(“$ 1” “$ 2” . . . )。shift 命令用来将存储在位置参数中的当前值左移一个位置。
$ $ 存储外壳程序的P I D。
$ ! 存储上一个后台执行命令的P I D。

六 关键字
1)if
if [ expression ]
then
commands
elif [ expression2 ]
then
commands
else
commands
fi
2)条件,条件之间可以使用&& 和||

-b file            若文件存在且是一个块特殊文件,则为真
-c file            
若文件存在且是一个字符特殊文件,则为真
-d file            
若文件存在且是一个目录,则为真
-e file            
若文件存在,则为真
-f file            
若文件存在且是一个规则文件,则为真
-g file            
若文件存在且设置了SGID位的值,则为真
-h file            
若文件存在且为一个符合链接,则为真
-k file            
若文件存在且设置了"sticky"位的值
-p file            
若文件存在且为一已命名管道,则为真
-r file            
若文件存在且可读,则为真
-s file            
若文件存在且其大小大于零,则为真
-u file            
若文件存在且设置了SUID位,则为真
-w file            
若文件存在且可写,则为真
-x file            
若文件存在且可执行,则为真
-o file            
若文件存在且被有效用户ID所拥有,则为真

-z string          string长度为0,则为真
-n string          
string长度不为0,则为真
string1 = string2  
若两个字符串相等,则为真
string1 != string2 
若两个字符串不相等,则为真

int1 -eq int2      int1等于int2,则为真
int1 -ne int2      
int1不等于int2,则为真
int1 -lt int2      
int1小于int2,则为真
int1 -le int2      
int1小于等于int2,则为真
int1 -gt int2      
int1大于int2,则为真
int1 -ge int2      
int1大于等于int2,则为真

!expr              expr为假则复合表达式为真。expr可以是任何有效的测试表达式
expr1 -a expr2     
expr1expr2都为真则整式为真
expr1 -o expr2     
expr1expr2有一个为真则整式为真

3)case
case string1 in
str1 )
commands ; ;
str2 )
commands ; ;
* )
commands ; ;
esac
4)for
for var1 in list
do
commands
done
5)while
while expression
do
statements
done 
6)until
until expression
do
commands
done
7)select
select menuitem [in list_of_items]
do
commands
done

七 子函数
fname () {
shellcommands
}
调用fname [parm1 parm2 parm3 ...]

#!/bin/sh
 
# verifycron - script checks a crontab file to ensure that it's
#    formatted properly.  Expects standard cron notation of
#       min hr dom mon dow CMD    
#    where min is 0-59, hr 0-23, dom is 1-31, mon is 1-12 (or names)
#    and dow is 0-7 (or names).  Fields can have ranges (a-e), lists
#    separated by commas (a,c,z), or an asterisk. Note that the step 
#    value notation of Vixie cron is not supported (e.g., 2-6/2).
 
 
validNum()
{
  # return 0 if valid, 1 if not. Specify number and maxvalue as args
  num=$1   max=$2
 
  if [ "$num" = "X" ] ; then
    return 0
  elif [ ! -z $(echo $num | sed 's/[[:digit:]]//g') ] ; then
    return 1
  elif [ $num -lt 0 -o $num -gt $max ] ; then
    return 1
  else
    return 0
  fi
}
 
validDay()
{
  # return 0 if a valid dayname, 1 otherwise
 
  case $(echo $1 | tr '[:upper:]' '[:lower:]') in
    sun*|mon*|tue*|wed*|thu*|fri*|sat*) return 0 ;;
    X) return 0 ;; # special case - it's an "*"
    *) return 1
  esac
}
 
validMon()
{
  # return 0 if a valid month name, 1 otherwise
 
   case $(echo $1 | tr '[:upper:]' '[:lower:]') in 
     jan*|feb*|mar*|apr*|may|jun*|jul*|aug*) return 0       ;;
     sep*|oct*|nov*|dec*)            return 0       ;;
     X) return 0 ;; # special case, it's an "*"
     *) return 1    ;;
   esac
}
 
fixvars()
{
  # translate all '*' into 'X' to bypass shell expansion hassles
  # save original as "sourceline" for error messages
 
  sourceline="$min $hour $dom $mon $dow $command"
   min=$(echo "$min" | tr '*' 'X')
  hour=$(echo "$hour" | tr '*' 'X')
   dom=$(echo "$dom" | tr '*' 'X')
   mon=$(echo "$mon" | tr '*' 'X')
   dow=$(echo "$dow" | tr '*' 'X')
}
 
if [ $# -ne 1 ] || [ ! -r $1 ] ; then
  echo "Usage: $0 usercrontabfile" >&2; exit 1
fi
 
lines=0  entries=0  totalerrors=0
 
while read min hour dom mon dow command
do
  lines="$(( $lines + 1 ))"
  errors=0
   
  if [ -z "$min" -o "${min%${min#?}}" = "#" ] ; then
    continue    # nothing to check
  elif [ ! -z $(echo ${min%${min#?}} | sed 's/[[:digit:]]//') ] ;  then
    continue    # first char not digit: skip!
  fi
 
  entries="$(($entries + 1))"
 
  fixvars
 
  #### Broken into fields, all '*' replaced with 'X' 
  # minute check
 
  for minslice in $(echo "$min" | sed 's/[,-]/ /g') ; do
    if ! validNum $minslice 60 ; then
      echo "Line ${lines}: Invalid minute value \"$minslice\""
      errors=1
    fi
  done
 
  # hour check
   
  for hrslice in $(echo "$hour" | sed 's/[,-]/ /g') ; do
    if ! validNum $hrslice 24 ; then
      echo "Line ${lines}: Invalid hour value \"$hrslice\""
      errors=1
    fi
  done
 
  # day of month check
 
  for domslice in $(echo $dom | sed 's/[,-]/ /g') ; do
    if ! validNum $domslice 31 ; then
      echo "Line ${lines}: Invalid day of month value \"$domslice\""
      errors=1
    fi
  done
 
  # month check
 
  for monslice in $(echo "$mon" | sed 's/[,-]/ /g') ; do
    if ! validNum $monslice 12 ; then
      if ! validMon "$monslice" ; then
        echo "Line ${lines}: Invalid month value \"$monslice\""
        errors=1
      fi
    fi
  done
 
  # day of week check
 
  for dowslice in $(echo "$dow" | sed 's/[,-]/ /g') ; do
    if ! validNum $dowslice 7 ; then
      if ! validDay $dowslice ; then
        echo "Line ${lines}: Invalid day of week value \"$dowslice\""
        errors=1
      fi
    fi
  done
 
  if [ $errors -gt 0 ] ; then
    echo ">>>> ${lines}: $sourceline"
    echo ""
    totalerrors="$(( $totalerrors + 1 ))"
  fi
done < $1
 
echo "Done. Found $totalerrors errors in $entries crontab entries."
 
exit 0
八 变量

假设执行 ./test.sh a b c 这样一个命令,则可以使用下面的参数来获取一些值:

$0 对应 "./test.sh" 这个值。如果执行的是 ./work/test.sh, 则对应 ./work/test.sh 这个值,而不是只返回文件名本身的部分。
$1 会获取到 a,即 $1 对应传给脚本的第一个参数。
$2 会获取到 b,即 $2 对应传给脚本的第二个参数。
$3 会获取到 c,即 $3 对应传给脚本的第三个参数。$4、$5 等参数的含义依此类推。
$# 会获取到 3,对应传入脚本的参数个数,统计的参数不包括 $0。
$@ 会获取到 "a" "b" "c",也就是所有参数的列表,不包括 $0。
$* 也会获取到 "a" "b" "c", 其值和 $@ 相同。但 "$*" 和 "$@" 有所不同。"$*" 把所有参数合并成一个字符串,而 "$@" 会得到一个字符串参数数组。
$? 可以获取到执行 ./test.sh a b c 命令后的返回值。在执行一个前台命令后,可以立即用 $? 获取到该命令的返回值。该命令可以是系统自身的命令,可以是 shell 脚本,也可以是自定义的 bash 函数。

九 退出

#!/bin/bash

touch /root/test 2> /dev/null

if [ $? -eq 0 ]
then
  echo "Successfully created file"
  exit 0
else
  echo "Could not create file" >&2
  exit 1
fi
.
发表在 linux | 标签为 , | sh & bash 资料已关闭评论

bash & sh

bash是 Bourne Again SHell 是linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容Bourne shell,也就是说用Bourne shell的脚本不加修改可以在bash中执行。
sh是Bourne shell 这个是UNIX标准的默认shell,对它评价是concise简洁 compact紧凑  fast高效 有AT&T编写,属于系统管理shell。

发表在 linux | 标签为 , | bash & sh已关闭评论

iptables 常用策略 / centos 7

1、清空存在的策略
当你开始创建新的策略,你可能想清除所有的默认策略,和存在的策略,可以这么做:
iptables -F  或者iptables --flush
iptables -L -n
iptables -L -n -v
iptables -D INPUT 4
2,设置默认策略
默认链策略是ACCEPT,改变所有的链策略为DROP:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
3,阻止一个指定的ip
BLOCK_THIS_IP=“x.x.x.x"
iptables -A INPUT -s ”$BLOCK_THIS_IP“ -j DROP
iptables -A INPUT -i eth0 -s "$BLOCK_THIS_IP" -j DROP
iptables -A INPUT -i eth0 -p tcp -s "$BLOCK_THIS_IP" -j DROP
4,允许SSH
允许所有通过eth0接口使用ssh协议连接本机:
iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
5,允许某个网段通过ssh连接
iptables -A INPUT -i eth0 -p tcp -s 192.168.100.0/24 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
6,允许http和https
允许所有进来的web流量:http协议的80端口
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT
允许所有进来的web流量:https协议的443端口
iptables -A INPUT -i eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT
7,多个策略联合一起
允许ssh,http,https:
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 22,80,443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp -m multiport --sports 22,80,443 -m state --state ESTABLISHED -j ACCEPT
8,允许SSH连接其他主机
iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
9,允许SSH连接指定的网段
iptables -A OUTPUT -o eth0 -p tcp -d 192.168.100.0/24 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
10,允许https出去
iptables -A OUTPUT -o eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT
11,对web请求做负载均衡(每三个包,均衡到指定服务器,需要扩展iptables)
iptables -A PREROUTING -i eth0 -p tcp --dport 443 -m state --state NEW -m nth --counter 0 --every 3 --packet 0 -j DNAT --to-destination 192.168.1.101:443
iptables -A PREROUTING -i eth0 -p tcp --dport 443 -m state --state NEW -m nth --counter 0 --every 3 --packet 1 -j DNAT --to-destination 192.168.1.102:443
iptables -A PREROUTING -i eth0 -p tcp --dport 443 -m state --state NEW -m nth --counter 0 --every 3 --packet 2 -j DNAT --to-destination 192.168.1.103:443
12,允许ping
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
13,允许ping远程
iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
14,允许本地回环
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
15,允许内网访问外部网络
这个例子eth1 连接外部网络,eth0连接内部网络
iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT
16,允许DNS出去
iptables -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT
iptables -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT
17,允许NIS连接
NIS端口是动态的,当ypbind启动时它分配端口。
首先运行 rpcinfo -p 显示得到端口号,这个例子使用端口850,853。
iptables -A INPUT -p tcp --dport 111 -j ACCEPT
iptables -A INPUT -p udp --dport 111 -j ACCEPT
iptables -A INPUT -p tcp --dport 853 -j ACCEPT
iptables -A INPUT -p udp --dport 853 -j ACCEPT
iptables -A INPUT -p tcp --dport 850 -j ACCEPT
iptables -A INPUT -p udp --dport 850 -j ACCEPT
上面的例子当ypbind重新启动时将失效,有2种解决方案:
(1)分配nis服务静态ip(2) 使用精妙的脚本
18,允许指定网段连接Rsync
iptables -A INPUT -i eth0 -p tcp -s 192.168.101.0/24 --dport 873 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 873 -m state --state ESTABLISHED -j ACCEPT
19,允许mysql从指定的网段连接
iptables -A INPUT -i eth0 -p tcp -s 192.168.100.0/24 --dport 3306 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 3306 -m state --state ESTABLISHED -j ACCEPT
20,允许sendmail或者postfix
iptables -A INPUT -i eth0 -p tcp --dport 25 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 25 -m state --state ESTABLISHED -j ACCEPT
21,允许IMAP和IMAPS
IMAP:
iptables -A INPUT -i eth0 -p tcp --dport 143 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 143 -m state --state ESTABLISHED -j ACCEPT
IMAPS:
iptables -A INPUT -i eth0 -p tcp --dport 993 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 993 -m state --state ESTABLISHED -j ACCEPT
22,允许POP3和POP3S
POP3:
iptables -A INPUT -i eth0 -p tcp --dport 110 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 110 -m state --state ESTABLISHED -j ACCEPT
POP3S:
iptables -A INPUT -i eth0 -p tcp --dport 995 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 995 -m state --state ESTABLISHED -j ACCEPT
23,预防DOS攻击
iptables -A INPUT -p tcp --dport 80 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
-m : 使用iptables扩展
--limit 25/minute : 限制分钟连接请求数
--limit-burst:触发阀值,一次涌入数据包数量
24,端口转发
来自442的都转到22端口
iptables -t nat -A PREROUTING -p tcp -d 192.168.102.37 --dport 422 -j DNAT --to 192.168.102.37:22
你还必须明确允许442端口
iptables -A INPUT -i eth0 -p tcp --dport 422 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 422 -m state --state ESTABLISHED -j ACCEPT
25,包丢弃日志
你也许想查看所有丢弃包的日志。
首先创建一个新链叫 LOGGING
iptables -N LOGGING
确保所有的连接跳到LOGGING
iptables -A INPUT -j LOGGING
记录这些包通过自定义名字 "log-prefix"
iptables -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables Packet Dropped:" --log-level 7
最后丢弃这些数据包
iptables -A LOGGING -j DROP

26,常用

iptables --flush
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
iptables -A INPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o lo -p all -j ACCEPT
iptables -A OUTPUT -p icmp -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 21 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 123 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT

26,一批示例

#查看iptables现有规则
iptables -L -n
#先允许所有,不然有可能会杯具
iptables -P INPUT ACCEPT
#清空所有默认规则
iptables -F
#清空所有自定义规则
iptables -X
#所有计数器归0
iptables -Z
#允许来自于lo接口的数据包(本地访问)
iptables -A INPUT -i lo -j ACCEPT
#开放22端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
#允许ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
#允许接受本机请求之后的返回数据 RELATED,是为FTP设置的
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#其他入站一律丢弃
iptables -P INPUT DROP
#所有出站一律绿灯
iptables -P OUTPUT ACCEPT
#所有转发一律丢弃
iptables -P FORWARD DROP

##BATCH:EXAMPLEs##
iptables -L -n
iptables -P INPUT ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

### 描述

iptables基础
①规则(rules):网络管理员预定义的条件
②链(chains): 是数据包传播的路径
③表(tables):内置3个表filter表,nat表,mangle表分别用于实现包过滤网络地址转换和包重构的功能
④filter表是系统默认的,INPUT表(进入的包),FORWORD(转发的包),OUTPUT(处理本地生成的包),filter表只能对包进行授受和丢弃的操作。
⑤nat表(网络地址转换),PREROUTING(修改即将到来的数据包),OUTPUT(修改在路由之前本地生成的数据包),POSTROUTING(修改即将出去的数据包)
⑥mangle表,PREROUTING,OUTPUT,FORWORD,POSTROUTING,INPUT

iptables命令格式
iptables [-t 表] -命令 匹配 操作 (大小写敏感)
动作选项
ACCEPT 接收数据包
DROP 丢弃数据包
REDIRECT 将数据包重新转向到本机或另一台主机的某一个端口,通常功能实现透明代理或对外开放内网的某些服务
SNAT 源地址转换
DNAT 目的地址转换
MASQUERADE IP伪装
LOG 日志功能

定义规则
①先拒绝所有的数据包,然后再允许需要的数据包
iptalbes -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
②查看nat表所有链的规则列表
iptables -t nat -L
③增加,插入,删除和替换规则
iptables [-t 表名] <-A|I|D|R> 链名 [规则编号] [-i|o 网卡名称] [-p 协议类型] [-s 源ip|源子网] [--sport 源端口号] [-d 目的IP|目标子网] [--dport 目标端口号] [-j 动作]
参数:
-A 增加
-I 插入
-D 删除
-R 替换

基于状态的匹配扩展(连接跟踪)
每个网络连接包括以下信息:源地址、目标地址、源端口、目的端口,称为套接字对(socket pairs);协议类型、连接状态(TCP协议)
和超时时间等。防火墙把这些信息称为状态(stateful)。
状态包过滤防火墙能在内存中维护一个跟踪状态的表,比简单包过滤防火墙具有更大的安全性,命令格式如下:
iptables -m state –-state [!]state [,state,state,state]
其中,state表是一个逗号分割的列表,用来指定连接状态,4种:
>NEW: 该包想要开始一个新的连接(重新连接或连接重定向)
>RELATED:该包是属于某个已经建立的连接所建立的新连接。举例:
FTP的数据传输连接和控制连接之间就是RELATED关系。
>ESTABLISHED:该包属于某个已经建立的连接。
>INVALID:该包不匹配于任何连接,通常这些包被DROP。

 

 

 

CentOS7默认的防火墙不是iptables,而是firewalle.

安装iptable iptable-service

#先检查是否安装了iptables
service iptables status
#安装iptables
yum install -y iptables
#升级iptables
yum update iptables
#安装iptables-services
yum install iptables-services

 

 

禁用/停止自带的firewalld服务

#停止firewalld服务
systemctl stop firewalld
#禁用firewalld服务
systemctl mask firewalld

 

设置现有规则

#查看iptables现有规则
iptables -L -n
#先允许所有,不然有可能会杯具
iptables -P INPUT ACCEPT
#清空所有默认规则
iptables -F
#清空所有自定义规则
iptables -X
#所有计数器归0
iptables -Z
#允许来自于lo接口的数据包(本地访问)
iptables -A INPUT -i lo -j ACCEPT
#开放22端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
#开放21端口(FTP)
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
#开放80端口(HTTP)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
#开放443端口(HTTPS)
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
#允许ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
#允许接受本机请求之后的返回数据 RELATED,是为FTP设置的
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#其他入站一律丢弃
iptables -P INPUT DROP
#所有出站一律绿灯
iptables -P OUTPUT ACCEPT
#所有转发一律丢弃
iptables -P FORWARD DROP

 

其他规则设定

#如果要添加内网ip信任(接受其所有TCP请求)
iptables -A INPUT -p tcp -s 45.96.174.68 -j ACCEPT
#过滤所有非以上规则的请求
iptables -P INPUT DROP
#要封停一个IP,使用下面这条命令:
iptables -I INPUT -s ***.***.***.*** -j DROP
#要解封一个IP,使用下面这条命令:
iptables -D INPUT -s ***.***.***.*** -j DROP

 

保存规则设定

#保存上述规则
service iptables save

 

开启iptables服务

#注册iptables服务
#相当于以前的chkconfig iptables on
systemctl enable iptables.service
#开启服务
systemctl start iptables.service
#查看状态
systemctl status iptables.service

 

解决vsftpd在iptables开启后,无法使用被动模式的问题

1.首先在/etc/sysconfig/iptables-config中修改或者添加以下内容

#添加以下内容,注意顺序不能调换
IPTABLES_MODULES="ip_conntrack_ftp"
IPTABLES_MODULES="ip_nat_ftp"

2.重新设置iptables设置

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

以下为完整设置脚本

#!/bin/sh
iptables -P INPUT ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
service iptables save
systemctl restart iptables.service

 

原文: https://blog.csdn.net/l1028386804/article/details/50779761

 

 

..

 

 

发表在 linux | 标签为 | iptables 常用策略 / centos 7已关闭评论

postfix main.cf

一、设定邮件主机的识别身分(重要)

  myhostname 邮局系统的主机名:如果系统设置得当,应该不用设置,系统会以gethostname()取得

  mydomain 邮局系统的域名:预设会以myhostname第一个点之后的作为domain名称

  myorigin 从本机寄出邮件的域名名称:自动补齐资讯所用的,通常使用网域名称

  mydestination 本地网域-可接收邮件的主机名或域名:指Postfix应该视为「本地网域」的所有网域名称

  (本地网域的部份会后续在设定)

  myhostname = host.domain.tld

  mydomain = domain.tld

  myorigin = $mydomain

  二、设定 Postfix 能使用的网路介面(重要)

  (如果是对外服务的邮件主机,就必须要设定为all,预设为localhost)

  inet_interfaces = all

  inet_interfaces = $myhostname

  inet_interfaces = $myhostname, localhost

  inet_interfaces = localhost

  三、先设定简易的代转(Relay)管控

  3.1 mynetworks_style 提供三种简易设定型态

  class =>与伺服器位于同一级IP网路的主机都可以使用代转服务

  subnet =>所在网域的任一个IP都可以使用代转服务

  host =>仅开放localhost为信任?可代转的主机

  mynetworks_style = class

  mynetworks_style = subnet

  mynetworks_style = host

  3.2 mynetworks 明确设定可使用relay的主机范围

  (优先于mynetworks_style,建议采用此种设定法)

  mynetworks = 192.168.0.0/24, 192.168.1.0/24, 127.0.0.0/8

  mynetworks = $config_directory/mynetworks

  mynetworks = hash:/etc/postfix/network_table

  四、设定Postfix会收下信件的网域

  Postfix会收下四种特定网域的信件,除了main.cf中的设定外,

  也应该被正确的设置在DNS中的MX纪录中。

  (1)本地网域(local domain)=>由mydestination参数设定

  (2)代转网域(relay domains)=>由relay_domains参数设定

  (3)虚拟网域(virtual domains)之虚拟别名(virtual aliases)

  =>由 virtual_alias_domains参数设定

  (4)虚拟网域之虚拟信箱(virtual mailboxes)

  =>由 virtual_mailbox_domains参数设定

  (上列四种设定不应重复,网域已设为本地网域,就不应该出现在代转网域)

  4.1 设定本地网域

  mydestination = $myhostname, localhost.$mydomain

  mydestination = $myhostname, localhost.$mydomain, $mydomain

  mydestination = $myhostname, localhost.$mydomain, $mydomain,

  mail.$mydomain, www.$mydomain, ftp.$mydomain

  4.2 设定代转网域

  Postfix 预设会提供代转服务的设置:

  (代转服务=该网域并非由本Postfix MTA所管控,但可以代转发)

  - 从信任的客户端(指ip符合$mynetworks)寄出到任何目的地

  - 从非信任的客户端寄出到符合$relay_domains或其下次网域的目的地

  若信件终点站是relay_domains参数中的网址,被视为外地邮件,

  由 relay MDA来执行投递作业。

  relay_domains 预设值= $mydestination.(主机名称)

  4.3 设定虚拟别名?网域

  4.4 设定虚拟信箱?网域

  五、改写位址格式

  5.1 Postfix预设会以$myorigin或mydomain附加到不完整的电邮位址。

  5.2 正式位址

  另外提供了一种正式位址代换的机制 canonical map(正格表)

  (1)修改 /etc/postfix/canonical

  (2)再执行 postmap /etc/postfix/canonical

  (3)记得要重新载入 postfix reload

  正格表的撰写方式:

  eric@example.com eric.wu@example.com

  eric@example.com eric@oreilly.com

  canonical_maps = hash:/etc/postfix/canonical

  Postfix也提供额外的参数设置特定的位址

  sender_canonical_maps 仅修改发信者位址

  recipient_canonical_maps 仅修改收件者位址

  (适用顺序为:sender->recipient->canonical)

  5.3 伪装主机名称

  用于隐藏内部主机名称

  masquerade_domains = example.com

  5.4 改变投递位址

  拒收某人或某网域信件,并回覆一封告知

  relocated_maps = hash:/etc/postfix/relocated

  relocated 撰写范例:

  kdent@ora.com kdent@oreilly.com

  @example.com oreilly.com

  5.5 不明使用者

  拒绝不存在的本地帐户/「不明使用者」(unknown user)

  unknown_local_recipient_reject_code = 550

  unknown_local_recipient_reject_code = 450

  如果收信地址的人名部份,在任何对照表、别名表、系统帐户都查不出来,

  这个人会被视为「不明使用者」(unknown user),系统会拒收。

  如果希望蒐集这类信件,使用下列设定,并指定集中收集的信箱

  local_recipient_maps =

  luser_relay = someuser

  (上列第一个参数维持空白,第二个参数指定一个别名或实际帐户)

  5.6 chroot

  最安全的设置方式是使用改变根目录(chroot),但非常复杂;

  postfix安装时预设并未使用 chroot,记得去检查 master.cf档桉,

  建议先取消 chroot,除非您真的很厉害。

  六、一些针对邮件限制的特殊设定

  限制一封信最多可以有几位收信者,预设值是1000

  smtpd_recipient_limit = 1000

  限制单封信件的体积上限,预设值为 10 MB(改为20MB)

  message_size_limit = 20480000

七、别名档资料库设定(aliases)

  使用default_database_type参数决定资料库格式,预设为 hash

  alias_maps = hash:/etc/postfix/aliases

  alias_database = hash:/etc/postfix/aliases

  (安装时随附的别名档范本,已经预设了一组惯例别名,最后都指向root,

  仅需要设置 root 别名,指向一个平常会收取信件的帐户信箱即可)

  八、本地信件与信箱设置

  预设使用的信箱格式是mbox,下列的参数设置,如果结尾没有"/",

  就表示是使用mbox;如果结尾跟着"/",就是使用 maildir格式。

  (建议还是使用最通用的mbox,与其他程式的相容性最高)

  所有本地收件人的名称,都必须列在local_recipient_maps参数所指的表,

  预设值是指向Unix系统的密码档与别名表,通常不需要修改它。

  local_recipient_maps = proxy:unix:passwd.byname $aliase_maps

  信箱投递作业

  mail_spool_directory = /var/mail

  mail_spool_directory = /var/spool/mail

  可以要求 Postfix 将信件放在使用者的主目录下

  home_mailbox = Mailbox

  home_mailbox = Maildir/

  用于将邮件投递到信箱的外部命令(通常用来过滤防堵邮件)

  mailbox_command = /usr/bin/procmail

  mailbox_command = /some/where/procmail -a "$EXTENSION"

  用于执行信箱投递作业的传输服务

  mailbox_transport = lmtp:unix:/file/name

  mailbox_transport = cyrus

  八、启用 SASL 验证(服务于远端、外部网域使用的认证机制)

  决定 Postfix SMTP server 是否要支援 SASL 验证

  smtpd_sasl_auth_enable = yes

  设定信件收件的限制规则

  smtpd_recipient_restrictions = permit_mynetworks,

  permit_sasl_authenticated, reject_unauth_destination

  确认已经通过认证的网域(这条有点疑问...)

  smtpd_sasl_local_domain =

  限制某些登入的方式(拒绝匿名登入)

  smtpd_sasl_security_options = noanonymous

  九、邮件代转设定(交换站、入境、出境闸道)

  (下列程序示范如何设定 gw.abc.com,让它将邮件交给正确的内部伺服器)

  (1)hr.abc.com, sales.abc.com的DNS MX均指向 gw.abc.com

  (2)编辑gw的main.cf档,将子网域列入 relay_domains 参数

  relay_domains = hr.abc.com, sales.abc.com

  (3)设定正确的传输表(transport map)

  transport_maps = hash:/etc/postfix/transport

  (4)编辑传输表(编完记得postmap一下)

  hr.abc.com relay:[m1.abc.com]

  sales.abc.com relay:[m2.abc.com]

  (5)汇整m1,m2的合法信箱名单成一个受理名单,放在gw上

  relay_recipient_maps = hash:/etc/postfix/relay_recipients

  (怎么收集、更新,是个大问题)

  (6)postfix reload

  设定出境邮件闸道(将外地邮件交给闸道系统代为递送)

  relayhost = $mydomain

  relayhost = gateway.my.domain

  relayhost = uucphost

  relayhost = [an.ip.add.ress]

  拒绝不知名用户的代转 REJECTING UNKNOWN RELAY USERS

  (其实是将所有合法使用者放入一个名单中)

  relay_recipient_maps = hash:/etc/postfix/relay_recipients

  十、设定 Open Relay Data Base

  smtpd_client_restrictions = hash:/etc/postfix/access,

  reject_rbl_client relays.ordb.org,

  reject_rhsbl_client dsn.rfc-ignorant.org

  除错管理

  debug_peer_level = 2

  debug_peer_list = 127.0.0.1

  debug_peer_list = some.domain

  debugger_command =

  PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin

  xxgdb $daemon_directory/$process_name $process_id & sleep 5

  如果并未安装X-windows可以改用下列:

  debugger_command =

  PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont;

  echo where) gdb $daemon_directory/$process_name $process_id2>&1

  >$config_directory/$process_name.$process_id.log & sleep 5

  其他、安装时的一些设置资讯

  sendmail_path = /usr/sbin/sendmail.postfix

  newaliases_path = /usr/bin/newaliases.postfix

  mailq_path = /usr/bin/mailq.postfix

  setgid_group = postdrop

  manpage_directory = /usr/share/man

  sample_directory = /usr/share/doc/postfix-2.0.16/samples

  readme_directory = /usr/share/doc/postfix-2.0.16/README_FILES

发表在 db | 标签为 | postfix main.cf已关闭评论

nginx Embedded Variables

The ngx_http_core_module module supports embedded variables with names matching the Apache Server variables. First of all, these are variables representing client request header fields, such as$http_user_agent, $http_cookie, and so on. Also there are other variables:

$arg_name
argument name in the request line
$args
arguments in the request line
$binary_remote_addr
client address in a binary form, value’s length is always 4 bytes
$body_bytes_sent
number of bytes sent to a client, not counting the response header; this variable is compatible with the “%B” parameter of the mod_log_config Apache module
$bytes_sent
number of bytes sent to a client (1.3.8, 1.2.5)
$connection
connection serial number (1.3.8, 1.2.5)
$connection_requests
current number of requests made through a connection (1.3.8, 1.2.5)
$content_length
“Content-Length” request header field
$content_type
“Content-Type” request header field
the name cookie
$document_root
root or alias directive’s value for the current request
$document_uri
same as $uri
$host
in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
$hostname
host name
$http_name
arbitrary request header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$https
on” if connection operates in SSL mode, or an empty string otherwise
$is_args
?” if a request line has arguments, or an empty string otherwise
$limit_rate
setting this variable enables response rate limiting; see limit_rate
$msec
current time in seconds with the milliseconds resolution (1.3.9, 1.2.6)
$nginx_version
nginx version
$pid
PID of the worker process
$pipe
p” if request was pipelined, “.” otherwise (1.3.12, 1.2.7)
$proxy_protocol_addr
client address from the PROXY protocol header, or an empty string otherwise (1.5.12)

The PROXY protocol must be previously enabled by setting the proxy_protocol parameter in thelisten directive.

$query_string
same as $args
$realpath_root
an absolute pathname corresponding to the root or alias directive’s value for the current request, with all symbolic links resolved to real paths
$remote_addr
client address
$remote_port
client port
$remote_user
user name supplied with the Basic authentication
$request
full original request line
$request_body
request body

The variable’s value is made available in locations processed by the proxy_pass,fastcgi_pass, uwsgi_pass, and scgi_pass directives.

$request_body_file
name of a temporary file with the request body

At the end of processing, the file needs to be removed. To always write the request body to a file, client_body_in_file_only needs to be enabled. When the name of a temporary file is passed in a proxied request or in a request to a FastCGI/uwsgi/SCGI server, passing the request body should be disabled by the proxy_pass_request_body off, fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off directives, respectively.

$request_completion
OK” if a request has completed, or an empty string otherwise
$request_filename
file path for the current request, based on the root or alias directives, and the request URI
$request_length
request length (including request line, header, and request body) (1.3.12, 1.2.7)
$request_method
request method, usually “GET” or “POST
$request_time
request processing time in seconds with a milliseconds resolution (1.3.9, 1.2.6); time elapsed since the first bytes were read from the client
$request_uri
full original request URI (with arguments)
$scheme
request scheme, “http” or “https
$sent_http_name
arbitrary response header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$server_addr
an address of the server which accepted a request

Computing a value of this variable usually requires one system call. To avoid a system call, the listen directives must specify addresses and use the bind parameter.

$server_name
name of the server which accepted a request
$server_port
port of the server which accepted a request
$server_protocol
request protocol, usually “HTTP/1.0” or “HTTP/1.1
$status
response status (1.3.2, 1.2.2)
$tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
information about the client TCP connection; available on systems that support the TCP_INFOsocket option
$time_iso8601
local time in the ISO 8601 standard format (1.3.12, 1.2.7)
$time_local
local time in the Common Log Format (1.3.12, 1.2.7)
$uri
current URI in request, normalized

The value of $uri may change during request processing, e.g. when doing internal redirects, or when using index files.

发表在 web server | 标签为 | nginx Embedded Variables已关闭评论

一张网站结构图

点击查看原图

发表在 technologys | 一张网站结构图已关闭评论

unicode,utf8,utf16

什么是编码

在阅读本文之前建议读者先去阅读这篇文章:http://www.freebuf.com/articles/others-articles/25623.html ,如果你没有耐心读完他也没关系,只需要明白三个道理:

1,这个世界上从来没有纯文本这回事,如果你想读出一个字符串,你必须知道它的编码。如果你不知道一段数据流的编码方式,你就永远不会知道这里面的内容。

2,Unicode是一个简单的标准,用来把字符映射到数字上。Unicode协会的人会帮你处理所有幕后的问题,包括为新字符指定编码。我们用的所有字符都在unicode里面有对应的映射,每个映射称为一个码点( http://en.wikipedia.org/wiki/Code_point 

3,Unicode并不告诉你字符是怎么编码成字节的。这是被编码方案决定的,通过UTF来指定。

读完前面这篇文章之后你也许就了解了一个二进制流到屏幕字符的过程:

二进制流->根据编码方式解码出码点->根据unicode码点解释出字符->系统渲染绘出这个字符

文本字符保存到计算机上的过程:

输入字符->根据字符找到对应码点->根据编码方式把码点编码成二进制流->保存二进制流到硬盘上

从这个过程我们可以知道能不能从二进制流读取出字符关键就在于能不能找到二进制流的编码,掌握了编码方式的信息就可以用对应的逆过程解码。

看到这里有读者一定会问:为什么要编码,根据二进制流计算码点不好吗?

原因是良好设计的编码可以为我们提供很多附加的功能,包括容错纠错(在网络通信中尤其重要),自同步(不必从文本头部开始就可以解码)等等。编码从信息论的角度上来说就是增加了冗余的信息,冗余的这部分信息就可以为我们提供额外的功能。

utf8的编码规则

我们来看utf8和utf16具体是如何编码的:

Utf8有如下特点:

1.可变长编码,由第一个字节决定该字符编码长度

2.向下兼容ascii码(这也是为什么用utf8编码可以完美打开ascii文本文件)

Utf8的编码规则:

  1. 一个字节的编码完全用于ascii码(从0-127)
  2. 大于127的码点都用多字节来编码,多字节包含开头字节和后续字节

开头字节以若干个1开头(长度为几就有几个1,因此只要读完开头字节就可以知道本字符共有多少个字节),后接1个0.后续字节都以10开头

  1. 从右到做,后续字节每个字节占用原码点6个位,剩余的放在开头字节。
  2. 开头字节和后续字节不共享任何数据,因此utf8是自同步的。举例来说我们看到一个字节以110…开头时,我们就知道这是一个2字节的字符的开头字节。

具体来举几个例子:

具体来举几个例子:

字符 码点 二进制 UTF-8 16进制  UTF-8
$ U+0024 0100100 00100100 24
¢ U+00A2 000 10100010 11000010 10100010 C2 A2
U+20AC 00100000 10101100 11100010 10000010 10101100 E2 82 AC
�� U+24B62

00010

01001011 01100010

11110000 10100100 

10101101 10100010

F0 A4 AD A2

 

 

 

实现了UTF8编码的java代码:

public class Utf8 {     /**      * @param codePoint in unicode
     * @return corresponding utf8 bytes
     * @throws Exception
     */     private static final long RightSix = (1 << 6) - 1;
    private static final long PrefixForContinuasByte = 1 << 7;

    public static long EncodeToUtf8(long codePoint) throws Exception
    {
        if (codePoint < 0 || codePoint > 0x1FFFFF)
            throw new Exception("Illegal code point!");
        if (codePoint <= 0x007F)
        {
            return codePoint;// ascii character         }
        else if (codePoint <= 0x07FF)
        {
            long byte1 = (6 << 5) + (codePoint >> 6);
            long byte2 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 8) + byte2;
        }
        else if (codePoint <= 0xFFFF)
        {
            long byte1 = (14 << 4) + (codePoint >> 12);
            long byte2 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix);
            long byte3 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 16) + (byte2 << 8) + byte3;
        }
        else         {
            long byte1 = (30 << 3) + (codePoint >> 18);
            long byte2 = PrefixForContinuasByte
                    + ((codePoint >> 12) & RightSix);
            long byte3 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix);
            long byte4 = PrefixForContinuasByte + (codePoint & RightSix);
            return (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4;
        }
    }public static void main(String[] args)
    {
        try         {
            while (true)
            {
                System.out.print("Input a number in Hex format:");
                Scanner sc = new Scanner(System.in);
                String s = sc.nextLine();
                // System.out.println("it is "+HexStringToLong(s)+" in decimal format");                 long utf8 = EncodeToUtf8(HexStringToLong(s));
                String hexString = Long.toHexString(utf8);
                System.out.println("You input " + s
                        + " in Hex format and we encode it to utf8 character "                         + hexString);
            }
        }
        catch (Exception e)
        {
            System.out.println(e.getLocalizedMessage());
            // TODO: handle exception         }

    }
}

运行结果:

Input a number in Hex format:24 You input 24 in Hex format and we encode it to utf8 character 24 Input a number in Hex format:A2 You input A2 in Hex format and we encode it to utf8 character c2a2Input a number in Hex format:20ACYou input 20AC in Hex format and we encode it to utf8 character e282acInput a number in Hex format:24B62You input 24B62 in Hex format and we encode it to utf8 character f0a4ada2

关于UTF-16的编码规则,读者可以参考这篇文章:http://en.wikipedia.org/wiki/UTF-16

这里附上UTF16-BE的编码代码:

public class Utf16 {     /**      * @param codePoint in unicode
     * @return corresponding utf16 bytes
     * @throws Numberformat Exception
     */     
    private static final long Substracted=0x10000; 
    private static final long AddToHigh=0xD800;
    private static final long AddToLow=0xDC00;
    private static long HexStringToLong(String s)
    {
        if (s.length() == 0)
            return 0;
        long ans = 0;
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);
            if (c >= '0' && c <= '9')
                ans = (ans << 4) + (c - '0');
            else if (c >= 'A' && c <= 'F')
                ans = (ans << 4) + (c - 'A' + 10);
            else                 throw new NumberFormatException();
        }
        return ans;
    }
    public static long EncodeToUtf16BE(long codePoint) throws Exception
    {
        if(codePoint<0||(codePoint<=0xDFFF&&codePoint>=0xD800)||codePoint>0x10FFFF)
            throw new NumberFormatException();
        if(codePoint<=0xD7FF)//Basic Multilingual Plane         {
            return codePoint;
        }
        else         {
            long sub=codePoint-Substracted;
            long high=sub>>10;
            long low=sub&0x3FF;
            long word1=AddToHigh+high;
            long word2=AddToLow+low;
            return (word1<<16)+word2;
        }    
    }
    public static void main(String[] args)
    {
        while(true)
        {
            System.out.print("Input a number in hex format");
            Scanner sc=new Scanner(System.in);            
            String s=sc.nextLine();
            try             {
                String utf16=Long.toHexString(EncodeToUtf16BE(HexStringToLong(s)));
                System.out.println("You input "+s+" we encode it to utf16-BE "+utf16);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            
            
        }
    }
}
发表在 technologys | 标签为 , , | unicode,utf8,utf16已关闭评论

SQL Server死锁

其实所有的死锁最深层的原因就是一个:资源竞争

 

表现一:

  一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了,同样用户B要等用户A释放表A才能继续这就死锁了。

  解决方法:

  这种死锁是由于你的程序的BUG产生的,除了调整你的程序的逻辑别无他法

  仔细分析你程序的逻辑:

  1:尽量避免同时锁定两个资源

  2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.

 

表现二:

  用户A读一条纪录,然后修改该条纪录。这是用户B修改该条纪录,这里用户A的事务里锁的性质由共享锁企图上升到独占锁(for update),而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。

  这种死锁比较隐蔽,但其实在稍大点的项目中经常发生。

  解决方法:

  让用户A的事务(即先读后写类型的操作),在select 时就是用Update lock

  语法如下:

        select * from table1 with(updlock) where ....

 

--------------------------------------------------------------------------------

接上面文章,继续探讨数据库死锁问题 

 

死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:
   trans1                                                    trans2
   ------------------------------------------------------------------------------------------------
   1.IDBConnection.BeginTransaction    1.IDBConnection.BeginTransaction
   2.update table A                                2.update table B
   3.update table B                                3.update table A
   4.IDBConnection.Commit                   4.IDBConnection.Commit 
   那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。

   好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。

   -- Batch #1

   CREATE DATABASE deadlocktest
   GO

   USE deadlocktest
   SET NOCOUNT ON
   DBCC TRACEON (1222, -1)
   -- 在SQL2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc    
   -- traceon(1204,3605,-1)看到所有的死锁信息。SqlServer 2005中,对于1204进行了增强,这就是1222。
   GO   
   
   IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1
   IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1
   IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2
   GO

 

   CREATE TABLE t1 (c1 int, c2 int, c3 int, c4 char(5000))
   GO

 

   DECLARE @x int
   SET @x = 1
   WHILE (@x <= 1000) BEGIN
            INSERT INTO t1 VALUES (@x*2, @x*2, @x*2, @x*2)
            SET @x = @x + 1
   END
   GO

 

   CREATE CLUSTERED INDEX cidx ON t1 (c1)
   CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)
   GO

 

   CREATE PROC p1 @p1 int AS SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO

 

   CREATE PROC p2 @p1 int AS
            UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
            UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   上述sql创建一个deadlock的示范数据库,插入了1000条数据,并在表t1上建立了c1列的聚集索引,和c2列的非聚集索引。另外创建了两个sp,分别是从t1中select数据和update数据。

   好,打开一个新的查询窗口,我们开始执行下面的query:

   -- Batch #2

 

   USE deadlocktest
   SET NOCOUNT ON
   WHILE (1=1) EXEC p2 4
   GO

 

   开始执行后,然后我们打开第三个查询窗口,执行下面的query:

   -- Batch #3

 

   USE deadlocktest
   SET NOCOUNT ON
   CREATE TABLE #t1 (c2 int, c3 int)
   GO

 

   WHILE (1=1) BEGIN
             INSERT INTO #t1 EXEC p1 4
             TRUNCATE TABLE #t1
   END
   GO

 

   开始执行,哈哈,很快,我们看到了这样的错误信息:
   Msg 1205, Level 13, State 51, Procedure p1, Line 4
   Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

 

   spid54发现了死锁。
   那么,我们该如何解决它?

   在SqlServer 2005中,我们可以这么做:
   1.在trans3的窗口中,选择EXEC p1 4,然后right click,看到了菜单了吗?选择Analyse Query in Database Engine Tuning Advisor。
   2.注意右面的窗口中,wordload有三个选择:负载文件、表、查询语句,因为我们选择了查询语句的方式,所以就不需要修改这个radio option了。
   3.点左上角的Start Analysis按钮
   4.抽根烟,回来后看结果吧!出现了一个分析结果窗口,其中,在Index Recommendations中,我们发现了一条信息:大意是,在表t1上增加一个非聚集索引索引:t2+t1。
   5.在当前窗口的上方菜单上,选择Action菜单,选择Apply Recommendations,系统会自动创建这个索引。

   重新运行batch #3,呵呵,死锁没有了。

为什么会死锁呢?再回顾一下两个sp的写法:
   CREATE PROC p1 @p1 int AS 
      SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO

   CREATE PROC p2 @p1 int AS
         UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!
   那么,什么导致了死锁?

   需要从事件日志中,看sql的死锁信息:
   Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”): 
   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”): 
   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
                
   The SELECT is waiting for a Shared KEY lock on index t1.cidx.  The UPDATE holds a conflicting X lock. 
   The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1.  The SELECT holds a conflicting S lock.

 

   首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划

   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
        |--Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))
               |--Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
                     |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)

 

   我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。
   好,我们接着看p2的执行计划。

   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         |--Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))
               |--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
                     |--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN ...
                           |--Top(ROWCOUNT est 0)
                                 |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)

 

   通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。

   实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。
   SO………,问题就这样被发现了。

 

   总结一下,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。

 

   那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:

   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
      |--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)

 

   哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。

 

   实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。

 

   下面的方法,有助于将死锁减至最少(详细情况,请看SQLServer联机帮助,搜索:将死锁减至最少即可。

       . 按同一顺序访问对象。 
       . 避免事务中的用户交互。 
       . 保持事务简短并处于一个批处理中。 
       . 使用较低的隔离级别。 
       . 使用基于行版本控制的隔离级别。 
              . 将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。 
              . 使用快照隔离。 
       . 使用绑定连接。

发表在 db | 标签为 | SQL Server死锁已关闭评论

SQL Server 索引结构

作者:freedk 
一、深入浅出理解索引结构
  实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别: 
  其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 
  如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 
  通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。
 
二、何时使用聚集索引或非聚集索引
下面的表总结了何时使用聚集索引或非聚集索引(很重要):
动作描述 使用聚集索引 使用非聚集索引
列经常被分组排序
返回某范围内的数据 不应
一个或极少不同值 不应 不应
小数目的不同值 不应
大数目的不同值 不应
频繁更新的列 不应
外键列
主键列
频繁修改索引列 不应
  事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。
发表在 db | 标签为 | SQL Server 索引结构已关闭评论

SQL Server死锁总结

当前锁查看:

use master
go

--检索死锁进程
select spid, blocked, loginame, last_batch, status, cmd, hostname, program_name
from sysprocesses
where spid in
( select blocked from sysprocesses where blocked <> 0 ) or (blocked <>0)

--或者:

select request_session_id,OBJECT_NAME(resource_associated_entity_id) tableName from
sys.dm_tran_locks
where resource_type='OBJECT'

--

kill   进程号(sp_lock/sp_who查找死锁的进程和对象) 

spid就是锁住表的进程

tableName就是被锁的表名

解锁

kill [spid]

 

 1. 死锁原理

    根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

    死锁的四个必要条件:
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

对应到SQL Server中,当在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁;这些资源可能是:单行(RID,堆中的单行)、索引中的键(KEY,行锁)、页(PAG8KB)、区结构(EXT,连续的8)、堆或B(HOBT) 、表(TAB,包括数据和索引)、文件(File,数据库文件)、应用程序专用资源(APP)、元数据(METADATA)、分配单元(Allocation_Unit)、整个数据库(DB)一个死锁示例如下图所示:

点击查看原图
    说明:
T1T2表示两个任务;R1R2表示两个资源;由资源指向任务的箭头(R1->T1R2->T2)表示该资源被改任务所持有;由任务指向资源的箭头(T1->S2T2->S1)表示该任务正在请求对应目标资源;
    其满足上面死锁的四个必要条件:
(1).互斥:资源S1S2不能被共享,同一时间只能由一个任务使用;
(2).请求与保持条件:T1持有S1的同时,请求S2T2持有S2的同时请求S1
(3).非剥夺条件:T1无法从T2上剥夺S2T2也无法从T1上剥夺S1
(4).循环等待条件:上图中的箭头构成环路,存在循环等待。

 

2. 死锁排查

(1). 使用SQL Server的系统存储过程sp_whosp_lock,可以查看当前数据库中的锁情况;进而根据objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看哪个资源被锁,用dbcc ld(@blk),可以查看最后一条发生给SQL ServerSql语句;

CREATE Table #Who(spid int,

    ecid int,

    status nvarchar(50),

    loginname nvarchar(50),

    hostname nvarchar(50),

    blk int,

    dbname nvarchar(50),

    cmd nvarchar(50),

    request_ID int);



CREATE Table #Lock(spid int,

    dpid int,

    objid int,

    indld int,

    [Type] nvarchar(20),

    Resource nvarchar(50),

    Mode nvarchar(10),

    Status nvarchar(10)

);



INSERT INTO #Who

    EXEC sp_who active  --看哪个引起的阻塞,blk 

INSERT INTO #Lock

    EXEC sp_lock  --看锁住了那个资源id,objid 



DECLARE @DBName nvarchar(20);

SET @DBName='NameOfDataBase'



SELECT #Who.* FROM #Who WHERE dbname=@DBName

SELECT #Lock.* FROM #Lock

    JOIN #Who

        ON #Who.spid=#Lock.spid

            AND dbname=@DBName;



--最后发送到SQL Server的语句

DECLARE crsr Cursor FOR

    SELECT blk FROM #Who WHERE dbname=@DBName AND blk<>0;

DECLARE @blk int;

open crsr;

FETCH NEXT FROM crsr INTO @blk;

WHILE (@@FETCH_STATUS = 0)

BEGIN;

    dbcc inputbuffer(@blk);

    FETCH NEXT FROM crsr INTO @blk;

END;

close crsr;

DEALLOCATE crsr;



--锁定的资源

SELECT #Who.spid,hostname,objid,[type],mode,object_name(objid) as objName FROM #Lock

    JOIN #Who

        ON #Who.spid=#Lock.spid

            AND dbname=@DBName

    WHERE objid<>0;



DROP Table #Who;

DROP Table #Lock;

(2). 使用 SQL Server Profiler 分析死锁:  Deadlock graph 事件类添加到跟踪。此事件类使用死锁涉及到的进程和对象的 XML 数据填充跟踪中的 TextData 数据列。SQL Server 事件探查器 可以将 XML 文档提取到死锁 XML (.xdl) 文件中,以后可在 SQL Server Management Studio 中查看该文件。

 

3. 避免死锁

    上面1中列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件,就可以避免死锁发生,一般有以下几种方法(FROM Sql Server 2005联机丛书)
(1).按同一顺序访问对象。(注:避免出现循环)
(2).避免事务中的用户交互。(注:减少持有资源的时间,较少锁竞争)
(3).保持事务简短并处于一个批处理中。(注:同(2),减少持有资源的时间)
(4).使用较低的隔离级别。(注:使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短,减少锁竞争)
(5).使用基于行版本控制的隔离级别2005中支持快照事务隔离和指定READ_COMMITTED隔离级别的事务使用行版本控制,可以将读与写操作之间发生的死锁几率降至最低:
SET ALLOW_SNAPSHOT_ISOLATION ON --事务可以指定 SNAPSHOT 事务隔离级别;
SET READ_COMMITTED_SNAPSHOT ON  --指定 READ_COMMITTED 隔离级别的事务将使用行版本控制而不是锁定。默认情况下(没有开启此选项,没有加with nolock提示)SELECT语句会对请求的资源加S(共享锁);而开启了此选项后,SELECT不会对请求的资源加S锁。
注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中只允许存在执行 ALTER DATABASE 命令的连接。在 ALTER DATABASE 完成之前,数据库中决不能有其他打开的连接。数据库不必一定要处于单用户模式中。
(6).使用绑定连接(注:绑定会话有利于在同一台服务器上的多个会话之间协调操作。绑定会话允许一个或多个会话共享相同的事务和锁(但每个回话保留其自己的事务隔离级别),并可以使用同一数据,而不会有锁冲突。可以从同一个应用程序内的多个会话中创建绑定会话,也可以从包含不同会话的多个应用程序中创建绑定会话。在一个会话中开启事务(begin tran)后,调用exec sp_getbindtoken @Token out;来取得Token,然后传入另一个会话并执行EXEC sp_bindsession @Token来进行绑定(最后的示例中演示了绑定连接)

 

4. 死锁处理方法:

(1). 根据2中提供的sql,查看那个spid处于wait状态,然后用kill spid来干掉(即破坏死锁的第四个必要条件:循环等待);当然这只是一种临时解决方案,我们总不能在遇到死锁就在用户的生产环境上排查死锁、Kill sp,我们应该考虑如何去避免死锁。

(2). 使用SET LOCK_TIMEOUT timeout_period(单位为毫秒)设定锁请求超时。默认情况下,数据库没有超时期限(timeout_period值为-1,可以用SELECT @@LOCK_TIMEOUT来查看该值,即无限期等待)。当请求锁超过timeout_period时,将返回错误。timeout_period值为0时表示根本不等待,一遇到锁就返回消息。设置锁请求超时,破环了死锁的第二个必要条件(请求与保持条件)

服务器: 消息 1222,级别 16,状态 50,行 1
已超过了锁请求超时时段。

(3). SQL Server内部有一个锁监视器线程执行死锁检查,锁监视器对特定线程启动死锁搜索时,会标识线程正在等待的资源;然后查找特定资源的所有者,并递归地继续执行对那些线程的死锁搜索,直到找到一个构成死锁条件的循环。检测到死锁后,数据库引擎 选择运行回滚开销最小的事务的会话作为死锁牺牲品,返回1205 错误,回滚死锁牺牲品的事务并释放该事务持有的所有锁,使其他线程的事务可以请求资源并继续运行。

 

5. 两个死锁示例及解决方法

5.1 SQL死锁

(1). 测试用的基础数据:

CREATE TABLE Lock1(C1 int default(0));
CREATE TABLE Lock2(C1 int default(0));
INSERT INTO Lock1 VALUES(1);
INSERT INTO Lock2 VALUES(1);

(2). 开两个查询窗口,分别执行下面两段sql

--Query 1
Begin Tran
  
Update Lock1 Set C1=C1+1;
  
WaitFor Delay '00:01:00';
  
SELECT * FROM Lock2
Rollback Tran;

 

--Query 2
Begin Tran
  
Update Lock2 Set C1=C1+1;
  
WaitFor Delay '00:01:00';
  
SELECT * FROM Lock1
Rollback Tran;

 

上面的SQL中有一句WaitFor Delay '00:01:00',用于等待1分钟,以方便查看锁的情况。

(3). 查看锁情况

在执行上面的WaitFor语句期间,执行第二节中提供的语句来查看锁信息:

点击查看原图Query1中,持有Lock1中第一行(表中只有一行数据)的行排他锁(RID:X),并持有该行所在页的意向更新锁(PAG:IX)、该表的意向更新锁(TAB:IX)Query2中,持有Lock2中第一行(表中只有一行数据)的行排他锁(RID:X),并持有该行所在页的意向更新锁(PAG:IX)、该表的意向更新锁(TAB:IX)

执行完WaitforQuery1查询Lock2,请求在资源上加S锁,但该行已经被Query2加上了X锁;Query2查询Lock1,请求在资源上加S锁,但该行已经被Query1加上了X锁;于是两个查询持有资源并互不相让,构成死锁。

(4). 解决办法

a). SQL Server自动选择一条SQL作死锁牺牲品:运行完上面的两个查询后,我们会发现有一条SQL能正常执行完毕,而另一个SQL则报如下错误:

服务器: 消息 1205,级别 13,状态 50,行 1
事务(进程 ID  xx)与另一个进程已被死锁在  lock 资源上,且该事务已被选作死锁牺牲品。请重新运行该事务。

这就是上面第四节中介绍的锁监视器干活了。

b). 按同一顺序访问对象:颠倒任意一条SQL中的UpdateSELECT语句的顺序。例如修改第二条SQL成如下:

--Query2
Begin Tran
  
SELECT * FROM Lock1--在Lock1上申请S锁
  WaitFor Delay '00:01:00';
  
Update Lock2 Set C1=C1+1;--Lock2:RID:X
Rollback Tran;

当然这样修改也是有代价的,这会导致第一条SQL执行完毕之前,第二条SQL一直处于阻塞状态。单独执行Query1Query2需要约1分钟,但如果开始执行Query1时,马上同时执行Query2,则Query2需要2分钟才能执行完;这种按顺序请求资源从一定程度上降低了并发性。

c). SELECT语句加With(NoLock)提示:默认情况下SELECT语句会对查询到的资源加S(共享锁)S锁与X(排他锁)不兼容;但加上With(NoLock)后,SELECT不对查询到的资源加锁(或者加Sch-S锁,Sch-S锁可以与任何锁兼容);从而可以是这两条SQL可以并发地访问同一资源。当然,此方法适合解决读与写并发死锁的情况,但加With(NoLock)可能会导致脏读。

SELECT * FROM Lock2 WITH(NOLock)
SELECT * FROM Lock1 WITH(NOLock)

d). 使用较低的隔离级别。SQL Server 2000支持四种事务处理隔离级别(TIL),分别为:READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLESQL Server 2005中增加了SNAPSHOT TIL默认情况下,SQL Server使用READ COMMITTED TIL,我们可以在上面的两条SQL前都加上一句SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,来降低TIL以避免死锁;事实上,运行在READ UNCOMMITTED TIL的事务,其中的SELECT语句不对结果资源加锁或加Sch-S锁,而不会加S锁;但还有一点需要注意的是:READ UNCOMMITTED TIL允许脏读,虽然加上了降低TIL的语句后,上面两条SQL在执行过程中不会报错,但执行结果是一个返回1,一个返回2,即读到了脏数据,也许这并不是我们所期望的。

e). SQL前加SET LOCK_TIMEOUT timeout_period,当请求锁超过设定的timeout_period时间后,就会终止当前SQL的执行,牺牲自己,成全别人。

f). 使用基于行版本控制的隔离级别(SQL Server 2005支持):开启下面的选项后,SELECT不会对请求的资源加S锁,不加锁或者加Sch-S锁,从而将读与写操作之间发生的死锁几率降至最低;而且不会发生脏读。

SET ALLOW_SNAPSHOT_ISOLATION ON
SET READ_COMMITTED_SNAPSHOT ON

       g). 使用绑定连接(使用方法见下一个示例。)

 

5.2 程序死锁(SQL阻塞)

看一个例子:一个典型的数据库操作事务死锁分析,按照我自己的理解,我觉得这应该算是C#程序中出现死锁,而不是数据库中的死锁;下面的代码模拟了该文中对数据库的操作过程:

复制代码

//略去的无关的code
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlTransaction tran 
= conn.BeginTransaction();
string sql1 = "Update Lock1 SET C1=C1+1";
string sql2 = "SELECT * FROM Lock1";
ExecuteNonQuery(tran, sql1); 
//使用事务:事务中Lock了Table
ExecuteNonQuery(null, sql2); //新开一个connection来读取Table

public static void ExecuteNonQuery(SqlTransaction tran, string sql)
{
    SqlCommand cmd 
= new SqlCommand(sql);
    
if (tran != null)
    
{
        cmd.Connection 
= tran.Connection;
        cmd.Transaction 
= tran;
        cmd.ExecuteNonQuery();
    }

    
else
    
{
        
using (SqlConnection conn = new SqlConnection(connectionString))
        
{
            conn.Open();
            cmd.Connection 
= conn;
            cmd.ExecuteNonQuery();
        }

    }

}

复制代码

执行到ExecuteNonQuery(null, sql2)时抛出SQL执行超时的异常,下图从数据库的角度来看该问题:

     点击查看原图      

     代码从上往下执行,会话1持有了表Lock1X锁,且事务没有结束,回话1就一直持有X锁不释放;而会话2执行select操作,请求在表Lock1上加S锁,但S锁与X锁是不兼容的,所以回话2的被阻塞等待,不在等待中,就在等待中获得资源,就在等待中超时。。。从中我们可以看到,里面并没有出现死锁,而只是SELECT操作被阻塞了。也正因为不是数据库死锁,所以SQL Server的锁监视器无法检测到死锁。

       我们再从C#程序的角度来看该问题:

          点击查看原图 

       C#程序持有了表Lock1上的X锁,同时开了另一个SqlConnection还想在该表上请求一把S锁,图中已经构成了环路;太贪心了,结果自己把自己给锁死了。。。

       虽然这不是一个数据库死锁,但却是因为数据库资源而导致的死锁,上例中提到的解决死锁的方法在这里也基本适用,主要是避免读操作被阻塞,解决方法如下:

       a). SELECT放在Update语句前:SELECT不在事务中,且执行完毕会释放S锁;
       b). SELECT也放加入到事务中:ExecuteNonQuery(tran, sql2);
       c). SELECTWith(NOLock)提示:可能产生脏读;
       d). 降低事务隔离级别:SELECT语句前加SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;同上,可能产生脏读;
       e). 使用基于行版本控制的隔离级别(同上例)。
       g). 使用绑定连接:取得事务所在会话的token,然后传入新开的connection中;执行EXEC sp_bindsession @Token后绑定了连接,最后执行exec sp_bindsession null;来取消绑定;最后需要注意的四点是:
    
(1). 使用了绑定连接的多个connection共享同一个事务和相同的锁,但各自保留自己的事务隔离级别;
    
(2). 如果在sql3字符串的“exec sp_bindsession null”换成“commit tran”或者“rollback tran”,则会提交整个事务,最后一行C#代码tran.Commit()就可以不用执行了(执行会报错,因为事务已经结束了-,-)
    
(3). 开启事务(begin tran)后,才可以调用exec sp_getbindtoken @Token out来取得Token;如果不想再新开的connection中结束掉原有的事务,则在这个connection close之前,必须执行“exec sp_bindsession null”来取消绑定连接,或者在新开的connectoin close之前先结束掉事务(commit/tran)
    
(4). (Sql server 2005 联机丛书)后续版本的 Microsoft SQL Server 将删除该功能。请避免在新的开发工作中使用该功能,并着手修改当前还在使用该功能的应用程序。 请改用多个活动结果集 (MARS) 或分布式事务。

复制代码

tran = connection.BeginTransaction();
string sql1 = "Update Lock1 SET C1=C1+1";
ExecuteNonQuery(tran, sql1); 
//使用事务:事务中Lock了测试表Lock1
string sql2 = @"DECLARE @Token varchar(255);
exec sp_getbindtoken @Token out;
SELECT @Token;
";
string token = ExecuteScalar(tran, sql2).ToString();
string sql3 = "EXEC sp_bindsession @Token;Update Lock1 SET C1=C1+1;exec sp_bindsession null;";
SqlParameter parameter 
= new SqlParameter("@Token", SqlDbType.VarChar);
parameter.Value 
= token;
ExecuteNonQuery(
null, sql3, parameter); //新开一个connection来操作测试表Lock1
tran.Commit();

复制代码

 

 

附:锁兼容性(FROM SQL Server 2005 联机丛书)

锁兼容性控制多个事务能否同时获取同一资源上的锁。如果资源已被另一事务锁定,则仅当请求锁的模式与现有锁的模式相兼容时,才会授予新的锁请求。如果请求锁的模式与现有锁的模式不兼容,则请求新锁的事务将等待释放现有锁或等待锁超时间隔过期。
点击查看原图

 
发表在 db | 标签为 | SQL Server死锁总结已关闭评论

sqlserver 内部函数、存储过程、角色

/*日期函数*/
DATEADD ( datepart , number, date )
--在向指定日期加上一段时间的基础上,返回新的 datetime 值。
DATEDIFF ( datepart , startdate , enddate )
--返回跨两个指定日期的日期和时间边界数。
DATENAME ( datepart , date )
--返回代表指定日期的指定日期部分的字符串。
DATEPART ( datepart , date )
--返回代表指定日期的指定日期部分的整数。
DAY ( date )
--返回代表指定日期的天的日期部分的整数。
GETDATE ( )
--按 datetime 值的 Microsoft? SQL Server? 标准内部格式返回当前系统日期和时间。
GETUTCDATE()
--返回表示当前 UTC 时间(世界时间坐标或格林尼治标准时间)的 datetime 值。
--当前的 UTC 时间得自当前的本地时间和运行 SQL Server 的计算机操作系统中的时区设置。
MONTH ( date )
--返回代表指定日期月份的整数。
YEAR ( date )
--返回表示指定日期中的年份的整数。
--------------------------------------------------------------------------
/*字符串处理函数*/
LCASE( )
LOWER( )
--将字符串转换为小写字母
LTRIM( )
--删除字符串前面的空格
SUBSTRING( )
--从字符串中提取一个或多个字符
UCASE( )
UPPER( )
--将字符串转换为大写字母
ROUND( )
--将数字按指定的小数位数四舍五入
FLOOR( )
--将数字向下四舍五入为最接近(最小)的整数
CEILING( )
--将数字向上四舍五入为最接近的整数
DATALENGTH( )
--返回指定的表达式所用的字节数
--------------------------------------------------------------------------
USER( )
USER_NAME( )
--返回当前用户名
CONVERT( )
--将数据从一种类型转换为另一种类型。
SOUNDEX( )
--为可创建"近似"搜索的指定表达式返回 Soundex 代码。
STR( )
--将数字数据转换为字符串,以便可以用文本运算符对其进行处理。
/*全局变量*/
@@CONNECTIONS
--服务器上次启动以来创建的连接数
@@CPU_BUSY 
--自 SQL Server 启动至今,系统持续运行的毫秒数。
@@CURSOR_ROWS 
--最近打开的游标中的行数
@@DATEFIRST  
--SET DATEFIRST 参数的当前值,该参数用于设置一个星期的第一天为哪一天。
@@ERROR 
--最后一个 T-SQL 错误的错误号
@@FETCH_STATUS
--如果最后一次提取的状态为成功状态,则为 0。如果出错,则为 -1
@@IDENTITY  
--最后一次插入的标识值
@@LANGUAGE  
--当前使用的语言的名称
@@MAX_CONNECTIONS
--可以创建的同时连接的最大数
@@ROWCOUNT  
--受上一个 SQL 语句影响的行数
@@SERVERNAME 
--本地服务器的名称
@@SERVICENAME 
--该计算机上的 SQL 服务的名称
@@TIMETICKS 
--当前计算机上每指令周期的微秒数
@@TRANSCOUNT 
--当前连接打开的事务数
@@VERSION  
--SQL Server 的版本信息
-----------------------------------------------------------------------
/*存储过程*/
sp_databases --列出服务器上的所有数据库
sp_server_info --列出服务器信息,如字符集,版本和排列顺序
sp_stored_procedures--列出当前环境中的所有存储过程
sp_tables --列出当前环境中所有可以查询的对象
sp_start_job --立即启动自动化任务
sp_stop_job --停止正在执行的自动化任务
sp_password --添加或修改登录帐户的密码
sp_configure --显示(不带选项)或更改(带选项)当前服务器的全局配置设置
sp_help --返回表的列名,数据类型,约束类型等
sp_helptext --显示规则,默认值,未加密的存储过程,用户定义的函数,
--触发器或视图的实际文本
sp_helpfile --查看当前数据库信息
sp_dboption --显示或更改数据库选项
sp_detach_db --分离数据库
sp_attach_db --附加数据库
sp_addumpdevice --添加设备
sp_dropdevice --删除设备
sp_pkeys --查看主键
sp_fkeys --查看外键
sp_helpdb --查看指定数据库相关文件信息
sp_addtype --自建数据类型
sp_droptype --删除自建数据类型
sp_rename --重新命名数据库
sp_executesql --执行SQL语句
sp_addlogin --添加登陆
sp_droplogin --删除登录
sp_grantdbaccess --把用户映射到登录,即添加一个数据库安全帐户并授予塔访问权限
sp_revokedbaccess--撤销用户的数据访问权,即从数据库中删除一个安全帐户
sp_addrole --添加角色
sp_addrolemember --向角色中添加成员,使其成为数据库角色的成员
sp_addsrvrolemember--修改登录使其成为固定服务器角色的成员
sp_grantlogin --允许使用组帐户或系统用户使用Windows身份验证连接到SQL
sp_defaultdb --修改一个登录的默认数据库
sp_helpindex --用于查看表的索引
sp_cursoropen --定义与游标和游标选项相关的SQL语句,然后生成游标
sp_cursorfetch --从游标中提取一行或多行
sp_cursorclose --关闭并释放游标
sp_cursoroption --设置各种游标选项
sp_cursor --用于请求定位更新
sp_cursorprepare --把与游标有关的T-SQL语句或批处理编译成执行计划,但并不创建游标
sp_cursorexecute --从由sp_cursorprepare创建的执行计划中创建并填充游标
sp_cursorunprepare --废弃由sp_cursorprepare生成的执行计划
sp_settriggerorder --指定第一个或最后一个激发的、与表关联的 AFTER 触发器。在第一个
--和最后一个触发器之间激发的 AFTER 触发器将按未定义的顺序执行
--------------------------------------------------------------------------------
/*服务器角色*/
sysadmin
--在 SQL Server 中进行任何活动。该角色的权限跨越所有其它固定服务器角色。
serveradmin
--配置服务器范围的设置。
setupadmin
--添加和删除链接服务器,并执行某些系统存储过程(如 sp_serveroption)。
securityadmin
--管理服务器登录。
processadmin
--管理在 SQL Server 实例中运行的进程。
dbcreator
--创建和改变数据库。
diskadmin
--管理磁盘文件。
bulkadmin
--执行 BULK INSERT 语句。
/*数据库角色*/
public
public 角色
--public 角色是一个特殊的数据库角色,每个数据库用户都属于它。public 角色:
--捕获数据库中用户的所有默认权限。
--无法将用户、组或角色指派给它,因为默认情况下它们即属于该角色。
--含在每个数据库中,包括 master、msdb、tempdb、model 和所有用户数据库。
--无法除去。
db_owner
--进行所有数据库角色的活动,以及数据库中的其它维护和配置活动。
--该角色的权限跨越所有其它固定数据库角色。
db_accessadmin
--在数据库中添加或删除 Windows NT 4.0 或 Windows 2000 组和用户以及 SQL Server 用户。
db_datareader
--查看来自数据库中所有用户表的全部数据。
db_datawriter
--添加、更改或删除来自数据库中所有用户表的数据
db_ddladmin
--添加、修改或除去数据库中的对象(运行所有 DDL)
db_securityadmin
--管理 SQL Server 2000 数据库角色的角色和成员,并管理数据库中的语句和对象权限
db_backupoperator
--有备份数据库的权限
db_denydatareader
--拒绝选择数据库数据的权限
db_denydatawriter
--拒绝更改数据库数据的权限

发表在 db | 标签为 | sqlserver 内部函数、存储过程、角色已关闭评论

SQL Server 错误日志

sp_cycle_errorlog

 

错误日志文件暴增:

这里我说的不仅仅指某个错误日志记录文件暴增,也指目录Program Files\Microsoft SQL Server\MSSQL.n\MSSQL\LOG所占空间暴增,如果你平时都不关注这些错误日志,也从不维护错误日志记录文件,那么很有可能它所占的空间非常大,大到让你吃惊的地步。几十G的我也见过,那么具体原因可能有以下种:

 1:SQL 内部错误的时候会产生非常多的DUMP文件

2:高可用的数据库服务器可能很少停机,而你又没有定期清理、清空这些错误日志信息,那么ERRORLOG.n/SQLAGENT.n文件增长会非常大。这样对于DBA使用错误日志查找信息就会比较困难,而且日志大了加载、写入以后性能也会受到影响。

3:还有一个情况,如果你数据库IP地址曝露在外网时,会遭到大量尝试登录sa的攻击,也会产生大量的审核失败日志信息。

4:就是一些SQL SERVER PROFILE文件没有删除,当然,这个本质跟日志文件暴增没啥关系,但是跟LOG文件夹的大小有些关系。

如何清理错误日志:

对于SQL SERVER日志、SQL SERVER AGENT日志记录,微软提供了一个存储过程sp_cycle_errorlog可以实现日志的循环。 这个存储过程的作用是关闭当前的错误日志文件,并循环错误日志扩展编号(就像重新启动服务器)。每次启动SQL Server 时,都会将当前错误日志重命名为errorlog.1;errorlog.1 变为errorlog.2,errorlog.2 变为errorlog.3,依次类推。最后一个errorlog.n将会被删除。sp_cycle_errorlog 可使您循环访问错误日志文件,而不必停止和启动服务器。

另外:日志过大说明你没有截断错误日志,错误日志是可以截断的,进入你的数据库输入DBCC ERRORLOG

每执行一次,当前的错误日志退出,让后建立新的错误日志,你只能删除 ERRORLOGn的错误日志没有号码的是正在使用的日志,删除会报错,如果它比较大,就DBCC ERRORLOG,而后他会变成ERRORLOG+编号,你就可以删除了,另外建议你把这些ERRORLOG 放到其他盘符,比较好管理。

 

发表在 db | 标签为 , , | SQL Server 错误日志已关闭评论

Outlook PST数据文件位置变更

Outlook数据文件的默认保存位置是C:\Users\用户名\AppData\Local\Microsoft\Outlook,通常出于文件安全考虑,会需要将PST文件保存到系统盘以外的磁盘,以免系统崩溃需要重装时遗失。

POP3类型的账户修改PST文件位置方法:

在【控制面板】>【邮件】>【邮件账户】中选中POP3/SMTP类型的账户,左下角即会出现“修改文件夹”的选项。

或者参考http://office.microsoft.com/zh-cn/outlook-help/HA010378229.aspx 中的步骤操作即可。

IMAP类型的账户修改PST文件位置方法:

对于IMAP类型账户的PST文件,既没有“修改文件夹”的选项,也无法按OFFICE官方操作指南中的操作。因为每次Outlook只要检测到默认路径下的PST文件不存在,就会自动在原位置重建.pst数据文件。

对于IMAP类型的账户,需要修改注册表以实现强制在目标路径下创建.pst文件的目的。

1. WIN+R 输入REGEDIT,回车打开注册表编辑器

2. 根据OFFICE版本定位到以下目录:

Outlook 2007:

HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Outlook

Outlook 2010 :

HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Outlook

注册表节点必需为:HKEY_CURRENT_USER

3. 新建字串值:ForcePSTPath,然后将其值设为想要保存.pst的路径。

4. 重新创建IMAP账户,此时.pst文件会被默认保存到步骤3设定的文件夹下。

发表在 article | 标签为 , | Outlook PST数据文件位置变更已关闭评论

adt

ADT插件在线添加
启动Eclipse,然后选择“ 帮助“ >“ 安装新软件"。
单击“ 添加“,在右上角。
在出现的对话框中添加存储库,输入“ADT插件”的名称和位置以下URL :https://dl-ssl.google.com/android/eclipse/
点击“确定”。
如果你有麻烦获取插件,尝试使用位置URL中的“HTTP”,而不是“https”开头(HTTPS出于安全原因)是首选。
在“可用的软件”对话框中,选择开发工具旁边的复选框,并单击“ 下一步“。
在下一个窗口中,你会看到一个下载的工具列表。单击“ 下一步“。
阅读并接受许可协议,然后单击“ 完成“。
如果你得到一个安全警告,说不能成立的真实性或有效性的软件,单击“确定”。
当安装完成后,重新启动Eclipse。

ADT插件离线添加

启动Eclipse,然后选择“ 帮助“ >“ 安装新软件。
单击“ 添加“,在右上角。
在添加存储库“对话框中,单击” 存档“。
选择的下载ADT-x.x.x.zip的文件,然后单击“确定”。
输入“ADT插件”的名称,并单击“确定”。
在“可用的软件”对话框中,选择开发工具旁边的复选框,并单击“ 下一步“。
在下一个窗口中,你会看到一个下载的工具列表。单击“ 下一步“。
阅读并接受许可协议,然后单击“ 完成“。
如果你得到一个安全警告,说不能成立的真实性或有效性的软件,单击“确定”。
当安装完成后,重新启动Eclipse。

https://dl.google.com/android/ADT-22.3.0.zip

https://dl.google.com/android/ADT-16.0.0.zip
https://dl.google.com/android/ADT-18.0.0.zip
https://dl.google.com/android/ADT-20.0.0.zip
https://dl.google.com/android/ADT-20.0.3.zip
https://dl.google.com/android/ADT-21.1.0.zip

 

 

 

发表在 android | 标签为 | adt已关闭评论