java部署要考慮的centos場景優化

      做爲一名碼農,咱們在windows場景開發,可實際上線時大多數時候是在linux下部署運行的,經歷的一些坑在此記錄下來,是通識性的知識,新秀們遇到的機率大。javascript

      先簡單介紹下,我早年是在windows xp作java開發,用的jdk1.4,tomcat5,部署上線環境是在windows server 2003 和windows server 2008場景,隨着linux的普及和大衆化,慢慢的轉移到了centos環境中來,固然也經歷了jdk版本的過渡升遷(1.4,1.6,1.8),tomcat也是如此(5,6,7),中間也用到過weblogic(國企有錢)和oracle 11g(國企有錢,中間件和數據都是正版)。css

安裝完乾淨的操做系統後(不知道如今的雲環境作了優化沒有,好比買了ecs),默認參數狀況下Linux對高併發支持並很差,主要受限於單進程最大打開文件數限制、內核TCP參數方面和IO事件分配機制等,面就從幾方面來調整使Linux系統可以支持高併發的場境。html

1.  先說說出現的問題:to many open files(花了我很久排查java代碼倒是os層面的),起初經過log日誌排查問題時覺得是java程序自己出現的bug,到後來查閱中英文資料才知道操做系統自己也要作配置的java

Linux平臺上,不管編寫客戶端程序仍是服務端程序,在進行高併發TCP鏈接處理時,最高的併發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是由於系統爲每一個TCP鏈接都要建立一個socket句柄,每一個socket句柄同時也是一個文件句柄)。可以使用ulimit命令查看系統容許當前用戶進程打開的文件數限制:linux

對於java級別的開發人員非系統管理員,務必要關注下參數:open files和 max user processes。nginx

open files: 表示當前用戶的每一個進程最多容許同時打開1024個文件,這1024個文件中還得除去每一個進程必然打開的標準輸入,標準輸出,標準錯誤,服務器監聽 socket,進程間通信的unix域socket等文件,那麼剩下的可用於客戶端socket鏈接的文件數就只有大概1024-10=1014個左右。也就是說缺省狀況下,基於Linux的通信程序最多容許同時1014個TCP併發鏈接。web

對於想支持更高數量的TCP併發鏈接的通信處理程序,就必須修改Linux對當前用戶的進程同時打開的文件數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在當前系統可以承受的範圍內進一步限制用戶同時打開的文件數;硬限制則是根據系統硬件資源情況(主要是系統內存)計算出來的系統最多可同時打開的文件數量。一般軟限制小於或等於硬限制。算法

Java進程file descriptor table中FD的總量能夠經過命令 lsof -p $java_pid | wc -l 查到。數據庫

 可經過如下方式修改該值:apache

第一步:編輯 /etc/security/limits.conf ,增長內容

vi /etc/security/limits.conf
#鍵入
* soft nofile 4096  
* hard nofile 4096 
#而後保存

’注: *’號表示修改全部用戶(也能夠指定具體用戶,好比有的中間件轉完後會建立默認用戶)的限制;soft或hard指定要修改軟限制仍是硬限制;4096則指定了想要修改的新的限制值,即最大打開文件數(請注意軟限制值要小於或等於硬限制)。修改完後保存文件。

第二步,編輯/etc/pam.d/login文件,在文件中添加以下行:

vi /etc/pam.d/login
#增長
session required /lib/security/pam_limits.so

這是告訴Linux在用戶完成系統登陸後,應該調用pam_limits.so模塊來設置系統對該用戶可以使用的各類資源數量的最大限制(包括用戶可打開的最大文件數限制),而pam_limits.so模塊就會從/etc/security/limits.conf文件中讀取配置來設置這些限制值。修改完後保存此文件。

第三步,查看Linux系統級的最大打開文件數限制,使用以下命令:

cat /proc/sys/fs/file-max
#輸出187932

該值代表這臺Linux系統最多容許同時打開(即包含全部用戶打開文件數總和)文件數,是Linux系統級硬限制,全部用戶級的打開文件數限制都不該超過這個數值。一般這個系統級硬限制是Linux系統在啓動時根據系統硬件資源情況計算出來的最佳的最大同時打開文件數限制。

若是沒有特殊須要,不該該修改此限制,除非想爲用戶級打開文件數限制設置超過此限制的值。修改此硬限制的方法是修改/etc/sysctl.conf文件內fs.file-max= 187932

這是讓Linux在啓動完成後強行將系統級打開文件數硬限制設置爲187932。修改完後保存此文件。

完成上述步驟後重啓系統,通常狀況下就能夠將Linux系統對指定用戶的單一進程容許同時打開的最大文件數限制設爲指定的數值。若是重啓後用ulimit-n命令查看用戶可打開文件數限制仍然低於上述步驟中設置的最大值,這多是由於在用戶登陸腳本/etc/profile中使用ulimit-n命令已經將用戶可同時打開的文件數作了限制。因爲經過ulimit-n修改系統對用戶可同時打開文件的最大數限制時,新修改的值只能小於或等於上次ulimit-n設置的值,所以想用此命令增大這個限制值是不可能的。因此,若是有上述問題存在,就只能去打開/etc/profile腳本文件,在文件中查找是否使用了ulimit-n限制了用戶可同時打開的最大文件數量,若是找到,則刪除這行命令,或者將其設置的值改成合適的值,而後保存文件,用戶退出並從新登陸系統便可。

至此,就爲支持高併發TCP鏈接處理的通信處理程序解除關於打開文件數量方面的系統限制。

2.  網絡方面,出現大量的tcp鏈接不上

在Linux上編寫支持高併發TCP鏈接的客戶端通信處理程序時,有時會發現儘管已經解除了系統對用戶同時打開文件數的限制,但仍會出現併發TCP鏈接數增長到必定數量時,再也沒法成功創建新的TCP鏈接的現象。出現這種如今的緣由有多種。

第一種狀況:多是由於Linux網絡內核對本地端口號範圍有限制。此時,進一步分析爲何沒法創建TCP鏈接,會發現問題出在connect()調用返回失敗,查看系統錯誤提示消息是「Can’t assign requested address」。同時,若是在此時用tcpdump工具監視網絡,會發現根本沒有TCP鏈接時客戶端發SYN包的網絡流量。這些狀況說明問題在於本地Linux系統內核中有限制。其實,問題的根本緣由在於Linux內核的TCP/ip協議實現模塊對系統中全部的客戶端TCP鏈接對應的本地端口號的範圍進行了限制(例如,內核限制本地端口號的範圍爲1024~32768之間)。

當系統中某一時刻同時存在太多的TCP客戶端鏈接時,因爲每一個TCP客戶端鏈接都要佔用一個惟一的本地端口號(此端口號在系統的本地端口號範圍限制中),若是現有的TCP客戶端鏈接已將全部的本地端口號佔滿,則此時就沒法爲新的TCP客戶端鏈接分配一個本地端口號了,所以系統會在這種狀況下在connect()調用中返回失敗,並將錯誤提示消息設爲「Can’t assign requested address」。有關這些控制邏輯能夠查看Linux內核源代碼,以linux2.6內核爲例,能夠查看tcp_ipv4.c文件中以下函數:

static int tcp_v4_hash_connect(struct sock *sk)

請注意上述函數中對變量sysctl_local_port_range的訪問控制。變量sysctl_local_port_range的初始化則是在tcp.c文件中的以下函數中設置:

void __init tcp_init(void)

內核編譯時默認設置的本地端口號範圍可能過小,所以須要修改此本地端口範圍限制。

第一步,修改/etc/sysctl.conf文件,在文件中添加以下行:

vi /etc/sysctl.conf
#添加
net.ipv4.ip_local_port_range = 1024 65000

這代表將系統對本地端口範圍限制設置爲1024~65000之間。請注意,本地端口範圍的最小值必須大於或等於1024;而端口範圍的最大值則應小於或等於65535。修改完後保存此文件。

第二步,執行sysctl命令:

sysctl -p

若是系統沒有錯誤提示,就代表新的本地端口範圍設置成功。若是按上述端口範圍進行設置,則理論上單獨一個進程最多能夠同時創建60000多個TCP客戶端鏈接。

第二種狀況:沒法創建TCP鏈接的緣由多是由於Linux網絡內核的IP_TABLE防火牆對最大跟蹤的TCP鏈接數有限制。此時程序會表現爲在 connect()調用中阻塞,如同死機,若是用tcpdump工具監視網絡,也會發現根本沒有TCP鏈接時客戶端發SYN包的網絡流量。因爲 IP_TABLE防火牆在內核中會對每一個TCP鏈接的狀態進行跟蹤,跟蹤信息將會放在位於內核內存中的conntrack database中,這個數據庫的大小有限,當系統中存在過多的TCP鏈接時,數據庫容量不足,IP_TABLE沒法爲新的TCP鏈接創建跟蹤信息,因而表現爲在connect()調用中阻塞。此時就必須修改內核對最大跟蹤的TCP鏈接數的限制,方法同修改內核對本地端口號範圍的限制是相似的:

第一步,修改/etc/sysctl.conf文件,在文件中添加以下行:

vi /etc/sysctl.conf
#追加
net.ipv4.ip_conntrack_max = 10240

這代表將系統對最大跟蹤的TCP鏈接數限制設置爲10240。請注意,此限制值要儘可能小,以節省對內核內存的佔用。

第二步,執行sysctl命令:

sysctl -p

若是系統沒有錯誤提示,就代表系統對新的最大跟蹤的TCP鏈接數限制修改爲功。若是按上述參數進行設置,則理論上單獨一個進程最多能夠同時創建10000多個TCP客戶端鏈接。

第三種狀況: TCP鏈接斷開後,會以TIME_WAIT狀態保留必定的時間,而後纔會釋放端口。當併發請求過多的時候,就會產生大量的TIME_WAIT狀態的鏈接,沒法及時斷開的話,會佔用大量的端口資源和服務器資源。這個時候咱們能夠優化TCP的內核參數,來及時將TIME_WAIT狀態的端口清理掉。

下面介紹的方法只對擁有大量TIME_WAIT狀態的鏈接致使系統資源消耗有效,若是不是這種狀況下,效果可能不明顯。可使用netstat命令去查TIME_WAIT狀態的鏈接狀態,輸入下面的組合命令,查看當前TCP鏈接的狀態和對應的鏈接數量:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

這個命令會輸出相似下面的結果:

LAST_ACK16

SYN_RECV348

ESTABLISHED70

FIN_WAIT1229

FIN_WAIT230

CLOSING33

TIME_WAIT18098

咱們只用關心TIME_WAIT的個數,在這裏能夠看到,有18000多個TIME_WAIT,這樣就佔用了18000多個端口。要知道端口的數量只有65535個,佔用一個少一個,會嚴重的影響到後繼的新鏈接。這種狀況下,咱們就有必要調整下Linux的TCP內核參數,讓系統更快的釋放TIME_WAIT鏈接。

編輯配置文件:/etc/sysctl.conf,在這個文件中,加入下面的幾行內容:

# vi /etc/sysctl.conf

net.ipv4.tcp_syncookies= 1

net.ipv4.tcp_tw_reuse= 1

net.ipv4.tcp_tw_recycle= 1

net.ipv4.tcp_fin_timeout= 30

輸入下面的命令,讓內核參數生效:

# sysctl-p

簡單的說明上面的參數的含義:

net.ipv4.tcp_syncookies= 1

表示開啓SYNCookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉;

net.ipv4.tcp_tw_reuse= 1

表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉;

net.ipv4.tcp_tw_recycle= 1

表示開啓TCP鏈接中TIME-WAITsockets的快速回收,默認爲0,表示關閉;

net.ipv4.tcp_fin_timeout

修改系統默認的TIMEOUT 時間。

在通過這樣的調整以後,除了會進一步提高服務器的負載能力以外,還可以防護小流量程度的DoS、CC和SYN攻擊。

此外,若是你的鏈接數自己就不少,咱們能夠再優化一下TCP的可以使用端口範圍,進一步提高服務器的併發能力。依然是往上面的參數文件中,加入下面這些配置:

net.ipv4.tcp_keepalive_time= 1200

net.ipv4.ip_local_port_range= 1024 65535

net.ipv4.tcp_max_syn_backlog= 8192

net.ipv4.tcp_max_tw_buckets= 5000

這幾個參數,建議只在流量很是大的服務器上開啓,會有顯著的效果。通常的流量小的服務器上,沒有必要去設置這幾個參數。

net.ipv4.tcp_keepalive_time= 1200

表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改成20分鐘。

ip_local_port_range= 1024 65535

表示用於向外鏈接的端口範圍。缺省狀況下很小,改成1024到65535。

net.ipv4.tcp_max_syn_backlog= 8192

表示SYN隊列的長度,默認爲1024,加大隊列長度爲8192,能夠容納更多等待鏈接的網絡鏈接數。

net.ipv4.tcp_max_tw_buckets= 5000

表示系統同時保持TIME_WAIT的最大數量,若是超過這個數字,TIME_WAIT將馬上被清除並打印警告信息。默認爲180000,改成5000。此項參數能夠控制TIME_WAIT的最大數量,只要超出了。

net.ipv4.tcp_max_syn_backlog= 65536

記錄的那些還沒有收到客戶端確認信息的鏈接請求的最大值。對於有128M內存的系統而言,缺省值是1024,小內存的系統則是128。

net.core.netdev_max_backlog= 32768

每一個網絡接口接收數據包的速率比內核處理這些包的速率快時,容許送到隊列的數據包的最大數目。

net.core.somaxconn= 32768

例如web應用中listen函數的backlog默認會給咱們內核參數的net.core.somaxconn限制到128,而nginx定義的NGX_LISTEN_BACKLOG默認爲511,因此有必要調整這個值。

net.core.wmem_default= 8388608

net.core.rmem_default= 8388608

net.core.rmem_max= 16777216 #最大socket讀buffer,可參考的優化值:873200

net.core.wmem_max= 16777216 #最大socket寫buffer,可參考的優化值:873200

net.ipv4.tcp_timestsmps= 0

時間戳能夠避免序列號的卷繞。一個1Gbps的鏈路確定會遇到之前用過的序列號。時間戳可以讓內核接受這種「異常」的數據包。這裏須要將其關掉。

net.ipv4.tcp_synack_retries= 2

爲了打開對端的鏈接,內核須要發送一個SYN並附帶一個迴應前面一個SYN的ACK。也就是所謂三次握手中的第二次握手。這個設置決定了內核放棄鏈接以前發送SYN+ACK包的數量。

net.ipv4.tcp_syn_retries= 2

在內核放棄創建鏈接以前發送SYN包的數量。

#net.ipv4.tcp_tw_len= 1

net.ipv4.tcp_tw_reuse= 1

開啓重用。容許將TIME-WAITsockets從新用於新的TCP鏈接。

net.ipv4.tcp_wmem= 8192 436600 873200

TCP寫buffer,可參考的優化值:8192 436600 873200

net.ipv4.tcp_rmem = 32768 436600 873200

TCP讀buffer,可參考的優化值:32768 436600 873200

net.ipv4.tcp_mem= 94500000 91500000 92700000

一樣有3個值,意思是:

net.ipv4.tcp_mem[0]:低於此值,TCP沒有內存壓力。

net.ipv4.tcp_mem[1]:在此值下,進入內存壓力階段。

net.ipv4.tcp_mem[2]:高於此值,TCP拒絕分配socket。

上述內存單位是頁,而不是字節。可參考的優化值是:7864321048576 1572864

net.ipv4.tcp_max_orphans= 3276800

系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。

若是超過這個數字,鏈接將即刻被複位並打印出警告信息。

這個限制僅僅是爲了防止簡單的DoS攻擊,不能過度依靠它或者人爲地減少這個值,

更應該增長這個值(若是增長了內存以後)。

net.ipv4.tcp_fin_timeout= 30

若是套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間。對端能夠出錯並永遠不關閉鏈接,甚至意外當機。缺省值是60秒。2.2 內核的一般值是180秒,你能夠按這個設置,但要記住的是,即便你的機器是一個輕載的WEB服務器,也有由於大量的死套接字而內存溢出的風險,FIN-WAIT-2的危險性比FIN-WAIT-1要小,由於它最多隻能吃掉1.5K內存,可是它們的生存期長些。

同時還涉及到一個TCP 擁塞算法的問題,你能夠用下面的命令查看本機提供的擁塞算法控制模塊:

sysctlnet.ipv4.tcp_available_congestion_control

對於幾種算法的分析,詳情能夠參考下:TCP擁塞控制算法的優缺點、適用環境、性能分析,好比高延時能夠試用hybla,中等延時能夠試用htcp算法等。

若是想設置TCP 擁塞算法爲hybla

net.ipv4.tcp_congestion_control=hybla

額外的,對於內核版高於於3.7.1的,咱們能夠開啓tcp_fastopen:

net.ipv4.tcp_fastopen= 3

三、使用支持高併發網絡I/O的編程技術(可參考Unix網絡編程一書)

在Linux上編寫高併發TCP鏈接應用程序時,必須使用合適的網絡I/O技術和I/O事件分派機制。

可用的I/O技術有同步I/O,非阻塞式同步I/O(也稱反應式I/O),以及異步I/O。在高TCP併發的情形下,若是使用同步I/O,這會嚴重阻塞程序的運轉,除非爲每一個TCP鏈接的I/O建立一個線程。可是,過多的線程又會因系統對線程的調度形成巨大開銷。所以,在高TCP併發的情形下使用同步 I/O是不可取的,這時能夠考慮使用非阻塞式同步I/O或異步I/O。非阻塞式同步I/O的技術包括使用select(),poll(),epoll等機制。異步I/O的技術就是使用AIO。

從I/O事件分派機制來看,使用select()是不合適的,由於它所支持的併發鏈接數有限(一般在1024個之內)。若是考慮性能,poll()也是不合適的,儘管它能夠支持的較高的TCP併發數,可是因爲其採用「輪詢」機制,當併發數較高時,其運行效率至關低,並可能存在I/O事件分派不均,致使部分TCP鏈接上的I/O出現「飢餓」現象。而若是使用epoll或AIO,則沒有上述問題(早期Linux內核的AIO技術實現是經過在內核中爲每一個 I/O請求建立一個線程來實現的,這種實現機制在高併發TCP鏈接的情形下使用其實也有嚴重的性能問題。但在最新的Linux內核中,AIO的實現已經獲得改進)。

綜上所述,在開發支持高併發TCP鏈接的Linux應用程序時,應儘可能使用epoll或AIO技術來實現併發的TCP鏈接上的I/O控制,這將爲提高程序對高併發TCP鏈接的支持提供有效的I/O保證。

4. 日誌裏出現了「 java.lang.OutOfMemoryError:

從兩方面論述,操做系統和java方面

4.1  操做系統配置

一開始的印象是java應用程序代碼的問題,也頗有概率是系統沒有作參數優化,繼續編輯 /etc/security/limits.conf 文件

vi /etc/security/limits.conf
#追加
• soft nproc 81920

• hard nproc 81920

注: 表示全部用戶,softhard表示軟限制、硬限制。(軟限制<=硬限制)

4.2  jvm方面的參數配置

jvm結構圖:

1,JVM內存劃分分爲年輕代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation)。
2,年輕代又分爲Eden和Survivor區。Survivor區由FromSpace和ToSpace組成。Eden區佔大容量,Survivor兩個區佔小容量,默認比例大概是8:2。
3,堆內存(Heap)=年輕代+老年代。非堆內存=永久代。
4,堆內存用途:存放的是對象,垃圾收集器就是收集這些對象,而後根據GC算法回收。
5,非堆內存用途:JVM自己使用,存放一些類、方法、常量、屬性等。
6,年輕代:新生成的對象首先放到年輕代的Eden區中,當Eden滿時,通過GC後,還存活的對象被複制到Survivor區的FromSpace中,若是Survivor區滿時,會再被複制到Survivor區的ToSpace區。若是還有存活對象,會再被複制到老年代。
7,老年代:在年輕代中通過GC後還存活的對象會被複制到老年代中。當老年代空間不足時,JVM會對老年代進行徹底的垃圾回收(Full GC)。若是GC後,仍是沒法存放從Survivor區複製過來的對象,就會出現OOM(Out of Memory)。
8,永久代:也稱爲方法區,存放靜態類型數據,好比類、方法、屬性等。

JVM內存設置不是越大越好,具體仍是根據項目對象實際佔用內存大小而定,能夠經過Java自帶的分析工具來查看。若是設置過大,會增長回收時間,從而增長暫停應用時間,並且從未遇到過專門弄臺服務器跑java程序,都是混合式場景跑着數據庫,tomcat,nginx等 

#按需配置優化,這只是參考
JAVA_OPTS="-server-Xms1024m -Xmx1536m -XX:PermSize=256m -XX:MaxPermSize=512m-XX:+UseConcMarkSweepGC -XX:+UseParallelGCThreads=8XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction=0 -XX:-PrintGC -XX:-PrintGCDetails-XX:-PrintGCTimeStamps -Xloggc:/log/java.log"

5.  經過tomcat(開源免費)優化配置,略

#只是參考,實際場景看須要
<Connectorport="8080"protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="1000"
               minSpareThreads="100"
               maxSpareThreads="200"
               acceptCount="900"
               disableUploadTimeout="true"
              connectionTimeout="20000"
               URIEncoding="UTF-8"
               enableLookups="false"
               redirectPort="8443"
               compression="on"
              compressionMinSize="1024"
              compressableMimeType="text/html,text/xml,text/css,text/javascript"/>

參數說明:
org.apache.coyote.http11.Http11NioProtocol:調整工做模式爲Nio
maxThreads:最大線程數,默認150。增大值避免隊列請求過多,致使響應緩慢。
minSpareThreads:最小空閒線程數。
maxSpareThreads:最大空閒線程數,若是超過這個值,會關閉無用的線程。
acceptCount:當處理請求超過此值時,將後來請求放到隊列中等待。
disableUploadTimeout:禁用上傳超時時間
connectionTimeout:鏈接超時,單位毫秒,0表明不限制
URIEncoding:URI地址編碼使用UTF-8
enableLookups:關閉dns解析,提升響應時間
compression:啓用壓縮功能
compressionMinSize:最小壓縮大小,單位Byte
compressableMimeType:壓縮的文件類型

Tomcat有三種工做模式:Bio、Nio和Apr

下面簡單瞭解下他們工做原理:

Bio(BlockingI/O):默認工做模式,阻塞式I/O操做,沒有任何優化技術處理,性能比較低。
Nio(New I/O orNon-Blocking):非阻塞式I/O操做,有Bio有更好的併發處理性能。
Apr(ApachePortable Runtime,Apache可移植運行庫):首選工做模式,主要爲上層的應用程序提供一個能夠跨越多操做系統平臺使用的底層支持接口庫。
tomcat利用基於Apr庫tomcat-native來實現操做系統級別控制,提供一種優化技術和非阻塞式I/O操做,大大提升併發處理能力。可是須要安裝apr和tomcat-native庫。

附一份參考 內核參數sysctl.conf (實際場景中作調整):

net.ipv4.ip_local_port_range = 1024 65536
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save=1
net.core.somaxconn = 262144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2

總結基本上完畢,若有新狀況接着寫。。。學好linux,對java從業人員有益無害,推薦兩本書《深刻理解計算機系統》和Unix網絡編程卷1

相關文章
相關標籤/搜索