前面的 2 篇文章,我们介绍了 Java 网络编程的基础,介绍了 UDP 与 TCP 的编程实现,今天我们利用前面的知识,通过 Java 网络编程来完成一个聊天室的功能,支持文本的群聊和私聊。

一、聊天室的原理

聊天室需要一个服务器来支持,多个客户端连接到服务器端,服务器的作用就是接收不同客户端的数据,并转发到其他客户端。

客户端可发发送数据给服务器端,同时客户端也需要接收服务器端返回的数据。客户端的发送数据和接收数据是两个独立的通道,互不影响。即客户端的输出与输入要独立,可以使用多线程来实现。

服务端要为每一个客户端建立一个通道,服务端也使用多线程来实现。

服务端需要创建一个通道的列表,统一管理客户端的通道,为了实现自己发的消息,别人可以看到,不需要返回自己的通道。这样就实现了群聊的功能。也就是自己发一个消息,其他人都可以看到。

在客户端程序里为每一个客户端设置一个名称,约定以@name#开头的格式为私聊,就可以实现私聊的功能。

当程序中发生异常时,线程就停止执行。

二、聊天室的功能

每个客户端在连接到服务器端时,要通过控制台输入自己的名称,然后开始发送消息到服务端,服务端在接收到客户端的连接时,首先输出谁进入了聊天室,然后把客户端发来的消息转发给其他客户端,实现群聊的功能,如果客户端按照约定以@name#开头的格式输入消息,服务端需要解析到客户端要私聊的对象,把消息单独发送给要私聊的客户端。

三、聊天室的代码实现

1、由于代码里会处理很多 IO 异常,当程序中发生异常时,线程就停止执行,并且关闭掉对应的资源,因此我们定义一个公告的关闭资源的类和方法。

public class Util {public static void closeAll(Closeable... io) {for (Closeable temp : io) {try {if (null != temp) {temp.close();}} catch (Exception e) {e.printStackTrace();}}}}

2、ChatChannel 类,实现服务器端的多线程,维护一个客户端的通道列表,服务器端既能接收客户端的数据,又能把数据转发给对应的客户端。

public class ChatChannel implements Runnable {public static List<ChatChannel> all = new ArrayList<ChatChannel>();// 通道列表private DataInputStream dis; // 输入流 private DataOutputStream dos;// 输出流 private String name;// 客户端名称 private boolean isRunning = true;public ChatChannel(Socket client) {try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());this.name = dis.readUTF();System.out.println(this.name + "进入了聊天室");this.send(this.name + ",您好!欢迎您进入聊天室");sendOthers(this.name + "进入了聊天室", true); // 系统消息 } catch (IOException e) {e.printStackTrace();Util.closeAll(dis, dos);isRunning = false;}}/** * 读取数据*/private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {e.printStackTrace();Util.closeAll(dis);isRunning = false;all.remove(this); // 移除自身 }return msg;}/** * 发送数据 */private void send(String msg) {if (msg != null && !"".equals(msg)) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {e.printStackTrace();Util.closeAll(dos);isRunning = false;all.remove(this); // 移除自身 }}}/** * @param msg 消息内容 * @param sysMsg 是否是系统消息 */private void sendOthers(String msg, boolean sysMsg) {// 加入私聊的判断,约定@name#格式为私聊 if (msg.startsWith("@") && msg.indexOf("#") > -1) { // 私聊 // 获取name String name = msg.substring(1, msg.indexOf("#"));String content = msg.substring(msg.indexOf("#") + 1);for (ChatChannel other : all) {if (name.equals(other.name)) {other.send(this.name + "悄悄地对您说:" + content);}}} else {for (ChatChannel other : all) {if (other == this) {continue;}if (sysMsg) {other.send("系统信息:" + msg);} else {// 发送其他客户端 other.send(this.name + "对所有人说:" + msg);}}}}@Overridepublic void run() {while (isRunning) {sendOthers(receive(), false); // 用户消息 }}}

3、创建服务端类 Server,使用多线程和通道容器。

public class Server {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8888);while (true) {Socket client = server.accept();ChatChannel channel = new ChatChannel(client);ChatChannel.all.add(channel);// 统一管理客户端的通道 new Thread(channel).start(); // 启动一条通道 }}}

4、客户端的消息发送线程类 Send,每个客户端要设定自己的名字,同时接收控制台输入的数据并发送给服务端。

public class Send implements Runnable {// 控制台输入 private BufferedReader console;// 输出流 private DataOutputStream dos;// 客户端名称 private String name;// 控制线程 private boolean isRunning = true;public Send(Socket client, String name) {try {console = new BufferedReader(new InputStreamReader(System.in));dos = new DataOutputStream(client.getOutputStream());this.name = name;send(this.name); // 把自己的名字发给服务端 } catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dos, console);}}/** * 从控制台接收数据并发送数据 */public void send(String msg) {try {if (msg != null && !"".equals(msg)) {dos.writeUTF(msg);dos.flush(); // 强制刷新 }} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dos, console);}}// 从控制台接收数据 private String getMsgFromConsole() {try {return console.readLine();} catch (IOException e) {e.printStackTrace();}return "";}@Overridepublic void run() {while (isRunning) {send(getMsgFromConsole());}}}

5、客户端的消息接收线程类 Receive,用于独立接收服务端返回的数据。

public class Receive implements Runnable {// 输入流 private DataInputStream dis;// 线程标识 private boolean isRunning = true;public Receive(Socket client) {try {dis = new DataInputStream(client.getInputStream());} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dis);}}/** * 接收数据 */public String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {e.printStackTrace();isRunning = false;Util.closeAll(dis);}return msg;}@Overridepublic void run() {while (isRunning) {System.out.println(receive());}}}

6、创建客户端类 Client,发送数据和接收数据分布使用独立的多线程处理。

public class Client {public static void main(String[] args) throws IOException {System.out.println("请输入您的名称:");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String name = br.readLine();if ("".equals(name)) {return;}Socket client = new Socket("localhost", 8888);new Thread(new Send(client, name)).start(); // 发送一条通道 new Thread(new Receive(client)).start(); // 接收一条通道 }}

四、聊天室功能测试

首先运行服务端 Server 类,然后运行客户端 Client 类,为第一个客户端起名叫 aaa,再运行客户端 Client 类,为第二个客户端起名叫 bbb,接着运行客户端 Client 类,为第三个客户端起名叫 ccc。

此时的控制台输出如下:

服务端控制台输出:aaa进入了聊天室bbb进入了聊天室ccc进入了聊天室客户端1控制台输出:请输入您的名称:aaaaaa,您好!欢迎您进入聊天室系统信息:bbb进入了聊天室系统信息:ccc进入了聊天室客户端2控制台输出:请输入您的名称:bbbbbb,您好!欢迎您进入聊天室系统信息:ccc进入了聊天室客户端3控制台输出:请输入您的名称:cccccc,您好!欢迎您进入聊天室

以上的测试结果说明,我们的聊天室已经支持了一个服务端可以支持多个客户端的连接,并且可以给多个客户端同时返回消息。

然后在控制台1里输入:“大家好,我是aaa,我喜欢Java。”,在控制台2里输入:“大家好,我是bbb,我喜欢唱歌。”,在控制台3里输入:“大家好,我是ccc,我喜欢跳舞。”,此时的客户端控制台输出如下:

客户端1控制台输出:请输入您的名称:aaaaaa,您好!欢迎您进入聊天室系统信息:bbb进入了聊天室系统信息:ccc进入了聊天室大家好,我是aaa,我喜欢Java。bbb对所有人说:大家好,我是bbb,我喜欢唱歌。ccc对所有人说:大家好,我是ccc,我喜欢跳舞。客户端2控制台输出:请输入您的名称:bbbbbb,您好!欢迎您进入聊天室系统信息:ccc进入了聊天室aaa对所有人说:大家好,我是aaa,我喜欢Java。大家好,我是bbb,我喜欢唱歌。ccc对所有人说:大家好,我是ccc,我喜欢跳舞。客户端3控制台输出:请输入您的名称:cccccc,您好!欢迎您进入聊天室aaa对所有人说:大家好,我是aaa,我喜欢Java。bbb对所有人说:大家好,我是bbb,我喜欢唱歌。大家好,我是ccc,我喜欢跳舞。

以上的测试说明,我们的聊天室已经支持了群聊。

下面我们来测试一下私聊的功能,按照私聊的约定格式以“@name#”开头,bbb与ccc私聊,在控制台2里输入:“@ccc#你好ccc,我是bbb,我们一起唱歌跳舞好吗?”,ccc给bbb回复,在控制台3里输入:“@bbb#你好bbb,不好意思,没有时间。”,此时的客户端控制台输出如下:

客户端1控制台输出:请输入您的名称:aaaaaa,您好!欢迎您进入聊天室系统信息:bbb进入了聊天室系统信息:ccc进入了聊天室大家好,我是aaa大家好,我是aaa,我喜欢Java。bbb对所有人说:大家好,我是bbb,我喜欢唱歌。ccc对所有人说:大家好,我是ccc,我喜欢跳舞。客户端2控制台输出:请输入您的名称:bbbbbb,您好!欢迎您进入聊天室系统信息:ccc进入了聊天室aaa对所有人说:大家好,我是aaaaaa对所有人说:大家好,我是aaa,我喜欢Java。大家好,我是bbb,我喜欢唱歌。ccc对所有人说:大家好,我是ccc,我喜欢跳舞。@ccc#你好ccc,我是bbb,我们一起唱歌跳舞好吗?ccc悄悄地对您说:你好bbb,不好意思,没有时间。客户端3控制台输出:请输入您的名称:cccccc,您好!欢迎您进入聊天室aaa对所有人说:大家好,我是aaaaaa对所有人说:大家好,我是aaa,我喜欢Java。bbb对所有人说:大家好,我是bbb,我喜欢唱歌。大家好,我是ccc,我喜欢跳舞。bbb悄悄地对您说:你好ccc,我是bbb,我们一起唱歌跳舞好吗?@bbb#你好bbb,不好意思,没有时间。

以上的测试说明,我们的聊天室已经支持了私聊的功能。

本篇文章综合运用了 Java 网络编程、IO 流、多线程的知识,完成了一个聊天室的功能,希望大家可以熟练掌握。

分类: 教程分享 标签: 暂无标签

评论

暂无评论数据

暂无评论数据

目录