客户端会话（ClientSession）是会话接口（Session）的子集。集群客户端会连接多个地址，内部会有多个会话，有些方法不便使用，所以有了 ClientSession 这个子集。非集群模式下，可以强转为 Session。

但因为侧端不同，客户端有自己的一些隐式特点

### 1、客户端会话

* ClientSession 关闭

| 编码 | 描述       | 说明 |
| -------- | -------- | -------- |
| 1000     | 因协议关闭开始（安全关闭）     |  “本端”可以再发消息。心跳继续。但不建议再使用    |
| 1001     | 因协议指令关闭     | “本端”再发消息会尝试重连。心跳继续     |
| 1002     | 因协议非法关闭     | 同上     |
| 1002     | 因协议非法关闭     | 同上    |
| 2001     | 因异常关闭     | 同上    |
| 2002     | 因重连关闭     | 同上    |
| 2008     | 因打开失败关闭     | 同上    |
| 2009     | 因用户主动关闭（不可再重连）     | 给“对端”发送关闭协议帧（成功的话会触发关闭事件）。心跳中止。<br/>“本端”再发消息会异常。需要手动重连后才能再发    |

如果是服务端的 Sessoin 没有心跳和重连。其它一样。

* ClientSession 心跳与自动重连

socket.d 是个长连接协议，通过心跳与自动重连保证连接的可靠性。心跳，客户端会定时发送 Ping 协议帧（默认为20秒，可配置）。服务端收到后会答复 Pong 协议帧。

心跳时，如果发现连接是无效的会尝试重新重连。这个也称为“自动重连”。

```java
//配置心跳间隔（单位毫秒）。//更多配置参考“配置类”
ClientSession session = SocketD.createClient("sd:ws://127.0.0.1:8602/?u=a&p=2")
        .config(c->c.heartbeatInterval(20_000)) 
        .open();
```

* ClientSession 手动重连与自动重连的区别

自动重连，是由心跳机制触发的行为。在主动调用 `session.close()` 心跳会取消，不再支持自动重连。如果还需要再连接或发送消息，需要主动调用 `session.reconnect()`。

手动重连，还可以应用在关闭事件里。在关闭时异步尝试重连（能连上去的机率比较低）。


```java
//手动重连应用1（能连上去的机率比较低。刚断开，环境可能不未恢复）
ClientSession sessoin = SocketD.createClient("sd:ws://127.0.0.1:8602/?u=a&p=2")
        .listen(new EventListener().doOnClose((s) -> {
            RunUtils.delay(s::reconnect, 1000);//1秒后尝试一次重连。
        }))
        .open();
        
//手动重连应用2（做单元测试经常会用）
session.close();
session.reconnect();
```

### 2、客户端会话接口

* ClientSession 完整接口（以 Java 为例）

```
/**
 * 客户会话
 */
public interface ClientSession extends Closeable {
    /**
     * 是否有效
     */
    boolean isValid();
    
    /**
     * 是否关闭中
     * */
    boolean isClosing();

    /**
     * 获取会话Id
     */
    String sessionId();

    /**
     * 手动重连（一般是自动）
     */
    void reconnect() throws IOException;

    /**
     * 发送
     *
     * @param event  事件
     * @param entity 实体
     * @return 流
     */
    SendStream send(String event, Entity entity) throws IOException;

    /**
     * 发送并请求
     *
     * @param event  事件
     * @param entity 实体
     * @return 流
     */
    default RequestStream sendAndRequest(String event, Entity entity) throws IOException {
        return sendAndRequest(event, entity, 0L);
    }

    /**
     * 发送并请求
     *
     * @param event   事件
     * @param entity  实体
     * @param timeout 超时（单位：毫秒）
     * @return 流
     */
    RequestStream sendAndRequest(String event, Entity entity, long timeout) throws IOException;


    /**
     * 发送并订阅（答复结束之前，不限答复次数）
     *
     * @param event  事件
     * @param entity 实体
     * @return 流
     */
    default SubscribeStream sendAndSubscribe(String event, Entity entity) throws IOException {
        return sendAndSubscribe(event, entity, 0L);
    }

    /**
     * 发送并订阅（答复结束之前，不限答复次数）
     *
     * @param event   事件
     * @param entity  实体
     * @param timeout 超时（单位：毫秒）
     * @return 流
     */
    SubscribeStream sendAndSubscribe(String event, Entity entity, long timeout) throws IOException;
    
    /**
     * 预关闭（用于两段式关闭，执行后对端的 session.isClosing() 为 true）
     */
    void preclose() throws IOException;
    
    /**
     * 关闭
     */
    void close() throws IOException;
}

```
