从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 错误。

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
目录
相关文章
|
22天前
|
JavaScript 前端开发 Java
JS中和java语法相近的用法和语句
JS中和java语法相近的用法和语句
14 1
|
1月前
|
Java
Java中内部类的介绍及其用法
Java中内部类的介绍及其用法
|
1月前
|
Java
java代码块的用法
java代码块的用法
|
1月前
|
存储 Java
JAVA中静态成员变量的用法
JAVA中静态成员变量的用法
|
1月前
|
前端开发 Java
Java中StringUtils这个工具类中:isEmpty 和 isBlank 的用法区别
Java中StringUtils这个工具类中:isEmpty 和 isBlank 的用法区别
33 0
|
1月前
|
Java 程序员 容器
Java 8 特性之Optional用法详解
NPE(NullPointerException)一直是Java程序员最深恶痛绝的异常,冗长的非空校验使代码的可读性和优雅性都大大降低。那么本篇文章,笔者将详细介绍一下Java 8 引入的特性--*Optional*类,看看它是如何避免空指针异常的。
50 0
|
1月前
|
Java
Java中RoundingMode枚举类的详细用法解析
Java中RoundingMode枚举类的详细用法解析
35 0
|
1月前
|
Java
Java equals、== 区别与用法
Java equals、== 区别与用法
20 0
|
2月前
|
供应链 数据可视化 搜索推荐
保姆级教程,彻底搞懂Java继承的五种用法
保姆级教程,彻底搞懂Java继承的五种用法
|
2月前
|
算法 安全 Java
【JavaSE专栏47】Java常用类Collections解析,你了解JAVA集合类的通用用法吗?
【JavaSE专栏47】Java常用类Collections解析,你了解JAVA集合类的通用用法吗?
推荐文章
更多