在Netty中經(jīng)常會(huì)看到這樣的代碼:
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
這里有一個(gè)SO_BACKLOG
參數(shù)像云,本篇文章解釋一下這個(gè)參數(shù)的具體用途庵楷。
TCP的連接隊(duì)列
我們看一下TCP三次握手的過程:
- 當(dāng) client 通過 connect 向 server 發(fā)出 SYN 包時(shí)暇藏,client 會(huì)維護(hù)一個(gè) socket 等待隊(duì)列洽胶,而 server 會(huì)維護(hù)一個(gè) SYN 隊(duì)列塔橡;
- 此時(shí)進(jìn)入半鏈接的狀態(tài)图焰,如果 socket 等待隊(duì)列滿了忌卤,server 則會(huì)丟棄,而 client 也會(huì)由此返回 connection time out楞泼;只要是 client 沒有收到 SYN+ACK驰徊,3s 之后笤闯,client 會(huì)再次發(fā)送,如果依然沒有收到棍厂,9s 之后會(huì)繼續(xù)發(fā)送颗味;
- 半連接 syn 隊(duì)列的長度為
max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
決定 - 當(dāng) server 收到 client 的 SYN 包后,會(huì)返回 SYN, ACK 的包加以確認(rèn)牺弹,client 的 TCP 協(xié)議棧會(huì)喚醒 socket 等待隊(duì)列浦马,發(fā)出 connect 調(diào)用;
- client 返回 ACK 的包后张漂,server 會(huì)進(jìn)入一個(gè)新的叫 accept 的隊(duì)列晶默,該隊(duì)列的長度為
min(backlog, somaxconn)
,默認(rèn)情況下航攒,somaxconn 的值為 128磺陡,表示最多有 129 的 ESTAB 的連接等待 accept(),而 backlog 的值則由int listen(int sockfd, int backlog)
中的第二個(gè)參數(shù)指定漠畜,listen 里面的 backlog 的含義請看這里币他。需要注意的是,一些 Linux 的發(fā)型版本可能存在對 somaxcon 錯(cuò)誤 truncating 方式憔狞; - 當(dāng) accept 隊(duì)列滿了之后蝴悉,即使 client 繼續(xù)向 server 發(fā)送 ACK 的包,也會(huì)不被相應(yīng)瘾敢,此時(shí)肛度,server 通過
/proc/sys/net/ipv4/tcp_abort_on_overflow
來決定如何返回施无,0 表示直接丟丟棄該 ACK,1 表示發(fā)送 RST 通知 client;相應(yīng)的蔚舀,client 則會(huì)分別返回read timeout
或者connection reset by peer
放吩。上面說的只是些理論乍炉,如果服務(wù)器不及時(shí)的調(diào)用 accept()替饿,當(dāng) queue 滿了之后,服務(wù)器并不會(huì)按照理論所述焦履,不再對 SYN 進(jìn)行應(yīng)答拓劝,返回 ETIMEDOUT。根據(jù)這篇文檔的描述嘉裤,實(shí)際情況并非如此郑临,服務(wù)器會(huì)隨機(jī)的忽略收到的 SYN,建立起來的連接數(shù)可以無限的增加屑宠,只不過客戶端會(huì)遇到延時(shí)以及超時(shí)的情況厢洞。
可以看到,整個(gè) TCP stack 有如下的兩個(gè) queue:
- 一個(gè)是 half open(syn queue) queue(max(tcp_max_syn_backlog, 64)),用來保存 SYN_SENT 以及 SYN_RECV 的信息躺翻,其大小通過
/proc/sys/net/ipv4/tcp_max_syn_backlog
指定丧叽,一般默認(rèn)值是512,不過這個(gè)設(shè)置有效的前提是系統(tǒng)的syncookies功能被禁用公你∮淮荆互聯(lián)網(wǎng)常見的TCP SYN FLOOD惡意DOS攻擊方式就是建立大量的半連接狀態(tài)的請求,然后丟棄陕靠,導(dǎo)致syns queue不能保存其它正常的請求迂尝。。 - 另外一個(gè)是 accept queue(min(somaxconn, backlog))剪芥,保存全連接狀態(tài)的請求垄开,其大小通過
/proc/sys/net/core/somaxconn
指定,在使用listen函數(shù)時(shí)税肪,內(nèi)核會(huì)根據(jù)傳入的backlog參數(shù)與系統(tǒng)參數(shù)somaxconn溉躲,取二者的較小值。
Netty中的backlog參數(shù)
查看EpollServerChannelConfig
中的backlog參數(shù):
private volatile int backlog = NetUtil.SOMAXCONN;
使用了NetUtil.SOMAXCONN
變量的值寸认,在NetUtil
中查看:
SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
@Override
public Integer run() {
// Determine the default somaxconn (server socket backlog) value of the platform.
// The known defaults:
// - Windows NT Server 4.0+: 200
// - Linux and Mac OS X: 128
int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
File file = new File("/proc/sys/net/core/somaxconn");
BufferedReader in = null;
try {
// file.exists() may throw a SecurityException if a SecurityManager is used, so execute it in the
// try / catch block.
// See https://github.com/netty/netty/issues/4936
if (file.exists()) {
in = new BufferedReader(new FileReader(file));
somaxconn = Integer.parseInt(in.readLine());
if (logger.isDebugEnabled()) {
logger.debug("{}: {}", file, somaxconn);
}
} else {
// Try to get from sysctl
Integer tmp = null;
if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) {
tmp = sysctlGetInt("kern.ipc.somaxconn");
if (tmp == null) {
tmp = sysctlGetInt("kern.ipc.soacceptqueue");
if (tmp != null) {
somaxconn = tmp;
}
} else {
somaxconn = tmp;
}
}
if (tmp == null) {
logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file,
somaxconn);
}
}
} catch (Exception e) {
logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file, somaxconn, e);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// Ignored.
}
}
}
return somaxconn;
}
});
可以看到,在默認(rèn)情況下somaxconn的值為:
- Windows NT Server 4.0+: 200
- Linux and Mac OS X: 128
這里也通過/proc/sys/net/core/somaxconn
文件來獲取somaxconn的值串慰。
上文中說到偏塞,內(nèi)核會(huì)根據(jù)傳入的backlog參數(shù)與系統(tǒng)參數(shù)somaxconn,取二者的較小值邦鲫,所以灸叼,如果想擴(kuò)大accept queue的大小,必須要同時(shí)調(diào)整這兩個(gè)參數(shù)庆捺。