/**
  * Cancel the statement with the given id.
  *
  * @param id the statement id
  */
 public void cancelStatement(int id) {
   for (Transfer transfer : transferList) {
     try {
       Transfer trans = transfer.openNewConnection();
       trans.init();
       trans.writeInt(clientVersion);
       trans.writeInt(clientVersion);
       trans.writeString(null);
       trans.writeString(null);
       trans.writeString(sessionId);
       trans.writeInt(SessionRemote.SESSION_CANCEL_STATEMENT);
       trans.writeInt(id);
       trans.close();
     } catch (IOException e) {
       trace.debug(e, "could not cancel statement");
     }
   }
 }
 private Transfer initTransfer(ConnectionInfo ci, String db, String server) throws IOException {
   Socket socket = NetUtils.createSocket(server, Constants.DEFAULT_TCP_PORT, ci.isSSL());
   Transfer trans = new Transfer(this);
   trans.setSocket(socket);
   trans.setSSL(ci.isSSL());
   trans.init();
   trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
   trans.writeInt(Constants.TCP_PROTOCOL_VERSION_15);
   trans.writeString(db);
   trans.writeString(ci.getOriginalURL());
   trans.writeString(ci.getUserName());
   trans.writeBytes(ci.getUserPasswordHash());
   trans.writeBytes(ci.getFilePasswordHash());
   String[] keys = ci.getKeys();
   trans.writeInt(keys.length);
   for (String key : keys) {
     trans.writeString(key).writeString(ci.getProperty(key));
   }
   try {
     done(trans);
     clientVersion = trans.readInt();
     trans.setVersion(clientVersion);
     if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_14) {
       if (ci.getFileEncryptionKey() != null) {
         trans.writeBytes(ci.getFileEncryptionKey());
       }
     }
     trans.writeInt(SessionRemote.SESSION_SET_ID);
     trans.writeString(sessionId);
     done(trans);
     if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_15) {
       autoCommit = trans.readBoolean();
     } else {
       autoCommit = true;
     }
     return trans;
   } catch (DbException e) {
     trans.close();
     throw e;
   }
 }
  @Override
  public void run() {
    try {
      transfer.init();
      trace("Connect");
      // TODO server: should support a list of allowed databases
      // and a list of allowed clients
      try {
        // 如果没有加-tcpAllowOthers参数,那么只接受本地连接
        if (!server.allow(transfer.getSocket())) {
          throw DbException.get(ErrorCode.REMOTE_CONNECTION_NOT_ALLOWED);
        }
        int minClientVersion = transfer.readInt();
        if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) {
          throw DbException.get(
              ErrorCode.DRIVER_VERSION_ERROR_2,
              "" + clientVersion,
              "" + Constants.TCP_PROTOCOL_VERSION_6);
        } else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_12) {
          throw DbException.get(
              ErrorCode.DRIVER_VERSION_ERROR_2,
              "" + clientVersion,
              "" + Constants.TCP_PROTOCOL_VERSION_12);
        }
        int maxClientVersion = transfer.readInt();
        if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_12) {
          clientVersion = Constants.TCP_PROTOCOL_VERSION_12;
        } else {
          clientVersion = minClientVersion;
        }
        transfer.setVersion(clientVersion);
        String db = transfer.readString();
        String originalURL = transfer.readString();
        if (db == null && originalURL == null) {
          String targetSessionId = transfer.readString();
          int command = transfer.readInt();
          stop = true;
          if (command == SessionRemote.SESSION_CANCEL_STATEMENT) {
            // cancel a running statement
            int statementId = transfer.readInt();
            server.cancelStatement(targetSessionId, statementId);
          } else if (command == SessionRemote.SESSION_CHECK_KEY) {
            // check if this is the correct server
            db = server.checkKeyAndGetDatabaseName(targetSessionId);
            if (!targetSessionId.equals(db)) {
              transfer.writeInt(SessionRemote.STATUS_OK);
            } else {
              transfer.writeInt(SessionRemote.STATUS_ERROR);
            }
          }
        }
        // 启动TcpServer时加"-baseDir"或者像这样System.setProperty("h2.baseDir", "E:\\H2\\baseDir")
        String baseDir = server.getBaseDir();
        if (baseDir == null) {
          baseDir = SysProperties.getBaseDir();
        }
        // 例如启动TcpServer时,指定了"-key mydb mydatabase",
        // 如果db变量是mydb,那么实际上就是mydatabase,相当于做一次映射
        // 如果db变量不是mydb,那么抛错: org.h2.jdbc.JdbcSQLException: Wrong user name or password [28000-170]
        db = server.checkKeyAndGetDatabaseName(db);
        ConnectionInfo ci = new ConnectionInfo(db);

        ci.setOriginalURL(originalURL);
        ci.setUserName(transfer.readString());
        // password参数的值已经转换成userPasswordHash和filePasswordHash了,
        // 不能由userPasswordHash和filePasswordHash得到原始的password
        ci.setUserPasswordHash(transfer.readBytes());
        ci.setFilePasswordHash(transfer.readBytes()); // 只有指定"CIPHER"参数时filePasswordHash才是非null的
        int len = transfer.readInt();
        for (int i = 0; i < len; i++) {
          ci.setProperty(transfer.readString(), transfer.readString());
        }
        // override client's requested properties with server settings
        if (baseDir != null) {
          ci.setBaseDir(baseDir);
        }
        if (server.getIfExists()) {
          // 启动TcpServer时加"-ifExists",限制只有数据库存在时客户端才能连接,也就是不允许在客户端创建数据库
          ci.setProperty("IFEXISTS", "TRUE");
        }
        session = Engine.getInstance().createSession(ci);
        transfer.setSession(session);
        transfer.writeInt(SessionRemote.STATUS_OK);
        transfer.writeInt(clientVersion);
        transfer.flush();
        // 每建立一个新的Session对象时,把它保存到内存数据库management_db_9092的SESSIONS表
        server.addConnection(threadId, originalURL, ci.getUserName());
        trace("Connected");
      } catch (Throwable e) {
        sendError(e);
        stop = true;
      }
      while (!stop) {
        try {
          process();
        } catch (Throwable e) {
          sendError(e);
        }
      }
      trace("Disconnect");
    } catch (Throwable e) {
      server.traceError(e);
    } finally {
      close();
    }
  }