tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端;

tcp_v4_connect为发起连接主流程,首先对必要参数进行检查,获取路由信息,改变连接状态成SYN_SENT,再调用inet_hash_connect将控制块加入到ehash,最后调用tcp_connect发送syn;

  1. /* This will initiate an outgoing connection. */
  2. int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
  3. {
  4. struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
  5. struct inet_sock *inet = inet_sk(sk);
  6. struct tcp_sock *tp = tcp_sk(sk);
  7. __be16 orig_sport, orig_dport;
  8. __be32 daddr, nexthop;
  9. struct flowi4 *fl4;
  10. struct rtable *rt;
  11. int err;
  12. struct ip_options_rcu *inet_opt;
  13.  
  14. /* timewait控制块结构 */
  15. struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;
  16.  
  17. /* 地址长度不合法 */
  18. if (addr_len < sizeof(struct sockaddr_in))
  19. return -EINVAL;
  20.  
  21. /* 地址族不合法 */
  22. if (usin->sin_family != AF_INET)
  23. return -EAFNOSUPPORT;
  24.  
  25. /* 设置下一跳和目的地址 */
  26. nexthop = daddr = usin->sin_addr.s_addr;
  27.  
  28. /* 获取ip选项 */
  29. inet_opt = rcu_dereference_protected(inet->inet_opt,
  30. lockdep_sock_is_held(sk));
  31.  
  32. /* 使用了源路由选项 */
  33. if (inet_opt && inet_opt->opt.srr) {
  34. if (!daddr)
  35. return -EINVAL;
  36. /* 下一跳地址设置为选项中的地址 */
  37. nexthop = inet_opt->opt.faddr;
  38. }
  39.  
  40. /* 获取源端口目的端口 */
  41. orig_sport = inet->inet_sport;
  42. orig_dport = usin->sin_port;
  43.  
  44. /* 查找路由 */
  45. fl4 = &inet->cork.fl.u.ip4;
  46. rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
  47. RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
  48. IPPROTO_TCP,
  49. orig_sport, orig_dport, sk);
  50. /* 查找失败 */
  51. if (IS_ERR(rt)) {
  52. err = PTR_ERR(rt);
  53. if (err == -ENETUNREACH)
  54. IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
  55. return err;
  56. }
  57.  
  58. /* 查找成功 */
  59.  
  60. /* 路由是组播或者广播 */
  61. if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
  62. ip_rt_put(rt);
  63. return -ENETUNREACH;
  64. }
  65.  
  66. /* 选项为空或者未启用源路由选项 */
  67. /* 设置目的地址为路由缓存中地址 */
  68. if (!inet_opt || !inet_opt->opt.srr)
  69. daddr = fl4->daddr;
  70.  
  71. /* 源地址为空 */
  72. /* 使用路由缓存中的源地址 */
  73. if (!inet->inet_saddr)
  74. inet->inet_saddr = fl4->saddr;
  75. /* 设置接收地址为源地址 */
  76. sk_rcv_saddr_set(sk, inet->inet_saddr);
  77.  
  78. /* 控制块中的时间戳存在&& 目的地址不是当前地址 */
  79. /* 控制块被使用过,重新初始化 */
  80. if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
  81. /* Reset inherited state */
  82. tp->rx_opt.ts_recent = ;
  83. tp->rx_opt.ts_recent_stamp = ;
  84. if (likely(!tp->repair))
  85. tp->write_seq = ;
  86. }
  87.  
  88. /* 设置目的端口 */
  89. inet->inet_dport = usin->sin_port;
  90. /* 设置目的地址 */
  91. sk_daddr_set(sk, daddr);
  92.  
  93. /* 获取ip选项长度 */
  94. inet_csk(sk)->icsk_ext_hdr_len = ;
  95. if (inet_opt)
  96. inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
  97.  
  98. /* 设置mss */
  99. tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;
  100.  
  101. /* Socket identity is still unknown (sport may be zero).
  102. * However we set state to SYN-SENT and not releasing socket
  103. * lock select source port, enter ourselves into the hash tables and
  104. * complete initialization after this.
  105. */
  106. /* 设置连接状态为TCP_SYN_SENT */
  107. tcp_set_state(sk, TCP_SYN_SENT);
  108.  
  109. /* 端口绑定,加入ehash */
  110. err = inet_hash_connect(tcp_death_row, sk);
  111. if (err)
  112. goto failure;
  113.  
  114. /* 设置hash值 */
  115. sk_set_txhash(sk);
  116.  
  117. /*
  118. 如果源端口或者目的端口发生变化,
  119. 重新获取路由,并更新sk的路由缓存
  120. */
  121. rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
  122. inet->inet_sport, inet->inet_dport, sk);
  123. if (IS_ERR(rt)) {
  124. err = PTR_ERR(rt);
  125. rt = NULL;
  126. goto failure;
  127. }
  128. /* OK, now commit destination to socket. */
  129. sk->sk_gso_type = SKB_GSO_TCPV4;
  130.  
  131. /* 存储目的路由缓存和网络设备特性到控制块 */
  132. sk_setup_caps(sk, &rt->dst);
  133. rt = NULL;
  134.  
  135. if (likely(!tp->repair)) {
  136. /* 获取发送序号 */
  137. if (!tp->write_seq)
  138. tp->write_seq = secure_tcp_seq(inet->inet_saddr,
  139. inet->inet_daddr,
  140. inet->inet_sport,
  141. usin->sin_port);
  142. /* 时间戳偏移 */
  143. tp->tsoffset = secure_tcp_ts_off(inet->inet_saddr,
  144. inet->inet_daddr);
  145. }
  146.  
  147. /* 设置ip首部的id */
  148. inet->inet_id = tp->write_seq ^ jiffies;
  149.  
  150. /* fastopen */
  151. if (tcp_fastopen_defer_connect(sk, &err))
  152. return err;
  153. if (err)
  154. goto failure;
  155.  
  156. /* 发送syn */
  157. err = tcp_connect(sk);
  158.  
  159. if (err)
  160. goto failure;
  161.  
  162. return ;
  163.  
  164. failure:
  165. /*
  166. * This unhashes the socket and releases the local port,
  167. * if necessary.
  168. */
  169. tcp_set_state(sk, TCP_CLOSE);
  170. ip_rt_put(rt);
  171. sk->sk_route_caps = ;
  172. inet->inet_dport = ;
  173. return err;
  174. }

__inet_hash_connect将端口检查通过的控制块加入到ehash;函数对是否设置端口进行了不同处理,若未设置端口,则需要查找一个端口;函数还调用check_established检查是否可以复用处在TIME_WAIT的控制块,以及调用inet_ehash_nolisten将端口对应的控制块加入的ehash;

  1. int __inet_hash_connect(struct inet_timewait_death_row *death_row,
  2. struct sock *sk, u32 port_offset,
  3. int (*check_established)(struct inet_timewait_death_row *,
  4. struct sock *, __u16, struct inet_timewait_sock **))
  5. {
  6. struct inet_hashinfo *hinfo = death_row->hashinfo;
  7. struct inet_timewait_sock *tw = NULL;
  8. struct inet_bind_hashbucket *head;
  9. int port = inet_sk(sk)->inet_num;
  10. struct net *net = sock_net(sk);
  11. struct inet_bind_bucket *tb;
  12. u32 remaining, offset;
  13. int ret, i, low, high;
  14. static u32 hint;
  15.  
  16. /* 存在端口 */
  17. if (port) {
  18. head = &hinfo->bhash[inet_bhashfn(net, port,
  19. hinfo->bhash_size)];
  20.  
  21. /* 找到端口绑定信息 */
  22. tb = inet_csk(sk)->icsk_bind_hash;
  23. spin_lock_bh(&head->lock);
  24.  
  25. /* 当前端口绑定的只有当前控制块 */
  26. if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
  27. /* 将控制块加入只ehash */
  28. inet_ehash_nolisten(sk, NULL);
  29. spin_unlock_bh(&head->lock);
  30. return ;
  31. }
  32. spin_unlock(&head->lock);
  33. /* No definite answer... Walk to established hash table */
  34. /* 检查复用情况 */
  35. ret = check_established(death_row, sk, port, NULL);
  36. local_bh_enable();
  37. return ret;
  38. }
  39.  
  40. /* 没有确定端口,则随机端口 */
  41.  
  42. inet_get_local_port_range(net, &low, &high);
  43. high++; /* [32768, 60999] -> [32768, 61000[ */
  44. remaining = high - low;
  45. if (likely(remaining > ))
  46. remaining &= ~1U;
  47.  
  48. offset = (hint + port_offset) % remaining;
  49. /* In first pass we try ports of @low parity.
  50. * inet_csk_get_port() does the opposite choice.
  51. */
  52. offset &= ~1U;
  53. other_parity_scan:
  54. port = low + offset;
  55.  
  56. /* 遍历端口 */
  57. for (i = ; i < remaining; i += , port += ) {
  58. if (unlikely(port >= high))
  59. port -= remaining;
  60. /* 保留端口 */
  61. if (inet_is_local_reserved_port(net, port))
  62. continue;
  63.  
  64. /* 找到端口对应的绑定hash桶 */
  65. head = &hinfo->bhash[inet_bhashfn(net, port,
  66. hinfo->bhash_size)];
  67. spin_lock_bh(&head->lock);
  68.  
  69. /* Does not bother with rcv_saddr checks, because
  70. * the established check is already unique enough.
  71. */
  72. /* 遍历绑定的链表中的节点 */
  73. inet_bind_bucket_for_each(tb, &head->chain) {
  74.  
  75. /* 找到端口相同节点 */
  76. if (net_eq(ib_net(tb), net) && tb->port == port) {
  77.  
  78. /* 设置被重用了,继续找,随机端口不能重用 */
  79. if (tb->fastreuse >= ||
  80. tb->fastreuseport >= )
  81. goto next_port;
  82. WARN_ON(hlist_empty(&tb->owners));
  83.  
  84. /* 检查timewait复用情况 */
  85. if (!check_established(death_row, sk,
  86. port, &tw))
  87. goto ok;
  88. goto next_port;
  89. }
  90. }
  91.  
  92. /* 遍历没有重复 */
  93.  
  94. /* 创建该端口的绑定信息节点,加入绑定hash */
  95. tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
  96. net, head, port);
  97. if (!tb) {
  98. spin_unlock_bh(&head->lock);
  99. return -ENOMEM;
  100. }
  101.  
  102. /* 设置默认重用标记 */
  103. tb->fastreuse = -;
  104. tb->fastreuseport = -;
  105. goto ok;
  106. next_port:
  107. spin_unlock_bh(&head->lock);
  108. cond_resched();
  109. }
  110.  
  111. /* 继续从下一半端口中找 */
  112. offset++;
  113. if ((offset & ) && remaining > )
  114. goto other_parity_scan;
  115.  
  116. return -EADDRNOTAVAIL;
  117.  
  118. ok:
  119. hint += i + ;
  120.  
  121. /* Head lock still held and bh's disabled */
  122.  
  123. /* 控制块加入该端口的使用者列表 */
  124. inet_bind_hash(sk, tb, port);
  125.  
  126. /* 初始化源端口,加入到ehash */
  127. if (sk_unhashed(sk)) {
  128. inet_sk(sk)->inet_sport = htons(port);
  129. inet_ehash_nolisten(sk, (struct sock *)tw);
  130. }
  131. /*有timewait控制块则从bind列表中移除 */
  132. if (tw)
  133. inet_twsk_bind_unhash(tw, hinfo);
  134. spin_unlock(&head->lock);
  135.  
  136. /* 调度销毁timewait控制块 */
  137. if (tw)
  138. inet_twsk_deschedule_put(tw);
  139. local_bh_enable();
  140. return ;
  141. }

__inet_check_established用于检查与相同端口中处于TIME_WAIT状态的控制块是否可以复用;

  1. /* called with local bh disabled */
  2. static int __inet_check_established(struct inet_timewait_death_row *death_row,
  3. struct sock *sk, __u16 lport,
  4. struct inet_timewait_sock **twp)
  5. {
  6. struct inet_hashinfo *hinfo = death_row->hashinfo;
  7. struct inet_sock *inet = inet_sk(sk);
  8. __be32 daddr = inet->inet_rcv_saddr;
  9. __be32 saddr = inet->inet_daddr;
  10. int dif = sk->sk_bound_dev_if;
  11. INET_ADDR_COOKIE(acookie, saddr, daddr);
  12. const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);
  13. struct net *net = sock_net(sk);
  14. unsigned int hash = inet_ehashfn(net, daddr, lport,
  15. saddr, inet->inet_dport);
  16. struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
  17. spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
  18. struct sock *sk2;
  19. const struct hlist_nulls_node *node;
  20. struct inet_timewait_sock *tw = NULL;
  21.  
  22. spin_lock(lock);
  23.  
  24. /* 遍历链表 */
  25. sk_nulls_for_each(sk2, node, &head->chain) {
  26.  
  27. /* hash不等 */
  28. if (sk2->sk_hash != hash)
  29. continue;
  30.  
  31. /* 找到节点 */
  32. if (likely(INET_MATCH(sk2, net, acookie,
  33. saddr, daddr, ports, dif))) {
  34. /* 节点连接处于timewait状态 */
  35. if (sk2->sk_state == TCP_TIME_WAIT) {
  36. tw = inet_twsk(sk2);
  37.  
  38. /* 可以复用 */
  39. if (twsk_unique(sk, sk2, twp))
  40. break;
  41. }
  42.  
  43. /* 不处于tw,或者不能复用 */
  44. goto not_unique;
  45. }
  46. }
  47.  
  48. /* Must record num and sport now. Otherwise we will see
  49. * in hash table socket with a funny identity.
  50. */
  51. /* 设置端口和hash */
  52. inet->inet_num = lport;
  53. inet->inet_sport = htons(lport);
  54. sk->sk_hash = hash;
  55. WARN_ON(!sk_unhashed(sk));
  56.  
  57. /* 节点加入ehash */
  58. __sk_nulls_add_node_rcu(sk, &head->chain);
  59. if (tw) {
  60. /* 删除tw节点 */
  61. sk_nulls_del_node_init_rcu((struct sock *)tw);
  62. __NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED);
  63. }
  64. spin_unlock(lock);
  65.  
  66. /* 增加使用计数 */
  67. sock_prot_inuse_add(sock_net(sk), sk->sk_prot, );
  68.  
  69. /* 设置能复用的控制块 */
  70. if (twp) {
  71. *twp = tw;
  72. } else if (tw) {
  73. /* Silly. Should hash-dance instead... */
  74. inet_twsk_deschedule_put(tw);
  75. }
  76. return ;
  77.  
  78. not_unique:
  79. spin_unlock(lock);
  80. return -EADDRNOTAVAIL;
  81. }

inet_ehash_nolisten用于将控制块加入ehash,并根据结果做不同处理;

  1. /* 添加到ehash中 */
  2. bool inet_ehash_nolisten(struct sock *sk, struct sock *osk)
  3. {
  4. /* 添加到ehash中 */
  5. bool ok = inet_ehash_insert(sk, osk);
  6.  
  7. if (ok) {
  8. /* 成功增加计数 */
  9. sock_prot_inuse_add(sock_net(sk), sk->sk_prot, );
  10. } else {
  11. /* 增加孤儿数量 */
  12. percpu_counter_inc(sk->sk_prot->orphan_count);
  13. /* 标识连接关闭状态 */
  14. sk->sk_state = TCP_CLOSE;
  15. /* 设置销毁标记 */
  16. sock_set_flag(sk, SOCK_DEAD);
  17. /* 销毁控制块 */
  18. inet_csk_destroy_sock(sk);
  19. }
  20. return ok;
  21. }

tcp_connect用于构造syn包并发送之,发送之后需要设置syn包的重传定时器;

  1. /* Build a SYN and send it off. */
  2. int tcp_connect(struct sock *sk)
  3. {
  4. struct tcp_sock *tp = tcp_sk(sk);
  5. struct sk_buff *buff;
  6. int err;
  7.  
  8. /* 检查重建路由 */
  9. if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
  10. return -EHOSTUNREACH; /* Routing failure or similar. */
  11.  
  12. /* 初始化控制块中与连接相关的成员 */
  13. tcp_connect_init(sk);
  14.  
  15. if (unlikely(tp->repair)) {
  16. tcp_finish_connect(sk, NULL);
  17. return ;
  18. }
  19.  
  20. /* 分配skb */
  21. buff = sk_stream_alloc_skb(sk, , sk->sk_allocation, true);
  22. if (unlikely(!buff))
  23. return -ENOBUFS;
  24.  
  25. /* 无数据的skb相关控制信息初始化 */
  26. tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
  27.  
  28. /* 设置发送syn的时间 */
  29. tp->retrans_stamp = tcp_time_stamp;
  30.  
  31. /* 加入发送队列 */
  32. tcp_connect_queue_skb(sk, buff);
  33.  
  34. /* enc拥塞通告支持 */
  35. tcp_ecn_send_syn(sk, buff);
  36.  
  37. /* Send off SYN; include data in Fast Open. */
  38. /* 发送syn */
  39. err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
  40. tcp_transmit_skb(sk, buff, , sk->sk_allocation);
  41. if (err == -ECONNREFUSED)
  42. return err;
  43.  
  44. /* We change tp->snd_nxt after the tcp_transmit_skb() call
  45. * in order to make this packet get counted in tcpOutSegs.
  46. */
  47. /* 设置序号信息 */
  48. tp->snd_nxt = tp->write_seq;
  49. tp->pushed_seq = tp->write_seq;
  50. TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);
  51.  
  52. /* Timer for repeating the SYN until an answer. */
  53. /* 启动重传定时器 */
  54. inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
  55. inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
  56. return ;
  57. }

TCP主动打开 之 第一次握手-发送SYN的更多相关文章

  1. TCP被动打开 之 第一次握手-接收SYN

    假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手:本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本 ...

  2. TCP被动打开 之 第二次握手-发送SYN+ACK

    假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...

  3. TCP主动打开 之 第二次握手-接收SYN+ACK

    假设客户端执行主动打开,已经经过第一次握手,即发送SYN包到服务器,状态变为SYN_SENT,服务器收到该包后,回复SYN+ACK包,客户端收到该包,进行主动打开端的第二次握手部分:流程中涉及到的函数 ...

  4. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

  5. TCP被动打开 之 第三次握手-接收ACK

    假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack: ...

  6. tcp 客户端 发送syn

    简介 sys_connect->inet_stream_connect->inet_stream_connect->tcp_v4_connect->tcp_connect对于t ...

  7. TCP连接建立系列 — 客户端发送SYN段

    主要内容:客户端调用connect()时的TCP层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd connect的TCP层实现 SOCK_STRE ...

  8. TCP/IP的三次握手和四次放手

    一开始个人对于三次握手和四次挥手这个东西还是有时候会忘记,可能理解的不是非常深刻,所以今天就自己动手来记录一下这个知识点,方便以后查看.总结完之后发现总结的还是可以的哈哈. 三次握手建立连接 第一次: ...

  9. IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式

    IP协议头IP包头格式: 1.版本号:4个bit,用来标识IP版本号.这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6.目前使用的IP协议版本号是4. 2.首部长度:4个 ...

随机推荐

  1. centos 7 源代码搭建部署 zabbix-4.0.13 LTS

    Zabbix 官网 >:https://www.zabbix.com/download 源代码地址>:https://www.zabbix.com/cn/download_sources# ...

  2. kubernetes资源清单之pod

    什么是pod? Pod是一组一个或多个容器(例如Docker容器),具有共享的存储/网络,以及有关如何运行这些容器的规范. Pod的内容始终位于同一地点,并在同一时间安排,并在共享上下文中运行. Po ...

  3. SQL语句复习【专题七】

    SQL语句复习[专题七] 完整性约束分类1)域完整性约束(非空not null,检查check)2)实体完整性约束(唯一unique,主键primary key)3)参照完整性约束(外键foreign ...

  4. 最贵的AMD 7nm显卡来了!这设计 够狂野

    ROG STRIX系列配备三个风扇(支持智能启停).多条热管.金属背板,厚度达2.5个插槽位,另有两个8针辅助供电,因此最大供电能力375W,必然会预先大幅超频,而且应该仍有手动超频空间,当然也少不了 ...

  5. 接口测试工具中 post请求如何传递多维数组

    1,请求参数为数组时,可以采用传递 json格式的形式传递请求参数(字段及字段对应的值如查是字符,都应该用双引号括起来.用单引号会无法识别),后台接收的数据为json . 2,直接以数组格式来请请求 ...

  6. 使用 Eclipse 构建的时候会出现 run as 中没有 maven package 选项

    注:该方法来自我学习时别人分享的出现问题的解决方法,并没有亲自测试,仅供参考 是因为建的是普通 java 工程,需要把它转换成 maven project. 1.右键工程--maven--Disabl ...

  7. c++第三次作业:类的友元

    C++第三次作业:类的友元 1.友元的关系提供了不同类或对象的成员函数之间.类的成员函数与一般函数之间进行数据共享的机制.通俗的说友元关系就是一个类主动声明其他函数是他的朋友,可以使其获得特殊访问权利 ...

  8. CeSharp支持MP4

    因为CefSharp不支持MP4格式(因为版权问题,MP3因为版权过期新版本已经支持了),需要自己下载源码重新编译以支持MP4,或者下载被人编译好的库.因时间问题,我直接在csdn上下载了一个(1c币 ...

  9. Mac下 CMD常用命令

    1.常用命令 pwd     当前工作目录 cd(不加参数) 进root cd(folder)      进入文件夹 cd ..     上级目录 cd ~     返回root cd -     返 ...

  10. MySQL数据库MyISAM存储引擎转为Innodb

    MySQL数据库MyISAM存储引擎转为Innodb  之前公司的数据库存储引擎全部为MyISAM,数据量和访问量都不是很大,所以一直都没什么问题.但是最近出现了MySQL数据表经常被锁的情况,直接导 ...