不為人知的網(wǎng)絡(luò)編程(十三):深入操作系統(tǒng),徹底搞懂127.0.0.1本機(jī)網(wǎng)絡(luò)通信

本文作者張彥飛,原題“127.0.0.1 之本機(jī)網(wǎng)絡(luò)通信過(guò)程知多少 ”,首次發(fā)布于“開(kāi)發(fā)內(nèi)功修煉”,轉(zhuǎn)載請(qǐng)聯(lián)系作者。本次有改動(dòng)。
1、引言
繼《你真的了解127.0.0.1和0.0.0.0的區(qū)別?》之后,這是我整理的第2篇有關(guān)本機(jī)網(wǎng)絡(luò)方面的網(wǎng)絡(luò)編程基礎(chǔ)文章。
這次的文章由作者張彥飛原創(chuàng)分享,寫作本文的原因是現(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)題在各種網(wǎng)絡(luò)通信應(yīng)用的技術(shù)實(shí)踐中將非常的有意義。
今天咱們就把 127.0.0.1 本機(jī)網(wǎng)絡(luò)通信相關(guān)問(wèn)題搞搞清楚!
為了方便討論,我把這個(gè)問(wèn)題拆分成3問(wèn):
1)127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過(guò)網(wǎng)卡嗎?
2)和外網(wǎng)網(wǎng)絡(luò)通信相比,在內(nèi)核收發(fā)流程上有啥差別?
3)使用 127.0.0.1 能比 192.168.x 更快嗎?
上面這幾個(gè)問(wèn)題,相信包括常期混跡于即時(shí)通訊網(wǎng)的即時(shí)通訊老鳥(niǎo)們?cè)趦?nèi),都是看似很熟悉,但實(shí)則仍然無(wú)法透徹講清楚的話題。這次,我們就來(lái)徹底搞清楚!

(本文同步發(fā)布于:http://www.52im.net/thread-3600-1-1.html)
2、系列文章
本文是系列文章中的第13篇,本系列文章的大綱如下:
《不為人知的網(wǎng)絡(luò)編程(一):淺析TCP協(xié)議中的疑難雜癥(上篇)》
《不為人知的網(wǎng)絡(luò)編程(二):淺析TCP協(xié)議中的疑難雜癥(下篇)》
《不為人知的網(wǎng)絡(luò)編程(三):關(guān)閉TCP連接時(shí)為什么會(huì)TIME_WAIT、CLOSE_WAIT》
《不為人知的網(wǎng)絡(luò)編程(四):深入研究分析TCP的異常關(guān)閉》
《不為人知的網(wǎng)絡(luò)編程(五):UDP的連接性和負(fù)載均衡》
《不為人知的網(wǎng)絡(luò)編程(六):深入地理解UDP協(xié)議并用好它》
《不為人知的網(wǎng)絡(luò)編程(七):如何讓不可靠的UDP變的可靠?》
《不為人知的網(wǎng)絡(luò)編程(八):從數(shù)據(jù)傳輸層深度解密HTTP》
《不為人知的網(wǎng)絡(luò)編程(九):理論聯(lián)系實(shí)際,全方位深入理解DNS》
《不為人知的網(wǎng)絡(luò)編程(十):深入操作系統(tǒng),從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》
《不為人知的網(wǎng)絡(luò)編程(十一):從底層入手,深度分析TCP連接耗時(shí)的秘密》
《不為人知的網(wǎng)絡(luò)編程(十二):徹底搞懂TCP協(xié)議層的KeepAlive?;顧C(jī)制》
《不為人知的網(wǎng)絡(luò)編程(十三):深入操作系統(tǒng),徹底搞懂127.0.0.1本機(jī)網(wǎng)絡(luò)通信》(*?本文)
3、作為對(duì)比,先看看跨機(jī)網(wǎng)路通信
在開(kāi)始講述本機(jī)通信過(guò)程之前,我們先看看跨機(jī)網(wǎng)絡(luò)通信(以Linux系統(tǒng)內(nèi)核中的實(shí)現(xiàn)為例來(lái)講解)。
3.1 跨機(jī)數(shù)據(jù)發(fā)送
從 send 系統(tǒng)調(diào)用開(kāi)始,直到網(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)存。

3.2 跨機(jī)數(shù)據(jù)接收
當(dāng)數(shù)據(jù)包到達(dá)另外一臺(tái)機(jī)器的時(shí)候,Linux 數(shù)據(jù)包的接收過(guò)程開(kāi)始了(更詳細(xì)的講解可以看看《深入操作系統(tǒng),從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》)。

▲ 上圖引用自《深入操作系統(tǒng),從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》
當(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á),開(kāi)始輪詢收包,收到后交由各級(jí)協(xié)議棧處理。當(dāng)協(xié)議棧處理完并把數(shù)據(jù)放到接收隊(duì)列的之后,喚醒用戶進(jìn)程(假設(shè)是阻塞方式)。
我們?cè)偻瑯訌膬?nèi)核組件和源碼視角看一遍。

3.3 跨機(jī)網(wǎng)絡(luò)通信匯總
關(guān)于跨機(jī)網(wǎng)絡(luò)通信的理解,可以通俗地用下面這張圖來(lái)總結(jié)一下:

4、本機(jī)網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送過(guò)程
在上一節(jié)中,我們看到了跨機(jī)時(shí)整個(gè)網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送過(guò)程 。
在本機(jī)網(wǎng)絡(luò) IO 的過(guò)程中,流程會(huì)有一些差別。為了突出重點(diǎn),本節(jié)將不再介紹整體流程,而是只介紹和跨機(jī)邏輯不同的地方。有差異的地方總共有兩個(gè),分別是路由和驅(qū)動(dòng)程序。
4.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?看起。
//file: net/ipv4/ip_output.c
intip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
?//檢查 socket 中是否有緩存的路由表
?rt = (struct rtable *)__sk_dst_check(sk, 0);
?if(rt == NULL) {
??//沒(méi)有緩存則展開(kāi)查找
??//則查找路由項(xiàng), 并緩存到 socket 中
??rt = ip_route_output_ports(...);
??sk_setup_caps(sk, &rt->dst);
?}
查找路由項(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)鍵代碼。
//file:include/net/ip_fib.h
static inline int fib_lookup(struct net *net, const struct flowi4 *flp, struct fib_result *res)
{
?struct fib_table *table;
?
?table = fib_get_table(net, RT_TABLE_LOCAL);
?if(!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
??return 0;
?
?table = fib_get_table(net, RT_TABLE_MAIN);
?if(!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
??return 0;
?return -ENETUNREACH;
}
在?fib_lookup?將會(huì)對(duì)?local?和?main?兩個(gè)路由表展開(kāi)查詢,并且是先查 local 后查詢?main。我們?cè)?Linux 上使用命令名可以查看到這兩個(gè)路由表, 這里只看 local 路由表(因?yàn)楸緳C(jī)網(wǎng)絡(luò) IO 查詢到這個(gè)表就終止了)。
#ip route list table local
local10.143.x.y dev eth0 proto kernel scope host src 10.143.x.y
local127.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ù)。
//file: net/ipv4/route.c
struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
?if(fib_lookup(net, fl4, &res)) {
?}
?if(res.type == RTN_LOCAL) {
??dev_out = net->loopback_dev;
??...
?}
?
?rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags);
?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)。
4.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 看起。
//file: net/core/dev.c
int dev_queue_xmit(struct sk_buff *skb)
{
?q = rcu_dereference_bh(txq->qdisc);
?if(q->enqueue) {//回環(huán)設(shè)備這里為 false
??rc = __dev_xmit_skb(skb, q, dev, txq);
??goto out;
?}
?
?//開(kāi)始回環(huán)設(shè)備處理
?if(dev->flags & IFF_UP) {
??dev_hard_start_xmit(skb, dev, txq, ...);
??...
?}
}
在 dev_hard_start_xmit 中還是將調(diào)用設(shè)備驅(qū)動(dòng)的操作函數(shù)。
//file: net/core/dev.c
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)
{
?//獲取設(shè)備驅(qū)動(dòng)的回調(diào)函數(shù)集合 ops
?const struct net_device_ops *ops = dev->netdev_ops;
?
?//調(diào)用驅(qū)動(dòng)的 ndo_start_xmit 來(lái)進(jìn)行發(fā)送
?rc = ops->ndo_start_xmit(skb, dev);
?...
}
4.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:
//file:drivers/net/loopback.c
static const struct net_device_ops loopback_ops = {
?.ndo_init????? = loopback_dev_init,
?.ndo_start_xmit = loopback_xmit,
?.ndo_get_stats64 = loopback_get_stats64,
};
所以對(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ì)的代碼。
//file:drivers/net/loopback.c
static netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{
?//剝離掉和原 socket 的聯(lián)系
?skb_orphan(skb);
?
?//調(diào)用netif_rx
?if(likely(netif_rx(skb) == NET_RX_SUCCESS)) {
?}
}
在?skb_orphan?中先是把 skb 上的 socket 指針去掉了(剝離了出來(lái))。
注意:在本機(jī)網(wǎng)絡(luò) IO 發(fā)送的過(guò)程中,傳輸層下面的 skb 就不需要釋放了,直接給接收方傳過(guò)去就行了??偹闶鞘×艘稽c(diǎn)點(diǎn)開(kāi)銷。不過(guò)可惜傳輸層的 skb 同樣節(jié)約不了,還是得頻繁地申請(qǐng)和釋放。
接著調(diào)用?netif_rx,在該方法中 中最終會(huì)執(zhí)行到 enqueue_to_backlog 中(netif_rx -> netif_rx_internal -> enqueue_to_backlog)。
//file: net/core/dev.c
static int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail)
{
?sd = &per_cpu(softnet_data, cpu);
?...
?__skb_queue_tail(&sd->input_pkt_queue, skb);
?...
?____napi_schedule(sd, &sd->backlog);
在?enqueue_to_backlog?把要發(fā)送的 skb 插入?softnet_data->input_pkt_queue?隊(duì)列中并調(diào)用?____napi_schedule?來(lái)觸發(fā)軟中斷。
//file:net/core/dev.c
static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi)
{
?list_add_tail(&napi->poll_list, &sd->poll_list);
?__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
只有觸發(fā)完軟中斷,發(fā)送過(guò)程就算是完成了。
5、本機(jī)網(wǎng)絡(luò)數(shù)據(jù)的接收過(guò)程
5.1 主要過(guò)程
在跨機(jī)的網(wǎng)絡(luò)包的接收過(guò)程中,需要經(jīng)過(guò)硬中斷,然后才能觸發(fā)軟中斷。
而在本機(jī)的網(wǎng)絡(luò) IO 過(guò)程中,由于并不真的過(guò)網(wǎng)卡,所以網(wǎng)卡實(shí)際傳輸,硬中斷就都省去了。直接從軟中斷開(kāi)始,經(jīng)過(guò)?process_backlog?后送進(jìn)協(xié)議棧,大體過(guò)程如下圖。

5.2 詳細(xì)過(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)《深入操作系統(tǒng),從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》一文中的?4.2 小節(jié))。
//file: net/core/dev.c
static void net_rx_action(struct softirq_action *h){
?while(!list_empty(&sd->poll_list)) {
??work = n->poll(n, weight);
?}
}
我們還記得對(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 中找到了蛛絲馬跡。
//file:net/core/dev.c
static int __init net_dev_init(void)
{
?for_each_possible_cpu(i) {
??sd->backlog.poll = process_backlog;
?}
}
原來(lái)struct?softnet_data?默認(rèn)的 poll 在初始化的時(shí)候設(shè)置成了?process_backlog?函數(shù),來(lái)看看它都干了啥。
static int process_backlog(struct napi_struct *napi, int quota)
{
?while(){
??while((skb = __skb_dequeue(&sd->process_queue))) {
???__netif_receive_skb(skb);
??}
??//skb_queue_splice_tail_init()函數(shù)用于將鏈表a連接到鏈表b上,
??//形成一個(gè)新的鏈表b,并將原來(lái)a的頭變成空鏈表。
??qlen = skb_queue_len(&sd->input_pkt_queue);
??if(qlen)
???skb_queue_splice_tail_init(&sd->input_pkt_queue, &sd->process_queue);
?}
}
這次先看對(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)《深入操作系統(tǒng),從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》一文中的 4.3 小節(jié))。
網(wǎng)絡(luò)再往后依次是傳輸層,最后喚醒用戶進(jìn)程,這里就不多展開(kāi)了。
6、本機(jī)網(wǎng)絡(luò)通信過(guò)程小結(jié)
我們來(lái)總結(jié)一下本機(jī)網(wǎng)絡(luò)通信的內(nèi)核執(zhí)行流程:

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

好了,回到正題,我們終于可以在單獨(dú)的章節(jié)里回答開(kāi)篇的三個(gè)問(wèn)題啦。
7、開(kāi)篇三個(gè)問(wèn)題的答案
1)問(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)問(wèn)題2:數(shù)據(jù)包在內(nèi)核中是個(gè)什么走向,和外網(wǎng)發(fā)送相比流程上有啥差別?
總的來(lái)說(shuō),本機(jī)網(wǎng)絡(luò) IO 和跨機(jī) IO 比較起來(lái),確實(shí)是節(jié)約了一些開(kāi)銷。發(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)啥開(kāi)銷。
3)問(wèn)題3:使用 127.0.0.1 能比 192.168.x 更快嗎?
先說(shuō)結(jié)論:我認(rèn)為這兩種使用方法在性能上沒(méi)有啥差別。
我覺(jué)得有相當(dāng)大一部分人都會(huì)認(rèn)為訪問(wèn)本機(jī) Server 的話,用 127.0.0.1 更快。原因是直覺(jué)上認(rèn)為訪問(wèn) IP 就會(huì)經(jīng)過(guò)網(wǎng)卡。
其實(shí)內(nèi)核知道本機(jī)上所有的 IP,只要發(fā)現(xiàn)目的地址是本機(jī) IP 就可以全走 loopback 回環(huán)設(shè)備了。本機(jī)其它 IP 和 127.0.0.1 一樣,也是不用過(guò)物理網(wǎng)卡的,所以訪問(wèn)它們性能開(kāi)銷基本一樣!
附錄:更多網(wǎng)絡(luò)編程系列文章
如果您覺(jué)得本系列文章過(guò)于專業(yè),您可先閱讀《網(wǎng)絡(luò)編程懶人入門》系列文章,該系列目錄如下:
《網(wǎng)絡(luò)編程懶人入門(一):快速理解網(wǎng)絡(luò)通信協(xié)議(上篇)》
《網(wǎng)絡(luò)編程懶人入門(二):快速理解網(wǎng)絡(luò)通信協(xié)議(下篇)》
《網(wǎng)絡(luò)編程懶人入門(三):快速理解TCP協(xié)議一篇就夠》
《網(wǎng)絡(luò)編程懶人入門(四):快速理解TCP和UDP的差異》
《網(wǎng)絡(luò)編程懶人入門(五):快速理解為什么說(shuō)UDP有時(shí)比TCP更有優(yōu)勢(shì)》
《網(wǎng)絡(luò)編程懶人入門(六):史上最通俗的集線器、交換機(jī)、路由器功能原理入門》
《網(wǎng)絡(luò)編程懶人入門(七):深入淺出,全面理解HTTP協(xié)議》
《網(wǎng)絡(luò)編程懶人入門(八):手把手教你寫基于TCP的Socket長(zhǎng)連接》
《網(wǎng)絡(luò)編程懶人入門(九):通俗講解,有了IP地址,為何還要用MAC地址?》
《網(wǎng)絡(luò)編程懶人入門(十):一泡尿的時(shí)間,快速讀懂QUIC協(xié)議》
《網(wǎng)絡(luò)編程懶人入門(十一):一文讀懂什么是IPv6》
《網(wǎng)絡(luò)編程懶人入門(十二):快速讀懂Http/3協(xié)議,一篇就夠!》
本站的《腦殘式網(wǎng)絡(luò)編程入門》也適合入門學(xué)習(xí),本系列大綱如下:
《腦殘式網(wǎng)絡(luò)編程入門(一):跟著動(dòng)畫(huà)來(lái)學(xué)TCP三次握手和四次揮手》
《腦殘式網(wǎng)絡(luò)編程入門(二):我們?cè)谧x寫Socket時(shí),究竟在讀寫什么?》
《腦殘式網(wǎng)絡(luò)編程入門(三):HTTP協(xié)議必知必會(huì)的一些知識(shí)》
《腦殘式網(wǎng)絡(luò)編程入門(四):快速理解HTTP/2的服務(wù)器推送(Server Push)》
《腦殘式網(wǎng)絡(luò)編程入門(五):每天都在用的Ping命令,它到底是什么?》
《腦殘式網(wǎng)絡(luò)編程入門(六):什么是公網(wǎng)IP和內(nèi)網(wǎng)IP?NAT轉(zhuǎn)換又是什么鬼?》
《腦殘式網(wǎng)絡(luò)編程入門(七):面視必備,史上最通俗計(jì)算機(jī)網(wǎng)絡(luò)分層詳解》
《腦殘式網(wǎng)絡(luò)編程入門(八):你真的了解127.0.0.1和0.0.0.0的區(qū)別?》
《腦殘式網(wǎng)絡(luò)編程入門(九):面試必考,史上最通俗大小端字節(jié)序詳解》
以下資料來(lái)自《TCP/IP詳解》,入門者必讀:
《TCP/IP詳解?-?第11章·UDP:用戶數(shù)據(jù)報(bào)協(xié)議》
《TCP/IP詳解?-?第17章·TCP:傳輸控制協(xié)議》
《TCP/IP詳解?-?第18章·TCP連接的建立與終止》
《TCP/IP詳解?-?第21章·TCP的超時(shí)與重傳》
以下系列適合服務(wù)端網(wǎng)絡(luò)編程開(kāi)發(fā)者閱讀:
《高性能網(wǎng)絡(luò)編程(一):?jiǎn)闻_(tái)服務(wù)器并發(fā)TCP連接數(shù)到底可以有多少》
《高性能網(wǎng)絡(luò)編程(二):上一個(gè)10年,著名的C10K并發(fā)連接問(wèn)題》
《高性能網(wǎng)絡(luò)編程(三):下一個(gè)10年,是時(shí)候考慮C10M并發(fā)問(wèn)題了》
《高性能網(wǎng)絡(luò)編程(四):從C10K到C10M高性能網(wǎng)絡(luò)應(yīng)用的理論探索》
《高性能網(wǎng)絡(luò)編程(五):一文讀懂高性能網(wǎng)絡(luò)編程中的I/O模型》
《高性能網(wǎng)絡(luò)編程(六):一文讀懂高性能網(wǎng)絡(luò)編程中的線程模型》
《高性能網(wǎng)絡(luò)編程(七):到底什么是高并發(fā)?一文即懂!》
《從根上理解高性能、高并發(fā)(一):深入計(jì)算機(jī)底層,理解線程與線程池》
《從根上理解高性能、高并發(fā)(二):深入操作系統(tǒng),理解I/O與零拷貝技術(shù)》
《從根上理解高性能、高并發(fā)(三):深入操作系統(tǒng),徹底理解I/O多路復(fù)用》
《從根上理解高性能、高并發(fā)(四):深入操作系統(tǒng),徹底理解同步與異步》
《從根上理解高性能、高并發(fā)(五):深入操作系統(tǒng),理解高并發(fā)中的協(xié)程》
《從根上理解高性能、高并發(fā)(六):通俗易懂,高性能服務(wù)器到底是如何實(shí)現(xiàn)的》
《從根上理解高性能、高并發(fā)(七):深入操作系統(tǒng),一文讀懂進(jìn)程、線程、協(xié)程》
以下系列適合移動(dòng)端資深網(wǎng)絡(luò)通信開(kāi)發(fā)者閱讀:
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(一):通信交換技術(shù)的百年發(fā)展史(上)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(二):通信交換技術(shù)的百年發(fā)展史(下)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(三):國(guó)人通信方式的百年變遷》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(四):手機(jī)的演進(jìn),史上最全移動(dòng)終端發(fā)展史》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(五):1G到5G,30年移動(dòng)通信技術(shù)演進(jìn)史》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(六):移動(dòng)終端的接頭人——“基站”技術(shù)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(七):移動(dòng)終端的千里馬——“電磁波”》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(八):零基礎(chǔ),史上最強(qiáng)“天線”原理掃盲》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(九):無(wú)線通信網(wǎng)絡(luò)的中樞——“核心網(wǎng)”》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十):零基礎(chǔ),史上最強(qiáng)5G技術(shù)掃盲》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十一):為什么WiFi信號(hào)差?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十二):上網(wǎng)卡頓?網(wǎng)絡(luò)掉線?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十三):為什么手機(jī)信號(hào)差?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十四):高鐵上無(wú)線上網(wǎng)有多難?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(十五):理解定位技術(shù),一篇就夠》
本文已同步發(fā)布于“即時(shí)通訊技術(shù)圈”公眾號(hào)。

▲ 本文在公眾號(hào)上的鏈接是:點(diǎn)此進(jìn)入。同步發(fā)布鏈接是:http://www.52im.net/thread-3600-1-1.html