我真的不太了解TCP套接字通信背后的理论,但是在实践中,我已经实现了以下代码:
服务器:
public class Server {
public static volatile ArrayList<ReplyThread> connections = new ArrayList<>();
public static void main(String[] args) {
new AcceptThread().start();
}
private static class AcceptThread extends Thread {
@Override
public void run() {
ServerSocket inSock;
try {
inSock = new ServerSocket(3074);
boolean loop = true;
while(loop) {
System.out.println("waiting for next connection");
connections.add(new ReplyThread(inSock.accept()));
System.out.println("connection made");
connections.get(connections.size() - 1).setName(""+(connections.size() - 1));
connections.get(connections.size() - 1).start();
}
inSock.close();
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
public static class ReplyThread extends Thread {
private static Socket sock;
private DataOutputStream out;
public ReplyThread(Socket newSock) {
sock = newSock;
}
@Override
public void run() {
try {
DataInputStream in = new DataInputStream(sock.getInputStream());
out = new DataOutputStream(sock.getOutputStream());
boolean loop = true;
while(loop) {
String msg = in.readUTF();
System.out.println(msg);
for (ReplyThread thread : connections) {
thread.output(sock, msg);
}
}
in.close();
} catch (SocketException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection terminated.");
this.interrupt();
System.out.println(this.getName() + " I was interrupted");
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
public final void output(Socket sock, String message) throws IOException {
this.out.writeUTF(this.getName() + ": " + message);
}
}
}
客户端:
package server;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
* @author RaKXeR
*/
public class Client {
public static Socket sock;
public static void main(String[] args) {
try {
sock = new Socket("localhost", 3074);
new writeThread().start();
new readThread().start();
} catch (IOException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static class readThread extends Thread {
@Override
public void run() {
boolean loop = true;
while (loop) {
try {
DataInputStream in = new DataInputStream(sock.getInputStream());
//BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
String msg = in.readUTF();
//String msg = in.readLine();
System.out.println(msg);
} catch (IOException ex) {
System.out.println("read error");
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
loop = false;
}
}
}
}
public static class writeThread extends Thread {
@Override
public void run() {
boolean loop = true;
while(loop) {
try {
DataOutputStream out = new DataOutputStream(sock.getOutputStream());
System.out.println("Type your message to the server: ");
Scanner scan = new Scanner(System.in);
out.writeUTF(scan.nextLine());
} catch (IOException ex) {
System.out.println("write error");
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
loop = false;
}
}
}
}
}
这个程序做得不多。你打开一个服务器,它启动一个线程等待服务器套接字上的传入连接。一旦你打开一个试图连接到服务器套接字的客户端,它就接受连接,并在我的线程数组列表中添加一个新线程,它开始等待来自该客户端/客户端套接字的消息。
这样一旦新客户端尝试连接,我只需将他添加到另一个线程,并设法立即与两个客户端通信。
我现在设置它的方式,只要1个客户端向服务器发送消息,服务器就会向连接的每个客户端发送相同的消息以及线程号。所有这些都按预期工作。
然而,我的问题是,一旦我阻止其中一个客户端运行,一切都会停止工作。你最好自己测试代码而不是我解释“停止工作”到底是什么意思,因为如果我知道我不认为我会问这个xD
所以,实际的问题:是什么导致了这个问题,我可以在不改变代码的所有内容的情况下解决这个问题吗?我觉得这与我重新使用服务器套接字有关创建几个客户端套接字,但我不确定。
编辑:您可以在下面看到Jim Garrison的回答,他解释了问题所在 - 我试图将其他客户端的消息发送给离线的客户端,这会抛出一个异常并停止线程。我所做的就是修复这个问题,将“终止”的“T”添加到已关闭的线程的名称中,并在发送信息之前检查线程的每个名称。它并不完美,但它是我现在拥有的解决方案。如果您使用此线程中的代码作为基础,我很抱歉,因为此代码不是那么好的xD但是无论如何,如果你这样做,我建议你改进它,就像我在原始程序中一样。这是固定的服务器代码:
package server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Server {
public static volatile ArrayList<ReplyThread> connections = new ArrayList<>();
public static void main(String[] args) {
new AcceptThread().start();
}
private static class AcceptThread extends Thread {
@Override
public void run() {
ServerSocket inSock;
try {
inSock = new ServerSocket(3074);
boolean loop = true;
while(loop) {
System.out.println("waiting for next connection");
connections.add(new ReplyThread(inSock.accept()));
System.out.println("connection made");
connections.get(connections.size() - 1).setName(""+(connections.size() - 1));
connections.get(connections.size() - 1).start();
}
inSock.close();
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
public static class ReplyThread extends Thread {
private static Socket sock;
private DataOutputStream out;
public ReplyThread(Socket newSock) {
sock = newSock;
}
@Override
public void run() {
try {
DataInputStream in = new DataInputStream(sock.getInputStream());
out = new DataOutputStream(sock.getOutputStream());
boolean loop = true;
while(loop) {
String msg = in.readUTF();
System.out.println(msg);
for (ReplyThread thread : connections) {
if (!thread.getName().contains("T")) thread.output(sock, msg);
}
}
in.close();
} catch (SocketException ex) {
//Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection terminated.");
this.setName(this.getName() + "T");
this.interrupt();
System.out.println(this.getName() + " I was interrupted");
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
public final void output(Socket sock, String message) throws IOException {
this.out.writeUTF(this.getName() + ": " + message);
}
}
}
答案 0 :(得分:2)
一个客户端终止后,相应的ReplyThread
获得SocketException
并终止。但是,您不清理连接数组列表。当每个仍然连接的客户端发送消息时,您仍尝试向现已关闭的客户端发送回复。这会引发异常,终止当前发送客户端的ReplyThread
。
换句话说,在一个客户端终止后,当收到该客户端的消息时,每个剩余客户端的ReplyThread
将会死亡。
解决方案是添加代码来处理连接终止,并确保服务器视图中哪些连接仍处于活动状态的正确性和一致性。
答案 1 :(得分:1)
客户端连接丢失后,服务器套接字不会关闭。问题是ReplyThread
意图将数据写入已关闭的套接字并产生异常。可能的解决方案是:
while(loop) {
String msg = in.readUTF();
System.out.println(msg);
/*for (ReplyThread thread : connections) {
thread.output(sock, msg);
}*/
synchronized (connections) {
for (Iterator iterator = connections.iterator(); iterator.hasNext();) {
ReplyThread thread = (ReplyThread) iterator.next();
if (thread.sock.isClosed()) {
iterator.remove();
} else {
try {
thread.output(thread.sock, msg);
} catch (IOException e0) {
iterator.remove();
}
}
}
}
}
由于ArrayList
不是线程安全的,因此我在示例代码中使用 synchronized 进行资源锁定。 connections
的另一个方法调用应该应用相同的修复。