Java 套接字:如果超过 1 个客户端,客户端-服务器通信将卡在多线程中
Java Socket: Client-server communication is stuck with multi-threading if more than 1 client
首先,我的代码只是一个演示我的多人游戏(2个或更多玩家可以同时玩)来演示我的问题,没有任何额外的东西。我已经在我的游戏中成功实现了点对点 (P2P) 通信。后来,我决定在我的游戏中添加对 client/server 通信的支持(即中央服务器也是玩家)。它应该比P2P容易得多。但是很奇怪!不幸的是,我正面临着我不知道如何解决的问题!现在问题来了:
假设,我有1台服务器和一些客户端(可能是1个或多个客户端)。他们都应该给出以下输出:
Starting...
A
B
C
D
E
F
...
...
Done!
他们都在没有使用多线程的情况下给出了上述输出。但是使用多线程,它给出上面的输出只有当有 1 个服务器和 1 个客户端时。
这是服务器代码。只显示重要部分; TODO
注释表示 send/receive 数据。 Common.totalClients
是要连接的客户端数量。
class ServerMain {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket[] sockets = new Socket[Common.totalClients];
ObjectOutputStream[] sendStreams = new ObjectOutputStream[Common.totalClients];
ObjectInputStream[] receiveStreams = new ObjectInputStream[Common.totalClients];
SendThread[] sendThreads = new SendThread[Common.totalClients];
ReceiveThread[] receiveThreads = new ReceiveThread[Common.totalClients];
// ... (here, all assignment of the above variables and closing serverSocket)
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients;
for (char sendChar = 'A'; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// sending server data to all clients
for (int i = 0; i < Common.totalClients; i++) {
sendThreads[i].send(sendChar); // TODO
//Common.send(sendStreams[i], sendChar);
}
System.out.println(sendChar);
for (int i = 0; i < Common.totalClients; i++) {
char receivedChar = receiveThreads[i].receive(); // TODO
//char receivedChar = Common.receive(receiveStreams[i]);
// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
System.out.println(receivedChar);
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
// ... (here, closing all sockets and interrupt all threads)
System.out.println("Done!");
}
}
这里是客户端代码(只是重要的部分)。第一个客户有 clientID
个 1。第二个客户有 clientID
的 2 等等。第一个客户应该首先是 运行,然后是第二个等等。 TODO
注释以指示 send/receive 数据。
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients + clientID;
for (char sendChar = 'A' + clientID; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// receiving data from server and other clients whose "clientID" is less than this client's "clientID" (via server)
for (int j = 0; j < clientID; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
// sending this client's data
sendThread.send(sendChar); // TODO
//Common.send(sendStream, sendChar);
System.out.println(sendChar);
// receiving data from other clients whose "clientID" is greater than this client's "clientID" (via server)
for (int j = clientID; j < Common.totalClients; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
不知道是哪个原因导致使用多线程没有得到预期的输出。同样,使用多线程,它仅适用于 1 个客户端(和服务器)!
这里是ReceiveThread
。请注意,如果连接的客户端超过 1 个,服务器和客户端都会卡在 try { ch = queue.take(); }
。
class ReceiveThread extends Thread {
private ObjectInputStream receiveStream;
private BlockingQueue<Character> queue = new ArrayBlockingQueue<Character>(Common.totalClients);
public ReceiveThread(ObjectInputStream receiveStream) {
this.receiveStream = receiveStream; start();
}
public void run() {
while (!Thread.interrupted()) {
try { queue.put(receiveStream.readChar()); }
catch (InterruptedException e) { return; }
catch (IOException e) { return; }
}
}
public char receive() {
char ch = '#';
try { ch = queue.take(); }
catch (InterruptedException e) { e.printStackTrace(); }
return ch;
}
}
这里是SendThread
代码:
class SendThread extends Thread {
private ObjectOutputStream sendStream;
private volatile boolean pending = false;
private volatile char sendChar;
public SendThread(ObjectOutputStream sendStream) {
this.sendStream = sendStream; start();
}
public void run() {
while (!Thread.interrupted()) {
if (pending) {
pending = false;
try {
sendStream.writeChar(sendChar);
sendStream.flush();
} catch (IOException e) { return; }
}
try { Thread.sleep(10); }
catch (InterruptedException e) { return; }
}
}
public void send(char ch) {
sendChar = ch; pending = true;
}
}
现在,如果 Common.totalClient
是 2(即 运行 有 2 个客户端),那么我得到以下输出:
服务器:(首先运行)
Starting...
A
客户端 1(clientID
为 1):(在服务器之后运行)
Starting...
A
B
B
客户端 2(clientID
为 2):(在客户端 1 之后运行)
Starting...
A
他们都卡在那儿,也不例外。为什么会出现这种行为?如何解决?请注意,我使用了相同的 SendThread
和 ReceiveThread
classes 来成功实现 P2P 通信。如果您有疑虑,请随时询问我在这里使用的更详细的代码。
编辑:
为方便起见,我添加了完整的 运行nable 项目(仅包含 5 个小的 .java 文件:2 个线程 classes;服务器、客户端 classes 和通用class)。目前在使用额外线程时出现故障。但它在没有额外线程的情况下按预期工作。要在没有额外线程的情况下对其进行测试,请执行以下操作:1. 注释 \ TODO
行,2. 取消注释 \ TODO
行之后的单行。 3. 注释额外的线程构造行(4 行)。这是link:(我已经删除了link,因为它不需要解决问题!)
您的服务器 "multithreaded wrong" 本身 。虽然您确实有 2* totalClients 个线程,但服务器上仍然只有 运行 个线程(主线程)。我的意思是你的主线程有一个 for
循环遍历每个客户端;如果其中一个客户端卡住了,您的主线程将被阻塞,您将无法接收或发送来自其他客户端的信息。
如何解决这个问题:将接收和发送代码放在各自的客户端线程中,而不是放在主线程中。你的 main 应该看起来更像 (pseudocode)
main func {
while true {
accept a socketconnection
make a sender thread for the new socket connection {
thread code (always ready to send)
}.start();
make a listener thread for the new socket connection {
thread code (listens continously)
}.start();
}
}
这里有一个明显的问题:将数据发送到 sendThreads[i]
而不是 sendThreads[j]
。 j
是循环变量,其实我想用它但是打错了。但是\ TODO
后面的评论是正确的!这就是它在不使用额外线程的情况下工作的原因。正如问题中所述,这里没有什么奇怪的!
所以ServerMain
class应该是(只修改应该修改的部分):
// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
sendThreads[j].send(receivedChar); // instead of sendThreads[i]
//Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
sendThreads[j].send(receivedChar); // instead of sendThreads[i]
//Common.send(sendStreams[j], receivedChar);
}
其实是一些愚蠢的错误!但这是我问题的实际答案。
首先,我的代码只是一个演示我的多人游戏(2个或更多玩家可以同时玩)来演示我的问题,没有任何额外的东西。我已经在我的游戏中成功实现了点对点 (P2P) 通信。后来,我决定在我的游戏中添加对 client/server 通信的支持(即中央服务器也是玩家)。它应该比P2P容易得多。但是很奇怪!不幸的是,我正面临着我不知道如何解决的问题!现在问题来了:
假设,我有1台服务器和一些客户端(可能是1个或多个客户端)。他们都应该给出以下输出:
Starting...
A
B
C
D
E
F
...
...
Done!
他们都在没有使用多线程的情况下给出了上述输出。但是使用多线程,它给出上面的输出只有当有 1 个服务器和 1 个客户端时。
这是服务器代码。只显示重要部分; TODO
注释表示 send/receive 数据。 Common.totalClients
是要连接的客户端数量。
class ServerMain {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket[] sockets = new Socket[Common.totalClients];
ObjectOutputStream[] sendStreams = new ObjectOutputStream[Common.totalClients];
ObjectInputStream[] receiveStreams = new ObjectInputStream[Common.totalClients];
SendThread[] sendThreads = new SendThread[Common.totalClients];
ReceiveThread[] receiveThreads = new ReceiveThread[Common.totalClients];
// ... (here, all assignment of the above variables and closing serverSocket)
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients;
for (char sendChar = 'A'; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// sending server data to all clients
for (int i = 0; i < Common.totalClients; i++) {
sendThreads[i].send(sendChar); // TODO
//Common.send(sendStreams[i], sendChar);
}
System.out.println(sendChar);
for (int i = 0; i < Common.totalClients; i++) {
char receivedChar = receiveThreads[i].receive(); // TODO
//char receivedChar = Common.receive(receiveStreams[i]);
// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
System.out.println(receivedChar);
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
// ... (here, closing all sockets and interrupt all threads)
System.out.println("Done!");
}
}
这里是客户端代码(只是重要的部分)。第一个客户有 clientID
个 1。第二个客户有 clientID
的 2 等等。第一个客户应该首先是 运行,然后是第二个等等。 TODO
注释以指示 send/receive 数据。
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients + clientID;
for (char sendChar = 'A' + clientID; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// receiving data from server and other clients whose "clientID" is less than this client's "clientID" (via server)
for (int j = 0; j < clientID; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
// sending this client's data
sendThread.send(sendChar); // TODO
//Common.send(sendStream, sendChar);
System.out.println(sendChar);
// receiving data from other clients whose "clientID" is greater than this client's "clientID" (via server)
for (int j = clientID; j < Common.totalClients; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
不知道是哪个原因导致使用多线程没有得到预期的输出。同样,使用多线程,它仅适用于 1 个客户端(和服务器)!
这里是ReceiveThread
。请注意,如果连接的客户端超过 1 个,服务器和客户端都会卡在 try { ch = queue.take(); }
。
class ReceiveThread extends Thread {
private ObjectInputStream receiveStream;
private BlockingQueue<Character> queue = new ArrayBlockingQueue<Character>(Common.totalClients);
public ReceiveThread(ObjectInputStream receiveStream) {
this.receiveStream = receiveStream; start();
}
public void run() {
while (!Thread.interrupted()) {
try { queue.put(receiveStream.readChar()); }
catch (InterruptedException e) { return; }
catch (IOException e) { return; }
}
}
public char receive() {
char ch = '#';
try { ch = queue.take(); }
catch (InterruptedException e) { e.printStackTrace(); }
return ch;
}
}
这里是SendThread
代码:
class SendThread extends Thread {
private ObjectOutputStream sendStream;
private volatile boolean pending = false;
private volatile char sendChar;
public SendThread(ObjectOutputStream sendStream) {
this.sendStream = sendStream; start();
}
public void run() {
while (!Thread.interrupted()) {
if (pending) {
pending = false;
try {
sendStream.writeChar(sendChar);
sendStream.flush();
} catch (IOException e) { return; }
}
try { Thread.sleep(10); }
catch (InterruptedException e) { return; }
}
}
public void send(char ch) {
sendChar = ch; pending = true;
}
}
现在,如果 Common.totalClient
是 2(即 运行 有 2 个客户端),那么我得到以下输出:
服务器:(首先运行)
Starting...
A
客户端 1(clientID
为 1):(在服务器之后运行)
Starting...
A
B
B
客户端 2(clientID
为 2):(在客户端 1 之后运行)
Starting...
A
他们都卡在那儿,也不例外。为什么会出现这种行为?如何解决?请注意,我使用了相同的 SendThread
和 ReceiveThread
classes 来成功实现 P2P 通信。如果您有疑虑,请随时询问我在这里使用的更详细的代码。
编辑:
为方便起见,我添加了完整的 运行nable 项目(仅包含 5 个小的 .java 文件:2 个线程 classes;服务器、客户端 classes 和通用class)。目前在使用额外线程时出现故障。但它在没有额外线程的情况下按预期工作。要在没有额外线程的情况下对其进行测试,请执行以下操作:1. 注释 \ TODO
行,2. 取消注释 \ TODO
行之后的单行。 3. 注释额外的线程构造行(4 行)。这是link:(我已经删除了link,因为它不需要解决问题!)
您的服务器 "multithreaded wrong" 本身 。虽然您确实有 2* totalClients 个线程,但服务器上仍然只有 运行 个线程(主线程)。我的意思是你的主线程有一个 for
循环遍历每个客户端;如果其中一个客户端卡住了,您的主线程将被阻塞,您将无法接收或发送来自其他客户端的信息。
如何解决这个问题:将接收和发送代码放在各自的客户端线程中,而不是放在主线程中。你的 main 应该看起来更像 (pseudocode)
main func {
while true {
accept a socketconnection
make a sender thread for the new socket connection {
thread code (always ready to send)
}.start();
make a listener thread for the new socket connection {
thread code (listens continously)
}.start();
}
}
这里有一个明显的问题:将数据发送到 sendThreads[i]
而不是 sendThreads[j]
。 j
是循环变量,其实我想用它但是打错了。但是\ TODO
后面的评论是正确的!这就是它在不使用额外线程的情况下工作的原因。正如问题中所述,这里没有什么奇怪的!
所以ServerMain
class应该是(只修改应该修改的部分):
// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
sendThreads[j].send(receivedChar); // instead of sendThreads[i]
//Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
sendThreads[j].send(receivedChar); // instead of sendThreads[i]
//Common.send(sendStreams[j], receivedChar);
}
其实是一些愚蠢的错误!但这是我问题的实际答案。