最近某平台出现了白屏,黑屏的问题,查了下tcp方面的东西,目前来说,这些知识并没有解决问题,但总结下来,学习下。一个tcp连接进来,是否能连,是否能够被处理,是由系统和具体的应用程序来决定的。
主要由2个内核参数,也就是2个队列决定,这是全局的,这里先解释下backlog这个单词的意思,就是堆积的作业,这里就是堆积的连接。
net.ipv4.tcp_max_syn_backlog
这个是半连接的队列长度,所谓半连接就是还没有完成3次握手的,状态是SYN_RECV
net.core.somaxconn 这个是一个socket的全连接队列长度,就是完成了3次握手但还没交给应用程序的队列长度,默认是128,状态是ESTABLISHED,需要注意,这个值,是基于socket的,例如socket1的队列不会影响到socket2的队列长度
在繁忙的系统,128这个值明显是不够的,如何判断somaxconn是否足够呢?可以用下面的命令,如果数字在不断增长,则应该增大somaxconn
netstat -s |grep listen
上面写的状态,可以用下面的命令看到
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
在应用程序层面,则是在开启socket监听的时候,用listen(backlog)来设置,下面是我们的演示程序tcp_listen.py,88就是指可以有88个连接等待
import time
import socket
import struct
HOST = 'localhost'
PORT = 8000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(88)
# /usr/include/linux/tcp.h
struct_tcp_info = struct.Struct('7B 24I')
while True:
buf = s.getsockopt(socket.IPPROTO_TCP,
socket.TCP_INFO,
1024)
tcp_info = struct_tcp_info.unpack(buf)
tcpi_unacked, tcpi_sacked = tcp_info[11:13]
print 'tcpi_unacked:', tcpi_unacked,\
'tcpi_sacked:', tcpi_sacked
time.sleep(1)
以下均是论述
全连接
,backlog
意思是堆积的作业数量
系统设置了tcp全连接的backlog,应用程序的listen又设置了backlog,那到底用那个?系统会对比2个backlog,取较小的一个,当系统设置net.core.somaxconn是128,而应用设置是listen(511),min(128,511),则是128
我们用tcp_listen.py来测试
在CentOS 5.5上,很奇怪,执行后,print出来的一直都是0
tcpi_unacked: 0 tcpi_sacked: 0
如果要在CentOS5.5上测试,需要借助Systemtap来做检测,而不是看上面脚本print出来的值,安装Systemtap的过程略过(其实安装过程比较复杂)
probe begin {
printf("inet listen Monitoring Started...\n")
}
probe kernel.function("inet_listen").return
{
accept_backlog = $backlog
recvq_backlog = @cast($sock->sk, "inet_connection_sock")->icsk_accept_queue->
listen_opt->nr_table_entries
printf("%-20s:accept backlog is %d, syn backlog is %d.\n", execname(),accept_backlog, recvq_backlog)
}
设置系统的backlog为1
sysctl -w net.core.somaxconn=1
程序设置,上面tcp_listen.py的值是88
s.listen(88)
我们开2个窗口,运行程序
stap listen.stp
python tcp_listen.py
可以看到 listen.stp的输出
python :accept backlog is 1, syn backlog is 512.
看到了,程序是88,系统是1,最后取较小值,就是1,后面的512是半连接队列的长度
openSUSE 12.3 64bit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在这个系统上tcplisten.py打印就正常,同样按照上面CentOS,设置系统的backlog的值为1,tcplisten.py的listen不变,执行tcp_listen.py,输出是::
tcpi_unacked: 0 tcpi_sacked: 1
上面的输出,0可以理解为半连接的队列长度,后面的1可以认为是全连接的队列长度
TCP状态转换图
上图是TCP的状态转换,我们来验证下tcp的连接处理过程
当 tcp 建立连接的 3 次握手完成后,将连接置入 ESTABLISHED 状态并交付给应用程序的 backlog 队列时,会检查 backlog 队列是否已满。若已满,通常行为是将连接还原至 SYNRECV 状态,也就是把它放到半连接队列里,保持上文提到的tcplisten.py里的listen为88,net.core.somaxconn = 1,运行tcp_listen.py,再开2个tab,telnet 8000端口,就是有2个telnet连接到8000端口::
telnet localhost 8000
此时通过
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
看到的都是ESTABLISHED状态的连接,此时,再开一个tab,同样执行telnet,并执行上面的netstat命令,则会看到
SYN_RECV 1
由此,验证了上面的连接处理过程中的半连接和全连接过程。
我们的全连接队列长度是1,但为什么上面一共是telnet了3次,才会出现SYN_RECV呢?我个人理解是第一个直接交给程序处理了,第二个是放在全连接队列,第三个才是放到半连接队列
关于白屏问题,我以为是上面的值引起的,上面的值,默认是0的,看了下系统的配置,也是0,而且白屏的时候,还没去到游戏连接哪里呢,估计还在某平台那里。
那么这个值是做什么的呢?上面的实验可以看到,我们全连接的队列满了,还可以进入半连接队列,如果将net.ipv4.tcp_abort_on_overflow设置为1,如果全连接队列满了,则服务器直接发送RST给客户端,用tcpdump可以看到,客户端如果要连接,则需要重头发起连接。
命令
sysctl -w net.ipv4.tcp_abort_on_overflow=1
开3个tab进行telnet,可以看到进行到底三个的时候,就提示
telnet: connect to address 127.0.0.1: Connection reset by peer
telnet: Unable to connect to remote host: Connection reset by peer
同时,用netstat命令看,也不会有SYN_RECV状态的连接
根据观察,net.core.somaxconn的值设置为1024比较合适,像静态资源机,如果没有使用cdn,则需要设置为2048,如果是跨服机,最好也设置为2048
最后解决黑屏和白屏、延迟5000ms的问题,是以下这个参数
echo 1 > /proc/sys/net/ipv4/tcp_sack
当时还设置了另外一个参数::
echo 1 > /proc/sys/net/ipv4/tcp_window_scaling
但查资料,这个参数在内核2.6.8开始,默认就是1了,所以这个对黑屏、白屏是没有影响的
最后,设置sack,虽然减轻了网络传输的压力,但对客户端造成了一定的性能损耗,见tcp_sack