现在我有一个Java程序,它使用线程和套接字来回显文本响应,就像真正的聊天窗口一样。目前,我的程序通过运行服务器和我想要的尽可能多的客户端来工作。当客户端输入消息时,该消息将回显到服务器以及发送消息的客户端。
我的问题是我希望任何客户端输入的消息不仅发送到服务器和他们自己,而且还发送到所有其他客户端。
以下是它目前的工作方式:
服务器:
收到的客户端消息:test1
客户端 1:
输入消息:测试1
测试1
客户端 2:
输入消息:
客户端 1 进入 test1,返回 test1,服务器也接收 test1。客户端 2 一无所获。我的目标是让在客户端中输入的任何消息显示在发送消息的客户端以及其他客户端和服务器上。
工作示例:
服务器:
收到的客户端消息:test1
收到的客户端消息:您好
客户端 1:
输入消息:测试1
测试1
来自客户端 2:您好
客户端 2:
输入消息:
从客户端 1:测试 1
你好
格式不必完全像那样,但这就是想法。到目前为止,我的代码如下。我读到我需要将我的客户添加到列表中,然后循环访问它们并向他们发送所有消息,但我不确定。任何帮助都会很棒。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Scanner;
public class EchoMultiThreadClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 4000)) {
//socket.setSoTimeout(5000);
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
Scanner scanner = new Scanner(System.in);
String echoString;
String response;
do {
System.out.println("Enter string to be echoed: ");
echoString = scanner.nextLine();
pw.println(echoString);
if(!echoString.equals("exit")) {
response = br.readLine();
System.out.println(response);
}
} while(!echoString.equals("exit"));
// }catch(SocketTimeoutException e) {
// System.out.println("The Socket has been timed out");
} catch (IOException e) {
System.out.println("Client Error: " + e.getMessage());
}
}
}
服务器代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
public class EchoMultiThreadServer {
private static Vector<Echoer> clients = new Vector<Echoer>();
public static void main(String [] args) {
try(ServerSocket serverSocket = new ServerSocket(4000)){
while(true) {
Socket socket = serverSocket.accept();
Echoer echoer = new Echoer(socket);
echoer.start();
clients.add(echoer);
}
}catch(IOException e) {
System.out.println("Server Exception"+e.getMessage());
}
}
}
线程代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Echoer extends Thread{
private Socket socket;
public Echoer(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter wr = new PrintWriter(socket.getOutputStream(), true);
while(true) {
String echoString = in.readLine();
System.out.println("Received Client Input: " + echoString);
if(echoString.equals("exit")) {
break;
}
wr.println(echoString);
}
}catch(IOException e) {
System.out.println("Oooops " + e.getMessage());
}finally {
try {
socket.close();
}catch(IOException e) {
// later
}
}
}
}
我可以看到您当前的逻辑存在两个问题:
- 在客户端,您实际上是在读取用户输入,然后发送到服务器并获得(单个)响应。所以这里的问题是你只得到一个响应,而你应该为每个用户输入行获取多个响应:即用户的输入加上其他用户的输入。由于您不知道其他用户的输入将在何时以及有多少,因此您需要异步。我的意思是你需要 2 个线程:一个用于读取用户输入,另一个用于读取服务器输入/响应(注意:我们仍然在客户端)。由于您已经拥有 2 个线程之一,即运行
main
方法的线程,因此您可以使用它而不是创建一个新线程。 - 在服务器端,您的
Echoer
读取用户输入,但仅将其发送回同一客户端。例如,您需要一个循环来将客户端的输入发送到所有其他客户端。
所以在我看来,正确的逻辑是:
客户端:
读取服务器的响应线程逻辑:
永远,做: 获取服务器的消息。 打印服务器发送给用户的消息。
main
方法:
连接到服务器。 启动"读取服务器的响应线程"。 获取用户输入。 当用户的输入不"退出"时,请执行: 将用户的输入发送到服务器。 获取用户输入。 断开与服务器的连接。
服务器端:
Echoer
线程:
永远,做: 阅读客户的消息。 对于每个客户,请执行以下操作: 将消息发送到客户端。
main
方法:
启动服务器套接字。 永远,做: 接受传入连接。 为接受的连接启动回声器线程。
虽然有一些缺失的地方,例如如何维护所有客户端的列表,但为此我可以看到您已经在服务器端使用了Vector<Echoer> clients
。因此,只需将该Vector
传递给您创建的每条Echoer
,这样他们就可以广播每条传入的消息。这里的重要说明:在服务器端,您有多个线程:主线程和每个线程Echoer
,因此请确保在主线程上修改Vector
同步,并在Echoer
s上广播时同步。
注释:
- 我假设在上述所有逻辑中,客户端发送消息没有特定的顺序。例如,如果总是先发送客户端A,然后发送客户端B,依此类推,并且整个过程都在重复,那么您根本不需要多线程。
- 请慢慢来。首先实现它,然后告诉我您是否遇到任何问题。
Edit 1:完整的示例代码。
客户端代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Client {
//This is the "Reading server's responses thread" I am talking about in the answer.
private static class ReadingRunnable implements Runnable {
private final BufferedReader serverInput;
public ReadingRunnable(final InputStream is) {
serverInput = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
}
@Override
public void run() {
try {
//While the server is not disconnected, we print each line to 'System.out':
for (String line = serverInput.readLine(); line != null; line = serverInput.readLine())
System.out.println(line);
}
catch (final IOException iox) {
iox.printStackTrace(System.out);
}
finally {
System.out.println("Input from server stopped.");
}
}
}
public static void main(final String[] args) {
try {
System.out.print("Connecting... ");
try (final Socket sck = new Socket("localhost", 50505);
final OutputStream os = sck.getOutputStream();
final InputStream is = sck.getInputStream()) {
System.out.println("Connected.");
new Thread(new ReadingRunnable(is)).start();
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
final Scanner scan = new Scanner(System.in);
for (String userInput = scan.nextLine(); !"exit".equalsIgnoreCase(userInput); userInput = scan.nextLine()) {
bw.write(userInput);
bw.newLine();
bw.flush();
}
}
}
catch (final IOException iox) {
iox.printStackTrace(System.out);
}
finally {
System.out.println("Output from user stopped.");
}
}
}
服务器代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Objects;
public class Server {
private static class Echoer implements Runnable {
private final ArrayList<Echoer> all;
private final BufferedWriter bw;
private final BufferedReader br;
public Echoer(final ArrayList<Echoer> all,
final InputStream is,
final OutputStream os) {
this.all = Objects.requireNonNull(all);
bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
}
//Instead of exposing 'bw' via a getter, I just built a helper method to send a message to the Echoer:
public void send(final String msg) throws IOException {
bw.write(msg);
bw.newLine();
bw.flush();
}
@Override
public void run() {
try {
for (String line = br.readLine(); line != null; line = br.readLine()) {
System.out.println(line); //Print the received line at the server.
synchronized (all) { //We are reading from a collection which may be modified at the same time by another (the main) Thread, so we need to synchronize.
//Broadcast the received line:
for (int i = all.size() - 1; i >= 0; --i) {
try {
all.get(i).send(line);
}
catch (final IOException iox) {
all.remove(i); //In case we cannot send to the client, disconnect him, ie remove him from the list in this simple case.
}
}
}
}
}
catch (final IOException iox) {
}
finally {
synchronized (all) {
all.remove(this); //Disconnect him, ie remove him from the list in this simple case.
}
System.out.println("Client disconnected.");
}
}
}
public static void main(final String[] args) throws IOException {
System.out.print("Starting... ");
try (final ServerSocket srv = new ServerSocket(50505)) {
final ArrayList<Echoer> all = new ArrayList<>();
System.out.println("Waiting for clients...");
while (true) {
final Socket sck = srv.accept();
try {
final OutputStream os = sck.getOutputStream();
final InputStream is = sck.getInputStream();
final Echoer e = new Echoer(all, is, os); //Pass all the Echoers at the new one.
synchronized (all) { //We will write to a collection which may be accessed at the same time by another (an Echoer) Thread, so we need to synchronize.
all.add(e); //Update list of Echoers.
}
new Thread(e).start(); //Start serving Echoer.
}
catch (final IOException iox) {
System.out.println("Failed to open streams for a client.");
}
}
}
}
}