nginx与Apache的涉及的计算机原理说明
Nginx才短短幾年,就拿下了web服務(wù)器大筆江山,眾所周知,Nginx在處理大并發(fā)靜態(tài)請求方面,效率明顯高于httpd,甚至能輕松解決C10K問題。下面我們就來聊聊Web服務(wù)器背后的一些原理。
注:本人不太擅長寫原理性的文章,因此本文大部分內(nèi)容摘自自 “Share your knowledge …” (孤城同學(xué))的博客
一、進(jìn)程、線程?
進(jìn)程是具有一定獨立功能的,在計算機中已經(jīng)運行的程序的實體。在早期系統(tǒng)中(如linux 2.4以前),進(jìn)程是基本運作單位,在支持線程的系統(tǒng)中(如windows,linux2.6)中,線程才是基本的運作單位,而進(jìn)程只是線程的容器。程序本身只是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序(那些指令和數(shù)據(jù))的真正運行實例。若干進(jìn)程有可能與同一個程序相關(guān)系,且每個進(jìn)程皆可以同步(循序)或異步(平行)的方式獨立運行。現(xiàn)代計算機系統(tǒng)可在同一段時間內(nèi)以進(jìn)程的形式將多個程序加載到存儲器中,并借由時間共享(或稱時分復(fù)用),以在一個處理器上表現(xiàn)出同時(平行性)運行的感覺。同樣的,使用多線程技術(shù)(多線程即每一個線程都代表一個進(jìn)程內(nèi)的一個獨立執(zhí)行上下文)的操作系統(tǒng)或計算機架構(gòu),同樣程序的平行線程,可在多 CPU 主機或網(wǎng)絡(luò)上真正同時運行(在不同的CPU上)。
二、常見Web服務(wù)方式
2.1 三種工作模型比較:
Web服務(wù)器要為用戶提供服務(wù),必須以某種方式,工作在某個套接字上。一般Web服務(wù)器在處理用戶請求是,一般有如下三種方式可選擇:多進(jìn)程方式、多線程方式、異步方式。
多進(jìn)程方式:為每個請求啟動一個進(jìn)程來處理。由于在操作系統(tǒng)中,生成進(jìn)程、銷毀進(jìn)程、進(jìn)程間切換都很消耗CPU和內(nèi)存,當(dāng)負(fù)載高是,性能會明顯降低。
優(yōu)點: 穩(wěn)定性!由于采用獨立進(jìn)程處理獨立請求,而進(jìn)程之間是獨立的,單個進(jìn)程問題不會影響其他進(jìn)程,因此穩(wěn)定性最好。
缺點: 資源占用!當(dāng)請求過大時,需要大量的進(jìn)程處理請求,進(jìn)程生成、切換開銷很大,而且進(jìn)程間資源是獨立的,造成內(nèi)存重復(fù)利用。
多線程方式:一個進(jìn)程中用多個線程處理用戶請求。由于線程開銷明顯小于進(jìn)程,而且部分資源還可以共享,因此效率較高。
優(yōu)點:開銷較小!線程間部分?jǐn)?shù)據(jù)是共享的,且線程生成與線程間的切換所需資源開銷比進(jìn)程間切換小得多。
缺點:穩(wěn)定性!線程切換過快可能造成線程抖動,且線程過多會造成服務(wù)器不穩(wěn)定。
異步方式:使用非阻塞方式處理請求,是三種方式中開銷最小的。但異步方式雖然效率高,但要求也高,因為多任務(wù)之間的調(diào)度如果出現(xiàn)問題,就可能出現(xiàn)整體故障,因此使用異步工作的,一般是一些功能相對簡單,但卻符合服務(wù)器任務(wù)調(diào)度、且代碼中沒有影響調(diào)度的錯誤代碼存在的程序。
優(yōu)點:性能最好!一個進(jìn)程或線程處理多個請求,不需要額外開銷,性能最好,資源占用最低。
缺點:穩(wěn)定性!某個進(jìn)程或線程出錯,可能導(dǎo)致大量請求無法處理,甚至導(dǎo)致整個服務(wù)宕機。
2.2 一個Web請求的處理過程:
即進(jìn)程向內(nèi)核進(jìn)行系統(tǒng)調(diào)用申請IO,內(nèi)核將資源從IO調(diào)度到內(nèi)核的buffer中(wait階段),內(nèi)核還需將數(shù)據(jù)從內(nèi)核buffer中復(fù)制(copy階段)到web服務(wù)器進(jìn)程所在的用戶空間,才算完成一次IO調(diào)度。這幾個階段都是需要時間的。根據(jù)wait和copy階段的處理等待的機制不同,可將I/O動作分為如下五種模式:
阻塞I/O
非阻塞I/O
I/O復(fù)用(select和poll)
信號(事件)驅(qū)動I/O(SIGIO)
異步I/O(aio)
3.1 I/O模型簡介
這里有必要先解釋一下阻塞、非阻塞,同步、異步、I/O的概念。
3.1.1 阻塞和非阻塞:
阻塞和非阻塞指的是執(zhí)行一個操作是等操作結(jié)束再返回,還是馬上返回。
比如餐館的服務(wù)員為用戶點菜,當(dāng)有用戶點完菜后,服務(wù)員將菜單給后臺廚師,此時有兩種方式:
第一種:就在出菜窗口等待,直到廚師炒完菜后將菜送到窗口,然后服務(wù)員再將菜送到用戶手中;
第二種:等一會再到窗口來問廚師,某個菜好了沒?如果沒有先處理其他事情,等會再去問一次;
第一種就是阻塞方式,第二種則是非阻塞的。
3.1.2 同步和異步:
同步和異步又是另外一個概念,它是事件本身的一個屬性。還拿前面點菜為例,服務(wù)員直接跟廚師打交道,菜出來沒出來,服務(wù)員直接指導(dǎo),但只有當(dāng)廚師將菜送到服務(wù)員手上,這個過程才算正常完成,這就是同步的事件。同樣是點菜,有些餐館有專門的傳菜人員,當(dāng)廚師炒好菜后,傳菜員將菜送到傳菜窗口,并通知服務(wù)員,這就變成異步的了。其實異步還可以分為兩種:帶通知的和不帶通知的。前面說的那種屬于帶通知的。有些傳菜員干活可能主動性不是很夠,不會主動通知你,你就需要時不時的去關(guān)注一下狀態(tài)。這種就是不帶通知的異步。
對于同步的事件,你只能以阻塞的方式去做。而對于異步的事件,阻塞和非阻塞都是可以的。非阻塞又有兩種方式:主動查詢和被動接收消息。被動不意味著一定不好,在這里它恰恰是效率更高的,因為在主動查詢里絕大部分的查詢是在做無用功。對于帶通知的異步事件,兩者皆可。而對于不帶通知的,則只能用主動查詢。
3.1.3 全異步I/O
回到I/O,不管是I還是O,對外設(shè)(磁盤)的訪問都可以分成請求和執(zhí)行兩個階段。請求就是看外設(shè)的狀態(tài)信息(比如是否準(zhǔn)備好了),執(zhí)行才是真正的I/O操作。在Linux 2.6之前,只有“請求”是異步事件,2.6之后才引入AIO(asynchronous I/O )把“執(zhí)行”異步化。別看Linux/Unix是用來做服務(wù)器的,這點上比Windows落后了好多,IOCP(Windows上的AIO,效率極高)在Win2000上就有了。所以學(xué)linux的別老覺得Windows這里不好那里不好(Windows的多線程機制也由于linux)。
3.1.4 I/O的五種模型
根據(jù)以上分析,I/O可分為五種模型:
3.2.5 異步I/O(aio)
當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結(jié)果。實際處理這個調(diào)用的部件在完成后,通過狀態(tài)、通知和回調(diào)來通知調(diào)用者的輸入輸出操作。具體過程如下圖:
<a href="http://www.toxingwang.com/wp-content/uploads/2013/11/aio.png" class="cboxElement" rel="example4" 1712"="" style="text-decoration: none; color: rgb(1, 150, 227);">
3.2.6 I/O 模型總結(jié)(如下圖)
從上圖中我們可以看出,可以看出,越往后,阻塞越少,理論上效率也是最優(yōu)。其五種I/O模型中,前三種屬于同步I/O,后兩者屬于異步I/O。
同步I/O:
阻塞I/O
非阻塞I/O
I/O復(fù)用(select和poll)
異步I/O:
信號驅(qū)動I/O(SIGIO) (半異步)
異步I/O(aio) (真正的異步)
異步 I/O 和 信號驅(qū)動I/O的區(qū)別:
信號驅(qū)動 I/O 模式下,內(nèi)核可以復(fù)制的時候通知給我們的應(yīng)用程序發(fā)送SIGIO 消息。
異步 I/O 模式下,內(nèi)核在所有的操作都已經(jīng)被內(nèi)核操作結(jié)束之后才會通知我們的應(yīng)用程序。
3.3 Linux I/O模型的具體實現(xiàn)[轉(zhuǎn)自孤城博客]
3.3.1 主要實現(xiàn)方式有以下幾種:
select
poll
epoll
kqueue
/dev/poll
iocp
注,其中iocp是Windows實現(xiàn)的,select、poll、epoll是Linux實現(xiàn)的,kqueue是FreeBSD實現(xiàn)的,/dev/poll是SUN的Solaris實現(xiàn)的。select、poll對應(yīng)第3種(I/O復(fù)用)模型,iocp對應(yīng)第5種(異步I/O)模型,那么epoll、kqueue、/dev/poll呢?其實也同select屬于同一種模型,只是更高級一些,可以看作有了第4種(信號驅(qū)動I/O)模型的某些特性,如callback機制。
3.3.2 為什么epoll、kqueue、/dev/poll比select高級?
答案是,他們無輪詢。因為他們用callback取代了。想想看,當(dāng)套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調(diào)函數(shù),當(dāng)他們活躍時,自動完成相關(guān)操作,那就避免了輪詢,這正是epoll、kqueue、/dev/poll做的。這樣子說可能不好理解,那么我說一個現(xiàn)實中的例子,假設(shè)你在大學(xué)讀書,住的宿舍樓有很多間房間,你的朋友要來找你。select版宿管大媽就會帶著你的朋友挨個房間去找,直到找到你為止。而epoll版宿管大媽會先記下每位同學(xué)的房間號,你的朋友來時,只需告訴你的朋友你住在哪個房間即可,不用親自帶著你的朋友滿大樓找人。如果來了10000個人,都要找自己住這棟樓的同學(xué)時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高并發(fā)服務(wù)器中,輪詢I/O是最耗時間的操作之一,select、epoll、/dev/poll的性能誰的性能更高,同樣十分明了。
3.3.3 Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?
誠然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系統(tǒng),但是由于其系統(tǒng)本身的局限性,大型服務(wù)器還是在UNIX下。而且正如上面所述,kqueue、epoll、/dev/poll 與 IOCP相比,就是多了一層從內(nèi)核copy數(shù)據(jù)到應(yīng)用層的阻塞,從而不能算作asynchronous I/O類。但是,這層小小的阻塞無足輕重,kqueue、epoll、/dev/poll 已經(jīng)做得很優(yōu)秀了。
3.3.4 總結(jié)一些重點
只有IOCP(windows實現(xiàn))是asynchronous I/O,其他機制或多或少都會有一點阻塞。
select(Linux實現(xiàn))低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設(shè)計改善
epoll(Linux實現(xiàn))、kqueue(FreeBSD實現(xiàn))、/dev/poll(Solaris實現(xiàn))是Reacor模式,IOCP是Proactor模式。
Apache 2.2.9之前只支持select模型,2.2.9之后支持epoll模型
Nginx 支持epoll模型
Java nio包是select模型
四、Apache Httpd的工作模式
4.1 apache三種工作模式
我們都知道Apache有三種工作模塊,分別為prefork、worker、event。
prefork:多進(jìn)程,每個請求用一個進(jìn)程響應(yīng),這個過程會用到select機制來通知。
worker:多線程,一個進(jìn)程可以生成多個線程,每個線程響應(yīng)一個請求,但通知機制還是select不過可以接受更多的請求。
event:基于異步I/O模型,一個進(jìn)程或線程,每個進(jìn)程或線程響應(yīng)多個用戶請求,它是基于事件驅(qū)動(也就是epoll機制)實現(xiàn)的。
4.2 prefork的工作原理
如果不用“--with-mpm”顯式指定某種MPM,prefork就是Unix平臺上缺省的MPM.它所采用的預(yù)派生子進(jìn)程方式也是 Apache1.3中采用的模式。prefork本身并沒有使用到線程,2.0版使用它是為了與1.3版保持兼容性;另一方面,prefork用單獨的子進(jìn)程來處理不同的請求,進(jìn)程之間是彼此獨立的,這也使其成為最穩(wěn)定的MPM之一。
4.3 worker的工作原理
相對于prefork,worker是2.0版中全新的支持多線程和多進(jìn)程混合模型的MPM。由于使用線程來處理,所以可以處理相對海量的請求,而系統(tǒng)資源的開銷要小于基于進(jìn)程的服務(wù)器。但是,worker也使用了多進(jìn)程,每個進(jìn)程又生成多個線程,以獲得基于進(jìn)程服務(wù)器的穩(wěn)定性,這種MPM的工作方 式將是Apache2.0的發(fā)展趨勢。
4.4 event 基于事件機制的特性
一個進(jìn)程響應(yīng)多個用戶請求,利用callback機制,讓套接字復(fù)用,請求過來后進(jìn)程并不處理請求,而是直接交由其他機制來處理,通過epoll機制來通知請求是否完成;在這個過程中,進(jìn)程本身一直處于空閑狀態(tài),可以一直接收用戶請求。可以實現(xiàn)一個進(jìn)程程響應(yīng)多個用戶請求。支持持海量并發(fā)連接數(shù),消耗更少的資源。
五、如何提高Web服務(wù)器的并發(fā)連接處理能力
有幾個基本條件:
基于線程,即一個進(jìn)程生成多個線程,每個線程響應(yīng)用戶的每個請求。
基于事件的模型,一個進(jìn)程處理多個請求,并且通過epoll機制來通知用戶請求完成。
基于磁盤的AIO(異步I/O)
支持mmap內(nèi)存映射,mmap傳統(tǒng)的web服務(wù)器,進(jìn)行頁面輸入時,都是將磁盤的頁面先輸入到內(nèi)核緩存中,再由內(nèi)核緩存中復(fù)制一份到web服務(wù)器上,mmap機制就是讓內(nèi)核緩存與磁盤進(jìn)行映射,web服務(wù)器,直接復(fù)制頁面內(nèi)容即可。不需要先把磁盤的上的頁面先輸入到內(nèi)核緩存去。
剛好,Nginx 支持以上所有特性。所以Nginx官網(wǎng)上說,Nginx支持50000并發(fā),是有依據(jù)的。
六、Nginx優(yōu)異之處
6.1 簡介
傳統(tǒng)上基于進(jìn)程或線程模型架構(gòu)的web服務(wù)通過每進(jìn)程或每線程處理并發(fā)連接請求,這勢必會在網(wǎng)絡(luò)和I/O操作時產(chǎn)生阻塞,其另一個必然結(jié)果則是對內(nèi)存或CPU的利用率低下。生成一個新的進(jìn)程/線程需要事先備好其運行時環(huán)境,這包括為其分配堆內(nèi)存和棧內(nèi)存,以及為其創(chuàng)建新的執(zhí)行上下文等。這些操作都需要占用CPU,而且過多的進(jìn)程/線程還會帶來線程抖動或頻繁的上下文切換,系統(tǒng)性能也會由此進(jìn)一步下降。另一種高性能web服務(wù)器/web服務(wù)器反向代理:Nginx(Engine X),nginx的主要著眼點就是其高性能以及對物理計算資源的高密度利用,因此其采用了不同的架構(gòu)模型。受啟發(fā)于多種操作系統(tǒng)設(shè)計中基于“事件”的高級處理機制,nginx采用了模塊化、事件驅(qū)動、異步、單線程及非阻塞的架構(gòu),并大量采用了多路復(fù)用及事件通知機制。在nginx中,連接請求由為數(shù)不多的幾個僅包含一個線程的進(jìn)程worker以高效的回環(huán)(run-loop)機制進(jìn)行處理,而每個worker可以并行處理數(shù)千個的并發(fā)連接及請求。
6.2 Nginx 工作原理
Nginx會按需同時運行多個進(jìn)程:一個主進(jìn)程(master)和幾個工作進(jìn)程(worker),配置了緩存時還會有緩存加載器進(jìn)程(cache loader)和緩存管理器進(jìn)程(cache manager)等。所有進(jìn)程均是僅含有一個線程,并主要通過“共享內(nèi)存”的機制實現(xiàn)進(jìn)程間通信。主進(jìn)程以root用戶身份運行,而worker、cache loader和cache manager均應(yīng)以非特權(quán)用戶身份運行。
主進(jìn)程主要完成如下工作:
讀取并驗正配置信息;
創(chuàng)建、綁定及關(guān)閉套接字;
啟動、終止及維護(hù)worker進(jìn)程的個數(shù);
無須中止服務(wù)而重新配置工作特性;
控制非中斷式程序升級,啟用新的二進(jìn)制程序并在需要時回滾至老版本;
重新打開日志文件;
編譯嵌入式perl腳本;
worker進(jìn)程主要完成的任務(wù)包括:
接收、傳入并處理來自客戶端的連接;
提供反向代理及過濾功能;
nginx任何能完成的其它任務(wù);
注:如果負(fù)載以CPU密集型應(yīng)用為主,如SSL或壓縮應(yīng)用,則worker數(shù)應(yīng)與CPU數(shù)相同;如果負(fù)載以IO密集型為主,如響應(yīng)大量內(nèi)容給客戶端,則worker數(shù)應(yīng)該為CPU個數(shù)的1.5或2倍。
6.3 Nginx 架構(gòu)
Nginx的代碼是由一個核心和一系列的模塊組成, 核心主要用于提供Web Server的基本功能,以及Web和Mail反向代理的功能;還用于啟用網(wǎng)絡(luò)協(xié)議,創(chuàng)建必要的運行時環(huán)境以及確保不同的模塊之間平滑地進(jìn)行交互。不過,大多跟協(xié)議相關(guān)的功能和某應(yīng)用特有的功能都是由nginx的模塊實現(xiàn)的。這些功能模塊大致可以分為事件模塊、階段性處理器、輸出過濾器、變量處理器、協(xié)議、upstream和負(fù)載均衡幾個類別,這些共同組成了nginx的http功能。事件模塊主要用于提供OS獨立的(不同操作系統(tǒng)的事件機制有所不同)事件通知機制如kqueue或epoll等。協(xié)議模塊則負(fù)責(zé)實現(xiàn)nginx通過http、tls/ssl、smtp、pop3以及imap與對應(yīng)的客戶端建立會話。在Nginx內(nèi)部,進(jìn)程間的通信是通過模塊的pipeline或chain實現(xiàn)的;換句話說,每一個功能或操作都由一個模塊來實現(xiàn)。例如,壓縮、通過FastCGI或uwsgi協(xié)議與upstream服務(wù)器通信,以及與memcached建立會話等。
6.4 Nginx 基礎(chǔ)功能
處理靜態(tài)文件,索引文件以及自動索引;
反向代理加速(無緩存),簡單的負(fù)載均衡和容錯;
FastCGI,簡單的負(fù)載均衡和容錯;
模塊化的結(jié)構(gòu)。過濾器包括gzipping, byte ranges, chunked responses, 以及 SSI-filter 。在SSI過濾器中,到同一個 proxy 或者 FastCGI 的多個子請求并發(fā)處理;
SSL 和 TLS SNI 支持;
6.5 Nginx IMAP/POP3 代理服務(wù)功能
使用外部 HTTP 認(rèn)證服務(wù)器重定向用戶到 IMAP/POP3 后端;
使用外部 HTTP 認(rèn)證服務(wù)器認(rèn)證用戶后連接重定向到內(nèi)部的 SMTP 后端;
認(rèn)證方法:
POP3: POP3 USER/PASS, APOP, AUTH LOGIN PLAIN CRAM-MD5;
IMAP: IMAP LOGIN;
SMTP: AUTH LOGIN PLAIN CRAM-MD5;
SSL 支持;
在 IMAP 和 POP3 模式下的 STARTTLS 和 STLS 支持;
6.6 Nginx 支持的操作系統(tǒng)
FreeBSD 3.x, 4.x, 5.x, 6.x i386; FreeBSD 5.x, 6.x amd64;
Linux 2.2, 2.4, 2.6 i386; Linux 2.6 amd64;
Solaris 8 i386; Solaris 9 i386 and sun4u; Solaris 10 i386;
MacOS X (10.4) PPC;
Windows 編譯版本支持 windows 系列操作系統(tǒng);
6.7 Nginx 結(jié)構(gòu)與擴展
一個主進(jìn)程和多個工作進(jìn)程,工作進(jìn)程運行于非特權(quán)用戶;
kqueue (FreeBSD 4.1+), epoll (Linux 2.6+), rt signals (Linux 2.2.19+), /dev/poll (Solaris 7 11/99+), select, 以及 poll 支持;
kqueue支持的不同功能包括 EV_CLEAR, EV_DISABLE (臨時禁止事件), NOTE_LOWAT, EV_EOF, 有效數(shù)據(jù)的數(shù)目,錯誤代碼;
sendfile (FreeBSD 3.1+), sendfile (Linux 2.2+), sendfile64 (Linux 2.4.21+), 和 sendfilev (Solaris 8 7/01+) 支持;
輸入過濾 (FreeBSD 4.1+) 以及 TCP_DEFER_ACCEPT (Linux 2.4+) 支持;
10,000 非活動的 HTTP keep-alive 連接僅需要 2.5M 內(nèi)存。
最小化的數(shù)據(jù)拷貝操作;
6.8 Nginx 其他HTTP功能
基于IP 和名稱的虛擬主機服務(wù);
Memcached 的 GET 接口;
支持 keep-alive 和管道連接;
靈活簡單的配置;
重新配置和在線升級而無須中斷客戶的工作進(jìn)程;
可定制的訪問日志,日志寫入緩存,以及快捷的日志回卷;
4xx-5xx 錯誤代碼重定向;
基于 PCRE 的 rewrite 重寫模塊;
基于客戶端 IP 地址和 HTTP 基本認(rèn)證的訪問控制;
PUT, DELETE, 和 MKCOL 方法;
支持 FLV (Flash 視頻);
帶寬限制;
6.9 為什么選擇Nginx
在高連接并發(fā)的情況下,Nginx是Apache服務(wù)器不錯的替代品: Nginx在美國是做虛擬主機生意的老板們經(jīng)常選擇的軟件平臺之一. 能夠支持高達(dá) 50,000 個并發(fā)連接數(shù)的響應(yīng), 感謝Nginx為我們選擇了 epoll and kqueue 作為開發(fā)模型。
Nginx作為負(fù)載均衡服務(wù)器: Nginx 既可以在內(nèi)部直接支持 Rails 和 PHP 程序?qū)ν膺M(jìn)行服務(wù), 也可以支持作為 HTTP代理 服務(wù)器對外進(jìn)行服務(wù). Nginx采用C進(jìn)行編寫, 不論是系統(tǒng)資源開銷還是CPU使用效率都比 Perlbal 要好很多。
作為郵件代理服務(wù)器: Nginx 同時也是一個非常優(yōu)秀的郵件代理服務(wù)器(最早開發(fā)這個產(chǎn)品的目的之一也是作為郵件代理服務(wù)器), Last.fm 描述了成功并且美妙的使用經(jīng)驗.
Nginx 是一個 [#installation 安裝] 非常的簡單 , 配置文件 非常簡潔(還能夠支持perl語法),Bugs 非常少的服務(wù)器: Nginx 啟動特別容易, 并且?guī)缀蹩梢宰龅?*24不間斷運行,即使運行數(shù)個月也不需要重新啟動. 你還能夠 不間斷服務(wù)的情況下進(jìn)行軟件版本的升級 。
Nginx 的誕生主要解決C10K問題
轉(zhuǎn)載于:https://blog.51cto.com/5404542/1740453
總結(jié)
以上是生活随笔為你收集整理的nginx与Apache的涉及的计算机原理说明的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓MP3播放器开发实例(1)之音乐列表
- 下一篇: Django REST framewor