要求:
- 多人群聊小程序,实现服务器与客户端之间的交互,客户端之间发的消息相互可以看见
- 服务器为充当中转站接收客户端发来的消息,并且将消息群发给其他客户端(群发时,不包括发此条信息的客户端)
- 当某一个客户端发送消息为“bye”,结束当前线程;
结果展示:
1、启动客户端
2、启动第一个客户端
服务器端:
第一个客户端:
3、启动第二个客户端
服务器端:
第二个客户端:
与第一个客户端相同。
4、客户端已发送消息后,会将消息同步到其他客户端
客户端1:
客户端2:
5、客户端2与之相同
6、当有客户端退出时
例:客户端1退出,此时客户端1线程已结束。
服务器提示客户端1断开连接。
源码:
服务器端 Server
package cn.abc.chat.v6;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
// 所有客户端的输出流
private List<PrintWriter> allOut;
// 服务器Socket
private ServerSocket serverSocket;
//客户端连接个数
private int number;
// 线程池
private ExecutorService pool;
// 构造方法,用于初始化
public Server() {
try {
serverSocket = new ServerSocket(8888);
allOut = new ArrayList<PrintWriter>();
// 创建10个线程
pool = Executors.newFixedThreadPool(10);
} catch (IOException e) {
e.printStackTrace();
}
}
// 服务端开启方法
public void start() {
number=1;
try {
System.out.println("等待客户端连接...");
while (true) {
// 监听客户端连接(accept()方法是阻塞方法)
Socket socket = serverSocket.accept();
System.out.println("客户端"+number+"已连接");
// 启动一个线程去处理该客户端的请求
ClientHander hander = new ClientHander(socket,number,allOut);
// new Thread(hander).start();
pool.execute(hander);
number++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
}
服务器处理客户端传来的信息类 ----------重点:实现Runnable接口
package cn.abc.chat.v6;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.List;
public class ClientHander implements Runnable{
private Socket socket;
private int number;
private List<PrintWriter> allOut;
public ClientHander(Socket socket, int number,List<PrintWriter> allOut) {
this.socket = socket;
this.number = number;
this.allOut=allOut;
}
PrintWriter pw = null;
@Override
public void run() {
try {
// 获取客户端输出流
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
pw = new PrintWriter(osw, true);
// 将客户端的输出流保存进共享集合
addOut(pw);
// 字节流
InputStream in = socket.getInputStream();
// 字符输入流可以知道编码器
InputStreamReader isr = new InputStreamReader(in, "UTF-8");
BufferedReader br = new BufferedReader(isr);
// 训话读入客户端发来的信息
String msg = null;
while ((msg = br.readLine()) != null) {
sendMessage(pw, "客户端 "+number+":"+msg);
if(msg.equals("bye")) {
System.out.println("用户"+number+"已退出连接。");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
allOut.remove(pw);
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 添加同步方法
private synchronized void addOut(PrintWriter out) {
allOut.add(out);
}
// 删除同步方法
private synchronized void removeOut(PrintWriter out) {
allOut.remove(out);
}
private synchronized void sendMessage(PrintWriter pw, String msg) {
for (PrintWriter o : allOut) {
// 怎样不发给自己
if (o != pw) {
o.println(msg);
}
}
}
}
客户端类
package cn.abc.chat.v6;
/*
* 客户端应用程序
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client {
// 客户端Socket
private Socket socket;
// 构造方法,用于初始化
public Client() {
try {
socket = new Socket("localhost", 8888);
} catch (Exception e) {
e.printStackTrace();
}
}
OutputStream out;
// 客户端工作方法
public void start() {
// 字节输出流
try {
// 启动接收服务器的信息的线程
ServerHandler handler = new ServerHandler(socket);
Thread t = new Thread(handler);
// 设置为守护线程(一旦主线程结束,子线程会主动结束)
t.setDaemon(true);
t.start();
// 字节输出流
out = socket.getOutputStream();
// 字节流
OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
// 字节流
PrintWriter pw = new PrintWriter(osw, true);
// 读取用户输入的内容(循环输入)
Scanner sc = new Scanner(System.in);
while (true) {
String line = sc.next();
pw.println(line);
if (line.equals("bye")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
客户端处理服务器传入流的信息-------实现Runnable接口
package cn.abc.chat.v6;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerHandler implements Runnable{
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
// 字符输入流可以知道编码器
InputStreamReader isr = new InputStreamReader(in, "UTF-8");
BufferedReader br = new BufferedReader(isr);
// 循环读
while (true) {
System.out.println(br.readLine());
}
} catch (Exception e) {
}
}
}