我正在研究一个简单的Java客户端-服务器游戏。服务器和客户端都同时运行多个线程。在服务器上,有两个并发任务(可调用地在多个线程上运行),它们彼此随机地进行战斗。迟早,客户端连接到服务器后,它们会相互阻塞(不知道可能出现死锁,可能会发生争用),并且整个连接会冻结。我在这里检查了许多相关主题,在客户端和服务器上尝试了许多修改,设计和逻辑的许多更改。不幸的是尚未设法解决它。如果您需要其他课程,请随时提问。非常感谢您的帮助。
下面是这两个阻塞任务类(服务器端)。
public class ClientUpdateTask extends AbstractClientTask {
private final String location = "update task execute";
public ClientUpdateTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
}
@Override
public Boolean call() throws /*Exception*/ IOException {
if(clientTaskLock.readLock().tryLock()) {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "update task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
//throw new RuntimeException("ex updating clients", e);
//throw e;
} finally {clientTaskLock.readLock().unlock();}
}
return false;
}
@Override
protected void execute() throws IOException {
if(!serverManager.areAtleastTwoConnected())
return;
Client client = serverManager.getClient(clientToken, location);
String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
client.printLine(GameConditions.SERVER_MODE_SEND);
String inSerEntsLenTxt = client.readLine();
printClientPacket(clientAddress, "after 1st readl, ents len: "+inSerEntsLenTxt);
if(inSerEntsLenTxt != null && !(inSerEntsLenTxt.isEmpty())) {
int inSerEntsLen = Integer.parseInt(inSerEntsLenTxt);
inStreamBuff = new char[inSerEntsLen];
inStreamBuff = client.readCharsToArray(inStreamBuff, 0, inSerEntsLen);
String inSerEnts = new String(inStreamBuff);
printClientPacket(clientAddress, "in ser ents: "+inSerEnts);
//List<Client> clients = serverManager.getClients();
if(clientTaskLock.writeLock().tryLock()) {
try {
for(Client clientOther : serverManager.getClients()) {
String clientOtherToken = clientOther.getToken();
if(!clientOtherToken.equals(clientToken)) {
clientOther.printLine(GameConditions.SERVER_MODE_RECEIVE);
clientOther.printLine(inSerEntsLen);
clientOther.writeChars(inSerEnts);
}
}
} finally {clientTaskLock.writeLock().unlock();}
}
}
}
}
public class ClientHeartbeatTask extends AbstractClientTask {
private final String location = "heartbeat task execute";
public ClientHeartbeatTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.HEARTBEAT_TASK, clientTaskLock);
}
@Override
public Boolean call() throws /*Exception*/ IOException {
if(clientTaskLock.readLock().tryLock()) {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "heartbeat task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
//throw new RuntimeException("ex listening client heartbeat", e);
//throw e;
} finally {clientTaskLock.readLock().unlock();}
}
return false;
}
@Override
protected void execute() throws IOException {
Client client = serverManager.getClient(clientToken, location);
String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
String inToken = "";
//System.out.println("listening...");
client.printLine(GameConditions.SERVER_MODE_HEARTBEAT); //1
client.printLine(clientToken);
inToken = client.readLine(); //2
if(inToken != null) {
Matcher tokenMatcher = GameConditions.TOKEN_PATTERN.matcher(inToken);
//System.out.println("in token not null");
if(tokenMatcher.matches()) {
printClientPacket(clientAddress, "after 1st readl, token matched: " + inToken);
serverManager.updateClient(clientToken, location);
}
} else {
long idle = (long) (System.currentTimeMillis() - client.getLastHeartBeat());
//System.out.println("idle: " + idle);
if(idle > GameConditions.CLIENT_MAX_IDLE_TIME) {
serverManager.disconnectClient(clientToken);
throw new IOException("client disconnected, throw to "
+ "close resources and stop thread");
}
}
}
}
编辑Donno毫不客气地说,但是到目前为止,我设法做到的最好的是,客户端任务(与这两个任务相通)接收来自服务器的消息(直到此事开始)。但是服务器是静音的,没有收到消息。
我忘记发布解决方案了,就在这里。从更新和心跳任务中删除了直接输入/输出流访问。它们都在客户端和服务器上的多个线程上运行,并且相信这是阻塞线程和整个通信的原因。而是将直接流访问权限移至具有两个ArrayBlockingQueues的单独的类(客户端)并为套接字的流读写提供线程安全接口。所有传入消息都添加到输入队列,所有传出消息都添加到输出队列。现在,在客户端和服务器中又创建了两个任务,一个任务读取输入队列,另一个任务从输出队列写入。而且在此过程中,我的大部分同步和锁被证明是无用的(并且可能还会阻塞线程,并且肯定会降低性能)。
更改的更新任务:
public class ClientUpdateTask extends AbstractClientTask {
private final String location = "update task execute";
public ClientUpdateTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
}
@Override
public Boolean call() throws /*Exception*/ IOException {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "update task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
}
}
@Override
protected void execute() throws IOException {
if(!serverManager.areAtleastTwoConnected())
return;
Client client = serverManager.getClient(clientToken, location);
String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
String inMessage = client.peekInMessage();
if(inMessage != null && !inMessage.isEmpty() &&
inMessage.startsWith(GameConditions.SERVER_MODE_SEND)) {
client.pollInMessage();
String[] inMessageSplitArray = inMessage.split(
GameConditions.MESSAGE_FRAGMENT_SEPARATOR);
String inSerEnts = inMessageSplitArray[1];
for(Client clientOther : serverManager.getClients()) {
String clientOtherToken = clientOther.getToken();
if(!clientOtherToken.equals(clientToken)) {
String outMessage = GameConditions.SERVER_MODE_RECEIVE
+ GameConditions.MESSAGE_FRAGMENT_SEPARATOR + inSerEnts;
clientOther.pushOutMessage(outMessage);
}
}
}
String outMessage = GameConditions.SERVER_MODE_SEND;
client.pushOutMessage(outMessage);
}
}
更改的心跳任务:
public class ClientHeartbeatTask extends AbstractClientTask {
private final String location = "heartbeat task execute";
public ClientHeartbeatTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.HEARTBEAT_TASK, clientTaskLock);
}
@Override
public Boolean call() throws /*Exception*/ IOException {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "heartbeat task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
}
}
@Override
protected void execute() throws IOException {
Client client = serverManager.getClient(clientToken, location);
String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
String inMessage = client.peekInMessage(); //2
if(inMessage != null && !inMessage.isEmpty() &&
inMessage.startsWith(GameConditions.SERVER_MODE_HEARTBEAT)) {
client.pollInMessage();
String[] inMessageSplitArray = inMessage.split(
GameConditions.MESSAGE_FRAGMENT_SEPARATOR);
String inToken = inMessageSplitArray[1];
Matcher tokenMatcher = GameConditions.TOKEN_PATTERN.matcher(inToken);
}
String outMessage = GameConditions.SERVER_MODE_HEARTBEAT +
GameConditions.MESSAGE_FRAGMENT_SEPARATOR + clientToken;
client.pushOutMessage(outMessage);
long idle = (long) (System.currentTimeMillis() - client.getLastHeartBeat());
if(idle > GameConditions.CLIENT_MAX_IDLE_TIME) {
System.out.println("client idle time expired");
serverManager.disconnectClient(clientToken);
throw new IOException("client disconnected, throw to "
+ "close resources and stop thread");
}
}
}
新输入阅读器任务:
public class ClientInputListenerTask extends AbstractClientTask {
private final String location = "input listener task execute";
public ClientInputListenerTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
}
@Override
protected void execute() throws IOException {
Client client = serverManager.getClient(clientToken, location);
String inputLine = client.readLine();
if(inputLine != null && !inputLine.isEmpty()) {
client.pushInMessage(inputLine);
System.out.println(client.getToken()+", in message: "+inputLine
+", last heartbeat: "+client.getLastHeartBeat());
serverManager.updateClient(client.getToken(), location);
}
}
@Override
public Boolean call() throws Exception {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "input listener task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
}
}
}
新的输出编写器任务:
public class ClientOutputPrinterTask extends AbstractClientTask {
private final String location = "input listener task execute";
public ClientOutputPrinterTask(String clientToken, ServerManager serverManager,
ReadWriteLock clientTaskLock) {
super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
}
@Override
protected void execute() throws IOException {
Client client = serverManager.getClient(clientToken, location);
String outputLine = client.pollOutMessage();
if(outputLine != null)
client.printLine(outputLine);
}
@Override
public Boolean call() throws Exception {
try {
execute();
return true;
} catch(IOException e) {
String exLocation = "input listener task execute ioex";
ClientDisconnectedException cde = new ClientDisconnectedException(
clientToken, exLocation, e.getMessage(), e);
error = cde;
throw cde;
}
}
}