fix(multiplayer): show connection timeout instead of kicked.

This commit is contained in:
huanghongxun 2021-10-10 21:51:36 +08:00
parent fca78cf419
commit 0fd8a78972
8 changed files with 69 additions and 12 deletions

View File

@ -29,6 +29,7 @@ public final class MultiplayerChannel {
@JsonType( @JsonType(
property = "type", property = "type",
subtypes = { subtypes = {
@JsonSubtype(clazz = HandshakeRequest.class, name = "handshake"),
@JsonSubtype(clazz = JoinRequest.class, name = "join"), @JsonSubtype(clazz = JoinRequest.class, name = "join"),
@JsonSubtype(clazz = KeepAliveRequest.class, name = "keepalive") @JsonSubtype(clazz = KeepAliveRequest.class, name = "keepalive")
} }
@ -36,6 +37,9 @@ public final class MultiplayerChannel {
public static class Request { public static class Request {
} }
public static class HandshakeRequest extends Request {
}
public static class JoinRequest extends Request { public static class JoinRequest extends Request {
private final String clientVersion; private final String clientVersion;
private final String username; private final String username;
@ -69,6 +73,7 @@ public final class MultiplayerChannel {
@JsonType( @JsonType(
property = "type", property = "type",
subtypes = { subtypes = {
@JsonSubtype(clazz = HandshakeResponse.class, name = "handshake"),
@JsonSubtype(clazz = JoinResponse.class, name = "join"), @JsonSubtype(clazz = JoinResponse.class, name = "join"),
@JsonSubtype(clazz = KeepAliveResponse.class, name = "keepalive"), @JsonSubtype(clazz = KeepAliveResponse.class, name = "keepalive"),
@JsonSubtype(clazz = KickResponse.class, name = "kick") @JsonSubtype(clazz = KickResponse.class, name = "kick")
@ -78,6 +83,9 @@ public final class MultiplayerChannel {
} }
public static class HandshakeResponse extends Response {
}
public static class JoinResponse extends Response { public static class JoinResponse extends Response {
private final int port; private final int port;

View File

@ -20,12 +20,14 @@ package org.jackhuang.hmcl.ui.multiplayer;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.*; import java.io.*;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.util.TimerTask;
import java.util.logging.Level; import java.util.logging.Level;
import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*; import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*;
@ -36,10 +38,12 @@ public class MultiplayerClient extends Thread {
private final int port; private final int port;
private int gamePort; private int gamePort;
private boolean connected = false;
private final EventManager<ConnectedEvent> onConnected = new EventManager<>(); private final EventManager<ConnectedEvent> onConnected = new EventManager<>();
private final EventManager<Event> onDisconnected = new EventManager<>(); private final EventManager<Event> onDisconnected = new EventManager<>();
private final EventManager<Event> onKicked = new EventManager<>(); private final EventManager<Event> onKicked = new EventManager<>();
private final EventManager<Event> onHandshake = new EventManager<>();
public MultiplayerClient(String id, int port) { public MultiplayerClient(String id, int port) {
this.id = id; this.id = id;
@ -66,7 +70,11 @@ public class MultiplayerClient extends Thread {
} }
public EventManager<Event> onKicked() { public EventManager<Event> onKicked() {
return onDisconnected; return onKicked;
}
public EventManager<Event> onHandshake() {
return onHandshake;
} }
@Override @Override
@ -80,15 +88,25 @@ public class MultiplayerClient extends Thread {
MultiplayerServer.Endpoint endpoint = new MultiplayerServer.Endpoint(socket, writer); MultiplayerServer.Endpoint endpoint = new MultiplayerServer.Endpoint(socket, writer);
LOG.info("Connected to 127.0.0.1:" + port); LOG.info("Connected to 127.0.0.1:" + port);
writer.write(JsonUtils.UGLY_GSON.toJson(new JoinRequest(MultiplayerManager.CATO_VERSION, id))); endpoint.write(new HandshakeRequest());
writer.newLine(); endpoint.write(new JoinRequest(MultiplayerManager.CATO_VERSION, id));
writer.flush();
LOG.fine("Sent join request with id=" + id); LOG.fine("Sent join request with id=" + id);
keepAliveThread = new KeepAliveThread(endpoint); keepAliveThread = new KeepAliveThread(endpoint);
keepAliveThread.start(); keepAliveThread.start();
TimerTask task = Lang.setTimeout(() -> {
// If after 15 seconds, we didn't receive the HandshakeResponse,
// We fail to establish the connection with server.
try {
socket.close();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to close socket", e);
}
}, 15 * 1000);
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (isInterrupted()) { if (isInterrupted()) {
@ -102,14 +120,21 @@ public class MultiplayerClient extends Thread {
if (response instanceof JoinResponse) { if (response instanceof JoinResponse) {
JoinResponse joinResponse = JsonUtils.fromNonNullJson(line, JoinResponse.class); JoinResponse joinResponse = JsonUtils.fromNonNullJson(line, JoinResponse.class);
setGamePort(joinResponse.getPort()); setGamePort(joinResponse.getPort());
connected = true;
onConnected.fireEvent(new ConnectedEvent(this, joinResponse.getPort())); onConnected.fireEvent(new ConnectedEvent(this, joinResponse.getPort()));
LOG.fine("Received join response with port " + joinResponse.getPort()); LOG.fine("Received join response with port " + joinResponse.getPort());
} else if (response instanceof KickResponse) { } else if (response instanceof KickResponse) {
onKicked.fireEvent(new Event(this));
LOG.fine("Kicked by the server"); LOG.fine("Kicked by the server");
onKicked.fireEvent(new Event(this));
return;
} else if (response instanceof KeepAliveResponse) { } else if (response instanceof KeepAliveResponse) {
} else if (response instanceof HandshakeResponse) {
LOG.fine("Established connection with server");
onHandshake.fireEvent(new Event(this));
task.cancel();
} else { } else {
LOG.log(Level.WARNING, "Unrecognized packet from server:" + line); LOG.log(Level.WARNING, "Unrecognized packet from server:" + line);
} }
@ -135,6 +160,10 @@ public class MultiplayerClient extends Thread {
onDisconnected.fireEvent(new Event(this)); onDisconnected.fireEvent(new Event(this));
} }
public boolean isConnected() {
return connected;
}
private static class KeepAliveThread extends Thread { private static class KeepAliveThread extends Thread {
private final MultiplayerServer.Endpoint endpoint; private final MultiplayerServer.Endpoint endpoint;

View File

@ -131,10 +131,6 @@ public final class MultiplayerManager {
session.addRelatedThread(client); session.addRelatedThread(client);
session.setClient(client); session.setClient(client);
if (handler != null) {
handler.onWaitingForJoinResponse();
}
TimerTask task = Lang.setTimeout(() -> { TimerTask task = Lang.setTimeout(() -> {
future.completeExceptionally(new JoinRequestTimeoutException()); future.completeExceptionally(new JoinRequestTimeoutException());
session.stop(); session.stop();
@ -162,6 +158,17 @@ public final class MultiplayerManager {
session.stop(); session.stop();
task.cancel(); task.cancel();
}); });
client.onDisconnected().register(disconnectedEvent -> {
if (!client.isConnected()) {
// We fail to establish connection with server
future.completeExceptionally(new ConnectionErrorException());
}
});
client.onHandshake().register(handshakeEvent -> {
if (handler != null) {
handler.onWaitingForJoinResponse();
}
});
client.start(); client.start();
}); });
@ -464,4 +471,7 @@ public final class MultiplayerManager {
public static class JoinRequestTimeoutException extends RuntimeException { public static class JoinRequestTimeoutException extends RuntimeException {
} }
public static class ConnectionErrorException extends RuntimeException {
}
} }

View File

@ -132,6 +132,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
FastDiscoveryTest tester = new FastDiscoveryTest(null, 0, "stun.qq.com", 3478); FastDiscoveryTest tester = new FastDiscoveryTest(null, 0, "stun.qq.com", 3478);
return tester.test(); return tester.test();
}).whenComplete(Schedulers.javafx(), (info, exception) -> { }).whenComplete(Schedulers.javafx(), (info, exception) -> {
LOG.log(Level.INFO, "Nat test result " + MultiplayerPageSkin.getNATType(info), exception);
if (exception == null) { if (exception == null) {
natState.set(info); natState.set(info);
} else { } else {
@ -292,7 +293,9 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
localPort, new MultiplayerManager.JoinSessionHandler() { localPort, new MultiplayerManager.JoinSessionHandler() {
@Override @Override
public void onWaitingForJoinResponse() { public void onWaitingForJoinResponse() {
hintQuestion.setQuestion(i18n("multiplayer.session.join.wait")); runInFX(() -> {
hintQuestion.setQuestion(i18n("multiplayer.session.join.wait"));
});
} }
}) })
.thenAcceptAsync(session -> { .thenAcceptAsync(session -> {
@ -338,6 +341,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
LOG.info("Cato already started"); LOG.info("Cato already started");
reject.accept(i18n("multiplayer.session.join.wait_timeout")); reject.accept(i18n("multiplayer.session.join.wait_timeout"));
return null; return null;
} else if (resolved instanceof MultiplayerManager.ConnectionErrorException) {
LOG.info("Failed to establish connection with server");
reject.accept(i18n("multiplayer.session.join.error.connection"));
return null;
} else { } else {
LOG.log(Level.WARNING, "Failed to join session", resolved); LOG.log(Level.WARNING, "Failed to join session", resolved);
reject.accept(i18n("multiplayer.session.join.error")); reject.accept(i18n("multiplayer.session.join.error"));

View File

@ -295,7 +295,7 @@ public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimated
} }
} }
private static String getNATType(DiscoveryInfo info) { public static String getNATType(DiscoveryInfo info) {
if (info == null) { if (info == null) {
return i18n("multiplayer.nat.testing"); return i18n("multiplayer.nat.testing");
} else if (info.isBlockedUDP()) { } else if (info.isBlockedUDP()) {

View File

@ -669,6 +669,7 @@ multiplayer.session.expired=Multiplayer session has expired. You should re-creat
multiplayer.session.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality. multiplayer.session.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality.
multiplayer.session.join=Join Session multiplayer.session.join=Join Session
multiplayer.session.join.error=Failed to join multiplayer session multiplayer.session.join.error=Failed to join multiplayer session
multiplayer.session.join.error.connection=Failed to join multiplayer session. Cannot establish connection.
multiplayer.session.join.hint=You must obtain the invitation code from the gamer who has already created a multiplayer session. multiplayer.session.join.hint=You must obtain the invitation code from the gamer who has already created a multiplayer session.
multiplayer.session.join.invitation_code=Invitation code multiplayer.session.join.invitation_code=Invitation code
multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer session. multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer session.

View File

@ -668,6 +668,7 @@ multiplayer.session.error.file_not_found=找不到 cato。請檢查防毒軟體
multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。 multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。
multiplayer.session.join=加入房間 multiplayer.session.join=加入房間
multiplayer.session.join.error=加入房間失敗 multiplayer.session.join.error=加入房間失敗
multiplayer.session.join.error.connection=加入房間失敗。無法與對方建立連接。如果你或對方的網路類型是差(對稱型),可能無法使用聯機功能。
multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間 multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間
multiplayer.session.join.invitation_code=邀請碼 multiplayer.session.join.invitation_code=邀請碼
multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼 multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼

View File

@ -669,6 +669,7 @@ multiplayer.session.error.file_not_found=找不到 cato。请检查杀毒软件
multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机。 multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机。
multiplayer.session.join=加入房间 multiplayer.session.join=加入房间
multiplayer.session.join.error=加入房间失败。如果你或对方的网络类型是差(对称型),可能无法使用联机功能。 multiplayer.session.join.error=加入房间失败。如果你或对方的网络类型是差(对称型),可能无法使用联机功能。
multiplayer.session.join.error.connection=加入房间失败。无法与对方建立连接。如果你或对方的网络类型是差(对称型),可能无法使用联机功能。
multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间 multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间
multiplayer.session.join.invitation_code=邀请码 multiplayer.session.join.invitation_code=邀请码
multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码 multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码