http://blog.csdn.net/zhangskd/article/details/11770647
分类: Linux TCP/IP Linux Kernel 2013-09-24 18:29 7853人阅读 评论(0) 收藏 举报

目录(?)[+]

概述

TCP校验和是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到

接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。

TCP的校验和是必需的,而UDP的校验和是可选的。

TCP和UDP计算校验和时,都要加上一个12字节的伪首部。

Author : zhangskd @ csdn blog

伪首部

伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。

伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。

定义

(1) RFC 793的TCP校验和定义

The checksum field is the 16 bit one's complement of the one's complement sum of all 16-bit words in the header and text.

If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right

with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computing

the checksum, the checksum field itself is replaced with zeros.

上述的定义说得很明确:

首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。

把TCP报头中的校验和字段置为0(否则就陷入鸡生蛋还是蛋生鸡的问题)。

其次,用反码相加法累加所有的16位字(进位也要累加)。

最后,对计算结果取反,作为TCP的校验和。

(2) RFC 1071的IP校验和定义

1. Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1's complement sum of these

16-bit integers is formed.

2. To generate a checksum, the checksum field itself is cleared, the 16-bit 1's complement sum is computed over

the octets concerned, and the 1's complement of this sum is placed in the checksum field.

3. To check a checksum, the 1's complement sum is computed over the same set of octets, including the checksum

field. If the result is all 1 bits (-0 in 1's complement arithmetic), the check succeeds.

可以看到,TCP校验和、IP校验和的计算方法是基本一致的,除了计算的范围不同。

实现

基于2.6.18、x86_64。

csum_tcpudp_nofold()按4字节累加伪首部到sum中。

  1. static inline unsigned long csum_tcpudp_nofold (unsigned long saddr, unsigned long daddr,
  2. unsigned short len, unsigned short proto,
  3. unsigned int sum)
  4. {
  5. asm("addl %1, %0\n"    /* 累加daddr */
  6. "adcl %2, %0\n"    /* 累加saddr */
  7. "adcl %3, %0\n"    /* 累加len(2字节), proto, 0*/
  8. "adcl $0, %0\n"    /*加上进位 */
  9. : "=r" (sum)
  10. : "g" (daddr), "g" (saddr), "g" ((ntohs(len) << 16) + proto*256), "0" (sum));
  11. return sum;
  12. }

csum_tcpudp_magic()产生最终的校验和。

首先,按4字节累加伪首部到sum中。

其次,累加sum的低16位、sum的高16位,并且对累加的结果取反。

最后,截取sum的高16位,作为校验和。

  1. static inline unsigned short int csum_tcpudp_magic(unsigned long saddr, unsigned long daddr,
  2. unsigned short len, unsigned short proto,
  3. unsigned int sum)
  4. {
  5. return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
  6. }
  7. static inline unsigned int csum_fold(unsigned int sum)
  8. {
  9. __asm__(
  10. "addl %1, %0\n"
  11. "adcl 0xffff, %0"
  12. : "=r" (sum)
  13. : "r" (sum << 16), "0" (sum & 0xffff0000)
  14. /* 将sum的低16位,作为寄存器1的高16位,寄存器1的低16位补0。
  15. * 将sum的高16位,作为寄存器0的高16位,寄存器0的低16位补0。
  16. * 这样,addl %1, %0就累加了sum的高16位和低16位。
  17. *
  18. * 还要考虑进位。如果有进位,adcl 0xfff, %0为:0x1 + 0xffff + %0,寄存器0的高16位加1。
  19. * 如果没有进位,adcl 0xffff, %0为:0xffff + %0,对寄存器0的高16位无影响。
  20. */
  21. );
  22. return (~sum) >> 16; /* 对sum取反,返回它的高16位,作为最终的校验和 */
  23. }

发送校验

  1. #define CHECKSUM_NONE 0 /* 不使用校验和,UDP可选 */
  2. #define CHECKSUM_HW 1 /* 由硬件计算报头和首部的校验和 */
  3. #define CHECKSUM_UNNECESSARY 2 /* 表示不需要校验,或者已经成功校验了 */
  4. #define CHECKSUM_PARTIAL CHECKSUM_HW
  5. #define CHECKSUM_COMPLETE CHECKSUM_HW

@tcp_transmit_skb()

icsk->icsk_af_ops->send_check(sk, skb->len, skb); /* 计算校验和 */

  1. void tcp_v4_send_check(struct sock *sk, int len, struct sk_buff *skb)
  2. {
  3. struct inet_sock *inet = inet_sk(sk);
  4. struct tcphdr *th = skb->h.th;
  5. if (skb->ip_summed == CHECKSUM_HW) {
  6. /* 只计算伪首部,TCP报头和TCP数据的累加由硬件完成 */
  7. th->check = ~tcp_v4_check(th, len, inet->saddr, inet->daddr, 0);
  8. skb->csum = offsetof(struct tcphdr, check); /* 校验和值在TCP首部的偏移 */
  9. } else {
  10. /* tcp_v4_check累加伪首部,获取最终的校验和。
  11. * csum_partial累加TCP报头。
  12. * 那么skb->csum应该是TCP数据部分的累加,这是在从用户空间复制时顺便累加的。
  13. */
  14. th->check = tcp_v4_check(th, len, inet->saddr, inet->daddr,
  15. csum_partial((char *)th, th->doff << 2, skb->csum));
  16. }
  17. }
  1. unsigned csum_partial(const unsigned char *buff, unsigned len, unsigned sum)
  2. {
  3. return add32_with_carry(do_csum(buff, len), sum);
  4. }
  5. static inline unsigned add32_with_carry(unsigned a, unsigned b)
  6. {
  7. asm("addl %2, %0\n\t"
  8. "adcl $0, %0"
  9. : "=r" (a)
  10. : "0" (a), "r" (b));
  11. return a;
  12. }

do_csum()用于计算一段内存的校验和,这里用于累加TCP报头。

具体计算时用到一些技巧:

1. 反码累加时,按16位、32位、64位来累加的效果是一样的。

2. 使用内存对齐,减少内存操作的次数。

  1. static __force_inline unsigned do_csum(const unsigned char *buff, unsigned len)
  2. {
  3. unsigned odd, count;
  4. unsigned long result = 0;
  5. if (unlikely(len == 0))
  6. return result;
  7. /* 使起始地址为XXX0,接下来可按2字节对齐 */
  8. odd = 1 & (unsigned long) buff;
  9. if (unlikely(odd)) {
  10. result = *buff << 8; /* 因为机器是小端的 */
  11. len--;
  12. buff++;
  13. }
  14. count = len >> 1; /* nr of 16-bit words,这里可能余下1字节未算,最后会处理*/
  15. if (count) {
  16. /* 使起始地址为XX00,接下来可按4字节对齐 */
  17. if (2 & (unsigned long) buff) {
  18. result += *(unsigned short *)buff;
  19. count--;
  20. len -= 2;
  21. buff += 2;
  22. }
  23. count >>= 1; /* nr of 32-bit words,这里可能余下2字节未算,最后会处理 */
  24. if (count) {
  25. unsigned long zero;
  26. unsigned count64;
  27. /* 使起始地址为X000,接下来可按8字节对齐 */
  28. if (4 & (unsigned long)buff) {
  29. result += *(unsigned int *)buff;
  30. count--;
  31. len -= 4;
  32. buff += 4;
  33. }
  34. count >>= 1; /* nr of 64-bit words,这里可能余下4字节未算,最后会处理*/
  35. /* main loop using 64byte blocks */
  36. zero = 0;
  37. count64 = count >> 3; /* 64字节的块数,这里可能余下56字节未算,最后会处理 */
  38. while (count64) { /* 反码累加所有的64字节块 */
  39. asm ("addq 0*8(%[src]), %[res]\n\t"    /* b、w、l、q分别对应8、16、32、64位操作 */
  40. "addq 1*8(%[src]), %[res]\n\t"    /* [src]为指定寄存器的别名,效果应该等同于0、1等 */
  41. "adcq 2*8(%[src]), %[res]\n\t"
  42. "adcq 3*8(%[src]), %[res]\n\t"
  43. "adcq 4*8(%[src]), %[res]\n\t"
  44. "adcq 5*8(%[src]), %[res]\n\t"
  45. "adcq 6*8(%[src]), %[res]\n\t"
  46. "adcq 7*8(%[src]), %[res]\n\t"
  47. "adcq %[zero], %[res]"
  48. : [res] "=r" (result)
  49. : [src] "r" (buff), [zero] "r" (zero), "[res]" (result));
  50. buff += 64;
  51. count64--;
  52. }
  53. /* 从这里开始,反序处理之前可能漏算的字节 */
  54. /* last upto 7 8byte blocks,前面按8个8字节做计算单位,所以最多可能剩下7个8字节 */
  55. count %= 8;
  56. while (count) {
  57. asm ("addq %1, %0\n\t"
  58. "adcq %2, %0\n"
  59. : "=r" (result)
  60. : "m" (*(unsigned long *)buff), "r" (zero), "0" (result));
  61. --count;
  62. buff += 8;
  63. }
  64. /* 带进位累加result的高32位和低32位 */
  65. result = add32_with_carry(result>>32, result&0xffffffff);
  66. /* 之前始按8字节对齐,可能有4字节剩下 */
  67. if (len & 4) {
  68. result += *(unsigned int *) buff;
  69. buff += 4;
  70. }
  71. }
  72. /* 更早前按4字节对齐,可能有2字节剩下 */
  73. if (len & 2) {
  74. result += *(unsigned short *) buff;
  75. buff += 2;
  76. }
  77. }
  78. /* 最早之前按2字节对齐,可能有1字节剩下 */
  79. if (len & 1)
  80. result += *buff;
  81. /* 再次带进位累加result的高32位和低32位 */
  82. result = add32_with_carry(result>>32, result & 0xffffffff);
  83. /* 这里涉及到一个技巧,用于处理初始地址为奇数的情况 */
  84. if (unlikely(odd)) {
  85. result = from32to16(result); /* 累加到result的低16位 */
  86. /* result为:0 0 a b
  87. * 然后交换a和b,result变为:0 0 b a
  88. */
  89. result = ((result >> 8) & 0xff) | ((result & oxff) << 8);
  90. }
  91. return result; /* 返回result的低32位 */
  92. }
  1. static inline unsigned short from32to16(unsigned a)
  2. {
  3. unsigned short b = a >> 16;
  4. asm ("addw %w2, %w0\n\t"
  5. "adcw $0, %w0\n"
  6. : "=r" (b)
  7. : "0" (b), "r" (a));
  8. return b;
  9. }

csum_partial_copy_from_user()用于拷贝用户空间数据到内核空间,同时计算用户数据的校验和,

结果保存到skb->csum中(X86_64)。

  1. /**
  2. * csum_partial_copy_from_user - Copy and checksum from user space.
  3. * @src: source address (user space)
  4. * @dst: destination address
  5. * @len: number of bytes to be copied.
  6. * @isum: initial sum that is added into the result (32bit unfolded)
  7. * @errp: set to -EFAULT for an bad source address.
  8. *
  9. * Returns an 32bit unfolded checksum of the buffer.
  10. * src and dst are best aligned to 64bits.
  11. */
  12. unsigned int csum_partial_copy_from_user(const unsigned char __user *src,
  13. unsigned char *dst, int len, unsigned int isum, int *errp)
  14. {
  15. might_sleep();
  16. *errp = 0;
  17. if (likely(access_ok(VERIFY_READ, src, len))) {
  18. /* Why 6, not 7? To handle odd addresses aligned we would need to do considerable
  19. * complications to fix the checksum which is defined as an 16bit accumulator. The fix
  20. * alignment code is primarily for performance compatibility with 32bit and that will handle
  21. * odd addresses slowly too.
  22. * 处理X010、X100、X110的起始地址。不处理X001,因为这会使复杂度大增加。
  23. */
  24. if (unlikely((unsigned long)src & 6)) {
  25. while (((unsigned long)src & 6) && len >= 2) {
  26. __u16 val16;
  27. *errp = __get_user(val16, (__u16 __user *)src);
  28. if (*errp)
  29. return isum;
  30. *(__u16 *)dst = val16;
  31. isum = add32_with_carry(isum, val16);
  32. src += 2;
  33. dst += 2;
  34. len -= 2;
  35. }
  36. }
  37. /* 计算函数是用纯汇编实现的,应该是因为效率吧 */
  38. isum = csum_parial_copy_generic((__force void *)src, dst, len, isum, errp, NULL);
  39. if (likely(*errp == 0))
  40. return isum; /* 成功 */
  41. }
  42. *errp = -EFAULT;
  43. memset(dst, 0, len);
  44. return isum;
  45. }

上述的实现比较复杂,来看下最简单的csum_partial_copy_from_user()实现(um)。

  1. unsigned int csum_partial_copy_from_user(const unsigned char *src,
  2. unsigned char *dst, int len, int sum,
  3. int *err_ptr)
  4. {
  5. if (copy_from_user(dst, src, len)) { /* 拷贝用户空间数据到内核空间 */
  6. *err_ptr = -EFAULT; /* bad address */
  7. return (-1);
  8. }
  9. return csum_partial(dst, len, sum); /* 计算用户数据的校验和,会存到skb->csum中 */
  10. }

接收校验

@tcp_v4_rcv

/* 检查校验和 */

if (skb->ip_summed != CHECKSUM_UNNECESSARY && tcp_v4_checksum_init(skb))

goto bad_packet;

接收校验的第一部分,主要是计算伪首部。

  1. static int tcp_v4_checksum_init(struct sk_buff *skb)
  2. {
  3. /* 如果TCP报头、TCP数据的反码累加已经由硬件完成 */
  4. if (skb->ip_summed == CHECKSUM_HW) {
  5. /* 现在只需要再累加上伪首部,取反获取最终的校验和。
  6. * 校验和为0时,表示TCP数据报正确。
  7. */
  8. if (! tcp_v4_check(skb->h.th, skb->len, skb->nh.iph->saddr, skb->nh.iph->daddr, skb->csum)) {
  9. skb->ip_summed = CHECKSUM_UNNECESSARY;
  10. return 0; /* 校验成功 */
  11. } /* 没有else失败退出吗?*/
  12. }
  13. /* 对伪首部进行反码累加,主要用于软件方法 */
  14. skb->csum = csum_tcpudp_nofold(skb->nh.iph->saddr, skb->nh.iph->daddr, skb->len, IPPROTO_TCP, 0);
  15. /* 对于长度小于76字节的小包,接着累加TCP报头和报文,完成校验;否则,以后再完成检验。*/
  16. if (skb->len <= 76) {
  17. return __skb_checksum_complete(skb);
  18. }
  19. }

接收校验的第二部分,计算报头和报文。

tcp_v4_rcv、tcp_v4_do_rcv()

| --> tcp_checksum_complete()

| --> __tcp_checksum_complete()

| --> __skb_checksum_complete()

tcp_rcv_established()

| --> tcp_checksum_complete_user()

| --> __tcp_checksum_complete_user()

| --> __tcp_checksum_complete()

| --> __skb_checksum_complete()

  1. unsigned int __skb_checksum_complete(struct sk_buff *skb)
  2. {
  3. unsigned int sum;
  4. sum = (u16) csum_fold(skb_checksum(skb, 0, skb->len, skb->csum));
  5. if (likely(!sum)) { /* sum为0表示成功了 */
  6. /* 硬件检测失败,软件检测成功了,说明硬件检测有误 */
  7. if (unlikely(skb->ip_summed == CHECKSUM_HW))
  8. netdev_rx_csum_fault(skb->dev);
  9. skb->ip_summed = CHECKSUM_UNNECESSARY;
  10. }
  11. return sum;
  12. }

计算skb包的校验和时,可以指定相对于skb->data的偏移量offset。

由于skb包可能由分页和分段,所以需要考虑skb->data + offset是位于此skb段的线性区中、

还是此skb的分页中,或者位于其它分段中。这个函数逻辑比较复杂。

  1. /* Checksum skb data. */
  2. unsigned int skb_checksum(const struct sk_buff *skb, int offset, int len, unsigned int csum)
  3. {
  4. int start = skb_headlen(skb); /* 线性区域长度 */
  5. /* copy > 0,说明offset在线性区域中。
  6. * copy < 0,说明offset在此skb的分页数据中,或者在其它分段skb中。
  7. */
  8. int i, copy = start - offset;
  9. int pos = 0; /* 表示校验了多少数据 */
  10. /* Checksum header. */
  11. if (copy > 0) { /* 说明offset在本skb的线性区域中 */
  12. if (copy > len)
  13. copy = len; /* 不能超过指定的校验长度 */
  14. /* 累加copy长度的线性区校验 */
  15. csum = csum_partial(skb->data + offset, copy, csum);
  16. if ((len -= copy) == 0)
  17. return csum;
  18. offset += copy; /* 接下来从这里继续处理 */
  19. pos = copy; /* 已处理数据长 */
  20. }
  21. /* 累加本skb分页数据的校验和 */
  22. for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  23. int end;
  24. BUG_TRAP(start <= offset + len);
  25. end = start + skb_shinfo(skb)->frags[i].size;
  26. if ((copy = end - offset) > 0) { /* 如果offset位于本页中,或者线性区中 */
  27. unsigned int csum2;
  28. u8 *vaddr; /* 8位够吗?*/
  29. skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
  30. if (copy > len)
  31. copy = len;
  32. vaddr = kmap_skb_frag(frag); /* 把物理页映射到内核空间 */
  33. csum2 = csum_partial(vaddr + frag->page_offset + offset - start, copy, 0);
  34. kunmap_skb_frag(vaddr); /* 解除映射 */
  35. /* 如果pos为奇数,需要对csum2进行处理。
  36. * csum2:a, b, c, d => b, a, d, c
  37. */
  38. csum = csum_block_add(csum, csum2, pos);
  39. if (! (len -= copy))
  40. return csum;
  41. offset += copy;
  42. pos += copy;
  43. }
  44. start = end; /* 接下来从这里处理 */
  45. }
  46. /* 如果此skb是个大包,还有其它分段 */
  47. if (skb_shinfo(skb)->frag_list) {
  48. struct sk_buff *list = skb_shinfo(skb)->frag_list;
  49. for (; list; list = list->next) {
  50. int end;
  51. BUG_TRAP(start <= offset + len);
  52. end = start + list->len;
  53. if ((copy = end - offset) > 0) { /* 如果offset位于此skb分段中,或者分页,或者线性区 */
  54. unsigned int csum2;
  55. if (copy > len)
  56. copy = len;
  57. csum2 = skb_checksum(list, offset - start, copy, 0); /* 递归调用 */
  58. csum = csum_block_add(csum, csum2, pos);
  59. if ((len -= copy) == 0)
  60. return csum;
  61. offset += copy;
  62. pos += copy;
  63. }
  64. start = end;
  65. }
  66. }
  67. BUG_ON(len);
  68. return csum;
  69. }

TCP校验和的原理和实现的更多相关文章

  1. TCP传输工作原理

    引言 在TCP/IP体系结构中,IP协议只管将数据包尽力传送到目的主机,无论数据传输正确与否,它都不做验证,不发确认,也不保证数据包的顺序,因而不具有可靠性.这一问题要由传输层TCP协议来解决,TCP ...

  2. TCP/IP协议原理与应用笔记18:构成子网和超网

    1. 引言: (1)类别IP编址(Classful IP)的缺陷 • 固定的3种IP网络规模      C类地址:少于255台主机的网络      B类地址:介于255~65535台主机的网络     ...

  3. TCP/IP协议原理与应用笔记17:IP编址(重点)

    1. IP地址(通用标识符) 对于同一个网络设备(主机或路由器)的不同网络连接,需要不同的IP地址进行标识 2. 主机标识符 主要有下面三种方式的主机标识方式: (1)Name:是什么,可读性强(了解 ...

  4. TCP/IP协议原理学习笔记

    昨天学习了杨宁老师的TCP/IP协议原理第一讲和第二讲,主要介绍了OSI模型,整理如下: OSI是open system innerconnection的简称,即开放式系统互联参考模型,它把网络协议从 ...

  5. TCP协议设计原理

    TCP协议设计原理 最近去了解TCP协议,发现这是一个特别值得深思的协议.在本篇博客中,不会长篇大论的给大家介绍TCP协议特点.包头格式以及TCP的连接和断开等基本原理,而是会带大家深入理解为什么要这 ...

  6. TCP回话劫持原理和利用

    由于 TCP 协议并没有对 TCP 的传输包进行身份验证,所以在我们知道一个 TCP 连接中的 seq 和 ack 的信息后就可以很容易的伪造传输包,假装任意一方与另一方进行通信,我们将这一过程称为 ...

  7. tcp校验和

    伪首部(pseudo header),通常指TCP伪首部和UDP伪首部 TCP的校验和是必需的,而UDP的校验和是可选的 TCP校验是需要校验包头和数据的 //共12字节 typedef struct ...

  8. TCP/IP协议原理【转载】

    前述        各种L2数据网具有不同的通信协议与帧结构,其网络节点设备可以是各种类型的数据交换机(X.25.FR.Ethernet和ATM等分组交换机):而L3数据网(IP网或internet) ...

  9. TCP/IP协议原理与应用笔记01:OSI网络参考模型

    1.OSI参考模型 第7层应用层:直接对应用程序提供服务,应用程序可以变化,但要包括电子消息传输   第6层表示层:格式化数据,以便为应用程序提供通用接口.这可以包括加密服务   第5层会话层:在两个 ...

随机推荐

  1. phpcms v9 数据源

    先到phpcms后台的模块下找到数据源 数据源有两种方式 1,内部数据源 2,外部数据源 这里只做外部数据源.添加一个外部数据源 1)必须填写数据库链接信息,指定一个数据名(在站内调用是使用) 2)进 ...

  2. Android开发跳槽、简历和面试的那些事

    年后不久,就迎来了一年一度的招聘旺季,尤其,对于互联网行业来说,近些年的3月份被视为换工作的最高峰,已经没什么可以争议的了. 至今为止,在小组Android开发招聘这块,已经面试有近30人了.最后得出 ...

  3. AWS CloudFront CDN直接全站加速折腾记The request could not be satisfied. Bad request

    ERROR The request could not be satisfied. Bad request. Generated by cloudfront (CloudFront) Request ...

  4. 手动创建VS单元测试,显示代码覆盖率

    Visual Studio 号称有史以来最强大的IDE,确实如此.创建单元测试也是一键完成:在方法的代码块中右键“Create Unit Test…”,勾选测试项,填项目名,完成.VS就会自动帮你创建 ...

  5. 推荐12个最好的 JavaScript 图形绘制库

    众多周知,图形和图表要比文本更具表现力和说服力.图表是数据图形化的表示,通过形象的图表来展示数据,比如条形图,折线图,饼图等等.可视化图表可以帮助开发者更容易理解复杂的数据,提高生产的效率和 Web  ...

  6. 在64位Windows7上安装64位Oracle11g

    我一直在用Oracle10g数据库,最近想看看11g怎么样,就试着装了一下,在安装过程中遇到的麻烦还不少,幸好有搜索引擎,根据前辈的指点,磕磕绊绊地也将Oracle装上了,作一下记录,以后也许能用得着 ...

  7. sharepoint2010如何本地化WebPart的Category、WebDisplayName 和 WebDescription 属性

    在项目中经常需要实现多语言其中包括webpart的属性也需要.那么如何实现呢? 首先需要资源文件,利用资源文件实现语言的翻译,如下图: 创建好资源后,下面我们来实现webpart属性的多语言.方法代码 ...

  8. linux命令学习使用记录

    1.文件批量重命名:把所有.xml文件重命名.txt,第一个参数为文件名中字符串,第二个参数为替换后文件名,第三个为当前目录文件列表 rename .xml .txt *.xml 2.解压不显示过程: ...

  9. [SQL] SQL Server 触发器

    触发器是一种特殊类型的存储过程,它不同于之前的我们介绍的存储过程.触发器主要是通过事件进行触发被自动调用执行的.而存储过程可以通过存储过程的名称被调用. Ø 什么是触发器 触发器对表进行插入.更新.删 ...

  10. Android-Universal-Image-Loader 框架使用

    1.Android-Universal-Image-Loader   github下载地址    https://github.com/nostra13/Android-Universal-Image ...