如何解决如何让我的多线程服务器/客户端聊天程序使用套接字向所有客户端回显消息? 客户端:服务器端:注意事项:编辑 1:完整的示例代码
现在我有一个 Java 程序,它使用线程和套接字来像真正的聊天窗口一样回显文本响应。目前,我的程序通过运行服务器而不是我想要的尽可能多的客户端来工作。当客户端输入消息时,该消息会回显到服务器以及发送该消息的客户端。
我的问题是,我希望任何客户端输入的消息不仅发送到服务器和他们自己,还发送到所有其他客户端。
这是它目前的工作原理:
服务器:
收到客户端消息:test1
客户端 1:
输入消息:test1
测试 1
客户端 2:
输入消息:
客户端1进入test1,收到test1,服务器也收到test1。客户端 2 一无所获。我的目标是让输入到客户端的任何消息显示在发送消息的客户端以及其他客户端和服务器上。
工作示例:
服务器:
收到客户端消息:test1
收到客户端消息:你好
客户端 1:
输入消息:test1
测试 1
来自客户 2:你好
客户端 2:
输入消息:
来自客户端 1:test1
你好
格式不必完全如此,但这就是想法。到目前为止,我的代码如下。我读过我需要将我的客户添加到列表中,然后遍历它们并将所有消息发送给他们,但我不确定。任何帮助都会很棒。
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
正在读取用户输入,但仅将其发送回同一个客户端。例如,您还需要一个循环来将客户端的输入发送给所有其他客户端。
那么在我看来正确的逻辑是:
客户端:
读取服务器的响应线程逻辑:
forever,do: get server's message. print server's message to user.
main
方法:
connect to server. start a "Reading server's responses thread". get user input. while the user's input it not "exit",do: send user's input to server. get user input. disconnect from server.
服务器端:
Echoer
线程:
forever,do: read client's message. for every client,do: send the message to the client.
main
方法:
start server socket. forever,do: accept incoming connection. start an Echoer thread for the accepted connection.
虽然有一些缺失的部分,例如如何维护所有客户端的列表,但我可以看到您已经在服务器端使用了 Vector<Echoer> clients
。因此,只需将 Vector
传递给您创建的每个 Echoer
,这样它们就可以广播每个传入的消息。 重要说明:在服务器端,您有多个线程:主线程和每个 Echoer
,因此请确保在修改时在 Vector
上同步它在主线程以及在 Echoer
上广播时。
注意事项:
- 我假设在上述所有逻辑中,客户端发送消息没有特定的顺序。例如,如果总是客户端 A 先发送,然后客户端 B 等等,并且整个过程不断重复,那么您根本不需要使用多线程。
- 请慢慢来。首先实施它,然后告诉我您是否遇到任何问题。
编辑 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.");
}
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。