TCP连接都是用TCP协议沟通的吗?
一般来说,TCP连接是标准的TCP三次握手完成的:
- 客户端发送SYN
- 服务端收到SYN之后,回复SYN + ACK
- 客户端收到SYN + ACK后,回复ACK
这里面SYN会在两端各发送一次,表示“我准备好了,可以开始连接了”。ACK也是两端各发送了一次,表示“我知道你准备好了,我们开始通信把”。
那既然是4个报文,为什么是三次发送呢?显然,服务端的SYN和ACK是合并在一起发送的,就节省了一次发送。这个在英文里叫 Piggybacking,就是背着走,搭顺风车的意思。
如果服务端不想接受这次握手,它会怎么做呢?可能有这么几种情况:
- 不搭理这次连接,当做什么都没有收到,什么都没有发生
- 给予回复,明确拒绝
第一种情况,因为服务端做了“静默丢包”,也就是虽然收到了SYN,但是它直接丢弃了,也不给客户端回复任何消息。这也导致了一个问题,就是客户端无法分清楚这个SYN到底是以下哪种情况?
- 在网络上丢失了,服务端收不到,自然不会有回复
- 对端收到了但是没有回,就是“静默丢包”
- 对端收到了也回了,但是这个回包在网络中丢了。
那么,从客户端的角度,对于SYN包发出去之后迟迟没有回应的情况,它的策略是做重试。而且不止一次。那会重试几次呢?重试多久呢? 下面来做个实验
实验
需要两台机器。
- 第一步,在服务端,执行下面这条命令,让Iptables静默丢弃掉发往自己80端口的数据包
iptables -I INPUT -p tcp --dport 80 -j DROP
- 第二步,在客户端启动tcpdump抓包
sudo tcpdump -i any -w telnet-80.pcap port 80
- 第三步,从客户端发起一次telnet(注意,服务端必须先启动telnet服务才能连接):
telnet 服务端IP 80
这个时候,这个telnet会挂起。
大约一两分钟后才会失败退出。这时,你可以把客户端的 tcpdump 停掉了(按下 Ctrl+C)。
然后用 Wireshark 打开这个抓包文件,看看里面是什么:
telnet挂起的原因就在这里:握手请求一直没有成功。客户端一共有7个SYN包发出,或者说,除了第一次SYN,后继还有6次重试。客户端当然也不是“傻子”,这么多次都失败,就放弃了连接尝试,把失败的消息传递给了用户空间程序,然后就是telent退出。
这里有个信息很值得关注。第二列是数据包之间的时间间隔,也就是 1 秒,2 秒,4.2秒,8.2 秒,16.1 秒,33 秒,每个间隔是上一个的两倍左右。到第6次重试失败之后,客户端就彻底放弃了。
这里的翻倍时间,就是指数退避原则的体现。这里的时间并不是精确的整秒,因为指数退避原则本身就不建议在精确的整秒做重试,最好是有所浮动,这样可以让重试成功的机会变得更大一些。
可能出现的错误
$ telnet 192.168.0.12
Trying 192.168.0.12...
telnet: Unable to connect to remote host: Connection refused
原因:
(1)输入命令netstat -tnl
查看端口状态
$ netstat -tnl
激活Internet连接 (仅服务器)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
tcp6 0 0 ::1:631 :::* LISTEN
可以看到没有进程在监听本地的23端口,说明本地的telnet服务没有启动。
解决
(1)安装xinetd和telnetd
sudo apt-get install xinetd telnetd
(2)创建文件/etc/inetd.conf
$ sudo vi /etc/inetd.conf
并加入以下一行
telnet stream tcp nowait telnetd /usr/sbin/tcpd /usr/sbin/in.telnetd
(3)修改文件/etc/xinetd.conf
$ sudo gedit /etc/xinetd.conf
并修改为以下内容
# Simple configuration file for xinetd
#
# Some defaults, and include /etc/xinetd.d/
defaults
{
# Please note that you need a log_type line to be able to use log_on_success
# and log_on_failure. The default is the following :
# log_type = SYSLOG daemon info
instances = 60
log_type = SYSLOG authpriv
log_on_success = HOST PID
log_on_failure = HOST
cps = 25 30
}
includedir /etc/xinetd.d
(4)创建文件/etc/xinetd.d/telnet
$ sudo gedit /etc/xinetd.d/telnet
并输入以下内容
# default: on
# description: The telnet server serves telnet sessions; it uses \
# unencrypted username/password pairs for authentication.
service telnet
{
disable = no
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/sbin/in.telnetd
log_on_failure += USERID
}
(5)启动服务sudo /etc/init.d/xinetd restart
(6)重启系统,查看端口状态
$ netstat -tnl
激活Internet连接 (仅服务器)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
tcp6 0 0 :::23 :::* LISTEN
tcp6 0 0 ::1:631 :::* LISTEN
可以看到正在监听23端口