分析:

聊天室需要多个客户端和一个服务端。

服务端负责转发消息。

客户端可以发送消息、接收消息。

  1. 消息分类:
  2. 群聊消息:发送除自己外所有人
  3. 私聊消息:只发送@的人
  4. 系统消息:根据情况分只发送个人和其他人
  5. 技术方面:
  6. 客户端和服务端收发消息,需要使用IO流,封装一个IOUtils工具类用来释放资源。
  7. 客户端需要同时收发消息,需要启动发送和接收两个消息,互不干扰
  8. 服务端需要接收每个客户端消息和对多个客户端发送消息,每连接上一个客户端需要启动一个线程,让后面进来的客户端不需要等待前面的客户端退出后才能建立连接。

……

还是上代码吧。

基础版:

搭建结构,实现多个客户端和服务端连接,保证服务端能正常转发消息。

我们约定:

当服务端在初始化、发送、接收时出现异常时分别输出:

------1------

------2------

------3------

  1. 当客户端,初始化发送线程、初始化接收线程、发送、接收异常时分别输出:
  2. ======1=====
  3. ======2=====
  4. ======3=====
  5. ======4=====

1、IO工具类

  1. package com.xzlf.chat;
  2. import java.io.Closeable;
  3. import java.io.IOException;
  4. /**
  5. * 工具类
  6. * @author xzlf
  7. *
  8. */
  9. public class IOUtils {
  10. /**
  11. * 释放资源
  12. * @param closeables
  13. */
  14. public static void close(Closeable...closeables) {
  15. for (Closeable closeable : closeables) {
  16. if(null != closeable) {
  17. try {
  18. closeable.close();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. }
  25. }

2、服务端

  1. package com.xzlf.chat;
  2. import java.io.DataInputStream;
  3. import java.io.DataOutputStream;
  4. import java.io.IOException;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. /**
  8. * 聊天室:服务器
  9. * @author xzlf
  10. *
  11. */
  12. public class TMultiChat {
  13. public static void main(String[] args) throws IOException {
  14. System.out.println("======server======");
  15. // 1、指定端口创建服务端
  16. ServerSocket server = new ServerSocket(8888);
  17. while(true) {
  18. // 2、每进来一个客户端启动一个线程
  19. Socket socket = server.accept();
  20. System.out.println("一个客户端建立了连接");
  21. new Thread(new Channel(socket)).start();
  22. }
  23. }
  24. // 一个Channel 代表一个客户端
  25. static class Channel implements Runnable{
  26. private Socket socket;
  27. private DataInputStream dis;
  28. private DataOutputStream dos;
  29. private boolean isRuning;
  30. public Channel(Socket socket) {
  31. this.socket = socket;
  32. this.isRuning = true;
  33. try {
  34. dis = new DataInputStream(socket.getInputStream());
  35. dos = new DataOutputStream(socket.getOutputStream());
  36. } catch (IOException e) {
  37. System.out.println("------1------");
  38. this.release();
  39. }
  40. }
  41. // 接收消息
  42. private String receive() {
  43. String msg = "";
  44. try {
  45. msg = dis.readUTF();
  46. } catch (IOException e) {
  47. System.out.println("------3------");
  48. this.release();
  49. }
  50. return msg;
  51. }
  52. // 发送消息
  53. private void send(String msg) {
  54. try {
  55. dos.writeUTF(msg);
  56. } catch (IOException e) {
  57. System.out.println("------2------");
  58. this.release();
  59. }
  60. }
  61. // 释放资源
  62. private void release() {
  63. this.isRuning = false;
  64. IOUtils.close(dis, dos, socket);
  65. }
  66. @Override
  67. public void run() {
  68. while(isRuning) {
  69. String msg = this.receive();
  70. if (!msg.equals("")) {
  71. this.send(msg);
  72. }
  73. }
  74. }
  75. }
  76. }

3、多线程封装发送端

  1. package com.xzlf.chat;
  2. import java.io.BufferedReader;
  3. import java.io.DataInputStream;
  4. import java.io.DataOutputStream;
  5. import java.io.IOException;
  6. import java.io.InputStreamReader;
  7. import java.io.ObjectInputStream.GetField;
  8. import java.net.Socket;
  9. /**
  10. * 利用多线程封装发送端
  11. *
  12. * @author xzlf
  13. *
  14. */
  15. public class Send implements Runnable{
  16. private Socket socket;
  17. private DataOutputStream dos;
  18. private BufferedReader console;
  19. private boolean isRuning;
  20. public Send(Socket socket) {
  21. this.socket = socket;
  22. this.isRuning = true;
  23. try {
  24. console = new BufferedReader(new InputStreamReader(System.in));
  25. dos = new DataOutputStream(socket.getOutputStream());
  26. } catch (IOException e) {
  27. System.out.println("======1=====");
  28. this.release();
  29. }
  30. }
  31. // 从控制台获取消息
  32. private String getStrFromConsole() {
  33. try {
  34. return console.readLine();
  35. } catch (IOException e) {
  36. e.printStackTrace();
  37. }
  38. return "";
  39. }
  40. // 发送消息
  41. public void send(String msg) {
  42. try {
  43. dos.writeUTF(msg);
  44. dos.flush();
  45. } catch (IOException e) {
  46. e.printStackTrace();
  47. System.out.println("======3=====");
  48. this.release();
  49. }
  50. }
  51. // 释放资源
  52. private void release() {
  53. this.isRuning = false;
  54. IOUtils.close(dos, console, socket);
  55. }
  56. @Override
  57. public void run() {
  58. while(isRuning) {
  59. String msg = getStrFromConsole();
  60. if (!msg.equals("")) {
  61. this.send(msg);
  62. }
  63. }
  64. }
  65. }

4、多线程封装接收端

  1. package com.xzlf.chat;
  2. import java.io.DataInputStream;
  3. import java.io.IOException;
  4. import java.net.Socket;
  5. /**
  6. * 使用多线程封装接收端
  7. * @author xzlf
  8. *
  9. */
  10. public class Receive implements Runnable {
  11. private Socket socket;
  12. private DataInputStream dis;
  13. private boolean isRuning;
  14. public Receive(Socket socket) {
  15. this.socket = socket;
  16. this.isRuning = true;
  17. try {
  18. dis = new DataInputStream(socket.getInputStream());
  19. } catch (IOException e) {
  20. System.out.println("======2=====");
  21. this.release();
  22. }
  23. }
  24. // 接收消息
  25. public String receive() {
  26. String msg = "";
  27. try {
  28. msg = dis.readUTF();
  29. } catch (IOException e) {
  30. System.out.println(e);
  31. System.out.println("======4=====");
  32. release();
  33. }
  34. return msg;
  35. }
  36. // 释放资源
  37. private void release() {
  38. this.isRuning = false;
  39. IOUtils.close(dis, socket);
  40. }
  41. @Override
  42. public void run() {
  43. while(isRuning) {
  44. String msg = receive();
  45. if(!msg.equals("")) {
  46. System.out.println(msg);
  47. }
  48. }
  49. }
  50. }

5、客户端

  1. package com.xzlf.chat;
  2. import java.io.IOException;
  3. import java.net.Socket;
  4. import java.net.UnknownHostException;
  5. /**
  6. * 聊天室:客户端
  7. * @author xzlf
  8. *
  9. */
  10. public class TMultiClient {
  11. public static void main(String[] args) throws UnknownHostException, IOException {
  12. System.out.println("======client======");
  13. // 1、指定ip + 端口 建立连接
  14. Socket socket = new Socket("localhost", 8888);
  15. // 2、客户端收发消息
  16. new Thread(new Send(socket)).start();
  17. new Thread(new Receive(socket)).start();
  18. }
  19. }

运行服务端和客户端:



先每个客户端只能自己跟自己聊。

实现群聊:

1、加入容器(使用JUC包下的并发容器CopyOnWriteArrayList),并添加给其他用户发送消息方法

添加容器:

  1. public class Chat {
  2. private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
  3. public static void main(String[] args) throws IOException {
  4. System.out.println("======server======");
  5. // 1、指定端口创建服务端
  6. ServerSocket server = new ServerSocket(8888);
  7. while(true) {
  8. // 2、每进来一个客户端启动一个线程
  9. Socket socket = server.accept();
  10. Channel c = new Channel(socket);
  11. all.add(c);
  12. System.out.println("一个客户端建立了连接");
  13. new Thread(c).start();
  14. }
  15. }
  1. 添加群发方法
  1. // 群聊:发送消息给其他人
  2. private void sendOthers(String msg, boolean isSys) {
  3. for(Channel other : all) {
  4. if(other == this) {
  5. continue;
  6. }
  7. if (!isSys) {
  8. // 群聊消息
  9. other.send(this.name + "说:" + msg);
  10. }else {
  11. // 系统消息
  12. other.send(msg);
  13. }
  14. }
  15. }

2、在初始化发送端,写入自己用户名并在初始化就发送

客户端启动时,输入用户名

  1. public class Client {
  2. public static void main(String[] args) throws UnknownHostException, IOException {
  3. System.out.println("======client======");
  4. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  5. System.out.print("请输入用户名:");
  6. String name = br.readLine();
  7. // 1、指定ip + 端口 建立连接
  8. Socket socket = new Socket("localhost", 8888);
  9. // 2、客户端收发消息
  10. new Thread(new Send(socket, name)).start();
  11. new Thread(new Receive(socket)).start();
  12. }
  13. }
  1. 发送线程初始化时立马发送用户名:
  1. public class Send implements Runnable{
  2. private Socket socket;
  3. private DataOutputStream dos;
  4. private BufferedReader console;
  5. private boolean isRuning;
  6. private String name;
  7. public Send(Socket socket, String name) {
  8. this.socket = socket;
  9. this.name = name;
  10. try {
  11. this.isRuning = true;
  12. console = new BufferedReader(new InputStreamReader(System.in));
  13. dos = new DataOutputStream(socket.getOutputStream());
  14. // 发送用户名
  15. this.send(name);
  16. } catch (IOException e) {
  17. System.out.println("======1=====");
  18. this.release();
  19. }
  20. }
  1. 服务端的channel类中初始化时立马接收用户名并保存

3、服务端(静态内部类Channel类中)在初始化时立即获取用户名并给用户发送欢迎信息同时给其他用户发提示信息(系统消息)

  1. static class Channel implements Runnable{
  2. private Socket socket;
  3. private DataInputStream dis;
  4. private DataOutputStream dos;
  5. private boolean isRuning;
  6. private String name;
  7. public Channel(Socket socket) {
  8. this.socket = socket;
  9. this.isRuning = true;
  10. try {
  11. dis = new DataInputStream(socket.getInputStream());
  12. dos = new DataOutputStream(socket.getOutputStream());
  13. // 获取用户名
  14. this.name = receive();
  15. this.send("欢迎你的到来");
  16. this.sendOthers(this.name + "来了xxx聊天室", true);
  17. } catch (IOException e) {
  18. System.out.println("------1------");
  19. this.release();
  20. }
  21. }

4、用户关闭线程,给其他用户发送提示信息,提示用户已离开

  1. // 释放资源
  2. private void release() {
  3. this.isRuning = false;
  4. IOUtils.close(dis, dos, socket);
  5. // 退出
  6. all.remove(this);
  7. sendOthers(this.name + "离开了聊天室。。。", true);
  8. }

运行测试:

实现私聊:

通过判断用户输入信息是否包含“@xxx:”确定是否为私聊,修改群发方法:

  1. /**
  2. * 群聊:获取自己的信息,发送消息给其他人
  3. * 私聊:约定数据格式: @xxx:msg
  4. * @param msg
  5. * @param isSys
  6. */
  7. private void sendOthers(String msg, boolean isSys) {
  8. if(msg.startsWith("@")) {
  9. // 私聊
  10. int endIndex = msg.indexOf(":");
  11. String targetName = msg.substring(1, endIndex);
  12. String info = msg.substring(endIndex + 1);
  13. for(Channel other : all) {
  14. if(other.name.equals(targetName)) {
  15. other.send(this.name + "悄悄对你说:" + info);
  16. }
  17. }
  18. }else {
  19. // 群聊
  20. for(Channel other : all) {
  21. if(other == this) {
  22. continue;
  23. }
  24. if (!isSys) {
  25. // 群聊消息
  26. other.send(this.name + "说:" + msg);
  27. }else {
  28. // 系统消息
  29. other.send(msg);
  30. }
  31. }
  32. }
  33. }

好了,现在已经实现了私聊。

运行测试一下:

需要完整代码的可以在下方留言。

Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊的更多相关文章

  1. Java 网络编程 -- 基于TCP 模拟多用户登录

    Java TCP的基本操作参考前一篇:Java 网络编程 – 基于TCP实现文件上传 实现多用户操作之前先实现以下单用户操作,假设目前有一个用户: 账号:zs 密码:123 服务端: public c ...

  2. Java WebSocket实现网络聊天室(群聊+私聊)

    1.简单说明 在网上看到一份比较nice的基于webSocket网页聊天项目,准备看看学习学习,如是有了这篇文章!原博主博客:http://blog.csdn.net/Amayadream/artic ...

  3. Java 网络编程 -- 基于TCP实现文件上传

    Java TCP 操作基本流程 一.创建服务器 1.指定端口, 使用serverSocket创建服务器 2.阻塞式连接 accept 3.操作:输入流 输出流 4.释放资源 二.创建客户端 1.使用S ...

  4. JAVA基础知识之网络编程——-基于TCP通信的简单聊天室

    下面将基于TCP协议用JAVA写一个非常简单的聊天室程序, 聊天室具有以下功能, 在服务器端,可以接受客户端注册(用户名),可以显示注册成功的账户 在客户端,可以注册一个账号,并用这个账号发送信息 发 ...

  5. java 25 - 5 网络编程之多线程实现聊天室

    平时聊天都是在同一个窗口的,所以,这个窗口同时实现发送数据和接收数据,这时就需要多线程实现. 建立一个类: 把聊天的发送端和接收端放在同一个类,启动一个窗口 public class CharRoom ...

  6. java 网络编程 UDP TCP

    网络编程 网络编程主要用于解决计算机与计算机(手机.平板..)之间的数据传输问题. 网络编程: 不需要基于html页面就可以达到数据之间的传输. 比如: feiQ , QQ , 微信....网页编程: ...

  7. java网络编程基础——TCP网络编程一

    基于TCP协议的网络编程 TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路. Java使用Socket对象来代表两端的通信端口,并通过 ...

  8. 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程

    Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...

  9. Java网络编程以及简单的聊天程序

    网络编程技术是互联网技术中的主流编程技术之一,懂的一些基本的操作是非常必要的.这章主要讲解网络编程,UDP和Socket编程,以及使用Socket做一个简单的聊天软件. 全部代码下载:链接 1.网络编 ...

随机推荐

  1. SpringCloud-Nacos/OpenFien/Gateway的基本介绍及快速上手

    一.Spring-Cloud-Alibaba-Nacos 注册中心 1.下载.安装 Nacos 下载地址:https://github.com/alibaba/nacos/releases 下载后解压 ...

  2. Redis 主从复制机制(集群)与 哨兵机制

    1,什么是redis 主从复制 支持多个数据库之间的数据同步.只能一个主数据库(master),可以一个或者多个从数据库(slave) 主数据库,可以读写 从数据库,只可以读 当主数据库,做写的操作的 ...

  3. ASP.NET Core单文件和多文件上传并保存到服务端

    前言: 在我们日常开发中,关于图片,视频,音频,文档等相关文件上传并保存到服务端中是非常常见的一个功能,今天主要是把自己在开发中常用的两种方式记录下来方便一下直接使用,并且希望能够帮助到有需要的同学! ...

  4. JS烟花案例优化版

    不明白为什么是烟花优化版本的先参考作者的烟花基础版本 烟花优化版本主要实在优化爆炸的范围和运动上做了优化,爆炸范围我们采用已圆的爆炸方式,以鼠标点击的位置为圆形爆炸的烟花效果 <!DOCTYPE ...

  5. C/C++知识总结 一 C/C++常识概述

    C/C++常识概述 程序与计算机语言 C/C++简介与发展 C/C++异同 C/C++编译特点 学习编程建议 程序与计算机语言 程序:是一组计算机能识别和执行.预先编好的一些指令操作合集. 计算机语言 ...

  6. 2783: 【基础】小 X 玩游戏(game)

    2783: [基础]小 X 玩游戏(game) 时间限制: 1 Sec 内存限制: 64 MB 提交: 752 解决: 294 [提交] [状态] [讨论版] [命题人:ghost79] 题目描述 听 ...

  7. Spring ApplicationContext 容器

    Spring ApplicationContext 容器 Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文. Application Con ...

  8. Golang入门(4):并发

    摘要 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求,这也是并发的必要性之一.Golang的并发控制比起Java来说,简单了不少.在Go ...

  9. python中使用163邮箱发送邮件一直报错的问题,谁能解决(已经各种百度完了,没能解决问题)

    1.报错如下: 2.代码如下:

  10. ACL,NAT的使用

     项目练习 练习一: 练习目的:通过配置路由器的dhcp功能使pc自动获取ip地址. Router>enable Router#configure terminal Router(config) ...