我想我已经观察到ChannelId随时间变化。这是正常行为吗?

这就是我认为的原因:

我正在编程一个游戏服务器,其中的连接持续很长时间。我想引用Channel,所以我将它们包装在Player对象中,并将ChannelId.asLongText()作为键映射到HashMap中。这是HashMap:

private HashMap<String, Player> players = new HashMap<String, Player>();

public void registerPlayer(Channel c, String name, String password) {
    Player p = new Player(c, name, password);
    players.put(c.id().asLongText(), p);

    try {
        Future<CredentialCheckResult> future = workerTasks.submit(new AuthenticateCredentialsTask(context.getDatabase().getConnection(), c.id().asLongText(), name, password));
        loginFutures.add(future);
    } catch (SQLException e) {
        System.out.println("["+Thread.currentThread().getName()+"] "+e.getMessage());
        return;
    }
    System.out.println("["+Thread.currentThread().getName()+"] Registered and added worker for logging in "+name+" ("+c.id().asLongText()+") \t\t total players = "+players.size());
}

public void unregisterPlayer(String id) {
    System.out.println("["+Thread.currentThread().getName()+"] Unregistering player ("+id+") \t\t total players = "+(players.size()-1));
    Player p = players.get(id);
    players.remove(id);

    if (p == null) {
        System.out.println("["+Thread.currentThread().getName()+"] P WAS NULL ("+id+") \t\t total players = "+players.size());
    }

    if (p.getChannel().isActive()) {
        p.getChannel().close();
    }
}


register和unregister方法是从另一个线程(称为GameEngine)运行的。 HashMap仅被该线程触摸。

请牢记以下是我的ServerHandler的外观:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    GameEngine game = context.getGameService();
    Packet p = (Packet) msg;

    if (p instanceof NewConnectionPacket) {
        NewConnectionPacket packet = (NewConnectionPacket) msg;
        game.queueLogic(new Runnable() {
            @Override
            public void run() {
                game.registerPlayer(ctx.channel(), packet.getUsername(), packet.getPassword());
            }
        });
    }
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    context.getGameService().queueLogic(new Runnable() {
        @Override
        public void run() {
            context.getGameService().unregisterPlayer(ctx.channel().id().asLongText());
        }
    });
    System.out.println("["+Thread.currentThread().getName()+"] "+" Kicked for inactive channel: "+ctx.channel().id().asLongText());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    System.err.println("["+Thread.currentThread().getName()+"] "+cause.getMessage());
    ctx.close();
}


GameEngine.queueLogic(Runnable)是线程安全的,并且仅将GameEngine线程的工作排队。

当我运行服务器并登录(对所有用户使用相同的凭据)几个用户,并迅速在客户端断开他们的连接时,服务器似乎无法从HashMap正确注销Player,因为是玩家。 get(ChannelId.asLongText())将返回null。

以下是发生这种情况的会话日志:

    May 21, 2018 10:33:23 PM com.mchange.v2.log.MLog
INFO: MLog clients using java 1.4+ standard logging.
May 21, 2018 10:33:23 PM com.mchange.v2.c3p0.C3P0Registry
INFO: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
[main] GameEngine starting up...
May 21, 2018 10:33:44 PM com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
INFO: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hge0wf9v10qklm1j8rb36|3b94d659, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge0wf9v10qklm1j8rb36|3b94d659, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/onlinerp, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
Mon May 21 22:33:44 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Mon May 21 22:33:44 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Mon May 21 22:33:44 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
[GameEngine] Registered and added worker for logging in mads (cc2f71fffe2d38f4-0000287c-00000001-4b49379c0cc5629d-2b8fd125)          total players = 1
[pool-1-thread-1] Looking up SQL data on player mads
[pool-1-thread-1] Finished looking up SQL data on player mads, result was VERIFIED
[GameEngine] Logged in succesfully mads
[GameEngine] Registered and added worker for logging in mads (cc2f71fffe2d38f4-0000287c-00000002-777e5a8cccc572e5-65acb968)          total players = 2
[pool-1-thread-1] Looking up SQL data on player mads
[pool-1-thread-1] Finished looking up SQL data on player mads, result was VERIFIED
[GameEngine] Logged in succesfully mads
[GameEngine] Registered and added worker for logging in mads (cc2f71fffe2d38f4-0000287c-00000003-22a191d14cc5787b-f58eab6f)          total players = 3
[pool-1-thread-1] Looking up SQL data on player mads
[pool-1-thread-1] Finished looking up SQL data on player mads, result was VERIFIED
[nioEventLoopGroup-3-3] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-3]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000003-22a191d14cc5787b-f58eab6f
[GameEngine] Unregistering player (cc2f71fffe2d38f4-0000287c-00000003-22a191d14cc5787b-f58eab6f)         total players = 2
Mon May 21 22:33:51 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Mon May 21 22:33:51 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Mon May 21 22:33:51 CEST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
[GameEngine] Registered and added worker for logging in mads (cc2f71fffe2d38f4-0000287c-00000004-edfb71804cc58079-6c340605)          total players = 3
[pool-1-thread-1] Looking up SQL data on player mads
[pool-1-thread-1] Finished looking up SQL data on player mads, result was VERIFIED
[nioEventLoopGroup-3-5] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-5]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000005-50468ea44cc5825e-52f26a37
[GameEngine] Unregistering player (cc2f71fffe2d38f4-0000287c-00000005-50468ea44cc5825e-52f26a37)         total players = 2
[GameEngine] P WAS NULL (cc2f71fffe2d38f4-0000287c-00000005-50468ea44cc5825e-52f26a37)       total players = 3
Exception in thread "GameEngine" java.lang.NullPointerException
    at com.hydrozoa.onlinerp_server.game.GameEngine.unregisterPlayer(GameEngine.java:89)
    at com.hydrozoa.onlinerp_server.net.RPServerHandler$2.run(RPServerHandler.java:49)
    at com.hydrozoa.onlinerp_server.game.GameEngine.run(GameEngine.java:128)
    at java.base/java.lang.Thread.run(Unknown Source)
[nioEventLoopGroup-3-4] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-4]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000004-edfb71804cc58079-6c340605
[nioEventLoopGroup-3-6] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-6]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000006-c3bd154facc587a0-3ef4f4b5
[nioEventLoopGroup-3-1] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-1]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000009-066e3818acc592cb-5618df97
[nioEventLoopGroup-3-8] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-8]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000008-65235126acc58ed4-10dc8b8f
[nioEventLoopGroup-3-2] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-2]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-0000000a-6034ea9b2cc59711-5b48e9f0
[nioEventLoopGroup-3-7] An existing connection was forcibly closed by the remote host
[nioEventLoopGroup-3-7]  Kicked for inactive channel: cc2f71fffe2d38f4-0000287c-00000007-693ab465acc58a8a-8251ba2a


我也尝试过使用ChannelId对象作为键。我觉得我为此感到无所适从。

我正在为服务器使用NioServerSocketChannel.class,为客户端使用NioSocketChannel.class。

如何以不随时间变化的方式引用渠道?

顺便说一句,我使用的是Netty 4.1.25,这是我到目前为止所能找到的最新版本。

最佳答案

事实证明,在所有情况下,我都试图从HashMap中删除Player对象,从而犯了一个错误。

客户端可能会连接,然后闲置(由于登录数据包发生注册,导致它们未被注册),最后断开连接。那就是发生了什么。

我的错。

总而言之,ChannelId似乎可以安全使用,并且据我所知,它不会随时间变化。

10-01 03:13