当前位置: 首页 > 编程日记 > 正文

Linux内核--网络栈实现分析(二)--数据包的传递过程--转


转载地址http://blog.csdn.net/yming0221/article/details/7492423

作者:闫明

本文分析基于Linux Kernel 1.2.13

注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。

上篇:

上一篇博文中我们从宏观上分析了Linux内核中网络栈的初始化过程,这里我们再从宏观上分析一下一个数据包在各网络层的传递的过程。

我们知道网络的OSI模型和TCP/IP模型层次结构如下:

上文中我们看到了网络栈的层次结构:

我们就从最底层开始追溯一个数据包的传递流程。

1、网络接口层

* 硬件监听物理介质,进行数据的接收,当接收的数据填满了缓冲区,硬件就会产生中断,中断产生后,系统会转向中断服务子程序。

* 在中断服务子程序中,数据会从硬件的缓冲区复制到内核的空间缓冲区,并包装成一个数据结构(sk_buff),然后调用对驱动层的接口函数netif_rx()将数据包发送给链路层。该函数的实现在net/inet/dev.c中,(在整个网络栈实现中dev.c文件的作用重大,它衔接了其下的驱动层和其上的网络层,可以称它为链路层模块的实现)

该函数的实现如下:

[cpp] view plaincopy
  1. /*
  2.  *  Receive a packet from a device driver and queue it for the upper
  3.  *  (protocol) levels.  It always succeeds. This is the recommended 
  4.  *  interface to use.
  5.  *    从设备驱动层接受到的数据发送到协议的
  6.  *    上层,该函数实际是一个接口。
  7.  */
  8. void netif_rx(struct sk_buff *skb)
  9. {
  10. static int dropping = 0;
  11. /*
  12.      *  Any received buffers are un-owned and should be discarded
  13.      *  when freed. These will be updated later as the frames get
  14.      *  owners.
  15.      */
  16. skb->sk = NULL;
  17. skb->free = 1;
  18. if(skb->stamp.tv_sec==0)
  19. skb->stamp = xtime;
  20. /*
  21.      *  Check that we aren't overdoing things.
  22.      */
  23. if (!backlog_size)
  24. dropping = 0;
  25. else if (backlog_size > 300)
  26. dropping = 1;
  27. if (dropping)
  28. {
  29. kfree_skb(skb, FREE_READ);
  30. return;
  31. }
  32. /*
  33.      *  Add it to the "backlog" queue. 
  34.      */
  35. #ifdef CONFIG_SKB_CHECK
  36. IS_SKB(skb);
  37. #endif  
  38. skb_queue_tail(&backlog,skb);//加入队列backlog
  39. backlog_size++;
  40. /*
  41.      *  If any packet arrived, mark it for processing after the
  42.      *  hardware interrupt returns.
  43.      */
  44. mark_bh(NET_BH);//下半部分bottom half技术可以减少中断处理程序的执行时间
  45. return;
  46. }


该函数中用到了bootom half技术,该技术的原理是将中断处理程序人为的分为两部分,上半部分是实时性要求较高的任务,后半部分可以稍后完成,这样就可以节省中断程序的处理时间。可整体的提高系统的性能。该技术将会在后续的博文中详细分析。

我们从上一篇分析中知道,在网络栈初始化的时候,已经将NET的下半部分执行函数定义成了net_bh(在socket.c文件中1375行左右)

[cpp] view plaincopy
  1. bh_base[NET_BH].routine= net_bh;//设置NET 下半部分的处理函数为net_bh


* 函数net_bh的实现在net/inet/dev.c中

[cpp] view plaincopy
  1. /*
  2.  *  When we are called the queue is ready to grab, the interrupts are
  3.  *  on and hardware can interrupt and queue to the receive queue a we
  4.  *  run with no problems.
  5.  *  This is run as a bottom half after an interrupt handler that does
  6.  *  mark_bh(NET_BH);
  7.  */
  8. void net_bh(void *tmp)
  9. {
  10. struct sk_buff *skb;
  11. struct packet_type *ptype;
  12. struct packet_type *pt_prev;
  13. unsigned short type;
  14. /*
  15.      *  Atomically check and mark our BUSY state. 
  16.      */
  17. if (set_bit(1, (void*)&in_bh))//标记BUSY状态
  18. return;
  19. /*
  20.      *  Can we send anything now? We want to clear the
  21.      *  decks for any more sends that get done as we
  22.      *  process the input.
  23.      */
  24. dev_transmit();//调用dev_tinit()函数发送数据
  25. /*
  26.      *  Any data left to process. This may occur because a
  27.      *  mark_bh() is done after we empty the queue including
  28.      *  that from the device which does a mark_bh() just after
  29.      */
  30. cli();//防止队列操作错误,需要关中断和开中断
  31. /*
  32.      *  While the queue is not empty
  33.      */
  34. while((skb=skb_dequeue(&backlog))!=NULL)//出队直到队列为空
  35. {
  36. /*
  37.          *  We have a packet. Therefore the queue has shrunk
  38.          */
  39. backlog_size--;//队列元素个数减一
  40. sti();
  41. /*
  42.         *   Bump the pointer to the next structure.
  43.         *   This assumes that the basic 'skb' pointer points to
  44.         *   the MAC header, if any (as indicated by its "length"
  45.         *   field).  Take care now!
  46.         */
  47. skb->h.raw = skb->data + skb->dev->hard_header_len;
  48. skb->len -= skb->dev->hard_header_len;
  49. /*
  50.         *   Fetch the packet protocol ID.  This is also quite ugly, as
  51.         *   it depends on the protocol driver (the interface itself) to
  52.         *   know what the type is, or where to get it from.  The Ethernet
  53.         *   interfaces fetch the ID from the two bytes in the Ethernet MAC
  54.         *   header (the h_proto field in struct ethhdr), but other drivers
  55.         *   may either use the ethernet ID's or extra ones that do not
  56.         *   clash (eg ETH_P_AX25). We could set this before we queue the
  57.         *   frame. In fact I may change this when I have time.
  58.         */
  59. type = skb->dev->type_trans(skb, skb->dev);//取出该数据包所属的协议类型
  60. /*
  61.          *  We got a packet ID.  Now loop over the "known protocols"
  62.          *  table (which is actually a linked list, but this will
  63.          *  change soon if I get my way- FvK), and forward the packet
  64.          *  to anyone who wants it.
  65.          *
  66.          *  [FvK didn't get his way but he is right this ought to be
  67.          *  hashed so we typically get a single hit. The speed cost
  68.          *  here is minimal but no doubt adds up at the 4,000+ pkts/second
  69.          *  rate we can hit flat out]
  70.          */
  71. pt_prev = NULL;
  72. for (ptype = ptype_base; ptype != NULL; ptype = ptype->next) //遍历ptype_base所指向的网络协议队列
  73. {
  74. //判断协议号是否匹配
  75. if ((ptype->type == type || ptype->type == htons(ETH_P_ALL)) && (!ptype->dev || ptype->dev==skb->dev))
  76. {
  77. /*
  78.                  *  We already have a match queued. Deliver
  79.                  *  to it and then remember the new match
  80.                  */
  81. if(pt_prev)
  82. {
  83. struct sk_buff *skb2;
  84. skb2=skb_clone(skb, GFP_ATOMIC);//复制数据包结构
  85. /*
  86.                      *  Kick the protocol handler. This should be fast
  87.                      *  and efficient code.
  88.                      */
  89. if(skb2)
  90. pt_prev->func(skb2, skb->dev, pt_prev);//调用相应协议的处理函数,
  91. //这里和网络协议的种类有关系
  92. //如IP 协议的处理函数就是ip_rcv
  93. }
  94. /* Remember the current last to do */
  95. pt_prev=ptype;
  96. }
  97. /* End of protocol list loop */
  98. /*
  99.          *  Is there a last item to send to ?
  100.          */
  101. if(pt_prev)
  102. pt_prev->func(skb, skb->dev, pt_prev);
  103. /*
  104.          *  Has an unknown packet has been received ?
  105.          */
  106. else
  107. kfree_skb(skb, FREE_WRITE);
  108. /*
  109.          *  Again, see if we can transmit anything now. 
  110.          *  [Ought to take this out judging by tests it slows
  111.          *   us down not speeds us up]
  112.          */
  113. dev_transmit();
  114. cli();
  115. }   /* End of queue loop */
  116. /*
  117.      *  We have emptied the queue
  118.      */
  119. in_bh = 0;//BUSY状态还原
  120. sti();
  121. /*
  122.      *  One last output flush.
  123.      */
  124. dev_transmit();//清空缓冲区
  125. }

2、网络层
* 就以IP数据包为例来说明,那么从链路层向网络层传递时将调用ip_rcv函数。该函数完成本层的处理后会根据IP首部中使用的传输层协议来调用相应协议的处理函数。

UDP对应udp_rcv、TCP对应tcp_rcv、ICMP对应icmp_rcv、IGMP对应igmp_rcv(虽然这里的ICMP,IGMP一般成为网络层协议,但是实际上他们都封装在IP协议里面,作为传输层对待)

这个函数比较复杂,后续会详细分析。这里粘贴一下,让我们对整体了解更清楚

[cpp] view plaincopy
  1. /*
  2.  *  This function receives all incoming IP datagrams.
  3.  */
  4. int ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
  5. {
  6. struct iphdr *iph = skb->h.iph;
  7. struct sock *raw_sk=NULL;
  8. unsigned char hash;
  9. unsigned char flag = 0;
  10. unsigned char opts_p = 0;   /* Set iff the packet has options. */
  11. struct inet_protocol *ipprot;
  12. static struct options opt; /* since we don't use these yet, and they
  13.                 take up stack space. */
  14. int brd=IS_MYADDR;
  15. int is_frag=0;
  16. #ifdef CONFIG_IP_FIREWALL
  17. int err;
  18. #endif  
  19. ip_statistics.IpInReceives++;
  20. /*
  21.      *  Tag the ip header of this packet so we can find it
  22.      */
  23. skb->ip_hdr = iph;
  24. /*
  25.      *  Is the datagram acceptable?
  26.      *
  27.      *  1.  Length at least the size of an ip header
  28.      *  2.  Version of 4
  29.      *  3.  Checksums correctly. [Speed optimisation for later, skip loopback checksums]
  30.      *  (4. We ought to check for IP multicast addresses and undefined types.. does this matter ?)
  31.      */
  32. if (skb->len<sizeof(struct iphdr) || iph->ihl<5 || iph->version != 4 ||
  33. skb->len<ntohs(iph->tot_len) || ip_fast_csum((unsigned char *)iph, iph->ihl) !=0)
  34. {
  35. ip_statistics.IpInHdrErrors++;
  36. kfree_skb(skb, FREE_WRITE);
  37. return(0);
  38. }
  39. /*
  40.      *  See if the firewall wants to dispose of the packet. 
  41.      */
  42. #ifdef  CONFIG_IP_FIREWALL
  43. if ((err=ip_fw_chk(iph,dev,ip_fw_blk_chain,ip_fw_blk_policy, 0))!=1)
  44. {
  45. if(err==-1)
  46. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev);
  47. kfree_skb(skb, FREE_WRITE);
  48. return 0;
  49. }
  50. #endif
  51. /*
  52.      *  Our transport medium may have padded the buffer out. Now we know it
  53.      *  is IP we can trim to the true length of the frame.
  54.      */
  55. skb->len=ntohs(iph->tot_len);
  56. /*
  57.      *  Next analyse the packet for options. Studies show under one packet in
  58.      *  a thousand have options....
  59.      */
  60. if (iph->ihl != 5)
  61. {   /* Fast path for the typical optionless IP packet. */
  62. memset((char *) &opt, 0, sizeof(opt));
  63. if (do_options(iph, &opt) != 0)
  64. return 0;
  65. opts_p = 1;
  66. }
  67. /*
  68.      *  Remember if the frame is fragmented.
  69.      */
  70. if(iph->frag_off)
  71. {
  72. if (iph->frag_off & 0x0020)
  73. is_frag|=1;
  74. /*
  75.          *  Last fragment ?
  76.          */
  77. if (ntohs(iph->frag_off) & 0x1fff)
  78. is_frag|=2;
  79. }
  80. /*
  81.      *  Do any IP forwarding required.  chk_addr() is expensive -- avoid it someday.
  82.      *
  83.      *  This is inefficient. While finding out if it is for us we could also compute
  84.      *  the routing table entry. This is where the great unified cache theory comes
  85.      *  in as and when someone implements it
  86.      *
  87.      *  For most hosts over 99% of packets match the first conditional
  88.      *  and don't go via ip_chk_addr. Note: brd is set to IS_MYADDR at
  89.      *  function entry.
  90.      */
  91. if ( iph->daddr != skb->dev->pa_addr && (brd = ip_chk_addr(iph->daddr)) == 0)
  92. {
  93. /*
  94.          *  Don't forward multicast or broadcast frames.
  95.          */
  96. if(skb->pkt_type!=PACKET_HOST || brd==IS_BROADCAST)
  97. {
  98. kfree_skb(skb,FREE_WRITE);
  99. return 0;
  100. }
  101. /*
  102.          *  The packet is for another target. Forward the frame
  103.          */
  104. #ifdef CONFIG_IP_FORWARD
  105. ip_forward(skb, dev, is_frag);
  106. #else
  107. /*      printk("Machine %lx tried to use us as a forwarder to %lx but we have forwarding disabled!\n",
  108.             iph->saddr,iph->daddr);*/
  109. ip_statistics.IpInAddrErrors++;
  110. #endif
  111. /*
  112.          *  The forwarder is inefficient and copies the packet. We
  113.          *  free the original now.
  114.          */
  115. kfree_skb(skb, FREE_WRITE);
  116. return(0);
  117. }
  118. #ifdef CONFIG_IP_MULTICAST  
  119. if(brd==IS_MULTICAST && iph->daddr!=IGMP_ALL_HOSTS && !(dev->flags&IFF_LOOPBACK))
  120. {
  121. /*
  122.          *  Check it is for one of our groups
  123.          */
  124. struct ip_mc_list *ip_mc=dev->ip_mc_list;
  125. do
  126. {
  127. if(ip_mc==NULL)
  128. {
  129. kfree_skb(skb, FREE_WRITE);
  130. return 0;
  131. }
  132. if(ip_mc->multiaddr==iph->daddr)
  133. break;
  134. ip_mc=ip_mc->next;
  135. }
  136. while(1);
  137. }
  138. #endif
  139. /*
  140.      *  Account for the packet
  141.      */
  142. #ifdef CONFIG_IP_ACCT
  143. ip_acct_cnt(iph,dev, ip_acct_chain);
  144. #endif  
  145. /*
  146.      * Reassemble IP fragments.
  147.      */
  148. if(is_frag)
  149. {
  150. /* Defragment. Obtain the complete packet if there is one */
  151. skb=ip_defrag(iph,skb,dev);
  152. if(skb==NULL)
  153. return 0;
  154. skb->dev = dev;
  155. iph=skb->h.iph;
  156. }
  157. /*
  158.      *  Point into the IP datagram, just past the header.
  159.      */
  160. skb->ip_hdr = iph;
  161. skb->h.raw += iph->ihl*4;
  162. /*
  163.      *  Deliver to raw sockets. This is fun as to avoid copies we want to make no surplus copies.
  164.      */
  165. hash = iph->protocol & (SOCK_ARRAY_SIZE-1);
  166. /* If there maybe a raw socket we must check - if not we don't care less */
  167. if((raw_sk=raw_prot.sock_array[hash])!=NULL)
  168. {
  169. struct sock *sknext=NULL;
  170. struct sk_buff *skb1;
  171. raw_sk=get_sock_raw(raw_sk, hash,  iph->saddr, iph->daddr);
  172. if(raw_sk)  /* Any raw sockets */
  173. {
  174. do
  175. {
  176. /* Find the next */
  177. sknext=get_sock_raw(raw_sk->next, hash, iph->saddr, iph->daddr);
  178. if(sknext)
  179. skb1=skb_clone(skb, GFP_ATOMIC);
  180. else
  181. break;  /* One pending raw socket left */
  182. if(skb1)
  183. raw_rcv(raw_sk, skb1, dev, iph->saddr,iph->daddr);
  184. raw_sk=sknext;
  185. }
  186. while(raw_sk!=NULL);
  187. /* Here either raw_sk is the last raw socket, or NULL if none */
  188. /* We deliver to the last raw socket AFTER the protocol checks as it avoids a surplus copy */
  189. }
  190. }
  191. /*
  192.      *  skb->h.raw now points at the protocol beyond the IP header.
  193.      */
  194. hash = iph->protocol & (MAX_INET_PROTOS -1);
  195. for (ipprot = (struct inet_protocol *)inet_protos[hash];ipprot != NULL;ipprot=(struct inet_protocol *)ipprot->next)
  196. {
  197. struct sk_buff *skb2;
  198. if (ipprot->protocol != iph->protocol)
  199. continue;
  200. /*
  201.     *   See if we need to make a copy of it.  This will
  202.     *   only be set if more than one protocol wants it.
  203.     *   and then not for the last one. If there is a pending
  204.     *   raw delivery wait for that
  205.     */
  206. if (ipprot->copy || raw_sk)
  207. {
  208. skb2 = skb_clone(skb, GFP_ATOMIC);
  209. if(skb2==NULL)
  210. continue;
  211. }
  212. else
  213. {
  214. skb2 = skb;
  215. }
  216. flag = 1;
  217. /*
  218.         * Pass on the datagram to each protocol that wants it,
  219.         * based on the datagram protocol.  We should really
  220.         * check the protocol handler's return values here...
  221.         */
  222. ipprot->handler(skb2, dev, opts_p ? &opt : 0, iph->daddr,
  223. (ntohs(iph->tot_len) - (iph->ihl * 4)),
  224. iph->saddr, 0, ipprot);
  225. }
  226. /*
  227.      * All protocols checked.
  228.      * If this packet was a broadcast, we may *not* reply to it, since that
  229.      * causes (proven, grin) ARP storms and a leakage of memory (i.e. all
  230.      * ICMP reply messages get queued up for transmission...)
  231.      */
  232. if(raw_sk!=NULL)    /* Shift to last raw user */
  233. raw_rcv(raw_sk, skb, dev, iph->saddr, iph->daddr);
  234. else if (!flag)     /* Free and report errors */
  235. {
  236. if (brd != IS_BROADCAST && brd!=IS_MULTICAST)
  237. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0, dev);
  238. kfree_skb(skb, FREE_WRITE);
  239. }
  240. return(0);
  241. }


3、传输层

如果在IP数据报的首部标明的是使用TCP传输数据,则在上述函数中会调用tcp_rcv函数。该函数的大体处理流程为:

“所有使用TCP 协议的套接字对应sock 结构都被挂入tcp_prot 全局变量表示的proto 结构之sock_array 数组中,采用以本地端口号为索引的插入方式,所以当tcp_rcv 函数接收到一个数据包,在完成必要的检查和处理后,其将以TCP 协议首部中目的端口号(对于一个接收的数据包而言,其目的端口号就是本地所使用的端口号)为索引,在tcp_prot 对应sock 结构之sock_array 数组中得到正确的sock 结构队列,在辅之以其他条件遍历该队列进行对应sock 结构的查询,在得到匹配的sock 结构后,将数据包挂入该sock 结构中的缓存队列中(由sock 结构中receive_queue 字段指向),从而完成数据包的最终接收。”

该函数的实现也会比较复杂,这是由TCP协议的复杂功能决定的。附代码如下:

[cpp] view plaincopy
  1. /*
  2.  *  A TCP packet has arrived.
  3.  */
  4. int tcp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
  5. unsigned long daddr, unsigned short len,
  6. unsigned long saddr, int redo, struct inet_protocol * protocol)
  7. {
  8. struct tcphdr *th;
  9. struct sock *sk;
  10. int syn_ok=0;
  11. if (!skb)
  12. {
  13. printk("IMPOSSIBLE 1\n");
  14. return(0);
  15. }
  16. if (!dev)
  17. {
  18. printk("IMPOSSIBLE 2\n");
  19. return(0);
  20. }
  21. tcp_statistics.TcpInSegs++;
  22. if(skb->pkt_type!=PACKET_HOST)
  23. {
  24. kfree_skb(skb,FREE_READ);
  25. return(0);
  26. }
  27. th = skb->h.th;
  28. /*
  29.      *  Find the socket.
  30.      */
  31. sk = get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
  32. /*
  33.      *  If this socket has got a reset it's to all intents and purposes 
  34.      *  really dead. Count closed sockets as dead.
  35.      *
  36.      *  Note: BSD appears to have a bug here. A 'closed' TCP in BSD
  37.      *  simply drops data. This seems incorrect as a 'closed' TCP doesn't
  38.      *  exist so should cause resets as if the port was unreachable.
  39.      */
  40. if (sk!=NULL && (sk->zapped || sk->state==TCP_CLOSE))
  41. sk=NULL;
  42. if (!redo)
  43. {
  44. if (tcp_check(th, len, saddr, daddr ))
  45. {
  46. skb->sk = NULL;
  47. kfree_skb(skb,FREE_READ);
  48. /*
  49.              *  We don't release the socket because it was
  50.              *  never marked in use.
  51.              */
  52. return(0);
  53. }
  54. th->seq = ntohl(th->seq);
  55. /* See if we know about the socket. */
  56. if (sk == NULL)
  57. {
  58. /*
  59.              *  No such TCB. If th->rst is 0 send a reset (checked in tcp_reset)
  60.              */
  61. tcp_reset(daddr, saddr, th, &tcp_prot, opt,dev,skb->ip_hdr->tos,255);
  62. skb->sk = NULL;
  63. /*
  64.              *  Discard frame
  65.              */
  66. kfree_skb(skb, FREE_READ);
  67. return(0);
  68. }
  69. skb->len = len;
  70. skb->acked = 0;
  71. skb->used = 0;
  72. skb->free = 0;
  73. skb->saddr = daddr;
  74. skb->daddr = saddr;
  75. /* We may need to add it to the backlog here. */
  76. cli();
  77. if (sk->inuse)
  78. {
  79. skb_queue_tail(&sk->back_log, skb);
  80. sti();
  81. return(0);
  82. }
  83. sk->inuse = 1;
  84. sti();
  85. }
  86. else
  87. {
  88. if (sk==NULL)
  89. {
  90. tcp_reset(daddr, saddr, th, &tcp_prot, opt,dev,skb->ip_hdr->tos,255);
  91. skb->sk = NULL;
  92. kfree_skb(skb, FREE_READ);
  93. return(0);
  94. }
  95. }
  96. if (!sk->prot)
  97. {
  98. printk("IMPOSSIBLE 3\n");
  99. return(0);
  100. }
  101. /*
  102.      *  Charge the memory to the socket. 
  103.      */
  104. if (sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)
  105. {
  106. kfree_skb(skb, FREE_READ);
  107. release_sock(sk);
  108. return(0);
  109. }
  110. skb->sk=sk;
  111. sk->rmem_alloc += skb->mem_len;
  112. /*
  113.      *  This basically follows the flow suggested by RFC793, with the corrections in RFC1122. We
  114.      *  don't implement precedence and we process URG incorrectly (deliberately so) for BSD bug
  115.      *  compatibility. We also set up variables more thoroughly [Karn notes in the
  116.      *  KA9Q code the RFC793 incoming segment rules don't initialise the variables for all paths].
  117.      */
  118. if(sk->state!=TCP_ESTABLISHED)       /* Skip this lot for normal flow */
  119. {
  120. /*
  121.          *  Now deal with unusual cases.
  122.          */
  123. if(sk->state==TCP_LISTEN)
  124. {
  125. if(th->ack)  /* These use the socket TOS.. might want to be the received TOS */
  126. tcp_reset(daddr,saddr,th,sk->prot,opt,dev,sk->ip_tos, sk->ip_ttl);
  127. /*
  128.              *  We don't care for RST, and non SYN are absorbed (old segments)
  129.              *  Broadcast/multicast SYN isn't allowed. Note - bug if you change the
  130.              *  netmask on a running connection it can go broadcast. Even Sun's have
  131.              *  this problem so I'm ignoring it 
  132.              */
  133. if(th->rst || !th->syn || th->ack || ip_chk_addr(daddr)!=IS_MYADDR)
  134. {
  135. kfree_skb(skb, FREE_READ);
  136. release_sock(sk);
  137. return 0;
  138. }
  139. /*  
  140.              *  Guess we need to make a new socket up 
  141.              */
  142. tcp_conn_request(sk, skb, daddr, saddr, opt, dev, tcp_init_seq());
  143. /*
  144.              *  Now we have several options: In theory there is nothing else
  145.              *  in the frame. KA9Q has an option to send data with the syn,
  146.              *  BSD accepts data with the syn up to the [to be] advertised window
  147.              *  and Solaris 2.1 gives you a protocol error. For now we just ignore
  148.              *  it, that fits the spec precisely and avoids incompatibilities. It
  149.              *  would be nice in future to drop through and process the data.
  150.              */
  151. release_sock(sk);
  152. return 0;
  153. }
  154. /* retransmitted SYN? */
  155. if (sk->state == TCP_SYN_RECV && th->syn && th->seq+1 == sk->acked_seq)
  156. {
  157. kfree_skb(skb, FREE_READ);
  158. release_sock(sk);
  159. return 0;
  160. }
  161. /*
  162.          *  SYN sent means we have to look for a suitable ack and either reset
  163.          *  for bad matches or go to connected 
  164.          */
  165. if(sk->state==TCP_SYN_SENT)
  166. {
  167. /* Crossed SYN or previous junk segment */
  168. if(th->ack)
  169. {
  170. /* We got an ack, but it's not a good ack */
  171. if(!tcp_ack(sk,th,saddr,len))
  172. {
  173. /* Reset the ack - its an ack from a 
  174.                        different connection  [ th->rst is checked in tcp_reset()] */
  175. tcp_statistics.TcpAttemptFails++;
  176. tcp_reset(daddr, saddr, th,
  177. sk->prot, opt,dev,sk->ip_tos,sk->ip_ttl);
  178. kfree_skb(skb, FREE_READ);
  179. release_sock(sk);
  180. return(0);
  181. }
  182. if(th->rst)
  183. return tcp_std_reset(sk,skb);
  184. if(!th->syn)
  185. {
  186. /* A valid ack from a different connection
  187.                        start. Shouldn't happen but cover it */
  188. kfree_skb(skb, FREE_READ);
  189. release_sock(sk);
  190. return 0;
  191. }
  192. /*
  193.                  *  Ok.. it's good. Set up sequence numbers and
  194.                  *  move to established.
  195.                  */
  196. syn_ok=1;   /* Don't reset this connection for the syn */
  197. sk->acked_seq=th->seq+1;
  198. sk->fin_seq=th->seq;
  199. tcp_send_ack(sk->sent_seq,sk->acked_seq,sk,th,sk->daddr);
  200. tcp_set_state(sk, TCP_ESTABLISHED);
  201. tcp_options(sk,th);
  202. sk->dummy_th.dest=th->source;
  203. sk->copied_seq = sk->acked_seq;
  204. if(!sk->dead)
  205. {
  206. sk->state_change(sk);
  207. sock_wake_async(sk->socket, 0);
  208. }
  209. if(sk->max_window==0)
  210. {
  211. sk->max_window = 32;
  212. sk->mss = min(sk->max_window, sk->mtu);
  213. }
  214. }
  215. else
  216. {
  217. /* See if SYN's cross. Drop if boring */
  218. if(th->syn && !th->rst)
  219. {
  220. /* Crossed SYN's are fine - but talking to
  221.                        yourself is right out... */
  222. if(sk->saddr==saddr && sk->daddr==daddr &&
  223. sk->dummy_th.source==th->source &&
  224. sk->dummy_th.dest==th->dest)
  225. {
  226. tcp_statistics.TcpAttemptFails++;
  227. return tcp_std_reset(sk,skb);
  228. }
  229. tcp_set_state(sk,TCP_SYN_RECV);
  230. /*
  231.                      *  FIXME:
  232.                      *  Must send SYN|ACK here
  233.                      */
  234. }
  235. /* Discard junk segment */
  236. kfree_skb(skb, FREE_READ);
  237. release_sock(sk);
  238. return 0;
  239. }
  240. /*
  241.              *  SYN_RECV with data maybe.. drop through
  242.              */
  243. goto rfc_step6;
  244. }
  245. /*
  246.      *  BSD has a funny hack with TIME_WAIT and fast reuse of a port. There is
  247.      *  a more complex suggestion for fixing these reuse issues in RFC1644
  248.      *  but not yet ready for general use. Also see RFC1379.
  249.      */
  250. #define BSD_TIME_WAIT
  251. #ifdef BSD_TIME_WAIT
  252. if (sk->state == TCP_TIME_WAIT && th->syn && sk->dead &&
  253. after(th->seq, sk->acked_seq) && !th->rst)
  254. {
  255. long seq=sk->write_seq;
  256. if(sk->debug)
  257. printk("Doing a BSD time wait\n");
  258. tcp_statistics.TcpEstabResets++;
  259. sk->rmem_alloc -= skb->mem_len;
  260. skb->sk = NULL;
  261. sk->err=ECONNRESET;
  262. tcp_set_state(sk, TCP_CLOSE);
  263. sk->shutdown = SHUTDOWN_MASK;
  264. release_sock(sk);
  265. sk=get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
  266. if (sk && sk->state==TCP_LISTEN)
  267. {
  268. sk->inuse=1;
  269. skb->sk = sk;
  270. sk->rmem_alloc += skb->mem_len;
  271. tcp_conn_request(sk, skb, daddr, saddr,opt, dev,seq+128000);
  272. release_sock(sk);
  273. return 0;
  274. }
  275. kfree_skb(skb, FREE_READ);
  276. return 0;
  277. }
  278. #endif  
  279. }
  280. /*
  281.      *  We are now in normal data flow (see the step list in the RFC)
  282.      *  Note most of these are inline now. I'll inline the lot when
  283.      *  I have time to test it hard and look at what gcc outputs 
  284.      */
  285. if(!tcp_sequence(sk,th,len,opt,saddr,dev))
  286. {
  287. kfree_skb(skb, FREE_READ);
  288. release_sock(sk);
  289. return 0;
  290. }
  291. if(th->rst)
  292. return tcp_std_reset(sk,skb);
  293. /*
  294.      *  !syn_ok is effectively the state test in RFC793.
  295.      */
  296. if(th->syn && !syn_ok)
  297. {
  298. tcp_reset(daddr,saddr,th, &tcp_prot, opt, dev, skb->ip_hdr->tos, 255);
  299. return tcp_std_reset(sk,skb);
  300. }
  301. /*
  302.      *  Process the ACK
  303.      */
  304. if(th->ack && !tcp_ack(sk,th,saddr,len))
  305. {
  306. /*
  307.          *  Our three way handshake failed.
  308.          */
  309. if(sk->state==TCP_SYN_RECV)
  310. {
  311. tcp_reset(daddr, saddr, th,sk->prot, opt, dev,sk->ip_tos,sk->ip_ttl);
  312. }
  313. kfree_skb(skb, FREE_READ);
  314. release_sock(sk);
  315. return 0;
  316. }
  317. rfc_step6:      /* I'll clean this up later */
  318. /*
  319.      *  Process urgent data
  320.      */
  321. if(tcp_urg(sk, th, saddr, len))
  322. {
  323. kfree_skb(skb, FREE_READ);
  324. release_sock(sk);
  325. return 0;
  326. }
  327. /*
  328.      *  Process the encapsulated data
  329.      */
  330. if(tcp_data(skb,sk, saddr, len))
  331. {
  332. kfree_skb(skb, FREE_READ);
  333. release_sock(sk);
  334. return 0;
  335. }
  336. /*
  337.      *  And done
  338.      */
  339. release_sock(sk);
  340. return 0;
  341. }


4、应用层

当用户需要接收数据时,首先根据文件描述符inode得到socket结构和sock结构,然后从sock结构中指向的队列recieve_queue中读取数据包,将数据包COPY到用户空间缓冲区。数据就完整的从硬件中传输到用户空间。这样也完成了一次完整的从下到上的传输。

下篇:

在博文Linux内核--网络栈实现分析(二)--数据包的传递过程(上)中分析了数据包从网卡设备经过驱动链路层,网络层,传输层到应用层的过程。

本文就分析一下本机产生数据是如何通过传输层,网络层到达物理层的。

综述来说,数据流程图如下:

一、应用层

应用层可以通过系统调用或文件操作来调用内核函数,BSD层的sock_write()函数会调用INET层的inet_wirte()函数。

[cpp] view plaincopy
  1. /*
  2.  *  Write data to a socket. We verify that the user area ubuf..ubuf+size-1 is
  3.  *  readable by the user process.
  4.  */
  5. static int sock_write(struct inode *inode, struct file *file, char *ubuf, int size)
  6. {
  7. struct socket *sock;
  8. int err;
  9. if (!(sock = socki_lookup(inode)))
  10. {
  11. printk("NET: sock_write: can't find socket for inode!\n");
  12. return(-EBADF);
  13. }
  14. if (sock->flags & SO_ACCEPTCON)
  15. return(-EINVAL);
  16. if(size<0)
  17. return -EINVAL;
  18. if(size==0)
  19. return 0;
  20. if ((err=verify_area(VERIFY_READ,ubuf,size))<0)
  21. return err;
  22. return(sock->ops->write(sock, ubuf, size,(file->f_flags & O_NONBLOCK)));
  23. }


INET层会调用具体传输层协议的write函数,该函数是通过调用本层的inet_send()函数实现功能的,inet_send()函数的UDP协议对应的函数为udp_write()

[cpp] view plaincopy
  1. static int inet_send(struct socket *sock, void *ubuf, int size, int noblock,
  2. unsigned flags)
  3. {
  4. struct sock *sk = (struct sock *) sock->data;
  5. if (sk->shutdown & SEND_SHUTDOWN)
  6. {
  7. send_sig(SIGPIPE, current, 1);
  8. return(-EPIPE);
  9. }
  10. if(sk->err)
  11. return inet_error(sk);
  12. /* We may need to bind the socket. */
  13. if(inet_autobind(sk)!=0)
  14. return(-EAGAIN);
  15. return(sk->prot->write(sk, (unsigned char *) ubuf, size, noblock, flags));
  16. }
  17. static int inet_write(struct socket *sock, char *ubuf, int size, int noblock)
  18. {
  19. return inet_send(sock,ubuf,size,noblock,0);
  20. }

二、传输层

在传输层udp_write()函数调用本层的udp_sendto()函数完成功能。

[cpp] view plaincopy
  1. /*
  2.  *  In BSD SOCK_DGRAM a write is just like a send.
  3.  */
  4. static int udp_write(struct sock *sk, unsigned char *buff, int len, int noblock,
  5. unsigned flags)
  6. {
  7. return(udp_sendto(sk, buff, len, noblock, flags, NULL, 0));
  8. }


udp_send()函数完成sk_buff结构相应的设置和报头的填写后会调用udp_send()来发送数据。具体的实现过程后面会详细分析。

而在udp_send()函数中,最后会调用ip_queue_xmit()函数,将数据包下放的网络层。

下面是udp_prot定义:

[cpp] view plaincopy
  1. struct proto udp_prot = {
  2. sock_wmalloc,
  3. sock_rmalloc,
  4. sock_wfree,
  5. sock_rfree,
  6. sock_rspace,
  7. sock_wspace,
  8. udp_close,
  9. udp_read,
  10. udp_write,
  11. udp_sendto,
  12. udp_recvfrom,
  13. ip_build_header,
  14. udp_connect,
  15. NULL,
  16. ip_queue_xmit,
  17. NULL,
  18. NULL,
  19. NULL,
  20. udp_rcv,
  21. datagram_select,
  22. udp_ioctl,
  23. NULL,
  24. NULL,
  25. ip_setsockopt,
  26. ip_getsockopt,
  27. 128,
  28. 0,
  29. {NULL,},
  30. "UDP",
  31. 0, 0
  32. };

[cpp] view plaincopy
  1. static int udp_send(struct sock *sk, struct sockaddr_in *sin,
  2. unsigned char *from, int len, int rt)
  3. {
  4. struct sk_buff *skb;
  5. struct device *dev;
  6. struct udphdr *uh;
  7. unsigned char *buff;
  8. unsigned long saddr;
  9. int size, tmp;
  10. int ttl;
  11. /* 
  12.      *  Allocate an sk_buff copy of the packet.
  13.      */
  14. ........................
  15. /*
  16.      *  Now build the IP and MAC header. 
  17.      */
  18. ..........................
  19. /*
  20.      *  Fill in the UDP header. 
  21.      */
  22. ..............................
  23. /*
  24.      *  Copy the user data. 
  25.      */
  26. memcpy_fromfs(buff, from, len);
  27. /*
  28.      *  Set up the UDP checksum. 
  29.      */
  30. udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);
  31. /* 
  32.      *  Send the datagram to the interface. 
  33.      */
  34. udp_statistics.UdpOutDatagrams++;
  35. sk->prot->queue_xmit(sk, dev, skb, 1);
  36. return(len);
  37. }



三、网络层

在网络层,函数ip_queue_xmit()的功能是将数据包进行一系列复杂的操作,比如是检查数据包是否需要分片,是否是多播等一系列检查,最后调用dev_queue_xmit()函数发送数据。

[cpp] view plaincopy
  1. /*
  2.  * Queues a packet to be sent, and starts the transmitter
  3.  * if necessary.  if free = 1 then we free the block after
  4.  * transmit, otherwise we don't. If free==2 we not only
  5.  * free the block but also don't assign a new ip seq number.
  6.  * This routine also needs to put in the total length,
  7.  * and compute the checksum
  8.  */
  9. void ip_queue_xmit(struct sock *sk, struct device *dev,
  10. struct sk_buff *skb, int free)
  11. {
  12. struct iphdr *iph;
  13. unsigned char *ptr;
  14. /* Sanity check */
  15. ............
  16. /*
  17.      *  Do some book-keeping in the packet for later
  18.      */
  19. ...........
  20. /*
  21.      *  Find the IP header and set the length. This is bad
  22.      *  but once we get the skb data handling code in the
  23.      *  hardware will push its header sensibly and we will
  24.      *  set skb->ip_hdr to avoid this mess and the fixed
  25.      *  header length problem
  26.      */
  27. ..............
  28. /*
  29.      *  No reassigning numbers to fragments...
  30.      */
  31. if(free!=2)
  32. iph->id      = htons(ip_id_count++);
  33. else
  34. free=1;
  35. /* All buffers without an owner socket get freed */
  36. if (sk == NULL)
  37. free = 1;
  38. skb->free = free;
  39. /*
  40.      *  Do we need to fragment. Again this is inefficient.
  41.      *  We need to somehow lock the original buffer and use
  42.      *  bits of it.
  43.      */
  44. ................
  45. /*
  46.      *  Add an IP checksum
  47.      */
  48. ip_send_check(iph);
  49. /*
  50.      *  Print the frame when debugging
  51.      */
  52. /*
  53.      *  More debugging. You cannot queue a packet already on a list
  54.      *  Spot this and moan loudly.
  55.      */
  56. .......................
  57. /*
  58.      *  If a sender wishes the packet to remain unfreed
  59.      *  we add it to his send queue. This arguably belongs
  60.      *  in the TCP level since nobody else uses it. BUT
  61.      *  remember IPng might change all the rules.
  62.      */
  63. ......................
  64. /*
  65.      *  If the indicated interface is up and running, send the packet.
  66.      */
  67. ip_statistics.IpOutRequests++;
  68. .............................
  69. .............................
  70. if((dev->flags&IFF_BROADCAST) && iph->daddr==dev->pa_brdaddr && !(dev->flags&IFF_LOOPBACK))
  71. ip_loopback(dev,skb);
  72. if (dev->flags & IFF_UP)
  73. {
  74. /*
  75.          *  If we have an owner use its priority setting,
  76.          *  otherwise use NORMAL
  77.          */
  78. if (sk != NULL)
  79. {
  80. dev_queue_xmit(skb, dev, sk->priority);
  81. }
  82. else
  83. {
  84. dev_queue_xmit(skb, dev, SOPRI_NORMAL);
  85. }
  86. }
  87. else
  88. {
  89. ip_statistics.IpOutDiscards++;
  90. if (free)
  91. kfree_skb(skb, FREE_WRITE);
  92. }
  93. }

四、驱动层(链路层)

在函数中,函数调用会调用具体设备的发送函数来发送数据包

dev->hard_start_xmit(skb, dev);

具体设备的发送函数在网络初始化的时候已经设置了。

这里以8390网卡为例来说明驱动层的工作原理,在net/drivers/8390.c中函数ethdev_init()函数中设置如下:

[cpp] view plaincopy
  1. /* Initialize the rest of the 8390 device structure. */
  2. int ethdev_init(struct device *dev)
  3. {
  4. if (ei_debug > 1)
  5. printk(version);
  6. if (dev->priv == NULL) {//申请私有空间
  7. struct ei_device *ei_local;//8390网卡设备的结构体
  8. dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL);//申请内核内存空间
  9. memset(dev->priv, 0, sizeof(struct ei_device));
  10. ei_local = (struct ei_device *)dev->priv;
  11. #ifndef NO_PINGPONG
  12. ei_local->pingpong = 1;
  13. #endif
  14. }
  15. /* The open call may be overridden by the card-specific code. */
  16. if (dev->open == NULL)
  17. dev->open = &ei_open;//设备的打开函数
  18. /* We should have a dev->stop entry also. */
  19. dev->hard_start_xmit = &ei_start_xmit;//设备的发送函数,定义在8390.c中
  20. dev->get_stats   = get_stats;
  21. #ifdef HAVE_MULTICAST
  22. dev->set_multicast_list = &set_multicast_list;
  23. #endif
  24. ether_setup(dev);
  25. return 0;
  26. }


驱动中的发送函数比较复杂,和硬件关系紧密,这里不再详细分析。

这样就大体分析了下网络数据从应用层到物理层的数据通路,后面会详细分析。

相关文章:

C++/C++11中std::stack的使用

栈stack 是一个容器适配器(container adaptor)类型&#xff0c;被特别设计用来运行于LIFO(Last-in First-out&#xff0c;后进先出)场景&#xff0c;在该场景中&#xff0c;只能从容器末尾添加和删除元素&#xff0c;其定义在stack头文件中。stack默认基于std::deque实现&#…

团队前四次作业——个人总结

团队前四次作业——个人总结 描述 团队名称待就业六人组相关团队第四次作业答辩——反思与总结做了哪些事&#xff1f;工作量、完成度 作业负责工作量完成度团队队员展示创意合照后期1h95%项目选题报告编写创新和收益部分2h85%项目原型设计原型设计6h95%需求规格说明书功能需求…

吴甘沙:天外飞“厕”、红绿灯消失,未来无人驾驶将被重新定义 | AI ProCon 2019

2019 年9 月 5 日至 7 日&#xff0c;由新一代人工智能产业技术创新战略联盟&#xff08;AITISA&#xff09;指导&#xff0c;鹏城实验室、北京智源人工智能研究院支持&#xff0c;专业中文 IT 技术社区 CSDN 主办的 2019 中国 AI 开发者大会&#xff08;AI ProCon 2019&#x…

MySQL基础day03_数据的导入、导出-MySQL 5.6

MySQL基础day03_数据的导入、导出-MySQL 5.6注&#xff1a;把数据按照一定格式存放到文件里才能进行数据的导入。1&#xff0c;数据导入的条件把文件里的内容保存到数据的表里&#xff1b;把数据按照一定格式存放文件里&#xff1b;注&#xff1a;默认情况下&#xff0c;只有管…

“含光”剑出,谁与争锋?阿里重磅发布首颗AI芯片含光800

作者 | 夕颜、胡巍巍 编辑 | 唐小引 出品 | AI 科技大本营&#xff08;ID:rgznai100&#xff09; 9 月末的杭州气温适宜&#xff0c;宜出游&#xff0c;宜在湖边餐厅浅酌一杯清茶消闲。但在钱塘江水支流河畔的云栖小镇&#xff0c;却完全一副与闲适氛围不相称的热闹景象。 …

c++面试题中经常被面试官面试的小问题总结(一)(本篇偏向基础知识)

原文作者&#xff1a;aircraft 原文链接&#xff1a;https://www.cnblogs.com/DOMLX/p/10711810.html 1.类中的函数定义后加了一个const代表什么&#xff1f; 代表它将具备以下三个性质&#xff1a;1.const对象只能调用const成员函数。2.const对象的值不能被修改&#xff0c;在…

矩阵特征分解介绍及雅克比(Jacobi)方法实现特征值和特征向量的求解(C++/OpenCV/Eigen)

对角矩阵(diagonal matrix)&#xff1a;只在主对角线上含有非零元素&#xff0c;其它位置都是零&#xff0c;对角线上的元素可以为0或其它值。形式上&#xff0c;矩阵D是对角矩阵&#xff0c;当且仅当对于所有的i≠j, Di,j 0. 单位矩阵就是对角矩阵&#xff0c;对角元素全部是1…

Entity Framework CodeFirst数据迁移

原文:Entity Framework CodeFirst数据迁移前言 紧接着前面一篇博文Entity Framework CodeFirst尝试。 我们知道无论是“Database First”还是“Model First”当模型发生改变了都可以通过Visual Studio设计视图进行更新&#xff0c;那么对于Code First如何更新已有的模型呢&…

限时早鸟票 | 2019 中国大数据技术大会(BDTC)超豪华盛宴抢先看!

2019 年12月5-7 日&#xff0c;由中国计算机学会主办&#xff0c;CCF 大数据专家委员会承办&#xff0c;CSDN、中科天玑数据科技股份有限公司协办的 2019 中国大数据技术大会&#xff0c;将于北京长城饭店隆重举行。届时&#xff0c;超过百位技术专家及行业领袖将齐聚于此&…

Google AI 系统 DeepMind无法通过 高中数学

Google 旗下 DeepMind 团队让 AI 系统接受一项高中程度的数学测试&#xff0c;结果在 40 道题目中只答对了 14 题&#xff0c;甚至连「1111111」也算错了。说来难以置信&#xff0c;Google AI 系统能打败人类世界棋王&#xff0c;却无法通过高中程度的数学考试。上周&#xff0…

C++11中std::tuple的使用

std::tuple是类似pair的模板。每个pair的成员类型都不相同&#xff0c;但每个pair都恰好有两个成员。不同std::tuple类型的成员类型也不相同&#xff0c;但一个std::tuple可以有任意数量的成员。每个确定的std::tuple类型的成员数目是固定的&#xff0c;但一个std::tuple类型的…

PHP Countable接口

实现该接口可以使用count()方法来获取集合的总数转载于:https://www.cnblogs.com/xiaodo0/p/3611307.html

矩阵奇异值分解简介及C++/OpenCV/Eigen的三种实现

奇异值分解(singular value decomposition, SVD)&#xff1a;将矩阵分解为奇异向量(singular vector)和奇异值(singular value)。通过奇异值分解&#xff0c;我们会得到一些与特征分解相同类型的信息。然而&#xff0c;奇异值分解有更广泛的应用。每个实数矩阵都有一个奇异值分…

经典!工业界深度推荐系统与CTR预估必读的论文汇总

&#xff08;图片付费下载自视觉中国&#xff09;来源 | 深度传送门&#xff08;ID: gh_5faae7b50fc5&#xff09;导读&#xff1a;本文是“深度推荐系统”专栏的第十一篇文章&#xff0c;这个系列将介绍在深度学习的强力驱动下&#xff0c;给推荐系统工业界所带来的最前沿的变…

docker上传自己的镜像

https://blog.csdn.net/boonya/article/details/74906927 需要注意的就是命名规范 docker push 注册用户名/镜像名 tag命令修改为规范的镜像&#xff1a; docker tag boonya/tomcat-allow-remote boonyadocker/tomcat-allow-remote转载于:https://www.cnblogs.com/MC-Curry/p/1…

多个class相同的input标签 获取当前值!方法!

2019独角兽企业重金招聘Python工程师标准>>> var a $(this).prev( ".你的class" ).val(); 转载于:https://my.oschina.net/u/1169079/blog/210082

C++11中std::forward_list单向链表的使用

std::forward_list是在C11中引入的单向链表或叫正向列表。forward_list具有插入、删除表项速度快、消耗内存空间少的特点&#xff0c;但只能向前遍历。与其它序列容器(array、vector、deque)相比&#xff0c;forward_list在容器内任意位置的成员的插入、提取(extracting)、移动…

即学即用的30段Python实用代码

&#xff08;图片付费下载自视觉中国&#xff09;原标题 | 30 Helpful Python Snippets That You Can Learn in 30 Seconds or Less作 者 | Fatos Morina翻 译 | Pita & AI开发者Python是目前最流行的语言之一&#xff0c;它在数据科学、机器学习、web开发、脚本编写、自…

如何配置IntelliJ IDEA发布JavaEE项目?

一、以war的形式运行项目 步骤1 新建或者导入项目后&#xff0c;选择File菜单-》Project Structure...&#xff0c;如下图&#xff1a; 步骤2 配置项目类型&#xff0c;名字可以自定义&#xff1a; 说明&#xff1a;这里的Artifact如果没有配置好的话&#xff0c;配置Tomcat时没…

网络分布式软件bonic清除

近期&#xff0c;有一款网格计算软件&#xff0c;在很多服务器上进行了部署&#xff0c;利用cpu进行运算。虽然未构成安全隐患&#xff0c;但是比较消耗资源&#xff0c;影响设备正常运行。今天对设备彻底检查&#xff0c;发现了一个分布式计算软件boinc&#xff0c;他是利用网…

C++/C++11中std::list双向链表的使用

std::list是双向链表&#xff0c;是一个允许在序列中任何一处位置以常量耗时插入或删除元素且可以双向迭代的顺序容器。std::list中的每个元素保存了定位前一个元素及后一个元素的信息&#xff0c;允许在任何一处位置以常量耗时进行插入或删除操作&#xff0c;但不能进行直接随…

React组件设计之边界划分原则

简述 结合SOLID中的单一职责原则来进行组件的设计 Do one thing and do it well javaScript作为一个弱类型并在函数式和面对对象的领域里疯狂试探语言。SOLID原则可能与其他语言例如&#xff08;java&#xff09;的表现可能是不同的。不过作为软件开发领域通用的原则&#xff0…

阿里AI labs发布两大天猫精灵新品,将与平头哥共同定制智能语音芯片

作者 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;2019 年&#xff0c;去年刮起的一阵智能音箱热浪似乎稍微冷却下来&#xff0c;新产品不再像雨后春笋一样层出不穷&#xff0c;挺过市场洗礼的产品更是凤毛麟角&#xff0c;这些产品的性能、技术支持和体验基…

js 中文匹配正则

为什么80%的码农都做不了架构师&#xff1f;>>> /^[\u4e00-\u9fa5]{2,4}$/gi.test() 匹配中文正则 转载于:https://my.oschina.net/fedde/blog/211852

Caffe中对cifar10执行train操作

参考Caffe source中examples/cifar10目录下内容。cifar10是一个用于普通物体识别的数据集&#xff0c;cifar10被分为10类,分别为airplane、automobile、bird、cat、deer、dog、frog、horse、ship、truck&#xff0c;关于cifar10的详细介绍可以参考&#xff1a; http://blog.csd…

解决掉这些痛点和难点,让知识图谱不再是“噱头”

&#xff08;图片付费下载自视觉中国&#xff09;作者| 夕颜出品| AI科技大本营&#xff08;ID:rgznai100&#xff09;2012 年&#xff0c;谷歌正式提出知识图谱的概念&#xff0c;当时&#xff0c;研究人员的主要目的是用来优化搜索引擎技术。今年初&#xff0c;谷歌前员工&am…

mongodb使用常用语法,持续更新

设置快捷命令D:\mongodb4.0.8\bin>mongod --config "D:\mongodb4.0.8\mongo.conf" --auth --install --serviceName "MongoDB"mongodb配置文件#数据库路径dbpathD:\mongodb4.0.8\data\db#日志输出文件路径logpathD:\mongodb4.0.8\data\log\MongoDB.log#…

Android之NDK开发的简单实例

NDK全称为Native Development Kit&#xff0c;是本地开发工具集。在Android开发中&#xff0c;有时为了能更好的重用以前的C/C的代码&#xff0c;需要将这些代码编译成相应的so&#xff0c;然后通地JNI以供上层JAVA调用。当然&#xff0c;也有的是为了更高的保护性和安全性。下…

阿里披露AI完整布局,飞天AI平台首次亮相

作者 | 夕颜编辑 | 唐小引出品 | AI 科技大本营&#xff08;ID:rgznai100&#xff09;9 月 26 日上午&#xff0c;在云栖大会阿里云飞天智能主论坛上&#xff0c;年轻的阿里巴巴副总裁、阿里云智能计算平台事业部总经理、高级研究员贾扬清与其在 Facebook 的老同事—— Faceboo…

使用Caffe基于cifar10进行物体识别

在http://blog.csdn.net/fengbingchun/article/details/72953284中对cifar10进行train&#xff0c;这里通过train得到的model&#xff0c;对图像进行识别。cifar10数据集共包括10类&#xff0c;按照0到9的顺序依次为airplane(飞机)、automobile(轿车)、bird(鸟)、cat(猫)、deer…