自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

127.0.0.1 之本機(jī)網(wǎng)絡(luò)通信過(guò)程知多少 ?!

網(wǎng)絡(luò) 通信技術(shù)
今天咱們就把 127.0.0.1 的網(wǎng)絡(luò) IO 問(wèn)題搞搞清楚!為了方便討論,我把這個(gè)問(wèn)題拆分成兩問(wèn):127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過(guò)網(wǎng)卡嗎?和外網(wǎng)網(wǎng)絡(luò)通信相比,在內(nèi)核收發(fā)流程上有啥差別?

[[401933]]

本文轉(zhuǎn)載自微信公眾號(hào)「開發(fā)內(nèi)功修煉」,作者張彥飛allen。轉(zhuǎn)載本文請(qǐng)聯(lián)系開發(fā)內(nèi)功修煉公眾號(hào)。

大家好,我是飛哥!

我們拆解完了 Linux 網(wǎng)絡(luò)包的接收過(guò)程,也搞定了網(wǎng)絡(luò)包的發(fā)送過(guò)程。內(nèi)核收發(fā)網(wǎng)絡(luò)包整體流程就算是摸清楚了。

正在飛哥對(duì)這兩篇文章洋洋得意的時(shí)候,收到了一位讀者的發(fā)來(lái)的提問(wèn):“飛哥, 127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 是咋通信的”。額,,這題好像之前確實(shí)沒(méi)講到。。

現(xiàn)在本機(jī)網(wǎng)絡(luò) IO 應(yīng)用非常廣。在 php 中 一般 Nginx 和 php-fpm 是通過(guò) 127.0.0.1 來(lái)進(jìn)行通信的。在微服務(wù)中,由于 side car 模式的應(yīng)用,本機(jī)網(wǎng)絡(luò)請(qǐng)求更是越來(lái)越多。所以,我想如果能深度理解這個(gè)問(wèn)題在實(shí)踐中將非常的有意義,在此感謝@文武 的提出。

今天咱們就把 127.0.0.1 的網(wǎng)絡(luò) IO 問(wèn)題搞搞清楚!為了方便討論,我把這個(gè)問(wèn)題拆分成兩問(wèn):

127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過(guò)網(wǎng)卡嗎?

和外網(wǎng)網(wǎng)絡(luò)通信相比,在內(nèi)核收發(fā)流程上有啥差別?

鋪墊完畢,拆解正式開始!!

一、跨機(jī)網(wǎng)路通信過(guò)程

在開始講述本機(jī)通信過(guò)程之前,我們還是先回顧一下跨機(jī)網(wǎng)絡(luò)通信。

1.1 跨機(jī)數(shù)據(jù)發(fā)送

從 send 系統(tǒng)調(diào)用開始,直到網(wǎng)卡把數(shù)據(jù)發(fā)送出去,整體流程如下:

在這幅圖中,我們看到用戶數(shù)據(jù)被拷貝到內(nèi)核態(tài),然后經(jīng)過(guò)協(xié)議棧處理后進(jìn)入到了 RingBuffer 中。隨后網(wǎng)卡驅(qū)動(dòng)真正將數(shù)據(jù)發(fā)送了出去。當(dāng)發(fā)送完成的時(shí)候,是通過(guò)硬中斷來(lái)通知 CPU,然后清理 RingBuffer。

不過(guò)上面這幅圖并沒(méi)有很好地把內(nèi)核組件和源碼展示出來(lái),我們?cè)購(gòu)拇a的視角看一遍。

等網(wǎng)絡(luò)發(fā)送完畢之后。網(wǎng)卡在發(fā)送完畢的時(shí)候,會(huì)給 CPU 發(fā)送一個(gè)硬中斷來(lái)通知 CPU。收到這個(gè)硬中斷后會(huì)釋放 RingBuffer 中使用的內(nèi)存。

1.2 跨機(jī)數(shù)據(jù)接收

當(dāng)數(shù)據(jù)包到達(dá)另外一臺(tái)機(jī)器的時(shí)候,Linux 數(shù)據(jù)包的接收過(guò)程開始了。

當(dāng)網(wǎng)卡收到數(shù)據(jù)以后,CPU發(fā)起一個(gè)中斷,以通知 CPU 有數(shù)據(jù)到達(dá)。當(dāng)CPU收到中斷請(qǐng)求后,會(huì)去調(diào)用網(wǎng)絡(luò)驅(qū)動(dòng)注冊(cè)的中斷處理函數(shù),觸發(fā)軟中斷。ksoftirqd 檢測(cè)到有軟中斷請(qǐng)求到達(dá),開始輪詢收包,收到后交由各級(jí)協(xié)議棧處理。當(dāng)協(xié)議棧處理完并把數(shù)據(jù)放到接收隊(duì)列的之后,喚醒用戶進(jìn)程(假設(shè)是阻塞方式)。

我們?cè)偻瑯訌膬?nèi)核組件和源碼視角看一遍。

1.3 跨機(jī)網(wǎng)絡(luò)通信匯總

二、本機(jī)發(fā)送過(guò)程

在第一節(jié)中,我們看到了跨機(jī)時(shí)整個(gè)網(wǎng)絡(luò)發(fā)送過(guò)程(嫌第一節(jié)流程圖不過(guò)癮,想繼續(xù)看源碼了解細(xì)節(jié)的同學(xué)可以參考 拆解 Linux 網(wǎng)絡(luò)包發(fā)送過(guò)程) 。

在本機(jī)網(wǎng)絡(luò) IO 的過(guò)程中,流程會(huì)有一些差別。為了突出重點(diǎn),將不再介紹整體流程,而是只介紹和跨機(jī)邏輯不同的地方。有差異的地方總共有兩個(gè),分別是路由和驅(qū)動(dòng)程序。

2.1 網(wǎng)絡(luò)層路由

發(fā)送數(shù)據(jù)會(huì)進(jìn)入?yún)f(xié)議棧到網(wǎng)絡(luò)層的時(shí)候,網(wǎng)絡(luò)層入口函數(shù)是 ip_queue_xmit。在網(wǎng)絡(luò)層里會(huì)進(jìn)行路由選擇,路由選擇完畢后,再設(shè)置一些 IP 頭、進(jìn)行一些 netfilter 的過(guò)濾后,將包交給鄰居子系統(tǒng)。

對(duì)于本機(jī)網(wǎng)絡(luò) IO 來(lái)說(shuō),特殊之處在于在 local 路由表中就能找到路由項(xiàng),對(duì)應(yīng)的設(shè)備都將使用 loopback 網(wǎng)卡,也就是我們常見(jiàn)的 lo。

我們來(lái)詳細(xì)看看路由網(wǎng)絡(luò)層里這段路由相關(guān)工作過(guò)程。從網(wǎng)絡(luò)層入口函數(shù) ip_queue_xmit 看起。

  1. //file: net/ipv4/ip_output.c 
  2. int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl) 
  3.  //檢查 socket 中是否有緩存的路由表 
  4.  rt = (struct rtable *)__sk_dst_check(sk, 0); 
  5.  if (rt == NULL) { 
  6.   //沒(méi)有緩存則展開查找 
  7.   //則查找路由項(xiàng), 并緩存到 socket 中 
  8.   rt = ip_route_output_ports(...); 
  9.   sk_setup_caps(sk, &rt->dst); 
  10.  } 

查找路由項(xiàng)的函數(shù)是 ip_route_output_ports,它又依次調(diào)用到 ip_route_output_flow、__ip_route_output_key、fib_lookup。調(diào)用過(guò)程省略掉,直接看 fib_lookup 的關(guān)鍵代碼。

  1. //file:include/net/ip_fib.h 
  2. static inline int fib_lookup(struct net *net, const struct flowi4 *flp, 
  3.         struct fib_result *res) 
  4.  struct fib_table *table
  5.  
  6.  table = fib_get_table(net, RT_TABLE_LOCAL); 
  7.  if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 
  8.   return 0; 
  9.  
  10.  table = fib_get_table(net, RT_TABLE_MAIN); 
  11.  if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 
  12.   return 0; 
  13.  return -ENETUNREACH; 

在 fib_lookup 將會(huì)對(duì) local 和 main 兩個(gè)路由表展開查詢,并且是先查 local 后查詢 main。我們?cè)?Linux 上使用命令名可以查看到這兩個(gè)路由表, 這里只看 local 路由表(因?yàn)楸緳C(jī)網(wǎng)絡(luò) IO 查詢到這個(gè)表就終止了)。

  1. #ip route list table local 
  2. local 10.143.x.y dev eth0 proto kernel scope host src 10.143.x.y 
  3. local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 

從上述結(jié)果可以看出,對(duì)于目的是 127.0.0.1 的路由在 local 路由表中就能夠找到了。fib_lookup 工作完成,返回__ip_route_output_key 繼續(xù)。

  1. //file: net/ipv4/route.c 
  2. struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4) 
  3.  if (fib_lookup(net, fl4, &res)) { 
  4.  } 
  5.  if (res.type == RTN_LOCAL) { 
  6.   dev_out = net->loopback_dev; 
  7.   ... 
  8.  } 
  9.  
  10.  rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags); 
  11.  return rth; 

對(duì)于是本機(jī)的網(wǎng)絡(luò)請(qǐng)求,設(shè)備將全部都使用 net->loopback_dev,也就是 lo 虛擬網(wǎng)卡。

接下來(lái)的網(wǎng)絡(luò)層仍然和跨機(jī)網(wǎng)絡(luò) IO 一樣,最終會(huì)經(jīng)過(guò) ip_finish_output,最終進(jìn)入到 鄰居子系統(tǒng)的入口函數(shù) dst_neigh_output 中。

本機(jī)網(wǎng)絡(luò) IO 需要進(jìn)行 IP 分片嗎?因?yàn)楹驼5木W(wǎng)絡(luò)層處理過(guò)程一樣會(huì)經(jīng)過(guò) ip_finish_output 函數(shù)。在這個(gè)函數(shù)中,如果 skb 大于 MTU 的話,仍然會(huì)進(jìn)行分片。只不過(guò) lo 的 MTU 比 Ethernet 要大很多。通過(guò) ifconfig 命令就可以查到,普通網(wǎng)卡一般為 1500,而 lo 虛擬接口能有 65535。

在鄰居子系統(tǒng)函數(shù)中經(jīng)過(guò)處理,進(jìn)入到網(wǎng)絡(luò)設(shè)備子系統(tǒng)(入口函數(shù)是 dev_queue_xmit)。

2.2 網(wǎng)絡(luò)設(shè)備子系統(tǒng)

網(wǎng)絡(luò)設(shè)備子系統(tǒng)的入口函數(shù)是 dev_queue_xmit。簡(jiǎn)單回憶下之前講述跨機(jī)發(fā)送過(guò)程的時(shí)候,對(duì)于真的有隊(duì)列的物理設(shè)備,在該函數(shù)中進(jìn)行了一系列復(fù)雜的排隊(duì)等處理以后,才調(diào)用 dev_hard_start_xmit,從這個(gè)函數(shù) 再進(jìn)入驅(qū)動(dòng)程序來(lái)發(fā)送。在這個(gè)過(guò)程中,甚至還有可能會(huì)觸發(fā)軟中斷來(lái)進(jìn)行發(fā)送,流程如圖:

但是對(duì)于啟動(dòng)狀態(tài)的回環(huán)設(shè)備來(lái)說(shuō)(q->enqueue 判斷為 false),就簡(jiǎn)單多了。沒(méi)有隊(duì)列的問(wèn)題,直接進(jìn)入 dev_hard_start_xmit。接著進(jìn)入回環(huán)設(shè)備的“驅(qū)動(dòng)”里的發(fā)送回調(diào)函數(shù) loopback_xmit,將 skb “發(fā)送”出去。

我們來(lái)看下詳細(xì)的過(guò)程,從網(wǎng)絡(luò)設(shè)備子系統(tǒng)的入口 dev_queue_xmit 看起。

  1. //file: net/core/dev.c 
  2. int dev_queue_xmit(struct sk_buff *skb) 
  3.  q = rcu_dereference_bh(txq->qdisc); 
  4.  if (q->enqueue) {//回環(huán)設(shè)備這里為 false 
  5.   rc = __dev_xmit_skb(skb, q, dev, txq); 
  6.   goto out
  7.  } 
  8.  
  9.  //開始回環(huán)設(shè)備處理 
  10.  if (dev->flags & IFF_UP) { 
  11.   dev_hard_start_xmit(skb, dev, txq, ...); 
  12.   ... 
  13.  } 

在 dev_hard_start_xmit 中還是將調(diào)用設(shè)備驅(qū)動(dòng)的操作函數(shù)。

  1. //file: net/core/dev.c 
  2. int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, 
  3.    struct netdev_queue *txq) 
  4.  //獲取設(shè)備驅(qū)動(dòng)的回調(diào)函數(shù)集合 ops 
  5.  const struct net_device_ops *ops = dev->netdev_ops; 
  6.  
  7.  //調(diào)用驅(qū)動(dòng)的 ndo_start_xmit 來(lái)進(jìn)行發(fā)送 
  8.  rc = ops->ndo_start_xmit(skb, dev); 
  9.  ... 

2.3 “驅(qū)動(dòng)”程序

對(duì)于真實(shí)的 igb 網(wǎng)卡來(lái)說(shuō),它的驅(qū)動(dòng)代碼都在 drivers/net/ethernet/intel/igb/igb_main.c 文件里。順著這個(gè)路子,我找到了 loopback 設(shè)備的“驅(qū)動(dòng)”代碼位置:drivers/net/loopback.c。在 drivers/net/loopback.c

  1. //file:drivers/net/loopback.c 
  2. static const struct net_device_ops loopback_ops = { 
  3.  .ndo_init      = loopback_dev_init, 
  4.  .ndo_start_xmit= loopback_xmit, 
  5.  .ndo_get_stats64 = loopback_get_stats64, 
  6. }; 

所以對(duì) dev_hard_start_xmit 調(diào)用實(shí)際上執(zhí)行的是 loopback “驅(qū)動(dòng)” 里的 loopback_xmit。為什么我把“驅(qū)動(dòng)”加個(gè)引號(hào)呢,因?yàn)?loopback 是一個(gè)純軟件性質(zhì)的虛擬接口,并沒(méi)有真正意義上的驅(qū)動(dòng),它的工作流程大致如圖。

我們?cè)賮?lái)看詳細(xì)的代碼。

  1. //file:drivers/net/loopback.c 
  2. static netdev_tx_t loopback_xmit(struct sk_buff *skb, 
  3.      struct net_device *dev) 
  4.  //剝離掉和原 socket 的聯(lián)系 
  5.  skb_orphan(skb); 
  6.  
  7.  //調(diào)用netif_rx 
  8.  if (likely(netif_rx(skb) == NET_RX_SUCCESS)) { 
  9.  } 

在 skb_orphan 中先是把 skb 上的 socket 指針去掉了(剝離了出來(lái))。

注意,在本機(jī)網(wǎng)絡(luò) IO 發(fā)送的過(guò)程中,傳輸層下面的 skb 就不需要釋放了,直接給接收方傳過(guò)去就行了??偹闶鞘×艘稽c(diǎn)點(diǎn)開銷。不過(guò)可惜傳輸層的 skb 同樣節(jié)約不了,還是得頻繁地申請(qǐng)和釋放。

接著調(diào)用 netif_rx,在該方法中 中最終會(huì)執(zhí)行到 enqueue_to_backlog 中(netif_rx -> netif_rx_internal -> enqueue_to_backlog)。

  1. //file: net/core/dev.c 
  2. static int enqueue_to_backlog(struct sk_buff *skb, int cpu, 
  3.          unsigned int *qtail) 
  4.  sd = &per_cpu(softnet_data, cpu); 
  5.  
  6.  ... 
  7.  __skb_queue_tail(&sd->input_pkt_queue, skb); 
  8.  
  9.  ... 
  10.  ____napi_schedule(sd, &sd->backlog); 

在 enqueue_to_backlog 把要發(fā)送的 skb 插入 softnet_data->input_pkt_queue 隊(duì)列中并調(diào)用 ____napi_schedule 來(lái)觸發(fā)軟中斷。

  1. //file:net/core/dev.c 
  2. static inline void ____napi_schedule(struct softnet_data *sd, 
  3.          struct napi_struct *napi) 
  4.  list_add_tail(&napi->poll_list, &sd->poll_list); 
  5.  __raise_softirq_irqoff(NET_RX_SOFTIRQ); 

只有觸發(fā)完軟中斷,發(fā)送過(guò)程就算是完成了。

三、本機(jī)接收過(guò)程

在跨機(jī)的網(wǎng)絡(luò)包的接收過(guò)程中,需要經(jīng)過(guò)硬中斷,然后才能觸發(fā)軟中斷。而在本機(jī)的網(wǎng)絡(luò) IO 過(guò)程中,由于并不真的過(guò)網(wǎng)卡,所以網(wǎng)卡實(shí)際傳輸,硬中斷就都省去了。直接從軟中斷開始,經(jīng)過(guò) process_backlog 后送進(jìn)協(xié)議棧,大體過(guò)程如圖。

接下來(lái)我們?cè)倏锤敿?xì)一點(diǎn)的過(guò)程。

在軟中斷被觸發(fā)以后,會(huì)進(jìn)入到 NET_RX_SOFTIRQ 對(duì)應(yīng)的處理方法 net_rx_action 中(至于細(xì)節(jié)參見(jiàn) 圖解Linux網(wǎng)絡(luò)包接收過(guò)程 一文中的 3.2 小節(jié))。

  1. //file: net/core/dev.c 
  2. static void net_rx_action(struct softirq_action *h){ 
  3.  while (!list_empty(&sd->poll_list)) { 
  4.   work = n->poll(n, weight); 
  5.  } 

我們還記得對(duì)于 igb 網(wǎng)卡來(lái)說(shuō),poll 實(shí)際調(diào)用的是 igb_poll 函數(shù)。那么 loopback 網(wǎng)卡的 poll 函數(shù)是誰(shuí)呢?由于poll_list 里面是 struct softnet_data 對(duì)象,我們?cè)?net_dev_init 中找到了蛛絲馬跡。

  1. //file:net/core/dev.c 
  2. static int __init net_dev_init(void) 
  3.  for_each_possible_cpu(i) { 
  4.   sd->backlog.poll = process_backlog; 
  5.  } 

原來(lái)struct softnet_data 默認(rèn)的 poll 在初始化的時(shí)候設(shè)置成了 process_backlog 函數(shù),來(lái)看看它都干了啥。

  1. static int process_backlog(struct napi_struct *napi, int quota) 
  2.  while(){ 
  3.   while ((skb = __skb_dequeue(&sd->process_queue))) { 
  4.    __netif_receive_skb(skb); 
  5.   } 
  6.  
  7.   //skb_queue_splice_tail_init()函數(shù)用于將鏈表a連接到鏈表b上, 
  8.   //形成一個(gè)新的鏈表b,并將原來(lái)a的頭變成空鏈表。 
  9.   qlen = skb_queue_len(&sd->input_pkt_queue); 
  10.   if (qlen) 
  11.    skb_queue_splice_tail_init(&sd->input_pkt_queue, 
  12.          &sd->process_queue); 
  13.    
  14.  } 

這次先看對(duì) skb_queue_splice_tail_init 的調(diào)用。源碼就不看了,直接說(shuō)它的作用是把 sd->input_pkt_queue 里的 skb 鏈到 sd->process_queue 鏈表上去。

然后再看 __skb_dequeue, __skb_dequeue 是從 sd->process_queue 上取下來(lái)包來(lái)處理。這樣和前面發(fā)送過(guò)程的結(jié)尾處就對(duì)上了。發(fā)送過(guò)程是把包放到了 input_pkt_queue 隊(duì)列里,接收過(guò)程是在從這個(gè)隊(duì)列里取出 skb。

最后調(diào)用 __netif_receive_skb 將 skb(數(shù)據(jù)) 送往協(xié)議棧。在此之后的調(diào)用過(guò)程就和跨機(jī)網(wǎng)絡(luò) IO 又一致了。

送往協(xié)議棧的調(diào)用鏈?zhǔn)?__netif_receive_skb => __netif_receive_skb_core => deliver_skb 后 將數(shù)據(jù)包送入到 ip_rcv 中(詳情參見(jiàn)圖解Linux網(wǎng)絡(luò)包接收過(guò)程 一文中的 3.3 小節(jié))。

網(wǎng)絡(luò)再往后依次是傳輸層,最后喚醒用戶進(jìn)程,這里就不多展開了。

四、本機(jī)網(wǎng)絡(luò) IO 總結(jié)

我們來(lái)總結(jié)一下本機(jī)網(wǎng)絡(luò) IO 的內(nèi)核執(zhí)行流程。

回想下跨機(jī)網(wǎng)絡(luò) IO 的流程是

我們現(xiàn)在可以回顧下開篇的三個(gè)問(wèn)題啦。

1)127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過(guò)網(wǎng)卡嗎?

通過(guò)本文的敘述,我們確定地得出結(jié)論,不需要經(jīng)過(guò)網(wǎng)卡。即使了把網(wǎng)卡拔了本機(jī)網(wǎng)絡(luò)是否還可以正常使用的。

2)數(shù)據(jù)包在內(nèi)核中是個(gè)什么走向,和外網(wǎng)發(fā)送相比流程上有啥差別?

總的來(lái)說(shuō),本機(jī)網(wǎng)絡(luò) IO 和跨機(jī) IO 比較起來(lái),確實(shí)是節(jié)約了一些開銷。發(fā)送數(shù)據(jù)不需要進(jìn) RingBuffer 的驅(qū)動(dòng)隊(duì)列,直接把 skb 傳給接收協(xié)議棧(經(jīng)過(guò)軟中斷)。但是在內(nèi)核其它組件上,可是一點(diǎn)都沒(méi)少,系統(tǒng)調(diào)用、協(xié)議棧(傳輸層、網(wǎng)絡(luò)層等)、網(wǎng)絡(luò)設(shè)備子系統(tǒng)、鄰居子系統(tǒng)整個(gè)走了一個(gè)遍。連“驅(qū)動(dòng)”程序都走了(雖然對(duì)于回環(huán)設(shè)備來(lái)說(shuō)只是一個(gè)純軟件的虛擬出來(lái)的東東)。所以即使是本機(jī)網(wǎng)絡(luò) IO,也別誤以為沒(méi)啥開銷。

最后再提一下,業(yè)界有公司基于 ebpf 來(lái)加速 istio 架構(gòu)中 sidecar 代理和本地進(jìn)程之間的通信。通過(guò)引入 BPF,才算是繞開了內(nèi)核協(xié)議棧的開銷,原理如下。

參見(jiàn):https://cloud.tencent.com/developer/article/1671568

 

責(zé)任編輯:武曉燕 來(lái)源: 開發(fā)內(nèi)功修煉
相關(guān)推薦

2010-04-14 18:36:50

無(wú)線局域網(wǎng)絡(luò)

2022-01-19 17:19:14

區(qū)塊鏈攻擊加密算法

2024-08-06 10:07:15

2012-02-13 22:50:59

集群高可用

2020-11-12 08:52:16

Python

2019-04-29 10:26:49

TCP網(wǎng)絡(luò)協(xié)議網(wǎng)絡(luò)通信

2009-08-24 17:20:13

C#網(wǎng)絡(luò)通信TCP連接

2022-12-05 09:25:17

Kubernetes網(wǎng)絡(luò)模型網(wǎng)絡(luò)通信

2024-02-20 19:53:57

網(wǎng)絡(luò)通信協(xié)議

2009-10-16 08:48:08

2019-09-25 08:25:49

RPC網(wǎng)絡(luò)通信

2021-08-30 13:08:56

Kafka網(wǎng)絡(luò)通信

2010-04-22 16:10:48

Aix操作系統(tǒng)網(wǎng)絡(luò)通信

2010-06-09 11:31:55

網(wǎng)絡(luò)通信協(xié)議

2016-08-25 11:17:16

CaaS華為

2022-05-13 10:59:14

容器網(wǎng)絡(luò)通信

2017-07-14 10:51:37

性能優(yōu)化SQL性能分析

2010-08-16 09:15:57

2013-12-23 14:00:31

Windows 8.2Windows 8.1

2021-12-04 11:17:32

Javascript繼承編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)