分析:

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

服务端负责转发消息。

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

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

……

还是上代码吧。

基础版:

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

我们约定:

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

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

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

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

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

1、IO工具类

package com.xzlf.chat;

import java.io.Closeable;
import java.io.IOException; /**
* 工具类
* @author xzlf
*
*/
public class IOUtils {
/**
* 释放资源
* @param closeables
*/
public static void close(Closeable...closeables) {
for (Closeable closeable : closeables) {
if(null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

2、服务端

package com.xzlf.chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
* 聊天室:服务器
* @author xzlf
*
*/
public class TMultiChat {
public static void main(String[] args) throws IOException {
System.out.println("======server======");
// 1、指定端口创建服务端
ServerSocket server = new ServerSocket(8888);
while(true) {
// 2、每进来一个客户端启动一个线程
Socket socket = server.accept();
System.out.println("一个客户端建立了连接");
new Thread(new Channel(socket)).start();
}
} // 一个Channel 代表一个客户端
static class Channel implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRuning;
public Channel(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
System.out.println("------1------");
this.release();
} } // 接收消息
private String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("------3------");
this.release();
}
return msg;
} // 发送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
} catch (IOException e) {
System.out.println("------2------");
this.release();
}
} // 释放资源
private void release() {
this.isRuning = false;
IOUtils.close(dis, dos, socket);
} @Override
public void run() {
while(isRuning) {
String msg = this.receive();
if (!msg.equals("")) {
this.send(msg);
}
}
}
}
}

3、多线程封装发送端

package com.xzlf.chat;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream.GetField;
import java.net.Socket; /**
* 利用多线程封装发送端
*
* @author xzlf
*
*/
public class Send implements Runnable{
private Socket socket;
private DataOutputStream dos;
private BufferedReader console;
private boolean isRuning; public Send(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
console = new BufferedReader(new InputStreamReader(System.in));
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
System.out.println("======1=====");
this.release();
}
} // 从控制台获取消息
private String getStrFromConsole() {
try {
return console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
} // 发送消息
public void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
System.out.println("======3=====");
this.release();
}
} // 释放资源
private void release() {
this.isRuning = false;
IOUtils.close(dos, console, socket);
} @Override
public void run() {
while(isRuning) {
String msg = getStrFromConsole();
if (!msg.equals("")) {
this.send(msg);
}
} } }

4、多线程封装接收端

package com.xzlf.chat;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket; /**
* 使用多线程封装接收端
* @author xzlf
*
*/
public class Receive implements Runnable { private Socket socket;
private DataInputStream dis;
private boolean isRuning; public Receive(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
System.out.println("======2=====");
this.release();
}
}
// 接收消息
public String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println(e);
System.out.println("======4=====");
release();
}
return msg;
} // 释放资源
private void release() {
this.isRuning = false;
IOUtils.close(dis, socket);
} @Override
public void run() {
while(isRuning) {
String msg = receive();
if(!msg.equals("")) {
System.out.println(msg);
}
} }
}

5、客户端

package com.xzlf.chat;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException; /**
* 聊天室:客户端
* @author xzlf
*
*/
public class TMultiClient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("======client======");
// 1、指定ip + 端口 建立连接
Socket socket = new Socket("localhost", 8888);
// 2、客户端收发消息
new Thread(new Send(socket)).start();
new Thread(new Receive(socket)).start(); }
}

运行服务端和客户端:



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

实现群聊:

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

添加容器:

public class Chat {
private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
public static void main(String[] args) throws IOException {
System.out.println("======server======");
// 1、指定端口创建服务端
ServerSocket server = new ServerSocket(8888);
while(true) {
// 2、每进来一个客户端启动一个线程
Socket socket = server.accept();
Channel c = new Channel(socket);
all.add(c);
System.out.println("一个客户端建立了连接");
new Thread(c).start();
}
}
添加群发方法
// 群聊:发送消息给其他人
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);
}
}
}

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

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

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

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

static class Channel implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRuning;
private String name;
public Channel(Socket socket) {
this.socket = socket;
this.isRuning = true;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// 获取用户名
this.name = receive();
this.send("欢迎你的到来");
this.sendOthers(this.name + "来了xxx聊天室", true);
} catch (IOException e) {
System.out.println("------1------");
this.release();
} }

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

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

运行测试:

实现私聊:

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

/**
* 群聊:获取自己的信息,发送消息给其他人
* 私聊:约定数据格式: @xxx:msg
* @param msg
* @param isSys
*/
private void sendOthers(String msg, boolean isSys) {
if(msg.startsWith("@")) {
// 私聊
int endIndex = msg.indexOf(":");
String targetName = msg.substring(1, endIndex);
String info = msg.substring(endIndex + 1);
for(Channel other : all) {
if(other.name.equals(targetName)) {
other.send(this.name + "悄悄对你说:" + info);
}
}
}else {
// 群聊
for(Channel other : all) {
if(other == this) {
continue;
}
if (!isSys) {
// 群聊消息
other.send(this.name + "说:" + msg);
}else {
// 系统消息
other.send(msg);
}
}
}
}

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

运行测试一下:

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

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. 学习 MyBatis 的一点小总结 —— 底层源码初步分析

    目录 MyBatis 如何获取数据库源? MyBatis 如何获取 sql 语句? MyBatis 如何执行 sql 语句? MyBatis 如何实现不同类型数据之间的转换? 在过去程序员使用 JDB ...

  2. 140行Python代码实现Flippy Bird

    140行代码实现Flippy Bird 话说这游戏中文名叫什么来着,死活想不起来了,算了话不多说,140行实现小游戏系列第二章,依然是简单小游戏,与数独游戏相比,在游戏界面显示上更难一些,但是在逻辑方 ...

  3. 【Ubuntu】常用命令汇总,整理ing

    Ubuntu 常用命令(在此页面中Ctrl+F即可快速查找) 在Ubuntu系统使用过程中,会不断地接触到命令行操作,下面对一些常用的命令进行汇总,方便查找. 1.文件操作 1.1 文件复制拷贝 cp ...

  4. C/C++知识总结 三 C/C++数据类型与输入输出

    C/C++数据类型与输入输出 基本数据类型 输入与输出 复合数据类型(将在下几篇博客中总结) C/C++数据类型 数据类型总图 数据类型差别 数据类型不同的意义 1)指明数据的大小,以便正确分配,访问 ...

  5. STM32CubeMX的安装

    1.下载STM32CubeMX 在ST的官方网站上下载STM32CubeMXXX软件的安装包. 下载的安装包如下图所示.双击SetupSTM32CubeMX-5.0.1.exe. 安装STM32Cub ...

  6. 《Three.js 入门指南》3.1.1 - 基本几何形状 - 球体(SphereGeometry)

    3.1 基本几何形状 球体(SphereGeometry) 构造函数: THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiS ...

  7. 1007 Maximum Subsequence Sum (25 分)

    1007 Maximum Subsequence Sum (25 分)   Given a sequence of K integers { N​1​​, N​2​​, ..., N​K​​ }. A ...

  8. 三通道低功耗ASK低频唤醒接收器PAN3501完全替代AS3933/GC3933

    低频唤醒接收器PAN3501软硬件兼容AS3933/GC3933,且新增了寄存器功能,可直接替换,供应稳定,高性价比. 产品介绍:    PAN3501是一款最多三个通道接收的低功耗ASK接收机,可用 ...

  9. java编写规范

    编码规范 转载于:https://www.cnblogs.com/ftl1012/p/javaCode.html 1 前言为确保系统源程序可读性,从而增强系统可维护性,java编程人员应具有基本类似的 ...

  10. 页面DIV弹出层 JS原生脚本

    <script type="text/javascript"> /*         * 弹出DIV层         */ function showDiv() { ...