从jedis源码中学习java socket client端的基本用法
Jedis
在很多教材或者教程上,通常都是很简单的一个例子来演示如何使用Java进行TCP通讯.在这款广泛被使用的开源组件中,我们能够更好的学习到一个企业级的组件在TCP连接的处理上,更应该关注哪些方面.有哪些是我们应该掌握或者了解的TCP知识.TCP协议本身相当复杂,我们做应用的可以先从应用层需要用到的相关知识开始了解.
jedis中,与redis服务端建立连接的代码在Connection这个类中.
public void connect() { if (!isConnected()) { try { socket = new Socket(); // ->@wjw_add socket.setReuseAddress(true); socket.setKeepAlive(true); // Will monitor the TCP connection is // valid socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to // ensure timely delivery of data socket.setSoLinger(true, 0); // Control calls close () method, // the underlying socket is closed // immediately // <-@wjw_add socket.connect(new InetSocketAddress(host, port), connectionTimeout); socket.setSoTimeout(soTimeout); if (ssl) { if (null == sslSocketFactory) { sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); } socket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, true); if (null != sslParameters) { ((SSLSocket) socket).setSSLParameters(sslParameters); } if ((null != hostnameVerifier) && (!hostnameVerifier.verify(host, ((SSLSocket) socket).getSession()))) { String message = String.format( "The connection to '%s' failed ssl/tls hostname verification.", host); throw new JedisConnectionException(message); } } outputStream = new RedisOutputStream(socket.getOutputStream()); inputStream = new RedisInputStream(socket.getInputStream()); } catch (IOException ex) { broken = true; throw new JedisConnectionException("Failed connecting to host " + host + ":" + port, ex); } } } public boolean isConnected() { return socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected() && !socket.isInputShutdown() && !socket.isOutputShutdown(); }
通过以上代码,可以看到用到了jdk中相关的Socket对象来与redis服务进行tcp连接.重点看几个方法
setReuseAddress(true) setKeepAlive(true) setTcpNoDelay(true) setSoLinger(true, 0) setSoTimeout(soTimeout)
Socket
setReuseAddress
要解释这个方法的作用,首先要了解TCP套接字的一个状态-time-wait.
众所周知,TCP的连接建立和断开要经历三次握手和四次挥手.
如图所示,
而time-wait这个状态就是连接发起方,通常就是client,在收到服务器端的FIN包后进入的状态.那为什么这个状态需要存在呢?客户端收到服务端发送的FIN包后,会向服务端发送一个ACK包,服务端接收到ACK包后进入CLOSED状态.但是由于网络或者其他问题,导致服务端有可能没有收到ACK包,导致服务器端重发FIN包.
time-wait这个需要保持多久呢?大概是2MSL(最大分段生存期).
MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。
了解了这些之后,我们可以看看这个方法的内部实现
public void setReuseAddress(boolean on) throws SocketException { if (isClosed()) throw new SocketException("Socket is closed"); getImpl().setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(on)); }
我们看到,它是对SO_REUSEADDR这个套接字选项进行了设置.
这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时 SO_REUSEADDR 选项非常有用。
对应linux操作系统响应的配置为
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭 net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
setKeepAlive
心跳检测是我们用来判断后端程序是否依旧正常服务的一个常用手段.TCP协议提供keepalive机制来监测TCP连接是否已经断开.
net.ipv4.tcp_keepalive_intvl = 20 重发keepalive包间隔 net.ipv4.tcp_keepalive_probes = 3 重发次数 net.ipv4.tcp_keepalive_time = 60 超过一段时间没有进行数据传输则进行心跳监测
setTcpNoDelay
要了解这个方法的作用,首先要知道TCP协议中一个注明的算法-Nagle算法.Nagle算法的初衷是避免小包拥塞网络.比如只需要发送一个字节的数据,可是由于TCP包本身就占用了几十个字节,这样是比较浪费网络资源的.Nagle算法导致了要么是等待ACK到达或者缓冲区满,才会发送新的数据.
再介绍一下TCP-Delayed-ACK ,它是将ACK和响应数据绑在一个包中发送,降低协议开销.
如果客户端采用了Nagle算法和服务端采用了Delayed-ACK,同时客户端采用write-write-read模式,就很容易造成客户端与服务端都在等待对方数据的问题.
Delayed Ack 是有个超时机制的,而默认的超时正好就是40ms.
这里有一篇文章很好的介绍了write-write-read这种问题
setTcpNoDelay其实就是讲Nagle算法关闭,使得每次无论数据包有多大都会被立即发送.
setSoLinger
SO_LINGER选项用来控制Socket关闭时的行为,默认情况下,执行Socket的close方法,该方法会立即返回,但底层的Socket实际上并不会立即关闭,他会立即延迟一段时间,知道发送完剩余的数据,才会真正的关闭Socket,断开连接。
setSoLinger(true, 0)
执行该方法,那么执行Socket的close方法,该方法也会立即返回,但底层的Socket也会立即关闭,所有未发送完的剩余数据被丢弃.
setSoTimeOut
官方文档中解释
启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。
将此选项设为非零的超时值时,在与此 Socket 关联的 InputStream 上调用 read() 将只阻塞此时间长度。如果超过超时值,将引发java.net.SocketTimeoutException,虽然 Socket
仍旧有效。选项必须在进入阻塞操作前被启用才能生效。超时值必须是 > 0 的数。超时值为 0 被解释为无穷大超时值。
参数:
timeout - 指定的以毫秒为单位的超时值。
抛出:
SocketException -
如果底层协议出现错误,例如 TCP 错误。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java之扫描包里面的class文件
一、class作为,编译过后的产物,在很多时候,我们需要通过反射去执行class的具体方法。但是扫描class就是一个很大的问题了。 二、所以我这里写了一个简单的class文件扫描方式。 三、主要是利用ClassLoader中能够通过包铭去需要目录的绝对路径特性,写的 四、例子: /** * 提供直接调用的方法 * @param packageName * @return * @throws IOException * @throws ClassNotFoundException */ public static List<Class> findClass(String packageName) throws IOException, ClassNotFoundException { return findClass(packageName, new ArrayList<>()); } /** * * @param packageName * @param clazzs * @return * @throws ClassNotFoundException * @...
- 下一篇
缓存穿透、并发和雪崩那些事
0 题记 缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本文讲解其产生原因和解决方案。 缓存穿透通常是由恶意攻击或者无意造成的;缓存并发是由设计不足造成的;缓存雪崩是由缓存同时失效造成的,三种问题都比较典型,也是难以防范和解决的。本节给出通用的解决方案,以供在缓存设计的过程中参考和使用。 1 缓存穿透 缓存穿透指的是使用不存在的key进行大量的高并发查询,这导致缓存无法命中,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,甚至使数据库服务被压死。 我们通常将空值缓存起来,再次接收到同样的查询请求时,若命中缓存并且值为空,就会直接返回,不会透传到数据库,避免缓存穿透。当然,有时恶意袭击者可以猜到我们使用了这种方案,每次都会使用不同的参数来查询,这就需要我们对输入的参数进行过滤,例如,如果我们使用ID进行查询,则可以对ID的格式进行分析,如果不符合产生ID的规则,就直接拒绝,或者在ID上放入时间信息,根据时间信息判断ID是否合法,或者是否是我们曾经生成的ID,这样可以拦截一定的无效请求。 当然,每个设计人员都应该对服务的可用性和健壮性负责,应该建设健壮的服务...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7设置SWAP分区,小内存服务器的救世主
- Linux系统CentOS6、CentOS7手动修改IP地址
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Hadoop3单机部署,实现最简伪集群
- Mario游戏-低调大师作品