天天看點

配置開發支援高并發TCP連接配接的Linux應用程式全攻略

1、修改使用者程序可打開檔案數限制

在Linux平台上,無論編寫用戶端程式還是服務端程式,在進行高并發TCP連接配接處理時,最高的并發數量都要受到系統對使用者單一程序同時可打開檔案數量 的限制(這是因為系統為每個TCP連接配接都要建立一個socket句柄,每個socket句柄同時也是一個檔案句柄)。可使用ulimit指令檢視系統允許 目前使用者程序打開的檔案數限制:

[[email protected] ~]$ ulimit -n

1024

這表示目前使用者的每個程序最多允許同時打開1024個檔案,這1024個檔案中還得除去每個程序必然打開的标準輸入,标準輸出,标準錯誤,伺服器監聽 socket,程序間通訊的unix域socket等檔案,那麼剩下的可用于用戶端socket連接配接的檔案數就隻有大概1024-10=1014個左右。 也就是說預設情況下,基于Linux的通訊程式最多允許同時1014個TCP并發連接配接。

對于想支援更高數量的TCP并發連接配接的通訊處理程式,就必須修改Linux對目前使用者的程序同時打開的檔案數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在目前系統能夠承受的範圍内進一步限制使用者同時打開的檔案數;硬限制則是根據系統 硬體資源狀況(主要是系統記憶體)計算出來的系統最多可同時打開的檔案數量。通常軟限制小于或等于硬限制。

修改上述限制的最簡單的辦法就是使用ulimit指令:

[[email protected] ~]$ ulimit -n <file_num>

上述指令中,在<file_num>中指定要設定的單一程序允許打開的最大檔案數。如果系統回顯類似于“Operation notpermitted”之類的話,說明上述限制修改失敗,實際上是因為在<file_num>中指定的數值超過了Linux系統對該使用者 打開檔案數的軟限制或硬限制。是以,就需要修改Linux系統對使用者的關于打開檔案數的軟限制和硬限制。

第一步,修改/etc/security/limits.conf檔案,在檔案中添加如下行:

speng soft nofile 10240

speng hard nofile 10240

其中speng指定了要修改哪個使用者的打開檔案數限制,可用'*'号表示修改所有使用者的限制;soft或hard指定要修改軟限制還是硬限制;10240則指定了想要修改的新的限制值,即最大打開檔案數(請注意軟限制值要小于或等于硬限制)。修改完後儲存檔案。

第二步,修改/etc/pam.d/login檔案,在檔案中添加如下行:

session required /lib/security/pam_limits.so

這是告訴Linux在使用者完成系統登入後,應該調用pam_limits.so子產品來設定系統對該使用者可使用的各種資源數量的最大限制(包括使用者可打開 的最大檔案數限制),而pam_limits.so子產品就會從/etc/security/limits.conf檔案中讀取配置來設定這些限制值。修改 完後儲存此檔案。

第三步,檢視Linux系統級的最大打開檔案數限制,使用如下指令:

[[email protected] ~]$ cat /proc/sys/fs/file-max

12158

這表明這台Linux系統最多允許同時打開(即包含所有使用者打開檔案數總和)12158個檔案,是Linux系統級硬限制,所有使用者級的打開檔案數限制 都不應超過這個數值。通常這個系統級硬限制是Linux系統在啟動時根據系統硬體資源狀況計算出來的最佳的最大同時打開檔案數限制,如果沒有特殊需要,不 應該修改此限制,除非想為使用者級打開檔案數限制設定超過此限制的值。修改此硬限制的方法是修改/etc/rc.local腳本,在腳本中添加如下行:

echo 22158 > /proc/sys/fs/file-max

這是讓Linux在啟動完成後強行将系統級打開檔案數硬限制設定為22158。修改完後儲存此檔案。

完成上述步驟後重新開機系統,一般情況下就可以将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 requestedaddress”。同時,如果在此時用tcpdump工具監視網絡,會發現根本沒有TCP連接配接時用戶端發SYN包的網絡流量。這些情況 說明問題在于本地Linux系統核心中有限制。其實,問題的根本原因在于Linux核心的TCP/IP協定實作子產品對系統中所有的用戶端TCP連接配接對應的 本地端口号的範圍進行了限制(例如,核心限制本地端口号的範圍為1024~32768之間)。當系統中某一時刻同時存在太多的TCP用戶端連接配接時,由于每 個TCP用戶端連接配接都要占用一個唯一的本地端口号(此端口号在系統的本地端口号範圍限制中),如果現有的TCP用戶端連接配接已将所有的本地端口号占滿,則此 時就無法為新的TCP用戶端連接配接配置設定一個本地端口号了,是以系統會在這種情況下在connect()調用中傳回失敗,并将錯誤提示消息設為“Can't assignrequested 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檔案,在檔案中添加如下行:

net.ipv4.ip_local_port_range = 1024 65000

這表明将系統對本地端口範圍限制設定為1024~65000之間。請注意,本地端口範圍的最小值必須大于或等于1024;而端口範圍的最大值則應小于或等于65535。修改完後儲存此檔案。

第二步,執行sysctl指令:

[[email protected] ~]$ sysctl -p

如果系統沒有錯誤提示,就表明新的本地端口範圍設定成功。如果按上述端口範圍進行設定,則理論上單獨一個程序最多可以同時建立60000多個TCP用戶端連接配接。

第二種無法建立TCP連接配接的原因可能是因為Linux網絡核心的IP_TABLE防火牆對最大跟蹤的TCP連接配接數有限制。此時程式會表現為在 connect()調用中阻塞,如同當機,如果用tcpdump工具監視網絡,也會發現根本沒有TCP連接配接時用戶端發SYN包的網絡流量。由于 IP_TABLE防火牆在核心中會對每個TCP連接配接的狀态進行跟蹤,跟蹤資訊将會放在位于核心記憶體中的conntrackdatabase中,這個資料庫 的大小有限,當系統中存在過多的TCP連接配接時,資料庫容量不足,IP_TABLE無法為新的TCP連接配接建立跟蹤資訊,于是表現為在connect()調用 中阻塞。此時就必須修改核心對最大跟蹤的TCP連接配接數的限制,方法同修改核心對本地端口号範圍的限制是類似的:

第一步,修改/etc/sysctl.conf檔案,在檔案中添加如下行:

net.ipv4.ip_conntrack_max = 10240

這表明将系統對最大跟蹤的TCP連接配接數限制設定為10240。請注意,此限制值要盡量小,以節省對核心記憶體的占用。

第二步,執行sysctl指令:

[[email protected] ~]$ sysctl -p

如果系統沒有錯誤提示,就表明系統對新的最大跟蹤的TCP連接配接數限制修改成功。如果按上述參數進行設定,則理論上單獨一個程序最多可以同時建立10000多個TCP用戶端連接配接。

3、使用支援高并發網絡I/O的程式設計技術

在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保證。

1、修改使用者程序可打開檔案數限制

在Linux平台上,無論編寫用戶端程式還是服務端程式,在進行高并發TCP連接配接處理時,最高的 并發數量都要受到系統對使用者單一程序同時可打開檔案數量的限制(這是因為系統為每個TCP連接配接都要建立一個socket句柄,每個socket句柄同時也 是一個檔案句柄)。可使用ulimit指令檢視系統允許目前使用者程序打開的檔案數限制:

[[email protected] ~]$ ulimit -n

1024

這 表示目前使用者的每個程序最多允許同時打開1024個檔案,這1024個檔案中還得除去每個程序必然打開的标準輸入,标準輸出,标準錯誤,伺服器監聽 socket,程序間通訊的unix域socket等檔案,那麼剩下的可用于用戶端socket連接配接的檔案數就隻有大概1024-10=1014個左右。 也就是說預設情況下,基于Linux的通訊程式最多允許同時1014個TCP并發連接配接。

對于想支援更高數量的TCP并發連接配接的通訊 處理程式,就必須修改Linux對目前使用者的程序同時打開的檔案數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在目前系統能夠承受的範圍内進一步限制使用者同時打開的檔案數;硬限制則是根據系統 硬體資源狀況(主要是系統記憶體)計算出來的系統最多可同時打開的檔案數量。通常軟限制小于或等于硬限制。

修改上述限制的最簡單的辦法就是使用ulimit指令:

[[email protected] ~]$ ulimit -n <file_num>

上 述指令中,在<file_num>中指定要設定的單一程序允許打開的最大檔案數。如果系統回顯類似于“Operation notpermitted”之類的話,說明上述限制修改失敗,實際上是因為在<file_num>中指定的數值超過了Linux系統對該使用者 打開檔案數的軟限制或硬限制。是以,就需要修改Linux系統對使用者的關于打開檔案數的軟限制和硬限制。

第一步,修改/etc/security/limits.conf檔案,在檔案中添加如下行:

speng soft nofile 10240

speng hard nofile 10240

其中speng指定了要修改哪個使用者的打開檔案數限制,可用'*'号表示修改所有使用者的限制;soft或hard指定要修改軟限制還是硬限制;10240則指定了想要修改的新的限制值,即最大打開檔案數(請注意軟限制值要小于或等于硬限制)。修改完後儲存檔案。

第二步,修改/etc/pam.d/login檔案,在檔案中添加如下行:

session required /lib/security/pam_limits.so

這 是告訴Linux在使用者完成系統登入後,應該調用pam_limits.so子產品來設定系統對該使用者可使用的各種資源數量的最大限制(包括使用者可打開的最 大檔案數限制),而pam_limits.so子產品就會從/etc/security/limits.conf檔案中讀取配置來設定這些限制值。修改完後 儲存此檔案。

第三步,檢視Linux系統級的最大打開檔案數限制,使用如下指令:

[[email protected] ~]$ cat /proc/sys/fs/file-max

12158

這 表明這台Linux系統最多允許同時打開(即包含所有使用者打開檔案數總和)12158個檔案,是Linux系統級硬限制,所有使用者級的打開檔案數限制都不 應超過這個數值。通常這個系統級硬限制是Linux系統在啟動時根據系統硬體資源狀況計算出來的最佳的最大同時打開檔案數限制,如果沒有特殊需要,不應該 修改此限制,除非想為使用者級打開檔案數限制設定超過此限制的值。修改此硬限制的方法是修改/etc/rc.local腳本,在腳本中添加如下行:

echo 22158 > /proc/sys/fs/file-max

這是讓Linux在啟動完成後強行将系統級打開檔案數硬限制設定為22158。修改完後儲存此檔案。

完 成上述步驟後重新開機系統,一般情況下就可以将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 requestedaddress”。同時,如果在此時用tcpdump工具監視網絡,會發現根本沒有TCP連接配接時用戶端發SYN包的網絡流量。這些情況 說明問題在于本地Linux系統核心中有限制。其實,問題的根本原因在于Linux核心的TCP/IP協定實作子產品對系統中所有的用戶端TCP連接配接對應的 本地端口号的範圍進行了限制(例如,核心限制本地端口号的範圍為1024~32768之間)。當系統中某一時刻同時存在太多的TCP用戶端連接配接時,由于每 個TCP用戶端連接配接都要占用一個唯一的本地端口号(此端口号在系統的本地端口号範圍限制中),如果現有的TCP用戶端連接配接已将所有的本地端口号占滿,則此 時就無法為新的TCP用戶端連接配接配置設定一個本地端口号了,是以系統會在這種情況下在connect()調用中傳回失敗,并将錯誤提示消息設為“Can't assignrequested 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檔案,在檔案中添加如下行:

net.ipv4.ip_local_port_range = 1024 65000

這表明将系統對本地端口範圍限制設定為1024~65000之間。請注意,本地端口範圍的最小值必須大于或等于1024;而端口範圍的最大值則應小于或等于65535。修改完後儲存此檔案。

第二步,執行sysctl指令:

[[email protected] ~]$ sysctl -p

如果系統沒有錯誤提示,就表明新的本地端口範圍設定成功。如果按上述端口範圍進行設定,則理論上單獨一個程序最多可以同時建立60000多個TCP用戶端連接配接。

第 二種無法建立TCP連接配接的原因可能是因為Linux網絡核心的IP_TABLE防火牆對最大跟蹤的TCP連接配接數有限制。此時程式會表現為在 connect()調用中阻塞,如同當機,如果用tcpdump工具監視網絡,也會發現根本沒有TCP連接配接時用戶端發SYN包的網絡流量。由于 IP_TABLE防火牆在核心中會對每個TCP連接配接的狀态進行跟蹤,跟蹤資訊将會放在位于核心記憶體中的conntrackdatabase中,這個資料庫 的大小有限,當系統中存在過多的TCP連接配接時,資料庫容量不足,IP_TABLE無法為新的TCP連接配接建立跟蹤資訊,于是表現為在connect()調用 中阻塞。此時就必須修改核心對最大跟蹤的TCP連接配接數的限制,方法同修改核心對本地端口号範圍的限制是類似的:

第一步,修改/etc/sysctl.conf檔案,在檔案中添加如下行:

net.ipv4.ip_conntrack_max = 10240

這表明将系統對最大跟蹤的TCP連接配接數限制設定為10240。請注意,此限制值要盡量小,以節省對核心記憶體的占用。

第二步,執行sysctl指令:

[[email protected] ~]$ sysctl -p

如果系統沒有錯誤提示,就表明系統對新的最大跟蹤的TCP連接配接數限制修改成功。如果按上述參數進行設定,則理論上單獨一個程序最多可以同時建立10000多個TCP用戶端連接配接。

3、使用支援高并發網絡I/O的程式設計技術

在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保證。

繼續閱讀