- 本文讲述了从实现单个多个客户的收发信息(基础简易版),到各种实现代码的封装(oop版),实现群聊(群聊过渡版),到最后实现私聊(终极版)的过程
- 本文内容是在初步学习网络编程时,练习强化记忆时的学习总结
- 主要利用了TCP网络编程和多线程
- 如有问题,欢迎指出

综合案例:在线聊天室

需求:使用TCP的Socket实现一个聊天室

  • 服务器端:一个线程专门发送消息,一个线程专门接收消息
  • 客户端:一个线程专门发送消息,一个线程专门接收消息

1. 基础简易版

1.1 一个客户收发多条消息

  • 目标:实现一个客户可以正常收发多条信息

服务器

/*** 在线聊天室: 服务器* 目标:实现一个客户可以正常收发多条消息*  服务器不生产内容,相当于一个转发站,将客户端的请求转发*/
public class MutiChat {public static void main(String[] args) throws IOException {System.out.println("-----Server-----");// 1、指定端口  使用ServerSocket创建服务器ServerSocket server = new ServerSocket(8888);// 2、利用Socket的accept方法,监听客户端的请求。阻塞,等待连接的建立Socket client = server.accept();System.out.println("一个客户端建立了连接");DataInputStream dis = new DataInputStream(client.getInputStream());DataOutputStream dos = new DataOutputStream(client.getOutputStream());boolean isRunning = true;while (isRunning) {// 3、接收消息String msg = dis.readUTF();// 4、返回消息dos.writeUTF(msg);dos.flush();}// 5、释放资源dos.close();dis.close();client.close();}
}

客户端

/*** 在线聊天室: 客户端* 目标:实现一个客户可以正常收发(多条)信息*/
public class MutiClient {public static void main(String[] args) throws IOException {System.out.println("-----Client-----");// 1、建立连接:使用Socket创建客户端 + 服务的地址和端口Socket client = new Socket("localhost", 8888);// 2、客户端发送消息BufferedReader console = new BufferedReader(new InputStreamReader(System.in));  // 对接控制台DataOutputStream dos = new DataOutputStream(client.getOutputStream());DataInputStream dis = new DataInputStream(client.getInputStream());boolean isRunning = true;while (isRunning) {String msg = console.readLine();dos.writeUTF(msg);dos.flush();// 3、获取消息msg = dis.readUTF();System.out.println(msg);}// 4、释放资源dos.close();dis.close();client.close();}
}

1.2 多个客户收发多条消息(不使用多线程)

  • 目标:实现多个客户可以正常收发多条信息
  • 出现排队问题:其他客户必须等待之前的客户退出,才能收发消息

服务器


public class MutiChat {public static void main(String[] args) throws IOException {System.out.println("-----Server-----");// 1、指定端口  使用ServerSocket创建服务器ServerSocket server = new ServerSocket(8888);// 2、利用Socket的accept方法,监听客户端的请求。阻塞,等待连接的建立while (true) {Socket client = server.accept();System.out.println("一个客户端建立了连接");DataInputStream dis = new DataInputStream(client.getInputStream());DataOutputStream dos = new DataOutputStream(client.getOutputStream());boolean isRunning = true;while (isRunning) {// 3、接收消息String msg = dis.readUTF();// 4、返回消息dos.writeUTF(msg);dos.flush();}// 5、释放资源dos.close();dis.close();client.close();}}
}

1.3 多个客户收发多条消息(多线程)

  • 目标:实现多个客户可以正常收发多条信息
  • 出现的问题:利用Lambda太复杂,代码过多不好维护;客户端读写没有分开,必须先写后读

服务器代码

public class ThreadMutiChat {public static void main(String[] args) throws IOException {System.out.println("-----Server-----");// 1、指定端口  使用ServerSocket创建服务器ServerSocket server = new ServerSocket(8888);// 2、利用Socket的accept方法,监听客户端的请求。阻塞,等待连接的建立while (true) {Socket client = server.accept();System.out.println("一个客户端建立了连接");// 加入多线程new Thread(()->{DataInputStream dis = null;DataOutputStream dos = null;try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());} catch (IOException e) {e.printStackTrace();}boolean isRunning = true;while (isRunning) {// 3、接收消息String msg = null;try {msg = dis.readUTF();// 4、返回消息dos.writeUTF(msg);dos.flush();} catch (IOException e) {//                        e.printStackTrace();isRunning = false;  // 停止线程}}// 5、释放资源try {if (null == dos) {dos.close();}} catch (IOException e) {e.printStackTrace();}try {if (null == dis) {dis.close();}} catch (IOException e) {e.printStackTrace();}try {if (null == client) {client.close();}} catch (IOException e) {e.printStackTrace();}}).start();}}
}

2. oop封装版

目标:封装使用多线程实现多个客户可以正常收发多条消息

  • 1、线程代理 Channel,一个客户代表一个 Channel
  • 2、实现方法:接收消息 - receive; 发送消息 - send; 释放资源 - release;
  • 3、其中释放资源 release方法中利用工具类 Utils:实现Closeable接口、可变参数
  • 好处:利用封装,是代码简洁,便于维护

服务器端

示例代码:

public class ThreadMutiChat {public static void main(String[] args) throws IOException {System.out.println("---服务器开始工作---");// 1、指定端口  使用ServerSocket创建服务器ServerSocket server = new ServerSocket(8888);// 2、利用Socket的accept方法,监听客户端的请求。阻塞,等待连接的建立while (true) {Socket client = server.accept();System.out.println("一个客户端建立了连接");new Thread(new Channel(client)).start();}}// 一个客户代表一个Channelstatic class Channel implements Runnable {private DataInputStream dis = null;private DataOutputStream dos = null;private Socket client;private boolean isRunning;public Channel(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());isRunning = true;} catch (IOException e) {System.out.println("---构造时出现异常---");release();}}// 接收消息private String receive() {String msg = "";        // 避免空指针try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---接受消息出现异常---");release();}return msg;}// 发送消息private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---发送消息出现异常---");release();}}// 释放资源private void release() {this.isRunning = false;// 封装Utils.close(dis, dos, client);}// 线程体@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {send(msg);}}}}
}

工具类Utils:实现Closeable接口,利用可变参数,达到释放资源的作用

示例代码

public class Utils {public static void close(Closeable... targets) {        // Closeable是IO流中接口,"..."可变参数// IO流和Socket都实现了Closeable接口,可以直接用for (Closeable target: targets) {try {// 只要是释放资源就要加入空判断if (null != target) {target.close();}}catch (Exception e) {e.printStackTrace();}}}
}

客户端:启用两个线程Send和Receive实现收发信息的分离

示例代码

public class ThreadMutiClient {public static void main(String[] args) throws IOException {System.out.println("-----客户端开始工作-----");Socket client = new Socket("localhost", 8888);new Thread(new Send(client)).start();new Thread(new Receive(client)).start();}
}

使用多线程封装客户的发送端 – Send类

实现方法:

  • 1、发送消息 - send()
  • 2、从控制台获取消息 - getStrFromConsole()
  • 3、释放资源 - release()
  • 4、线程体 - run()

示例代码

public class Send implements Runnable {private BufferedReader console;private DataOutputStream dos;private Socket client;private boolean isRunning;public Send(Socket client) {this.client = client;console = new BufferedReader(new InputStreamReader(System.in));  // 对接控制台try {dos = new DataOutputStream(client.getOutputStream());isRunning = true;} catch (IOException e) {System.out.println("---客户发送端构造时异常---");release();}}// 从控制台获取消息private String getStrFromConsole() {try {return console.readLine();} catch (IOException e) {e.printStackTrace();return "";}}// 发送消息private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---客户发送端发送消息异常---");release();}}@Overridepublic void run() {while (isRunning) {String msg = getStrFromConsole();if (!msg.equals("")) {send(msg);}}}// 释放资源private void release() {this.isRunning = false;Utils.close(dos,client);}
}

使用多线程封装客户的接收端 – Receive类

实现方法:

  • 1、接收消息 - send
  • 2、释放资源 - release()
  • 3、线程体 - run()

示例代码

public class Receive implements Runnable {private DataInputStream dis;private Socket client;private boolean isRunning;public Receive(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());isRunning = true;} catch (IOException e) {System.out.println("---客户接收端构造时异常---");release();}}// 接收消息private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---客户接收端接收消息异常---");release();}return msg;}// 释放资源private void release() {isRunning = false;Utils.close(dis, client);}@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {System.out.println(msg);}}}
}

3. 群聊过渡版

目标:加入容器,实现群聊

服务器端

  • 1、建立 CopyOnWriteArrayList<Channel> 容器,容器中的元素是Channel客户端代理。要对元素进行修改同时遍历时,推荐使用此容器,避免出问题。
  • 2、实现方法void sendOthers(String msg)将信息发送给除自己外的其他人。

服务器端实现代码

public class Chat {// 建立 CopyOnWriteArrayList<Channel> 容器private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();public static void main(String[] args) throws IOException {System.out.println("---服务器开始工作---");ServerSocket server = new ServerSocket(8888);while (true) {Socket client = server.accept();System.out.println("一个客户端建立了连接");Channel c = new Channel(client);all.add(c);     // 管理所有的成员new Thread(c).start();}}// 一个客户代表一个Channelstatic class Channel implements Runnable {private DataInputStream dis;private DataOutputStream dos;private Socket client;private boolean isRunning;private String name;public Channel(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());isRunning = true;// 获取名称this.name = receive();// 欢迎this.send("欢迎你的到来");sendOthers(this.name + "加入群聊", true);} catch (IOException e) {System.out.println("---构造时出现问题---");release();}}// 接收消息private String receive() {String msg = "";        // 避免空指针try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---接受消息出现问题---");release();}return msg;}// 发送消息private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---发送消息出现问题---");release();}}// 群聊:把自己的消息发送给其他人private void sendOthers(String msg, boolean isSys) {for (Channel other : all) {if (other == this) {continue;}if (!isSys) {other.send(this.name + ": " + msg);  // 群聊消息} else {other.send("系统消息:" + msg);    // 系统消息}}}// 线程体@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {sendOthers(msg, false);}}}// 释放资源private void release() {this.isRunning = false;// 封装Utils.close(dis, dos, client);// 退出all.remove(this);sendOthers(this.name + "离开聊天室", true);}}
}

客户端实现代码

public class Client {public static void main(String[] args) throws IOException {System.out.println("-----客户端开始工作-----");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));Socket client = new Socket("localhost", 8888);System.out.println("请输入用户名:");      // 不考虑重名String name = br.readLine();new Thread(new Send(client, name)).start();new Thread(new Receive(client)).start();}
}

客户端Send类实现代码

public class Send implements Runnable {private BufferedReader console;private DataOutputStream dos;private Socket client;private boolean isRunning;private String name;public Send(Socket client, String name) {this.client = client;console = new BufferedReader(new InputStreamReader(System.in));this.isRunning = true;this.name = name;try {dos = new DataOutputStream(client.getOutputStream());// 发送名称send(name);} catch (IOException e) {System.out.println("Send类构造时异常");release();}}// 从控制台获取消息private String getStrFromConsole() {try {return console.readLine();} catch (IOException e) {e.printStackTrace();return "";}}// 发送消息private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---客户发送端发送消息异常---");release();}}@Overridepublic void run() {while (isRunning) {String msg = getStrFromConsole();if (!msg.equals("")) {send(msg);}}}// 释放资源private void release() {this.isRunning = false;Utils.close(dos,client);}
}

客户端Receive类实现代码

public class Receive implements Runnable {private DataInputStream dis;private Socket client;private boolean isRunning;public Receive(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());isRunning = true;} catch (IOException e) {System.out.println("---客户接收端构造时异常---");release();}}// 接收消息private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---客户接收端接收消息异常---");release();}return msg;}// 释放资源private void release() {isRunning = false;Utils.close(dis, client);}@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {System.out.println(msg);}}}
}

运行结果


4. 终极版:实现私聊

私聊形式:@XXX:

实现方法
1、boolean isPrivate = msg.startsWith("@")用于判断是否为私聊

  • 利用String类中方法
  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开头

2、String targetName = msg.substring(1, index)用于判断用户名;msg = msg.substring(index+1)用于判断发送的信息

  • 利用String类中方法
  • substring(int beginIndex):返回一个字符串,该字符串是此字符串的子字符串
  • substring(int beginIndex, int endIndex):返回一个字符串,该字符串是此字符串的子字符串

服务器端实现代码

public class Chat {private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();public static void main(String[] args) throws IOException {System.out.println("---服务器开始工作---");ServerSocket server = new ServerSocket(8888);while (true) {Socket client = server.accept();System.out.println("一个客户端建立了连接");Channel c = new Channel(client);all.add(c);     // 管理所有的成员new Thread(c).start();}}static class Channel implements Runnable {private DataInputStream dis;private DataOutputStream dos;private Socket client;private boolean isRunning;private String name;public Channel(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());isRunning = true;// 获取名称this.name = receive();// 欢迎this.send("欢迎你的到来");sendOthers(this.name + "加入群聊", true);} catch (IOException e) {System.out.println("---构造时出现问题---");release();}}private String receive() {String msg = "";        // 避免空指针try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---接受消息出现问题---");release();}return msg;}private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---发送消息出现问题---");release();}}/*** 群聊:把自己的消息发送给其他人* 私聊:约定数据格式:@XXX:msg* @param msg* @param isSys*/private void sendOthers(String msg, boolean isSys) {boolean isPrivate = msg.startsWith("@");if (isPrivate) { // 私聊int index = msg.indexOf(":");       // 第一次冒号出现的位置// 获取目标和数据String targetName = msg.substring(1, index);msg = msg.substring(index+1);for (Channel other: all) {if (other.name.equals(targetName)) { // 目标other.send(this.name + "对您说悄悄话: " + msg);  // 群聊消息}}} else {for (Channel other : all) {if (other == this) {continue;}if (!isSys) {other.send(this.name + ": " + msg);  // 群聊消息} else {other.send("系统消息:" + msg);    // 系统消息}}}}@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {sendOthers(msg, false);}}}private void release() {this.isRunning = false;Utils.close(dis, dos, client);all.remove(this);sendOthers(this.name + "离开聊天室", true);}}
}

客户端实现代码

public class Client {public static void main(String[] args) throws IOException {System.out.println("-----客户端开始工作-----");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));Socket client = new Socket("localhost", 8888);System.out.println("请输入用户名:");      // 不考虑重名String name = br.readLine();new Thread(new Send(client, name)).start();new Thread(new Receive(client)).start();}
}

客户端Send类实现代码

public class Send implements Runnable {private BufferedReader console;private DataOutputStream dos;private Socket client;private boolean isRunning;private String name;public Send(Socket client, String name) {this.client = client;console = new BufferedReader(new InputStreamReader(System.in));this.isRunning = true;this.name = name;try {dos = new DataOutputStream(client.getOutputStream());// 发送名称send(name);} catch (IOException e) {System.out.println("Send类构造时异常");release();}}private String getStrFromConsole() {try {return console.readLine();} catch (IOException e) {e.printStackTrace();return "";}}private void send(String msg) {try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {System.out.println("---客户发送端发送消息异常---");release();}}@Overridepublic void run() {while (isRunning) {String msg = getStrFromConsole();if (!msg.equals("")) {send(msg);}}}private void release() {this.isRunning = false;Utils.close(dos,client);}
}

客户端Receive类实现代码

public class Receive implements Runnable {private DataInputStream dis;private Socket client;private boolean isRunning;public Receive(Socket client) {this.client = client;try {dis = new DataInputStream(client.getInputStream());isRunning = true;} catch (IOException e) {System.out.println("---客户接收端构造时异常---");release();}}private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {System.out.println("---客户接收端接收消息异常---");release();}return msg;}private void release() {isRunning = false;Utils.close(dis, client);}@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {System.out.println(msg);}}}
}

Utils类实现代码

public class Utils {public static void close(Closeable... targets) {for (Closeable target: targets) {try {if (null != target) {target.close();}}catch (Exception e) {e.printStackTrace();}}}
}

输出结果

JAVA实现在线聊天室(层层递进)相关推荐

  1. 基于Java的在线聊天室

    shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断 发送文件:依靠FileInputStream,DataInputStream,getI ...

  2. 基于Java语言的Web在线聊天室

    在线聊天室 能够实现登录,注册,聊天功能,最终效果如下图所示 注册页面 <%@ page language="java" contentType="text/htm ...

  3. JAVA利用多线程和Socket制作GUI界面的在线聊天室

    目录 前言 功能设计 GUI画面展示 服务器端 客户端 私聊窗口 主要代码 服务器端 客户端 其它代码 打包成jar 打包成exe文件 如何让其它电脑访问聊天室? 最后 前言 最近刚好是期末,碰上Ja ...

  4. Java网络编程,使用Java实现UDP和TCP网络通信协议,以及基于UDP的在线聊天室。

    文章目录 前言 一.网络编程概念 1.网络 2. 网络编程的目的 3.想要达到这个效果需要什么 4.网络分层 二.网络编程Java类 1.IP地址:InetAddress 2.端口 3.TCP连接 3 ...

  5. Java+Springboot+Websocket在线聊天室

    1.什么是websocket? websocket是由HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯.它是一种在单个TCP连接上进行全双工通信的协议.W ...

  6. 简单两步,用Java实现网络在线聊天室

    Echat在线聊天室 一款轻量级的基于SpringBoot + WebSocket的在线聊天室项目,在MccreeFei的聊天室基础上,将其升级为SpringBoot版本,去掉了JSP文件,去掉了xm ...

  7. rudesocket如何使用_[WebSocket入门]手把手搭建WebSocket多人在线聊天室(SpringBoot+WebS...

    前言 本文中搭建了一个简易的多人聊天室,使用了WebSocket的基础特性. 源代码来自老外的一篇好文: 本文内容摘要: 初步理解WebSocket的前后端交互逻辑 手把手使用 SpringBoot ...

  8. 基于Java+Swing实现聊天室

    基于Java+Swing实现聊天室 一.系统介绍 二.功能展示 三.其它 1.其他系统实现 四.获取源码 一.系统介绍 Java聊天室系统主要用于实现在线聊天,基本功能包括:服务端和客户端.本系统结构 ...

  9. java web聊天室论文_基于Java网页版聊天室的设计与实现毕业论文含开题报告及文献综述(样例3)...

    <基于Java网页版聊天室的设计与实现毕业论文含开题报告及文献综述.doc>由会员分享,可免费在线阅读全文,更多与<基于Java网页版聊天室的设计与实现毕业论文含开题报告及文献综述& ...

最新文章

  1. GCC 参数列举及解释
  2. iOS开发-简单解析JSON数据
  3. LA3602DNA序列
  4. spark指定hive字段_Spark2.0集成Hive操作的相关配置与注意事项
  5. tokudb mysql_【MySQL】TokuDB引擎安装教程
  6. boost::phoenix模块使用 BLL 和 boost::function 进行测试
  7. Jenkins 配置邮箱 530Authentication required ,535 uthentication failed 的解决方法
  8. vue.js的学习中的简单案例
  9. 2008服务器打不开iso文件,Windows Server无法挂载ISO文件:以下是修复它的方法
  10. 用户体验 | 银行如何优化APP用户体验
  11. 跟我学c++中级篇——decay
  12. springboot后端数据校验以及异常处理
  13. 如何将夜晚图片转化为白天图片 matlab,Python|记一次图片夜景效果尝试
  14. MySQL NDB Cluster部署方案与实践
  15. 北京一周行(有关《MOOC课程设计实战》高级研修班)
  16. 同步IO 和异步IO
  17. IT项目管理实践经验分享
  18. 零基础想学好编程!C语言最难啃的 3 块硬骨头,你全吃透了吗?
  19. “飞思卡尔”杯智能车设计与实践
  20. 基于 Tensorflow 轻松实现 XOR 运算!| CSDN 博文精选

热门文章

  1. java lobo使用_[持续更新]Cobra:Java HTML parser用法详解
  2. 怎么锁定计算机不让其他人安装,win10如何锁定电脑不让别人打开
  3. 软考,个人快速成长最好的投资
  4. android命令打包,android 命令打包
  5. JAVA-date(计算时间差)
  6. 2018-5-22-Python全栈开发day9-Python开发课程简介part2
  7. 设计模式C++实现_2_简单工厂模式
  8. logback 常用配置详解appender
  9. 如何把备份的bak还原到新的数据库
  10. Winrunner与QTP