分析:

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

服务端负责转发消息。

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

消息分类:
群聊消息:发送除自己外所有人
私聊消息:只发送@的人
系统消息:根据情况分只发送个人和其他人
技术方面:
客户端和服务端收发消息,需要使用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. return console.log()结果为undefined现象的解答

    console.log总是出现undefined--麻烦的console //本文为作者自己思考后总结出的一些理论知识,若有错误,欢迎指出 bug出现 ​ 需求如下:新建一个car对象,调用其中的de ...

  2. CocoaPods 安装卸载

    建议升级10.15的系统,什么都装好了 sudo gem install cocoapods pod setup搞定不能有search命令,可以pod init,下载用的是cdn,打开项目正常使用 问 ...

  3. 文件输入输出实例&Ptask的编写

    前言 最近在写Ptask,顺便了解了如何进行文件读入输出.而在Ptask中最重要,也是最最容易出bug的地方就是文件操作.那么如何进行文件输入输出,在程序中起到重要作用呢? 输入 首先为了保证可以在控 ...

  4. 【纯净镜像】原版Windows7集成USB3.0+NVME补丁+UEFI引导旗舰版下载

    系统简述: 1. 基于MSDN原版Windows7 Ultimate With SP1系统制作,无任何插件和垃圾软件. 2. 系统集成IE11浏览器,装完系统后默认浏览器就是IE11. 3.系统注入了 ...

  5. java Jsoup.clean 处理入参时,会将换行符解析成空字符串问题

    Json 中clean方法有两个: 一:会格式化入参,将换行符替换成空格 clean(String bodyHtml, String baseUri, Whitelist whitelist) 二:n ...

  6. WEB缓存系统之varnish代理以及健康状态检测配置

    前文我们聊了下varnish的缓存项修剪配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12666406.html:今天我来说一下varnish作为代理服务 ...

  7. SpringCloud(三)之我学 Hystrix

    1.断路器 在消费服务的启动类,添加注解:@EnableCircuitBreaker,在消费服务的调用类上,添加注解:@HystrixCommand(fallbackMethod = "&q ...

  8. alembic的常用参数

    alembic的常用参数 命令和参数解释 1 .init:创建一个alembic仓库. 2 .revision:创建一个新的版本文件. 3 .--autogenerate:自动将当前的模型修改,生成迁 ...

  9. zendframework3

    1.开发时关闭cache,正式上线后打开cache application config file (config/application.config.php),  disable this cac ...

  10. 机器学习4- 多元线性回归+Python实现

    目录 1 多元线性回归 2 多元线性回归的Python实现 2.1 手动实现 2.1.1 导入必要模块 2.1.2 加载数据 2.1.3 计算系数 2.1.4 预测 2.2 使用 sklearn 1 ...