从今天起学习新知识了,再也不是复习老知识了。。
聊天室极简实现,要求:
- 有群聊和私聊两种模式;
- 当用户进入聊天室时,自己的视角显示“欢迎您进入聊天室”,聊天室其他成员显示“XXX进入聊天室”;
- 私聊模式中规定信息格式:@XXX:msg。为了好实现。
码代码前分析:
- 客户端需要开辟两个线程分别接收消息和发送消息;
- 服务器端需要不断监听端口是否被新的客户端连接,若有新客户端连接则开辟新线程负责该客户端与服务器间的通信,由此实现个客户端不影响不会阻塞。
具体代码实现:
- 先写一个工具类,用来关闭资源:方法参数为可变参数
public class ChatUtils {
public static void release(Closeable... targets) {
try {
for (Closeable target : targets) {
if (target != null) {
target.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 客户端代码:
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("———————客户端——————");
System.out.println("请输入用户名:");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String name = bufferedReader.readLine();
//建立连接:使用socket建立客户端+服务的地址和端口
Socket client = new Socket("localhost", 8888);
System.out.println("欢迎进入聊天室!");
//发送消息和接受消息分别创建两个线程互不打扰
new Thread(new Send(client, name)).start();
new Thread(new Receive(client)).start();
}
/**
* 从控制台读后再发送到服务端
*/
static class Send implements Runnable {
private BufferedReader br; //从控制台读
private DataOutputStream dos; //发到服务器端
private Socket client;
private boolean flag = true;
private String name; //用户名
Send(Socket client, String name) {
this.client = client;
this.name = name;
this.br = new BufferedReader(new InputStreamReader(System.in));
try {
this.dos = new DataOutputStream(client.getOutputStream());
dos.writeUTF(name);//在构造函数中将用户名发出去
dos.flush();
} catch (IOException e) {
flag = false;
ChatUtils.release(br, dos, client);
e.printStackTrace();
}
}
@Override
public void run() {
while (flag) { //不断从控制台读取后发送
try {
String str = br.readLine();
dos.writeUTF(str);
dos.flush();
} catch (IOException e) {
flag = false;
ChatUtils.release(br, dos, client);
e.printStackTrace();
}
}
}
}
/**
* 从服务端读消息
*/
static class Receive implements Runnable {
private DataInputStream dis;
private Socket client;
private boolean flag = true;
Receive(Socket client) {
this.client = client;
try {
this.dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
flag = false;
ChatUtils.release(dis, client);
e.printStackTrace();
}
}
@Override
public void run() {
while (flag) { //不断从服务端收取消息
try {
System.out.println(dis.readUTF());
} catch (IOException e) {
flag = false;
ChatUtils.release(dis, client);
e.printStackTrace();
}
}
}
}
}
- 服务端代码:
public class Server {
static CopyOnWriteArrayList<Channel> list = new CopyOnWriteArrayList<>(); //装连接的容器,需要并发读写
public static void main(String[] args) throws IOException {
System.out.println("———————服务端——————");
//指定端口 使用serversocket创建服务器
ServerSocket server = new ServerSocket(8888);
//服务器不断扫描是否有新客户端连接,若有则开辟新线程
while (true) {
Socket client = server.accept(); //阻塞式等待连接 accept
Channel channel = new Channel(client);
list.add(channel);
System.out.println("客户端:" + channel.name + ",已连接!");
new Thread(channel).start();
}
}
static class Channel implements Runnable {
private Socket client;
private DataInputStream dis;
private DataOutputStream dos;
private Boolean flag = true;
String name; //客户端名称
Channel(Socket client) {
this.client = client;
try {
this.dis = new DataInputStream(client.getInputStream());
this.dos = new DataOutputStream(client.getOutputStream());
this.name = receive(); //从客户端传来名称,注意这一步要在构造函数中完成
sendOthers(this.name, true); //有客户端进入房间后要发出系统消息通知其他客户端
} catch (IOException e) {
flag = false;
ChatUtils.release(dis, dos, client); //释放连接
e.printStackTrace();
}
}
/**
* 不断读取当前客户端发送的消息发送给其他连接的客户端
*/
@Override
public void run() {
while (flag) {
String msg = receive();
if (!msg.equals("")) {
sendOthers(msg, false); //此时非系统消息
}
}
}
/**
* 发送消息给当前的客户端
*
* @param str
*/
void send(String str) {
try {
dos.writeUTF(str);
dos.flush();
} catch (IOException e) {
flag = false;
ChatUtils.release(dis, dos, client);
e.printStackTrace();
}
}
/**
* 接受当前客户端的消息
*
* @return
*/
String receive() {
String str = "";
try {
str = dis.readUTF();
} catch (IOException e) {
flag = false;
ChatUtils.release(dis, dos, client);
e.printStackTrace();
}
return str;
}
/**
* 转发给其他服务器,有两种模式:私聊模式与群聊模式。
* 规定私聊模式的格式为:@XXX:msg。
* 群聊模式中分系统消息和对话消息。
*
* @param str
*/
void sendOthers(String str, boolean isSys) {
boolean isPrivate = str.startsWith("@");
if (isPrivate) { //私聊模式
int index = str.indexOf(":");
String toName = str.substring(1, index); //找到对方名字
String msg = str.substring(index + 1); //分离出消息
for (Channel channel : list) { //找到客户端私聊
if (channel.name.equals(toName)) {
channel.send(this.name + "对您说:" + msg);
break;
}
}
} else { //群聊模式
for (Channel channel : list) {
if (channel == this) { //无需发给自己
continue;
}
if (isSys) { //系统消息
channel.send("用户:" + this.name + ",进入聊天室");
} else { //对话消息
channel.send(this.name + "对所有人说:" + str);
}
}
}
}
}
}
导入的包就省略了,由此可以简易实现聊天室。