TCP 共有9 个网路状态,其中涉及到关闭的状态有5 个。
在我们编写网络相关程序的时候,这5 个状态经常出现。因为这5 个状态相互关联,相互纠缠,而且状态变化触发都是由应用触发,但是又涉及操作系统和网络,所以正确的理解TCP 在关闭时网络状态变化情况,为我们诊断网络中各种问题,快速定位故障有着非常重要的作用和意义。
为此,我根据W.Richard Stevens 的《TCP/IP 详解》一书,将这5 个状态制成一个时序图(其实原书也有这个图,但不是很好理解),其中黑色字体来表示应用程序行为,红色字体是操作系统行为。
=============================================================
===========================================================
深刻理解上面两幅图对于编写基于TCP/IP的应用直观重要,尤其是在链接不正常时,非常有助于定位问题所在。下面给出一些常见问题和解答。
netstat -na -P tcp -f inet | grep TIME_WAIT
--------------------------------------------------------------------------
不要设置 tcpb_time_wait_expire 成零,只要是一个很小的值就可以了。这里必须同
时设置 tcpb_time_wait_expire 和 tcpb_state ,只设置其中一个达不到效果。
利用 adb 从 TCPS_ESTABLISHED 变为 TCPS_CLOSE_WAIT ,可以使一条 TCP 连接不再工作, 但这条连接并未销毁, tcpb_t 结构也未删除。
利用 adb 从 TCPS_ESTABLISHED 变为 TCPS_CLOSED ,会导致整个操作系统崩溃。可能是下 层 tcpb_t 结构被删除,而上层 socket 并不了解,出现非法指针。
简化一下 kill_timewait.sh
--------------------------------------------------------------------------
#! /sbin/sh
ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | egrep 'TIME_WAIT'
echo
/usr/bin/echo 'TCPB address to terminate: \c'
read tcpb_addr
echo
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
\$q
NSFOCUS_EOF
--------------------------------------------------------------------------
还可以写一个脚本自动清除所有 TIME_WAIT 状态 TCP 连接
--------------------------------------------------------------------------
#! /sbin/sh
ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcpb_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
\$q
NSFOCUS_EOF
Done
=========================================
Q:
我正在写一个
unix server
程序,不是
daemon
,经常需要在命令行上重启它,绝大多数时候工作正常,但是某些时候会报告
"bind: address in use"
,于是重启失
败。
A: Andrew Gierth
server
程序总是应该在调用
bind()
之前设置
SO_REUSEADDR
套接字选项。至于
TIME_WAIT
状态,你无法避免,那是
TCP
协议的一部分。
Q:
如何避免等待
60
秒之后才能重启服务
A: Erik Max Francis
使用
setsockopt
,比如
--------------------------------------------------------------------------
int option = 1;
if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,
sizeof( option ) ) < 0 )
{
die( "setsockopt" );
}
--------------------------------------------------------------------------
Q:
编写
TCP/SOCK_STREAM
服务程序时,
SO_REUSEADDR
到底什么意思?
A:
这个套接字选项通知内核,如果端口忙,但
TCP
状态位于
TIME_WAIT
,可以重用端口。如果端口忙,而
TCP
状态位于其他状态,重用端口时依旧得到一个错误信息,指明
"
地址已经使用中
"
。
如果你的服务程序停止后想立即重启
,而新套接字依旧使用同一端口,此时
SO_REUSEADDR
选项非常有用。必须意识到,此时任何非期
望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不
可能。
一个套接字由相关五元组构成,协议
、本地地址、本地端口、远程地址、远程端
口。
SO_REUSEADDR
仅仅表示可以重用本地本地地址、本地端口
,整个相关五元组
还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据
。必须慎重使用
SO_REUSEADDR
选项。
Q:
在客户机
/
服务器编程中
(TCP/SOCK_STREAM)
,如何理解
TCP
自动机
TIME_WAIT
状态?
A: W. Richard Stevens <1999
年逝世,享年
49
岁
>
下面我来解释一下
TIME_WAIT
状态,这些在
<>
中
2.6
节解释很清楚了。
MSL(
最大分段生存期
)
指明
TCP
报文在
Internet
上最长生存时间,每个具体的
TCP
实现
都必须选择一个确定的
MSL
值。
RFC 1122
建议是
2
分钟,但
BSD
传统实现采用了
30
秒。
TIME_WAIT
状态最大保持时间是
2 * MSL
,也就是
1-4
分钟。
IP
头部有一个
TTL
,最大值
255
。尽管
TTL
的单位不是秒
(
根本和时间无关
)
,我们仍需
假设,
TTL
为
255
的
TCP
报文在
Internet
上生存时间不能超过
MSL
。
TCP
报文在传送过程中可能因为路由故障被迫缓冲延迟、选择非最优路径等等,结果发送方
TCP
机制开始超时重传
。前一个
TCP
报文可以称为
"
漫游
TCP
重复报文
"
,后一个
TCP
报文可以称为
"
超时重传
TCP
重复报文
"
,作为面向连接的可靠协议,
TCP
实现必须正确处理这种重复报文,因为二者可能最终都到达。
一个通常的
TCP
连接终止
可以用图描述如下:
client server
FIN M
close -----------------> (
被动关闭
)
ACK M+1
<-----------------
FIN N
<----------------- close
ACK N+1
----------------->
为什么需要
TIME_WAIT
状态?
假设最终的
ACK
丢失
,
server
将重发
FIN
,
client
必须维护
TCP
状态信息以便可以重发最终的
ACK
,否则会发送
RST
,结果
server
认为发生错误。
TCP
实现必须可靠地终止连接的两个方向
(
全双工关闭
)
,
client
必须进
TIME_WAIT
状态,因为
client
可能面临重发最终
ACK
的情形。
{
scz 2001-08-31 13:28
先调用
close()
的一方会进入
TIME_WAIT
状态
}
此外,考虑一种情况,
TCP
实现可能面临先后两个同样的相关五元组。如果前一个连接处在
TIME_WAIT
状态,而允许另一个拥有相同相关五元组的连接出现,可能处理
TCP
报文时,两个连接互相干扰。使用
SO_REUSEADDR
选项就需要考虑这种情况。
为什么
TIME_WAIT
状态需要保持
2MSL
这么长的时间?
如果
TIME_WAIT
状态保持时间不足够长
(
比如小于
2MSL)
,第一个连接就正常终止了。
第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二个连接。
TCP
实现必须防止某个连接的重复报文在连接终止后出现,所以让
TIME_WAIT
状态保持时间足够长
(2MSL)
,连接相应方向上的
TCP
报文要么完全响应完毕,要么被丢弃。建立第二个连接的时候,不会混淆。
A:
小四
在
Solaris 7
下有内核参数对应
TIME_WAIT
状态保持时间
# ndd -get /dev/tcp tcp_time_wait_interval
240000
# ndd -set /dev/tcp tcp_time_wait_interval 1000
缺省设置是
240000ms
,也就是
4
分钟。如果用
ndd
修改这个值,最小只能设置到
1000ms
,
也就是
1
秒。显然内核做了限制,需要
Kernel Hacking
。
# echo "tcp_param_arr/W 0t0" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
tcp_param_arr: 0x3e8 = 0x0
# ndd -set /dev/tcp tcp_time_wait_interval 0
我不知道这样做有什么灾难性后果,参看
<>
的声明。
Q: TIME_WAIT
状态保持时间为
0
会有什么灾难性后果?在普遍的现实应用中,好象也就是服务器不稳定点,不见得有什么灾难性后果吧?
D: rain@bbs.whnet.edu.cn
Linux
内核源码
/usr/src/linux/include/net/tcp.h
中
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to successfully
* close the socket, about 60 seconds */
最好不要改为
0
,改成
1
。端口分配是从上一次分配的端口号
+1
开始分配的,所以一般
不会有什么问题。端口分配算法在
tcp_ipv4.c
中
tcp_v4_get_port
中。
监听地址问题
[root@localhost
i386]# netstat -nlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign
Address State
PID/Program name
tcp 0 0 0.0.0.0:3306
0.0.0.0:*
LISTEN 2804/mysqld
tcp 0 0
222.230.14.16:80 0.0.0.0:*
LISTEN 4433/(squid)
tcp 0 0
127.0.0.1:80
0.0.0.0:*
LISTEN 4350/httpd
tcp 0 0 127.0.0.1:2006
0.0.0.0:*
LISTEN
27724/php
tcp 0 0
0.0.0.0:25
0.0.0.0:*
LISTEN 26234/master
tcp 0 0
0.0.0.0:443
0.0.0.0:*
LISTEN
27724/php
tcp 0 0
:::22
:::*
LISTEN 2674/sshd
udp 0 0
0.0.0.0:16384
0.0.0.0:*
4433/(squid)
udp 0 0
0.0.0.0:3130
0.0.0.0:*
4433/(squid)
我说的本地地址的四种类型:
0 0.0.0.0
222.230.14.16
127.0.0.1
:::
都是本地地址为何要有这四种类型呢?都是代表什么含义和作用呢?
一般 0.0.0.0 表示 all or any address , 以 listen 0.0.0.0 來說 , 表示 listen 主机所有 interface 的 ip 位址 .
至於你說的 222.230.14.16 or 127.0.0.1 那都只是表示 listen 特定 ip 位址 而已 ..
::: 那表示 listen 主机所有 interface 的 ipv6 位址 .
网络字节序问题
低地址
。。。。。。。。高地址
04 03 02 01
------》方法
1
01 02 03 04
------》方法
2
上面是整数 0x01020304 的两种表示方法,其中方法 1 和方法 2 的区别就是高位放到高地址还是低地址。
方法 1 叫做小端格式,方法 2 叫做大端格式,网络上使用的大端格式 ,而主机格式随着不同的机器不同,为了使得不同的主机格式能够无歧义的和网络格式相互赋值,一般牵涉到网络的开发库会定义一套两种格式之间的转换函数,这样直接使用转换函数就可以完成两者之间的转换。
例如在 windows 中有 htons , ntohs , htonl , ntohl 等一套函数,分别用来完成 2 个字节和 4 个字节的转换。