刚接触TCP/IP通信设计的人根据范例可以很快编出一个通信程序,据此一些人可能会认为TCP/IP编程很简单。其实不然,TCP/IP编程具有较为丰富的内容。其编程的丰富性主要体现在通信方式和报文格式的多样性上。
通信方式
主要有以下三大类:
1.一个Client方连接一个Server方,或称点对点(peer to peer):
2.多个Client方连接一个Server方,这也是通常的并发服务器方式。
3.一个Client方连接多个Server方,这种方式很少见,主要用于一个客户向多个服务器发送请求情况。
连接方式
1.长连接
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送和接收。
连接->传输数据->保持连接 -> 传输数据-> 。。。->关闭连接。
1.1服务器中的长连接
我们在追求性能的时候,会选择使用长连接,这里我有一个服务器程序, 负责监听本地6032,多个 client 负责循环发送请求。执行
lsof -i:6032
命令可以查看端口的相关使用情况:
-
说明了服务器正在监听本地的6032端口,处理发送到本地 6032 端口的请求*:6032(LISTEN)
- 前两条信息说明请求的发送情况,验证了 TCP 是一个双向的通信过程,由于我在本地启动了2个客户端模拟器,所以你能够看到是本地的 21095 ,21598端口与6032端口在通信。我们并没有手动设置 53078 这个客户端端口,他是随机的,但也阐释了一个道理:即使是发送请求的一方,也需要占用一个端口。
- 稍微说一下 FD 这个参数(第四列),他代表了文件句柄,每新增一条连接都会占用新的文件句柄,如果你在使用 TCP 通信的过程中出现了
的异常,那就应该检查一下,你是不是创建了太多的连接,而没有关闭。细心的读者也会联想到长连接的另一个好处,那就是会占用较少的文件句柄。open too many files
1.2 长连接的维护
因为客户端请求的服务可能分布在多个服务器上,客户端端自然需要跟对端创建多条长连接,使用长连接,我们遇到的第一个问题就是要如何维护长连接。实际上我们客户端和服务端都使用
ip:port
维护了端对端的长连接,Channel 或者Session便是对连接的抽象。我会在后续的涉及到网络编程的地方来用代码来说明举例。
1.3 长连接保活/心跳
为什么需要连接的保活心跳?当双方已经建立了连接,但因为网络问题,链路不通,这样长连接就不能使用了。需要明确的一点是,通过 netstat,lsof 等指令查看到连接的状态处于
ESTABLISHED
状态并不是一件非常靠谱的事,因为连接可能已死,但没有被系统感知到,更不用提假死这种疑难杂症了。如果保证长连接可用是一件技术活。通常我们会用客户端和服务器之间保持心跳连接,也就是说客户端和服务器之间定时发送心跳协议,如果服务器在一定时间范围内收到来自客户端的心跳协议并返回给客户端,那么客户端认为服务器是“活”的,否则认为此连接可能是死连接,主动断开和服务器之间的连接。
1.4 连接的保活:KeepAlive
首先想到的是 TCP 中的 KeepAlive 机制。KeepAlive 并不是 TCP 协议的一部分,但是大多数操作系统都实现了这个机制。KeepAlive 机制开启后,在一定时间内(一般时间为 7200s,参数
tcp_keepalive_time
)在链路上没有数据传送的情况下,TCP 层将发送相应的KeepAlive探针以确定连接可用性,探测失败后重试 10(参数
tcp_keepalive_probes
)次,每次间隔时间 75s(参数
tcp_keepalive_intvl
),所有探测失败后,才认为当前连接已经不可用。
在 Netty 中开启 KeepAlive:
bootstrap.option(ChannelOption.TCP_NODELAY, true)
Linux 操作系统中设置 KeepAlive 相关参数,修改
/etc/sysctl.conf
文件:
net.ipv4.tcp_keepalive_time=90net.ipv4.tcp_keepalive_intvl=15net.ipv4.tcp_keepalive_probes=2
KeepAlive 机制是在网络层面保证了连接的可用性,但站在应用框架层面我们认为这还不够。主要体现在两个方面:
- KeepAlive 的开关是在应用层开启的,但是具体参数(如重试测试,重试间隔时间)的设置却是操作系统级别的,位于操作系统的
配置中,这对于应用来说不够灵活。/etc/sysctl.conf
- KeepAlive 的保活机制只在链路空闲的情况下才会起到作用,假如此时有数据发送,且物理链路已经不通,操作系统这边的链路状态还是 ESTABLISHED,这时会发生什么?自然会走 TCP 重传机制,要知道默认的 TCP 超时重传,指数退避算法也是一个相当长的过程。
- KeepAlive 本身是面向网络的,并不是面向于应用的,当连接不可用时,可能是由于应用本身 GC 问题,系统 load 高等情况,但网络仍然是通的,此时,应用已经失去了活性,所以连接自然应该认为是不可用的。
看来,应用层面的连接保活还是必须要做的。
2.短连接
Client方与Server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。
过程:
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。
之前的文章里我已经说过一个TCP的连接建立和关闭,通常是需要三次握手和四次挥手的。所以长连接和短连接的优缺点也就突显了出来。
连接 | 优点 | 缺点 |
长连接 | 传输速度快,server可以主动发送数据给client | 保持的连接会占用和多系统资源, 后台设计相对要复杂 |
短连接 |
总结
- 如果业务来往比较频繁,则选择长连接,比如游戏里玩家升级装备,强化武器,卸载装备,抽奖,移动等频繁操作可以采用TCP长连接。
- 如果server要主动给client发数据,则选择长连接,比如游戏里的广播通知,战斗状态的同步通知是需要服务器来主动告诉客户端的。
- 像WEB网站的http服务一般都用短链接(http1.0只支持短连接,1.1keep alive 带时间,操作次数限制的长连接),因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。