Java多客户端聊天服务器私聊选项

huangapple go评论73阅读模式
英文:

Java multi-client chat server private message options

问题

我是stackoverflow的新手,如果之前有类似的问题请原谅,但我做了一个快速搜索,没有找到类似标题的问题。我正在Java上开发一个多客户端聊天应用程序。我正在按照教程进行操作,我可以发送消息,每个应用程序中的用户都可以看到。但我想知道如何创建并发送私人消息给聊天中的特定用户。

import java.io.*;
import java.net.*;
import java.util.HashSet;
import java.util.Set;

public class ChatServer {
    private int port;

    private Set<String> userNames = new HashSet<>();
    private Set<UserThread> userThreads = new HashSet<>();

    public ChatServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) {
        new ChatServer(9999).execute();
    }

    private void execute() {
        try {
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println("Server is running");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New user connected");
                UserThread newUser = new UserThread(socket, this);
                userThreads.add(newUser);
                newUser.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addUserName(String s) {
        this.userNames.add(s);
    }

    public void broadcast(String serverMessage, UserThread excludeUser) {
        for (UserThread aUser : userThreads) {
            if (aUser != excludeUser)
                aUser.sendMessage(serverMessage);
        }
    }
}

以上是我的服务器代码。

public void run() {
    Console console = System.console();
    String userName = console.readLine("Enter your username : ");
    writer.println(userName);
    String text;
    do {
        text = console.readLine("[" + userName + "]: ");
        if (text.startsWith("[")) {
            isTargeted = true;
            this.aimUserName = text.substring(text.indexOf("[") + 1, text.indexOf("]"));
        } else {
            isTargeted = false;
        }
        writer.println(text);
    } while (!text.equals("bye"));

    try {
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

而这段代码是我写入线程类的一部分。正如你所看到的,如果消息以'[name]'部分开头,'name'表示我们想要发送私人消息的用户。通过这样做,我可以获取我想要发送私人消息的用户的名称,但我无法弄清楚如何将此消息仅广播给特定的用户。我相信我需要在ChatServer类中配置我的广播函数,但我真的不知道怎么做。我应该遵循哪些步骤?

--编辑--

我一直在思考我的问题,我做了一些添加以解决我的问题。首先,我认为我应该把我拥有的一切分享给你。我之前分享过我的ChatServer类。我拥有的其他类包括:

import java.io.IOException;
import java.net.Socket;
public class ChatClient {
public static void main(String[] args) {
new ChatClient().execute();
}
private void execute() {
try {
Socket socket = new Socket("localhost", 3);
System.out.println("Connected to chat server");
new ReadThread(socket, this).start();
new WriteThread(socket, this).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;

public class ReadThread extends Thread{
	private BufferedReader reader;
	private Socket socket;
	private ChatClient client;
	public ReadThread(Socket socket, ChatClient client) {
		this.socket = socket;
		this.client = client;
		
		InputStream input;
		try {
			input = this.socket.getInputStream();
			this.reader = new BufferedReader(new InputStreamReader(input));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public void run() {
		while(true) {
			try {
				String response = this.reader.readLine();
				System.out.println("\n" + response);
			} catch (IOException e) {
				e.printStackTrace();
				break;
			}
		}
	}
}

import java.net.Socket;
import java.io.*;
public class UserThread extends Thread {

	private Socket socket;
	private ChatServer server;
	PrintWriter writer = null;
	public String userName;
	public UserThread(Socket socket, ChatServer chatServer) {
		this.socket = socket;
		this.server = chatServer;
	}
	
	
	public void run() {
		try {
			InputStream input = socket.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(input));
			OutputStream output = socket.getOutputStream();
			writer = new PrintWriter(output,true);
			
			String userName = reader.readLine();
			this.userName = userName;
			server.addUserName(userName);
			String serverMessage = "New user connected: " + userName;
			server.broadcast(serverMessage,this);
			
			
			
			String clientMessage;
			do {
				clientMessage = reader.readLine();
				serverMessage = "[" + userName + "] : " + clientMessage;
				server.privatebr(serverMessage, targetUserName);
				server.broadcast(serverMessage, this);
			}while(!clientMessage.equals("bye"));
			
			
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	public void sendMessage(String serverMessage) {
		writer.println(serverMessage);
	}
	

}

import java.net.*;
import java.io.*;
public class WriteThread extends Thread {
	private Socket socket;
	private ChatClient client;
	private PrintWriter writer;
	public WriteThread(Socket socket, ChatClient client) {
		this.socket = socket;
		this.client = client;
		OutputStream output;
		try {
			output = socket.getOutputStream();
			this.writer = new PrintWriter(output, true);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public void run() {
		Console console = System.console();
		String userName = console.readLine("Enter your username : ");
		writer.println(userName);
		String text;
		do {
			text = console.readLine("[" + userName + "]: ");
			if(text.startsWith("[")){
				String aimUserName = text.substring(text.indexOf("[")+1,text.indexOf("]"));
				System.out.println("Private Message to: " + aimUserName);}
			writer.println(text);
		}while(!text.equals("bye"));
		
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
}

这些代码可以正常工作,我可以很清楚地进行多方聊天。但是在处理私人聊天的时候,我在ChatServer中添加了以下一行:

public void privatebr(String serverMessage, String targetUserName){
		for(UserThread aUser: user

<details>
<summary>英文:</summary>

I am new at stackoverflow and I am sorry if this kind of a question is asked before but did a quick search and I could not find any title like mine. I am working on a multi-client chat application on Java. I was following the tutorials and I can send messages that every user in the application can see. But I wonder how to create and send a private message to a spesific user into the chat. 

```java
import java.io.*;
import java.net.*;
import java.util.HashSet;
import java.util.Set;

public class ChatServer {
    private int port;

    private Set&lt;String&gt; userNames = new HashSet&lt;&gt;();
    private Set&lt;UserThread&gt; userThreads = new HashSet&lt;&gt;();

    public ChatServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) {
        new ChatServer(9999).execute();
    }

    private void execute() {
        try {
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println(&quot;Server is running&quot;);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(&quot;New user connected&quot;);
                UserThread newUser = new UserThread(socket, this);
                userThreads.add(newUser);
                newUser.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addUserName(String s) {
        this.userNames.add(s);
    }

    public void broadcast(String serverMessage, UserThread excludeUser) {
        for (UserThread aUser : userThreads) {
            if (aUser != excludeUser)
                aUser.sendMessage(serverMessage);
        }
    }
}

The code above is my server code.

public void run() {
    Console console = System.console();
    String userName = console.readLine(&quot;Enter your username : &quot;);
    writer.println(userName);
    String text;
    do {
        text = console.readLine(&quot;[&quot; + userName + &quot;]: &quot;);
        if (text.startsWith(&quot;[&quot;)) {
            isTargeted = true;
            this.aimUserName = text.substring(text.indexOf(&quot;[&quot;) + 1, text.indexOf(&quot;]&quot;));
            //System.out.println(&quot;Private Message to: &quot; + aimUserName);
        } else {
            isTargeted = false;
        }
        writer.println(text);
    } while (!text.equals(&quot;bye&quot;));

    try {
        socket.close();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

and this code above is a part of my write thread class. As you can see, if a message starts with '[name]' part, the "name" means the user that we want to send a private message. By doing this, I can get the name of the user that I want to send a private message but I could not figure out how to broadcast this message just to that spesific user. I believe I need to configure my broadcast function in ChatServer class but I don't really know how to do. What steps should I follow?

--Edit--

I've been working on my question and I did some additions to solve my problem. First of all, I think I should share everything I have to you. I shared my ChatServer class previously. Other classes I have are:

import java.io.IOException;
import java.net.Socket;
public class ChatClient {
public static void main(String[] args) {
new ChatClient().execute();
}
private void execute() {
try {
Socket socket = new Socket(&quot;localhost&quot;, 3);
System.out.println(&quot;Connected to chat server&quot;);
new ReadThread(socket, this).start();
new WriteThread(socket, this).start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;
public class ReadThread extends Thread{
private BufferedReader reader;
private Socket socket;
private ChatClient client;
public ReadThread(Socket socket, ChatClient client) {
this.socket = socket;
this.client = client;
InputStream input;
try {
input = this.socket.getInputStream();
this.reader = new BufferedReader(new InputStreamReader(input));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
while(true) {
try {
String response = this.reader.readLine();
System.out.println(&quot;\n&quot; + response);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
}
}
}
import java.net.Socket;
import java.io.*;
public class UserThread extends Thread {
private Socket socket;
private ChatServer server;
PrintWriter writer = null;
public String userName;
public UserThread(Socket socket, ChatServer chatServer) {
this.socket = socket;
this.server = chatServer;
}
public void run() {
try {
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
OutputStream output = socket.getOutputStream();
writer = new PrintWriter(output,true);
String userName = reader.readLine();
this.userName = userName;
server.addUserName(userName);
String serverMessage = &quot;New user connected: &quot; + userName;
server.broadcast(serverMessage,this);
String clientMessage;
do {
clientMessage = reader.readLine();
serverMessage = &quot;[&quot; + userName + &quot;] : &quot; + clientMessage;
server.broadcast(serverMessage, this);		
}while(!clientMessage.equals(&quot;bye&quot;));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendMessage(String serverMessage) {
writer.println(serverMessage);
}
}
import java.net.*;
import java.io.*;
public class WriteThread extends Thread {
private Socket socket;
private ChatClient client;
private PrintWriter writer;
public WriteThread(Socket socket, ChatClient client) {
this.socket = socket;
this.client = client;
OutputStream output;
try {
output = socket.getOutputStream();
this.writer = new PrintWriter(output, true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
Console console = System.console();
String userName = console.readLine(&quot;Enter your username : &quot;);
writer.println(userName);
String text;
do {
text = console.readLine(&quot;[&quot; + userName + &quot;]: &quot;);
if(text.startsWith(&quot;[&quot;)){
String aimUserName = text.substring(text.indexOf(&quot;[&quot;)+1,text.indexOf(&quot;]&quot;));
System.out.println(&quot;Private Message to: &quot; + aimUserName);}
writer.println(text);
}while(!text.equals(&quot;bye&quot;));
/*do {
text = console.readLine(&quot;[&quot; + userName + &quot;]: &quot;);
writer.println(text);
}while(!text.equals(&quot;bye&quot;));*/
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

These codes work properly and I can multi-chat very clean. But while working on private chat stuff, I added to the ChatServer the line of:

public void privatebr(String serverMessage, String targetUserName){
for(UserThread aUser: userThreads){
if(aUser.userName == targetUserName)
aUser.sendMessage(serverMessage);
}

to the UserThread, I edited the part as:

String clientMessage;
do {
clientMessage = reader.readLine();
serverMessage = &quot;[&quot; + userName + &quot;] : &quot; + clientMessage;
if(clientMessage.startsWith(&quot;[&quot;)){
String targetUserName = clientMessage.substring(clientMessage.indexOf(&quot;[&quot;)+1,clientMessage.indexOf(&quot;]&quot;));
serverMessage = &quot;[&quot; + userName + &quot;] : &quot; + clientMessage;
server.privatebr(serverMessage, targetUserName);
}else{
server.broadcast(serverMessage, this);
}
}while(!clientMessage.equals(&quot;bye&quot;));

But when I did all these edits, the normal multi-chat progress became broken where is my fault? Why everything has broken?

答案1

得分: 1

好问题!回答你提出的问题是,你应该维护一个用户到他们的Socket连接的Map,这样在进行私信时,你可以选择要发送消息的用户。你还需要一个消息协议来实现这一点(见下文)

... 但是我必须告诉你,在今天的时代使用Sockets和SocketServer类就像重新发明轮子一样过时了。在创建聊天服务器时,最好从使用WebSocket协议开始。即使在这种情况下,你可能还需要定义一个消息协议(就像我做的那样 - 我创建了一个使用JSON和消息类型的消息协议,WebSocket事件中的字符串消息首先被解析为对象)

在所有平台上都有支持WebSocket的实现:Java、.NET、Python、PHP等。这应该是你的起点。

--- 更新 ---

我理解你的立场。为了帮助你理解Socket / ServerSocket,以下是一些指引和资源

  1. DatagramSockets(又名UDP):这是一种与常规TCP不同的传输协议,被Shockwave和Flash使用,也是Flash存在问题的根本原因。我强烈建议不要使用这个。
  2. Data和Object Input/OutputStreams:"Data"流仅适用于Java(无法连接到其他平台上构建的技术)。对象流类似,只是你通过流传输整个实际对象(同样仅适用于Java)。几乎没有人再使用这些了。
  3. SocketException:使用java.net.[Server]Socket时,你可能会遇到这个异常。当你在套接字上等待更多数据(通过read / readLine调用)时,套接字关闭时会发生此异常。我花了很长时间才弄清楚这一点,但这个异常是你的朋友!当连接关闭时(无论是在客户端还是服务器端),它会被触发。它允许等待套接字的线程唤醒,并允许你进行任何必要的清理工作。SocketException是IOException的子类,所以你可能甚至没有意识到这是什么。但现在至少我已经警告过你了。
  4. 流(Streams) vs. 写入器(Writers)和读取器(Readers):写入器和读取器用于将原始字节解释为Java字符和字符串。这是必要的,因为有多种文本格式(即ASCII、windows-xx、utf-8、utf-16)。读取器和写入器帮助你以不同的文本格式读取和写入文本(还可以从图像格式解释图像)。
  5. 缓冲写入器(Buffered Writers)和流(Streams):这些用于低效的读写。对于写入,这意味着你可以写入消息的一部分,直到你准备好发送为止。对于读取,这意味着逐行读取流,而不是一次性读取所有内容。
  6. TUS: tjacobs/io - https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/io/ 这是我几年前在SourceForge上发布的一个旧的Java库集合,但这里的许多类与处理Socket有关。特别是,请查看SocketServerEx、DataFetcher、Main/App、Timeout,以及可能的IOUtils。总之,确实看一下DataFetcher,它是一个轻量级的可线程化的回调I/O监听框架。

祝你好运,玩得开心!

英文:

Good question! To answer the question you asked is that you should maintain a Map of Users to their Socket connections, so that way with DMs you can just select the user(s) that you want to message. You will also need a messaging protocol for that (see below)

...But I have to tell you that using Sockets and SocketServer classes in today's day and age is like re-inventing the wheel. The place to start in doing a chat server is using the web sockets protocol. Even under this, you will probably want to define a message protocol (like I did - I created a messaging protocol using JSON and message types, where the string message in the websocket event onMessage first gets parsed into an object)

There are implementations for supporting WS on all platforms: java, .net, python, php etc. This should be your starting point.

--- Update ---

I understand where you are coming from. To help you along in understanding Sockets / ServerSockets, here are a couple of pointers & resources

  1. DatagramSockets (aka UDP): This is a different transmission protocol than the regular TCP, used by Shockwave and then Flash, and is the fundamental reason that Flash is problematic. I strongly recommend against this
  2. Data & Object Input/OutputStreams: "Data" streams are Java only (can't connect to technologgy built on other platforms). Object streams are similar, except you are transporting actual whole objects through the stream (also Java only) No one* (almost no one) uses these anymore.
  3. SocketException: Using java.net.[Server]Socket(s), you are likely to encounter this exception. It happens when you are waiting for more data (through a read / readLine call) on a socket, and the socket closes. It took me a long time to figure this out, but THIS EXCEPTION IS YOUR FRIEND! You get it when the connection has closed (either on the client or server side). It allows the thread that was waiting on the socket to wake up, and allows you to do whatever clean-up you need to do. SocketException is a subclass of IOException, so you may not even realize what this is. But now at least I have warned you
  4. Streams vs. Writers and Readers: Writers and Readers are for interpreting raw bytes as Java characters and Strings. This is necessary, as there are multiple text formats (i.e. ascii, windows-xx, utf-8, utf-16). Readers and Writers help you read and write text in different text formats (and also interpreting Images from image formats).
  5. Buffered Writers and Streams: These are for INEFFICIENT reading and writing. For writing, this means enabling you to write part of a message and not send it until you are ready. For reading, this means reading streams line by line for example rather than reading everything at one go.
  6. TUS: tjacobs/io - https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/io/ this is an old collection of Java libraries I put on SourceForge years ago, but a lot of the classes here pertain to dealing with Sockets. In particular, see SocketServerEx, DataFetcher, Main/App, Timeout, and maybe IOUtils. And of everything, really look at DataFetcher which is a lightweight threadableframework for Callback I/O listening.

Good luck and have fun!

huangapple
  • 本文由 发表于 2020年5月30日 00:45:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/62090799.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定