目录
一、实现思路
用户输入@name的方式发送消息(类似于QQ你@别人),但是想通过服务器端从信息中解析出这个@的人的名字不太可能,因为服务器端不知道截多长才是用户的名字。所以只能判断信息中是否包含了@name。String类的contains()方法可以做到。
服务端有以下两种思路去实现私聊
1.实现思路一
先判断用户是否@人了。
如果@人了,则发送消息的用户的服务套接字让其他的服务套接字判断这个@的名字是不是自己服务的用户的名字,如果是就转发,不是就不转发。
如果没@,则全部转发。
2.实现思路二
先判断用户是否@人了。
如果@人了,发送消息的用户的服务套接字根据@的名字让该名字的服务套接字转发。(哈希表)
如果没@,则全部转发。
二、实现代码
我们的用户端只负责输入,因此只需要改变服务端的代码。(这里也只记录服务器端的代码,用户端的代码和工具类代码在上一篇)。
1.思路一(服务端代码)
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import com.csi.qunliaoTest.OpenClient.ReceiveMsgThread;
public class OpenServer {
//一个列表存储服务Socket
List<ClientSocket> clientSockets =new ArrayList<>();
ServerSocket serverSocket = null;
//构造方法中创建服务器ServerSocket
public OpenServer() {
try {
//因为本代码是在云服务器运行,直接用InetAddress.getLocalHost()方法绑定云服务器的IP地址及端口号
serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost());
System.out.println("----------服务器----------");
} catch (IOException e) {
//发生异常。调用自己写的Utils类关闭服务器
Utils.close(serverSocket);
}
//ServerSocket绑定成功,开始等待用户接入
if(serverSocket.isBound())
acceptClient();
}
//等待用户,并为他生成服务Socket
private void acceptClient(){
//while循环让服务器一直可以接入用户,一个用户接入服务器,服务器就生成一个为该用户服务的Socket
while(true) {
Socket socket=null;
try {
//ServerScoket的accept()方法是一个阻塞方法,他会在这里等用户接入,直到有用户接入,才会运行下面的代码
socket=serverSocket.accept();
System.out.println("一个用户接入.....");
//用ClientSocket类包装Scoket类,ClientSocket类是自己写的内部线程类,该类实现了接收和转发用户消息
ClientSocket clientSocket=new ClientSocket(socket);
//开启服务线程
clientSocket.start();
//将这个包装了服务Scoket的对象添加进列表
clientSockets.add(clientSocket);
} catch (IOException e) {
Utils.close(socket);
}
}
}
public static void main(String[] args) {
//执行服务器代码
new OpenServer();
}
class ClientSocket extends Thread{
Socket socket=null;
DataInputStream dataInputStream=null;
DataOutputStream dataOutputStream=null;
String name = null;
public ClientSocket(Socket socket) {
this.socket = socket;
//包装服务Socket的输入输出流,异常就调用closeScoket()方法
try {
dataInputStream=new DataInputStream(socket.getInputStream());
} catch (IOException e) {
closeSocket(dataInputStream,socket);
}
try {
dataOutputStream=new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
closeSocket(dataOutputStream,socket);
}
}
//上面的代码我们关闭的都是装饰流,因为关闭装饰流会将内部流也关闭,Socket也会因此关闭,同时我们也要将列表里对应Socket的删除
public void closeSocket(Closeable...closeables) {
Utils.close(closeables);
System.out.println("一位用户退出");
clientSockets.remove(this);
}
@Override
public void run() {
//读取用户姓名,并让列表所有用户转发欢迎信息
//数据流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是将数据以UTF-8的编码方式发出或者接收
try {
//readUTF()方法也是阻塞方法,读取用户发来的名字
name=dataInputStream.readUTF();
} catch (IOException e1) {
closeSocket(dataInputStream);
}
//循环列表,除了自己,其他服务Scoket全部转发消息
for(ClientSocket clientSocket:clientSockets) {
if(clientSocket!=this) {
try {
clientSocket.dataOutputStream.writeUTF("欢迎"+name+"进入聊天室");
clientSocket.dataOutputStream.flush();//清空缓存区,让缓存区的数据全部出来
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
//知道名字后就一直等待接收用户端发的消息,异常就关闭Scoket并跳出循环
while(true) {
String msg = null;
//在读取到的消息前加上姓名
try {
msg = name+":"+dataInputStream.readUTF();
} catch (IOException e) {
closeSocket(dataInputStream);
break;
}
//判断信息中是否@人了,如果@人了,其他每个服务Socket判断@的名字里是不是自己服务的用户名字;如果没有@,则全部转发
if(msg.contains("@")) {
for(ClientSocket clientSocket:clientSockets) {
if(clientSocket!=this) {
try {
if(msg.contains("@"+clientSocket.name)) { //找到了是自己的用户端名字
//将@名字替换为空,replace返回一个心得字符串
clientSocket.dataOutputStream.writeUTF(msg.replace("@"+clientSocket.name, ""));
clientSocket.dataOutputStream.flush();
}
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
}
else {
for(ClientSocket clientSocket:clientSockets) {
if(clientSocket!=this) {
try {
clientSocket.dataOutputStream.writeUTF(msg);
clientSocket.dataOutputStream.flush();
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
}
}
}
}
}
2.思路二(服务端代码)
用户名不要写一样的名字,他是根据名字找套接字,当查找到这个名字就去哈希表找对应的套接字,如果名字相同,他只会找第一个与这个名字相同的套接字。
package com.csi.siliaoTest;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.csi.siliaoTest.OpenServer.ClientSocket;
public class OpenServer2 {
//一个哈希表存储用户名字和服务套接字
HashMap<String, ClientSocket> map= new HashMap<>();
//一个列表存储服务Socket
List<ClientSocket> clientSockets =new ArrayList<>();
//一个列表存储用户名字
List<String> names = new ArrayList<>();
ServerSocket serverSocket = null;
//构造方法中创建服务器ServerSocket
public OpenServer2() {
try {
//因为本代码是在云服务器运行,直接用InetAddress.getLocalHost()方法绑定云服务器的IP地址及端口号
serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost());
System.out.println("----------服务器----------");
} catch (IOException e) {
//发生异常。调用自己写的Utils类关闭服务器
Utils.close(serverSocket);
}
//ServerSocket绑定成功,开始等待用户接入
if(serverSocket.isBound())
acceptClient();
}
//等待用户,并为他生成服务Socket
private void acceptClient(){
//while循环让服务器一直可以接入用户,一个用户接入服务器,服务器就生成一个为该用户服务的Socket
while(true) {
Socket socket=null;
try {
//ServerScoket的accept()方法是一个阻塞方法,他会在这里等用户接入,直到有用户接入,才会运行下面的代码
socket=serverSocket.accept();
System.out.println("一个用户接入.....");
//用ClientSocket类包装Socket类,ClientSocket类是自己写的内部线程类,该类实现了接收和转发用户消息
ClientSocket clientSocket=new ClientSocket(socket);
//开启服务线程
clientSocket.start();
//将这个包装了服务Socket的对象添加进列表
clientSockets.add(clientSocket);
} catch (IOException e) {
Utils.close(socket);
}
}
}
public static void main(String[] args) {
//执行服务器代码
new OpenServer2();
}
class ClientSocket extends Thread{
Socket socket=null;
DataInputStream dataInputStream=null;
DataOutputStream dataOutputStream=null;
String name = null;
public ClientSocket(Socket socket) {
this.socket = socket;
//包装服务Socket的输入输出流,异常就调用closeScoket()方法
try {
dataInputStream=new DataInputStream(socket.getInputStream());
} catch (IOException e) {
closeSocket(dataInputStream,socket);
}
try {
dataOutputStream=new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
closeSocket(dataOutputStream,socket);
}
}
//上面的代码我们关闭的都是装饰流,因为关闭装饰流会将内部流也关闭,Socket也会因此关闭,同时我们也要将列表里对应Socket的删除
public void closeSocket(Closeable...closeables) {
Utils.close(closeables);
System.out.println("一位用户退出");
clientSockets.remove(this);
names.remove(name);
map.remove(name);
}
@Override
public void run() {
//读取用户姓名,并让列表所有用户转发欢迎信息
//数据流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是将数据以UTF-8的编码方式发出或者接收
try {
//readUTF()方法也是阻塞方法,读取用户发来的名字并添加哈希表和列表
name=dataInputStream.readUTF();
names.add(name);
map.put(name, this);
} catch (IOException e1) {
closeSocket(dataInputStream);
}
//循环列表,除了自己,其他服务Socket全部转发消息
for(ClientSocket clientSocket:clientSockets) {
if(clientSocket!=this) {
try {
clientSocket.dataOutputStream.writeUTF("欢迎"+name+"进入聊天室");
clientSocket.dataOutputStream.flush();//清空缓存区,让缓存区的数据全部出来
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
//知道名字后就一直等待接收用户端发的消息,异常就关闭Scoket并跳出循环
while(true) {
String msg = null;
//在读取到的消息前加上姓名
try {
msg = name+":"+dataInputStream.readUTF();
} catch (IOException e) {
closeSocket(dataInputStream);
break;
}
//判断信息中是否@人了,如果@人了,查找是否有@的这个名字的用户
if(msg.contains("@")) {
ClientSocket clientSocket = null;
for(String aname: names) {
if (msg.contains("@"+aname)) {
try {clientSocket =map.get(aname);
clientSocket.dataOutputStream.writeUTF(msg.replace("@"+aname, ""));
clientSocket.dataOutputStream.flush();
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
}
else {
for(ClientSocket clientSocket:clientSockets) {
if(clientSocket!=this) {
try {
clientSocket.dataOutputStream.writeUTF(msg);
clientSocket.dataOutputStream.flush();
} catch (IOException e) {
clientSocket.closeSocket(clientSocket.dataOutputStream);
}
}
}
}
}
}
}
}
注意:不要写一样的用户名字,他是在哈希表里根据名字找套接字,如果名字相同,他只会找第一个与这个名字相同的套接字。如果写一样得名字就只有一个用户会收到@的信息,并且会收到好几条(有几个重名就有几条)。如下图,我写了两个张三。