JAVA ·

JAVA TCP之socket和长连接

近来有个近场通信的需求。需求是如下:

  • 同步4个数据库文件。
  • 实时进行数据交换。

之前尝试了用android的经典蓝牙来完成这两个需求!当写好蓝牙通信的时候,同步数据库文件发现太慢了!只能换了。那就使用wifi direct,在找到对方设备后,使用tcp socket方式进行数据交换,尝试了下在交换文件的方面比经典蓝牙要快很多很多。但是又需要实时交换数据(就是server和client需要互相实时交换数据)。这就必须要保持一个socket长连接(client和server一直连接不断开)。

TCP Socket

Socket和Http都是建立在对tcp/ip的封装,http是一种基于tcp/ip的一种通信协议,而socket只是单纯的对tcp/ip的接口封装,称之为套接字。

JAVA中用ServerSocket来创建一个Socket服务。

port //监听的端口号
backlog //请求队列最大值
InetAddress //服务的地址
ServerSocket server = new ServerSocket(Port,backlog,InetAddress);
//开始监听请求
server.accept(); //返回一个Socket实例

客户端就使用Socket来创建请求。

//host 服务器地址
//port 服务器端口
//stream socket的类型,流和数据报类型,true是流,false是数据报
Socket client = new Socket(host,port,stream);

建立连接后,就可以获取socket中的OutputStream和InputStream,从而进行数据交换。

长连接

在一般情况下,客户端和服务端在完成各自的数据交换后,就会各自释放socket和占用的计算机资源(对内存,栈内存,线程等)。而且每次进行数据交换必须是客户端先发起连接请求,服务端回复。但是在某些应用场景或需求下,这种模式就不再适用了。如即时通讯,天气更新等,这种场景下,服务器一旦有数据更新,就必须及时告诉客户端,但是客户端何时发起请求,服务端不清楚,也就不能及时告诉客户端。于是聪明的开发者就采用遍历的方式,即针对数据实时的api进行间隔一定时间段去向服务器发出请求。但是这样就很消耗计算机资源,而且每次建立连接需要TCP三次握手,而且很多时候服务器并没有更新数据,如此服务器必须建立连接并且处理,当请求过载的时候,服务器就会很慢,从而造成服务器死机,服务不可用。客户端做无用功,浪费用户流量,体验差。那么如何更好的满足上面需求呢?伟大的程序员就说:只要建立连接后不断开就可以呀!真的是菜如狗。伟大的程序员说的就是长连接了。 建立连接容易,可是维护一个长连接就不简单。

客户端和服务端一旦建立了连接,任何一方都可能出现异常断开(如断电,死机,网络掉线,进程崩溃,有些防火墙会关闭长时间没有数据交流的连接等等)。当任何一方出现异常断开的时候,而另一方却不知道连接已断开,因此也不会释放其所占用的计算机资源(站着茅坑不拉屎),也就无法重新就建立了,所以需要一种机制来判断连接是否还保持。而这种机制就是:心跳机制。

心跳机制

心跳机制就是问答策略:客户端或者服务端在间隔一定时间段后去询问对方,对方收到就会回答。如果在一定时间内没有收到回复就再去尝试几次询问,如果一直没有回复就是说明连接已断开,然后各自释放资源,重新建立长连接。目前我所知道有以下两种方式。

  • TCP协议自带 keep-alive功能。
  • 用户自己实现。

Keep-Alive

KeepAlive:建立连接后,TCP每隔一段时间发送探测的数据到服务器,服务器在连接的情况下就回复一个数据给客户端。如客户端没有收到回复,就会再发几次探测数据,以确定连接是否真的断开。优点:

  1. 探测和回复的数据包异常的少
  2. 不要程序去额外的进行维护

TCP对其默认是关闭的,需要代码设置。有关KeepAlive还有其他几个属性:

int keepIdle;   // 在idle时间内没有数据交换 才发送  
int keepInterval   //时间间隔 
int keepCount   //探测的次数

在android系统中可以使用Adb shell命令进行查看。

adb shell
cat /proc/sys/net/ipv4/tcp_KeepAlive_time //7200s
cat /proc/sys/net/ipv4/tcp_KeepAlive_intvl  //75s
cat /proc/sys/net/ipv4/tcp_KeepAlive_probes //9次

同以上这些属性可以看出KeepAlive的缺点:

  • 应用层无法更细粒度的控制。
  • 保持的时间有限是2个小时
  • 要满足一定时间间隔才能发送探测包 75s对于有些应用太长。
  • 仅仅只能检测出链接是否可用,却检测不到对方是否运行良好(如进程死锁,堵塞,进程还能否进程业务处理)。
  • 在某种情况下运营商会过滤你的KeepAlive包(这个是在知乎看到的,真假不知道)

所以在必要的情况下,我们需要自己实现一种心跳机制。

自定义心跳

自定义心跳:和KeepAlive的逻辑一样。不同的是应用层自己实现心跳不依赖传输层。 优点:

  • 可以设置时间间隔,精确到ms级别 也是可以的。
  • 可以自定义探测数据和回复数据
  • 应用层可以更好方便控制粒度。
  • 不仅可以检测连接是否正常,还能知道对方进程是否允许良好。
  • 运营商肯定不会过滤掉。
  • 可以通过socket进行其他一些数据交换。

缺点:

  • 需要主动去维护心跳,添加开发的负担。
  • 需要额外处理心跳逻辑,增加服务器和客户端的压力。

就目前很多应用都是采用自定义心跳的方式来实现长连接的。只是每个应用的的实现上或许有些区别,但是大部分逻辑上都却区别不大。下面是我简单用代码模拟的心跳实现,具体在开发的时候,你需要考虑心跳包的大小,内容,时间间隔,线程的使用,锁机制等等。以下是git的连接:https://github.com/GithubRyze/JavaHeartRate.git

代码中有些地方还是需要进一步的处理,细节方面也没有去仔细处理!!

秋名山上行人希,常有高手论高低,如今道路依旧在,不见当年老司机!!

参与评论