udp 发送ip段报文接口ip_append_data

ip_append_data 函数主要用来udp 套接字以及raw套接字发送报文的接口。在tcp中发送ack 以及rest段的ip_send_unicast_reply也会调用;其主要作用是将收到的大数据报文拆分成多个等于小于MTU的SKB,为网络层实现ip分片做准备。

ip_append_data 在udp tcp raw 套接字以及icmp 都有被调用到,因此复制数据时有时只需要复制传输层负载部分;此函数并不传输数据,只是将数据放在大小合适的一个缓冲区中,让后续的函数可以借此构成一些片段(必要的话)并进行传输。所以次函数并不建立或操作任何ip报头。要把数据报文显示的传输,需要调用ip_push_pending_frames(会处理ip报头)才可以。如果L4层想要快速的发送报文,每次调用ip_append_data后,就需要调用ip_push_pending_frames 但是此函数是为了L4可以可以将尽可能的把多一点的数据存暂时放在缓冲区里面(直到PMTU大小)。然后一次传输,这样效率更高。

tcp stcp 做了很多准备工作,而使得ip层处理的相对少,但是左边的raw ip 以及udp 等把所有的分段工作都留给了ip层

  1. /*
  2. * ip_append_data() and ip_append_page() can make one large IP datagram
  3. * from many pieces of data. Each pieces will be holded on the socket
  4. * until ip_push_pending_frames() is called. Each piece can be a page
  5. * or non-page data.
  6. *
  7. * Not only UDP, other transport protocols - e.g. raw sockets - can use
  8. * this interface potentially.
  9. *
  10. * LATER: length must be adjusted by pad at tail, when it is required.
  11. @transhdrlen :L4报头大小length:要传输的数量(包含L4报头和有效载荷)
  12. @ipc:正确发送封包的必须信息rtp:路由缓存
  13. @sk:此次封包背后的套接字
  14. @from:指向邋L4层的有效载荷
  15. @flags:
  16. #define MSG_PROBE 0x10 /* Do not send. Only probe path f.e. for MTU
  17. #define MSG_DONTWAIT 0x40 /* Nonblocking io
  18. #define MSG_MORE 0x8000 /* Sender will send more 应用层使用
  19. 告诉L4层马上会有更多的其他传输 此标志会传输到L3
  20. */
  21. int ip_append_data(struct sock *sk, struct flowi4 *fl4,
  22. int getfrag(void *from, char *to, int offset, int len,
  23. int odd, struct sk_buff *skb),
  24. void *from, int length, int transhdrlen,
  25. struct ipcm_cookie *ipc, struct rtable **rtp,
  26. unsigned int flags)
  27. {
  28. struct inet_sock *inet = inet_sk(sk);
  29. int err;
  30. /*使用MSG_PROBE 并不会真正传输数据,而是进行路径MTU的探测*/
  31. if (flags&MSG_PROBE)
  32. return 0;
  33. /*
  34. * 如果传输控制块的输出队列为空,则需要为传输控制块设置一些临时
  35. * 信息。
  36. */
  37. if (skb_queue_empty(&sk->sk_write_queue)) {
  38. err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
  39. if (err)
  40. return err;
  41. } else {
  42. /*队列不为空,则使用上次的路由,IP选项,以及分片长度 */
  43. transhdrlen = 0;//传输层报头长度=0
  44. }
  45.  
  46. return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
  47. sk_page_frag(sk), getfrag,
  48. from, length, transhdrlen, flags);
  49. }

2、

_ip_append_data

  1. static int __ip_append_data(struct sock *sk,
  2. struct flowi4 *fl4,
  3. struct sk_buff_head *queue,
  4. struct inet_cork *cork,
  5. struct page_frag *pfrag,
  6. int getfrag(void *from, char *to, int offset,
  7. int len, int odd, struct sk_buff *skb),
  8. void *from, int length, int transhdrlen,
  9. unsigned int flags)
  10. {
  11. struct inet_sock *inet = inet_sk(sk);
  12. struct sk_buff *skb;
  13.  
  14. struct ip_options *opt = cork->opt;
  15. int hh_len;
  16. int exthdrlen;
  17. int mtu;
  18. int copy;
  19. int err;
  20. int offset = 0;
  21. unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
  22. int csummode = CHECKSUM_NONE;
  23. struct rtable *rt = (struct rtable *)cork->dst;
  24. u32 tskey = 0;
  25. /*这里skb有两种情况,如果队列为空,
  26. 则skb = NULL,否则为尾部skb的指针 */
  27. skb = skb_peek_tail(queue);
  28. /*参考《understand linux network internal》图21-10*/
  29. exthdrlen = !skb ? rt->dst.header_len : 0;
  30. mtu = cork->fragsize;
  31. if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
  32. sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
  33. tskey = sk->sk_tskey++;
  34.  
  35. hh_len = LL_RESERVED_SPACE(rt->dst.dev);/*链路层首部长度 */
  36. /*
  37. * IP数据包的数据需4字节对齐,为加速计算直接将IP数据包的数据根据当前
  38. * MTU 8字节对齐,然后重新得到用于分片的长度。
  39. */
  40. fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);/* IP首部(包括IP选项)长度 */
  41. maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;/* 最大IP首部长度,注意对齐 */
  42. maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu;
  43. /*
  44. * 如果输出的数据长度超出一个IP数据包能容纳的长度,则向输出该
  45. *数据报的 套接字发送EMSGSIZE出错信息。
  46. */
  47. if (cork->length + length > maxnonfragsize - fragheaderlen) {/*一个IP数据包最大大小不能超过64K */
  48. ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
  49. mtu - (opt ? opt->optlen : 0));
  50. return -EMSGSIZE;
  51. }
  52.  
  53. /*
  54. * transhdrlen > 0 means that this is the first fragment and we wish
  55. * it won't be fragmented in the future.
  56. */
  57. /*
  58. * 如果IP数据包没有分片,且输出网络设备支持硬件执行校验和,则设置
  59. * CHECKSUM_PARTIAL,表示由硬件来执行校验和。
  60. */
  61. if (transhdrlen &&
  62. length + fragheaderlen <= mtu &&
  63. rt->dst.dev->features & (NETIF_F_HW_CSUM | NETIF_F_IP_CSUM) &&
  64. !(flags & MSG_MORE) &&
  65. !exthdrlen)/*由硬件执行校验和计算 */
  66. csummode = CHECKSUM_PARTIAL;
  67.  
  68. cork->length += length;/*更新数据长度 */
  69. /* 对于UDP报文,新加的数据长度大于MTU,并且需要进行分片,则需要
  70. * 进行分片处理
  71. * 这里相当于《understand linux network internel》图21-11最左边的那条支线
  72. * 注意:这里需要加入判断skb是否为NULL*/
  73. if (((length > mtu) || (skb && skb_is_gso(skb))) &&
  74. (sk->sk_protocol == IPPROTO_UDP) &&
  75. (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
  76. (sk->sk_type == SOCK_DGRAM) && !sk->sk_no_check_tx) {
  77. err = ip_ufo_append_data(sk, queue, getfrag, from, length,
  78. hh_len, fragheaderlen, transhdrlen,
  79. maxfraglen, flags);
  80. if (err)
  81. goto error;
  82. return 0;
  83. }
  84.  
  85. /* So, what's going on in the loop below?
  86. *
  87. * We use calculated fragment length to generate chained skb,
  88. * each of segments is IP fragment ready for sending to network after
  89. * adding appropriate IP header.
  90. */
  91.  
  92. if (!skb)
  93. goto alloc_new_skb;
  94. /* 参照《understand linux network internel》图21-11
  95. * 主要可以分为4条支线,copy <= 0和copy > 0两种与是否设置NETIF_F_SG
  96. * 标志两种的组合。
  97. * 这几种组合可以结合《understand linux network internel》图21-3~图21-6
  98. * 来看。*/
  99. while (length > 0) {
  100. /* Check if the remaining data fits into current packet.
  101. * 检测待发送数据是否能全部复制到最后一个SKB的剩余空间中。如果可以,
  102. * 则说明是IP分片中的上一个分片,可以不用4字节对齐,否则需要4字节
  103. * 对齐,因此用8字节对齐后的MTU减去上一个SKB的数据长度,得到上一个
  104. * SKB的剩余空间大小,也就是本次复制数据的长度.
  105. * 当本次复制数据的长度copy小于等于0时,说明上一个SKB已经填满或
  106. * 空间不足8B,需要分配新的SKB。
  107. * 当copy大于0时,说明上一个SKB有剩余空间,数据可以复制到该SKB中去。
  108. *copy > 0 : 最后一个skb还有一些空余空间
  109. * copy = 0 : 最后一个skb已经被填满
  110. * copy < 0 : 有些数据必须从当前IP片段中删除移动到新的片段*/
  111. copy = mtu - skb->len;
  112. if (copy < length)
  113. copy = maxfraglen - skb->len;
  114. /*
  115. * 如果上一个SKB已经填满或空间不足8B,或者不存在上一个SKB,则将数据复制到
  116. * 新分配的SKB中去。
  117. */
  118. if (copy <= 0) {
  119. /*
  120. * 如果上一个SKB(通常是在调用ip_append_data()时,
  121. * 输出队列中最后一个SKB)中存在多余8字节对齐的MTU的数据,
  122. * 则这些数据需移动到当前SKB中,确保最后一个IP分片之外的
  123. * 数据能够4字节对齐,因此需计算移动到当前SKB的数据长度。
  124. */
  125. char *data;
  126. unsigned int datalen;
  127. unsigned int fraglen;
  128. unsigned int fraggap;
  129. unsigned int alloclen;
  130. struct sk_buff *skb_prev;
  131. alloc_new_skb:
  132. skb_prev = skb;
  133. if (skb_prev)/*需要计算从上一个skb中复制到新的新的skb中的数据长度 */
  134. fraggap = skb_prev->len - maxfraglen;/* 就是copy取反 */
  135. else
  136. fraggap = 0;
  137.  
  138. /*
  139. * If remaining data exceeds the mtu,
  140. * we know we need more fragment(s).
  141. */
  142. /*
  143. * 如果剩余数据的长度超过MTU,则需要更多的分片。
  144. */
  145. /*
  146. * 计算需要复制到新SKB中的数据长度。因为如果前一个SKB
  147. * 还能容纳数据,则有一部分数据会复制到前一个SKB中。
  148. */
  149. datalen = length + fraggap;
  150. /*
  151. * 如果剩余的数据一个分片不够容纳,则根据MTU重新计算本次
  152. * 可发送的数据长度。
  153. */
  154. if (datalen > mtu - fragheaderlen)
  155. datalen = maxfraglen - fragheaderlen;
  156. /*
  157. * 根据本次复制的数据长度以及IP首部长度,计算三层
  158. * 首部及其数据的总长度
  159. */
  160. fraglen = datalen + fragheaderlen;
  161. /*
  162. * 如果后续还有数据要输出且网络设备不支持聚合分散I/O,则将
  163. * MTU作为分配SKB的长度,使分片达到最长,为后续的数据
  164. * 预备空间。否则按数据的长度(包括IP首部)分配SKB的空间
  165. * 即可。
  166. */
  167. if ((flags & MSG_MORE) &&
  168. !(rt->dst.dev->features&NETIF_F_SG))
  169. alloclen = mtu;
  170. else
  171. alloclen = fraglen;
  172.  
  173. alloclen += exthdrlen;
  174.  
  175. /* The last fragment gets additional space at tail.
  176. * Note, with MSG_MORE we overallocate on fragments,
  177. * because we have no idea what fragment will be
  178. * the last.
  179. */
  180. /*
  181. * 如果是最后一个分片,且是根据目的路由启用IPsec的情况,
  182. * 则可能需要多分配一些空间来支持IPsec。
  183. */
  184. if (datalen == length + fraggap)
  185. alloclen += rt->dst.trailer_len;
  186. /*
  187. * 根据是否存在传输层首部,确定用何种方法分配SKB。
  188. * 如果存在传输层首部,则可以确定该分片为分片组中的
  189. * 第一个分片,因此在分配SKB时需要考虑更多的情况,如
  190. * 输出操作是否超时,传输层是否发生未处理的致命错误,
  191. * 发送通道是否已关闭等。当分片不是第一个分片时,
  192. * 则无需考虑以上情况
  193. */
  194. if (transhdrlen) {
  195. skb = sock_alloc_send_skb(sk,
  196. alloclen + hh_len + 15,
  197. (flags & MSG_DONTWAIT), &err);
  198. } else {
  199. skb = NULL;
  200. if (atomic_read(&sk->sk_wmem_alloc) <=
  201. 2 * sk->sk_sndbuf)
  202. skb = sock_wmalloc(sk,
  203. alloclen + hh_len + 15, 1,
  204. sk->sk_allocation);
  205. if (unlikely(!skb))
  206. err = -ENOBUFS;
  207. }
  208. if (!skb)
  209. goto error;
  210.  
  211. /*
  212. * Fill in the control structures
  213. */
  214. /*
  215. * 填充用于校验的控制信息
  216. */
  217. skb->ip_summed = csummode;//设置校验位
  218. skb->csum = 0;
  219. skb_reserve(skb, hh_len);
  220. /*
  221. * 为数据包预留用于存放二层首部、三层首部和数据的空间,
  222. * 并设置SKB中指向三层和四层的指针。
  223. */
  224. /* only the initial fragment is time stamped */
  225. skb_shinfo(skb)->tx_flags = cork->tx_flags;
  226. cork->tx_flags = 0;
  227. skb_shinfo(skb)->tskey = tskey;
  228. tskey = 0;
  229.  
  230. /*
  231. * Find where to start putting bytes.
  232. *///得到数据位置
  233. data = skb_put(skb, fraglen + exthdrlen); /*预留L2,L3首部空间 */
  234. skb_set_network_header(skb, exthdrlen);/*设置L3层的指针 */ //得到传输层的头部
  235. skb->transport_header = (skb->network_header +
  236. fragheaderlen);
  237. data += fragheaderlen + exthdrlen;
  238. /*
  239. * 如果上一个SKB的数据超过8字节对齐MTU,则将超出数据和
  240. * 传输层首部复制到当前SKB,重新计算校验和,并以8字节
  241. * 对齐MTU为长度截取上一个SKB的数据。
  242. */
  243. if (fraggap) { /*填充原来的skb尾部的空间 */
  244. skb->csum = skb_copy_and_csum_bits(
  245. skb_prev, maxfraglen,
  246. data + transhdrlen, fraggap, 0);
  247. skb_prev->csum = csum_sub(skb_prev->csum,
  248. skb->csum);
  249. data += fraggap;
  250. pskb_trim_unique(skb_prev, maxfraglen);
  251. }
  252.  
  253. copy = datalen - transhdrlen - fraggap;//得到所需要拷贝的数据的大小
  254. if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {//开始拷贝数据
  255. err = -EFAULT;
  256. kfree_skb(skb);
  257. goto error;
  258. }
  259.  
  260. offset += copy;/* 计算下次需要复制的数据长度*/
  261. length -= datalen - fraggap;
  262. transhdrlen = 0;
  263. exthdrlen = 0;
  264. csummode = CHECKSUM_NONE;
  265.  
  266. /*
  267. * Put the packet on the pending queue.
  268. */
  269. __skb_queue_tail(queue, skb); /*将skb添加的尾部 */
  270. continue;
  271. }
  272.  
  273. if (copy > length)
  274. copy = length;
  275.  
  276. if (!(rt->dst.dev->features&NETIF_F_SG)) {
  277. unsigned int off;
  278. /*不支持分散聚合,《understand
  279. linux netowrk internel》图21-11
  280. 中的分支,直接填充缓存*/
  281. off = skb->len;
  282. if (getfrag(from, skb_put(skb, copy),
  283. offset, copy, off, skb) < 0) {
  284. __skb_trim(skb, off);
  285. err = -EFAULT;
  286. goto error;
  287. }
  288. } else {
  289. int i = skb_shinfo(skb)->nr_frags;
  1. //如果支持S/G I/O则开始进行相应操作
  2. ///i为当前已存储的个数。
  1. err = -ENOMEM;
  2. if (!sk_page_frag_refill(sk, pfrag))
  3. goto error;
  4.  
  5. if (!skb_can_coalesce(skb, i, pfrag->page,
  6. pfrag->offset)) {/*已经分配了页面 */
  7. err = -EMSGSIZE;
  8. if (i == MAX_SKB_FRAGS)
  9. goto error;
  10. //当剩余的空间不够放将要拷贝的数据时,则先将剩余的空间拷贝完毕。然后下次循环再进行拷贝剩下的。
  11. __skb_fill_page_desc(skb, i, pfrag->page,
  12. pfrag->offset, 0);
  13. skb_shinfo(skb)->nr_frags = ++i;
  14. get_page(pfrag->page);
  15. }
  16. copy = min_t(int, copy, pfrag->size - pfrag->offset);
  17. if (getfrag(from,
  18. page_address(pfrag->page) + pfrag->offset,
  19. offset, copy, skb->len, skb) < 0)
  20. goto error_efault;
  21.  
  22. pfrag->offset += copy;
  23. skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
  24. skb->len += copy;
  25. skb->data_len += copy;
  26. skb->truesize += copy;
  27. atomic_add(copy, &sk->sk_wmem_alloc);
  28. }
  29. offset += copy;
  30. length -= copy;
  31. }
  32.  
  33. return 0;
  34.  
  35. error_efault:
  36. err = -EFAULT;
  37. error:
  38. cork->length -= length;
  39. IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
  40. return err;
  41. }
  42.  
  43. static int ip_setup_cork(struct sock *sk, struct inet_cork *cork,
  44. struct ipcm_cookie *ipc, struct rtable **rtp)
  45. {
  46. struct ip_options_rcu *opt;
  47. struct rtable *rt;
  48. /*
  49. * 如果传输控制块的输出队列为空,则需要为传输控制块设置一些临时
  50. * 信息。
  51. * 如果输出数据包中存在IP选项,则将IP选项信息复制到临时信息块中,
  52. * 并设置IPCORK_OPT,表示临时信息块中存在IP选项。由于存在IP选项,
  53. * 因此需要设置临时信息块中的目的地址,因为在IP选项中存在
  54. * 源路由选项。
  55. * 同时还设置了IP数据包分片大小,输出路由缓存、初始化当前发送
  56. * 数据包中数据的长度(如果启用了IPsec,则还要加上IPsec首部的
  57. * 长度)等。
  58. */
  59. /*
  60. * setup for corking.
  61. */
  62. opt = ipc->opt;
  63. if (opt) {
  64. if (!cork->opt) {
  65. cork->opt = kmalloc(sizeof(struct ip_options) + 40,
  66. sk->sk_allocation);
  67. if (unlikely(!cork->opt))
  68. return -ENOBUFS;
  69. }
  70. memcpy(cork->opt, &opt->opt, sizeof(struct ip_options) + opt->opt.optlen);
  71. cork->flags |= IPCORK_OPT;
  72. cork->addr = ipc->addr;
  73. }
  74. rt = *rtp;
  75. if (unlikely(!rt))
  76. return -EFAULT;
  77. /*
  78. * We steal reference to this route, caller should not release it
  79. */
  80. *rtp = NULL;
  81. cork->fragsize = ip_sk_use_pmtu(sk) ?
  82. dst_mtu(&rt->dst) : rt->dst.dev->mtu;
  83. cork->dst = &rt->dst;
  84. cork->length = 0;
  85. cork->ttl = ipc->ttl;
  86. cork->tos = ipc->tos;
  87. cork->priority = ipc->priority;
  88. cork->tx_flags = ipc->tx_flags;
  89.  
  90. return 0;
  91. }

ip_append_page只被udp使用。tcp不使用ip_append_data和ip_push_pending_frams是因为它把一些逻辑放到tcp_sendmsg来实现了。因此相似的,0拷贝接口,tcp不使用ip_append_page是因为他在do_tcp_sendpage中实现了相同的逻辑。

报文转发示意图

此图来自(https://blog.csdn.net/lee244868149/article/details/77823276)

IP 层收发报文简要剖析5--ip报文发送2的更多相关文章

  1. IP 层收发报文简要剖析3--ip输入报文分片重组

    在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { ...

  2. IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames

    L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点, ...

  3. IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver

    ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Pa ...

  4. IP 层收发报文简要剖析1-ip报文的输入

    ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并 ...

  5. IP 层收发报文简要剖析4--ip 报文发送

    无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ...

  6. IP 层收发报文简要剖析6--ip_forward 报文转发

    //在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * ...

  7. TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系 (转载)

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  8. 原 TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  9. Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

    本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函 ...

随机推荐

  1. java基础语句翻译

    public static void main(String[] args) { System.out.println("人生中的第一个代码-----"); } } package ...

  2. RLP序列化算法

    RLP RLP(Recursive Length Prefix)递归长度前缀编码,是由以太坊提出的序列化/反序列化标准,相比json格式体积更小,相比protobuf对多语言的支持更强. RLP将数据 ...

  3. 最近集训的图论(思路+实现)题目汇总(内容包含tarjan、分层图、拓扑、差分、奇怪的最短路):

    (集训模拟赛2)抢掠计划(tarjan强) 题目:给你n个点,m条边的图,每个点有点权,有一些点是"酒吧"点,终点只能在"酒吧",起点给定,路可以重复经过,但点 ...

  4. JVM系列【6】GC与调优5-日志分析

    JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 主要内容 分析PS.CMS.G1的回收日志,目标使大概能读懂GC日志. 测 ...

  5. HTML语义化罗嗦罗嗦

    CSS还未诞生之前,为了实现一些样式效果.设计师必须使用一些物理标签,例如font.b等.这样会造成页面中充满了为实现各种样式的标签,特别是使用table标签来实现一些特殊的布局,俗称为"标 ...

  6. windows搭建SVN服务MD版

    windows搭建SVN服务MD 1下载TortoiseSVN 官网下载 根据自己系统环境选择适合的版本 2 安装TortoiseSVN 双击运行程序 出现第一个小坑 原来是你的系统没有打 kb299 ...

  7. Linux文件系统和管理-2文件操作命令(下)

    移动和重命名文件 mv 命令可以实现文件或目录的移动和改名 剪切的效果 同一分区移动数据,速度很快:数据位置没有变化 不同分区移动数据,速度相对慢:数据位置发生了变化 格式 和cp基本一样 mv [O ...

  8. Linux命令的内部命令执行

    一个命令可能既是内部命令也是外部命令 因为内部命令优先级高,先执行内部命令 [04:21:44 root@C8[ ~]#type -a echo echo is a shell builtin ech ...

  9. if else 太多?看我用 Java 8 轻松干掉!

    之前我用 Java 8 写了一段逻辑,就是类似下面这样的例子: /* * 来源公众号:Java技术栈 */ if(xxxOrder != null){ if(xxxOrder.getXxxShippi ...

  10. Linux文件操作常用命令

    一.一些文件操作命令. 1.cd /home  进入"home目录" 2.cd ../ 返回上一级目录 3.cd -  返回上次所在的目录 4.pwd 显示工程路径 5.ll 显示 ...