mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-01-30 14:39:56 +08:00
fix(multiplayer): show connection timeout instead of kicked.
This commit is contained in:
parent
fca78cf419
commit
0fd8a78972
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
|
@ -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()) {
|
||||||
|
@ -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.
|
||||||
|
@ -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=邀請碼不正確,請向開服玩家獲取邀請碼
|
||||||
|
@ -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=邀请码不正确,请向开服玩家获取邀请码
|
||||||
|
Loading…
Reference in New Issue
Block a user