linux系統中影響tcp連線數的主要因素是記憶體和允許的檔案描述符個數,因為每個tcp連線都要佔用一定內存,而每個socket就是一個檔案描述符,另外1024以下的連接埠通常為保留連接埠。
本教學操作環境:linux7.3系統、Dell G3電腦。
在tcp應用程式中,server事先在某個固定連接埠監聽,client主動發起連接,經過三次握手後建立tcp連接。那麼對單機,其最大並發tcp連線數是多少呢?
如何識別一個TCP連線
在決定最大連線數之前,先來看看系統如何識別一個tcp連線。系統用一個4四元組來唯一標識一個TCP連線:{localip, localport,remoteip,remoteport}。
client最大tcp連線數
client每次發起tcp連線要求時,除非綁定端口,通常會讓系統選取一個空閒的本機連接埠(local port),該連接埠是獨佔的,不能和其他tcp連線共用。 tcp埠的資料型別是unsigned short,因此本機埠個數最大隻有65536,埠0有特殊意義,不能使用,這樣可用埠最多只有65535,所以在全部作為client端的情況下,一個client最大tcp連線數為65535,這些連線可以連到不同的serverip。
server最大tcp連線數
server通常固定在某個本機連接埠上監聽,等待client的連線要求。在不考慮位址重複使用(unix的SO_REUSEADDR選項)的情況下,即使server端有多個ip,本地監聽埠也是獨佔的,因此server端tcp連接4元組中只有remoteip(也就是clientip)和remote port(客戶端port)是可變的,因此最大tcp連線為客戶端ip數×客戶端port數,對IPV4,不考慮ip位址分類等因素,最大tcp連線數約為2的32次方(ip數)× 2的16次方(port數),也就是server端單機最大tcp連線數約為2的48次方。
實際的tcp連接數
上面給出的是理論上的單機最大連接數,在實際環境中,受到機器資源、作業系統等的限制,特別是sever端,其最大並發tcp連線數遠遠不能達到理論上限。在unix/linux下限制連接數的主要因素是內存和允許的文件描述符個數(每個tcp連接都要佔用一定內存,每個socket就是一個文件描述符),另外1024以下的端口通常為保留端口。在預設2.6內核配置下,經過試驗,每個socket佔用內存在15~20k之間。
所以,對server端,透過增加記憶體、修改最大檔案描述符個數等參數,單機最大並發TCP連線數超過10萬,甚至上百萬是沒問題的。
這明顯是進入了思維的誤區,65535是指可用的連接埠總數,並不代表伺服器同時只能接受65535個並發連線。
舉個例子:
我們做了一個網站,綁定的是TCP的80端口,結果是所有訪問這個網站的用戶都是透過伺服器的80端口訪問,而不是其他連接埠。可見埠是可以復用的。即使Linux伺服器只在80埠偵聽服務, 也允許有10萬、100萬個用戶連接伺服器。 Linux系統不會限制連線數至於伺服器能不能承受這麼多的連接,取決於伺服器的硬體配置、軟體架構及最佳化。
我們知道兩個行程如果需要進行通訊最基本的一個前提是:能夠唯一的標示一個行程。在本地進程通訊中我們可以使用PID來唯一標示一個進程,但PID只在本地唯一,網路中的兩個進程PID衝突幾率很大。
這時候就需要另闢它徑了,IP位址可以唯一標示主機,而TCP層協定和埠號可以唯一標示主機的一個進程,這樣可以利用IP位址+協定+埠號唯一標示網路中的一個進程。
能夠唯一標示網路中的進程後,它們就可以利用socket進行通訊了。 socket(套接字)是在應用層和傳輸層之間的抽象層,它把TCP/IP層複雜的操作抽象化為幾個簡單的介面供應用層呼叫已實現進程在網路中通訊。
socket源自Unix,是一種"開啟—讀取/寫入—關閉"模式的實現,伺服器和客戶端各自維護一個"文件",建立連線開啟後,可以寫入內容到自己檔案供對方讀取或讀取對方內容,通訊結束時關閉檔案。
唯一能夠確定一個連線有4個東西:
1. 伺服器的IP
2. 伺服器的Port
3. 客戶端的IP
4. 客戶端的Port
伺服器的IP和Port可以保持不變,只要客戶端的IP和Port彼此不同就可以決定一個連線數。
一個socket是可以建立多個連線的,一個TCP連線的標記為一個四元組(source_ip, source_port, destination_ip, destination_port),即(來源IP,來源端口,目的IP,目的端口)四個元素的組合。只要四個元素的組合中有一個元素不一樣,就可以區別不同的連結。
舉個例子:
->你的主機IP位址是1.1.1.1, 在8080埠監聽
->當一個來自2.2.2.2 發來一條連接請求,連接埠為5555。這條連接的四元組為(1.1.1.1, 8080, 2.2.2.2, 5555)
->這時2.2.2.2又發第二條連接請求,連接埠為6666。新連接的四元組為(1.1.1.1, 8080, 2.2.2.2, 6666)
那麼,你主機的8080埠建立了兩個連線;
->(2.2.2.2)發送的第三條連線請求,連接埠為5555(或6666)。第三條連線的請求就無法建立,因為沒有辦法區分於上面兩條連線。
同理,可以在同一個連接埠號碼和IP位址上綁定一個TCP socket和一個UDP socket
因為連接埠號碼雖然一樣,但由於協定不一樣,所以連接埠是完全獨立的。
TCP/UDP一般採用五元組來定位一個連接:
source_ip, source_port, destination_ip, destination_port,protocol_type
#即(來源IP ,來源端口,目的IP,目的端口,協議號)
綜上所述,伺服器的並發數並不是由TCP的65535個端口決定的。伺服器同時能夠承受的同時數量是由頻寬、硬體、程式設計等多方面因素決定的。
所以也就能理解淘寶、騰訊、頭條、百度、新浪、嗶嗶嗶嗶等為什麼能夠承受住每秒種幾億次的並發訪問,是因為他們採用的是服務器集群。伺服器叢集分佈在全國各地的大型機房,當訪問量小的時候會關閉一些伺服器,當訪問量大的時候回不斷的開啟新的伺服器。
在linux下寫網頁伺服器程式的朋友肯定都知道每一個tcp連線都要佔一個檔案描述符,一旦這個檔案描述符使用完了,新的連線到來回給我們的錯誤是「Socket/File:Can't open so many files」。
這時你需要明白作業系統對可以開啟的最大檔案數的限制。
進程限制
執行ulimit -n 輸出1024,說明對於一個進程而言最多只能開啟1024個文件,所以你要採用此預設配置最多也就可以並發上千個TCP連線。
暫時修改:ulimit -n 1000000,但這種暫時修改只對目前登入使用者目前的使用環境有效,系統重新啟動或使用者登出後就會失效。
重啟後失效的修改(不過我在CentOS 6.5下測試,重啟後未發現失效):編輯/etc/security/limits.conf 文件, 修改後內容為
* soft nofile 1000000
* hard nofile 1000000
#永久修改:編輯/etc/rc .local,在其後添加如下內容
ulimit -SHn 1000000
執行cat /proc/sys/fs/file-nr 輸出
9344 0 592026
net.ipv4.netfilter.ip_conntrack_max = 1000000
######### #############常識二:連接埠號碼範圍限制? #########作業系統上連接埠號碼1024以下是系統保留的,從1024-65535是使用者使用的。由於每個TCP連線都要佔一個連接埠號,所以我們最多可以有60000多個並發連線。我想有這種錯誤思路朋友不在少數吧? (其中我過去就一直這麼認為)######我們來分析一下吧###如何識別TCP連線:系統用4四元組來唯一地識別一個TCP連線:{local ip, local port,remote ip,remote port}。好吧,我們拿出《UNIX網路程式設計:卷一》第四章中對accept的講解來看看概念性的東西,第二個參數cliaddr代表了客戶端的ip位址和連接埠號碼。而我們作為服務端實際上只使用了bind時這一個端口,說明端口號65535並不是並發量的限制。
server最大tcp連線數:server通常固定在某個本機連接埠上監聽,等待client的連線要求。不考慮位址重複使用(unix的SO_REUSEADDR選項)的情況下,即使server端有多個ip,本地監聽埠也是獨佔的,因此server端tcp連接4元組中只有remote ip(也就是client ip)和remote port (客戶端port)是可變的,因此最大tcp連線為客戶端ip數×客戶端port數,對IPV4,不考慮ip位址分類等因素,最大tcp連線數約為2的32次方(ip數)×2的16次方(port數),也就是server端單機最大tcp連線數約為2的48次方。
在Linux平台上,無論編寫客戶端程序還是服務端程序,在進行高並發TCP連接處理時,最高的並發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是因為系統為每個TCP連接都要建立一個socket句柄,每個socket句柄同時也是一個文件句柄)。可使用ulimit指令檢視系統允許目前使用者進程開啟的檔案數限制:
[speng@as4 ~]$ ulimit -n 1024
這表示目前使用者的每個行程最多允許同時開啟1024個文件,這1024個檔案中還得除去每個行程必然打開的標準輸入,標準輸出,標準錯誤,伺服器監聽socket,進程間通訊的unix域socket等文件,那麼剩下的可用於客戶端socket連接的文件數就只有大概1024-10=1014個左右。也就是說預設情況下,基於Linux的通訊程式最多允許同時1014個TCP並發連線。
對於想支援更高數量的TCP並發連接的通訊處理程序,就必須修改Linux對當前用戶的進程同時打開的文件數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在目前系統能夠承受的範圍內進一步限制使用者同時開啟的檔案數;硬限制則是根據系統硬體資源狀況(主要是系統記憶體)計算出來的系統最多可同時開啟的檔案數量。通常軟限制小於或等於硬限制。
修改上述限制的最簡單的方法就是使用ulimit指令:
[speng@as4 ~]$ ulimit -n
上述指令中,在中指定要設定的單一行程允許開啟的最大檔案數。如果系統回顯類似於「Operation notpermitted」之類的話,表示上述限制修改失敗,實際上是因為在中指定的數值超過了Linux系統對該使用者開啟檔案數的軟限製或硬限制。因此,就需要修改Linux系統對使用者的關於開啟檔案數的軟限制和硬限制。
第一步,修改/etc/security/limits.conf文件,在文件中加入以下行:
... # End of file speng soft nofile 10240 speng hard nofile 10240 root soft nofile 65535 root hard nofile 65535 * soft nofile 65535 * hard nofile 65535 [test@iZwz9e1dh1nweaex8ob5b7Z config]$
其中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系統層級的最大開啟檔案數限制,使用下列指令:
[speng@as4 ~]$ 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连接处理的通讯处理程序解除关于打开文件数量方面的系统限制。
在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命令:
[speng@as4 ~]$ 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命令:
[speng@as4 ~]$ sysctl -p
如果系统没有错误提示,就表明系统对新的最大跟踪的TCP连接数限制修改成功。如果按上述参数进行设置,则理论上单独一个进程最多可以同时建立10000多个TCP客户端连接。
在Linux上编写高并发TCP连接应用程序时,必须使用合适的网络I/O技术和I/O事件分派机制。
可用的I/O技术有同步I/O,非阻塞式同步I/O(也称反应式I/O),以及异步I/O。《BIO,NIO,AIO的理解》
在高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保证。
内核参数sysctl.conf的优化
/etc/sysctl.conf 是用来控制linux网络的配置文件,对于依赖网络的程序(如web服务器和cache服务器)非常重要,RHEL默认提供的最好调整。
推荐配置(把原/etc/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
这个配置参考于cache服务器varnish的推荐配置和SunOne 服务器系统优化的推荐配置。
varnish调优推荐配置的地址为:http://varnish.projects.linpro.no/wiki/Performance
不过varnish推荐的配置是有问题的,实际运行表明“net.ipv4.tcp_fin_timeout = 3”的配置会导致页面经常打不开;并且当网友使用的是IE6浏览器时,访问网站一段时间后,所有网页都会打不开,重启浏览器后正常。可能是国外的网速快吧,我们国情决定需要调整“net.ipv4.tcp_fin_timeout = 10”,在10s的情况下,一切正常(实际运行结论)。
修改完毕后,执行:
/sbin/sysctl -p /etc/sysctl.conf /sbin/sysctl -w net.ipv4.route.flush=1
命令生效。为了保险起见,也可以reboot系统。
调整文件数:
linux系统优化完网络必须调高系统允许打开的文件数才能支持大的并发,默认1024是远远不够的。
执行命令:
echo ulimit -HSn 65536 >> /etc/rc.local echo ulimit -HSn 65536 >>/root/.bash_profile ulimit -HSn 65536
备注:
对mysql用户可同时打开文件数设置为10240个;
将Linux系统可同时打开文件数设置为1000000个(一定要大于对用户的同时打开文件数限制);
将Linux系统对最大追踪的TCP连接数限制为20000个(但是,建议设置为10240;因为对mysql用户的同时打开文件数已经限制在10240个;且较小的值可以节省内存);
将linux系统端口范围配置为1024~30000(可以支持60000个以上连接,不建议修改;默认已经支持20000个以上连接);
综合上述四点,TCP连接数限制在10140个。
这10240个文件中还得除去每个进程必然打开的标准输入,标准输出,标准错误,服务器监听 socket,进程间通讯的unix域socket等文件。
因此,当需要对TCP连接数进行调整时只需要调整ulimit参数。
Linux下查看tcp连接数及状态命令:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
相关推荐:《Linux视频教程》
以上是linux系統哪些因素影響tcp連接數的詳細內容。更多資訊請關注PHP中文網其他相關文章!