分析:

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

服务端负责转发消息。

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

消息分类:
群聊消息:发送除自己外所有人
私聊消息:只发送@的人
系统消息:根据情况分只发送个人和其他人
技术方面:
客户端和服务端收发消息,需要使用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. POJ 3461 Oulipo KMP算法(模板)

    题意: 给两组字符串a和b,求a在b中出现的次数 关于KMP: 马拉车算法是处理回文串,而KMP是处理前后缀的相同字符串的最长长度. a | a | b | a | a | f | a | a 数组 ...

  2. Ardupilot(PX4)飞控驱动蜂鸣器和RGB细节

    Ardupilot(PX4)飞控驱动蜂鸣器细节 飞控代码细节 任务调用频率50HZ(20ms),buzzer.update() 内部将频率减少到10HZ(100ms) 单响(SINGLE_BUZZ) ...

  3. 吴恩达DeepLearning.ai的Sequence model作业Dinosaurus Island

    目录 1 问题设置 1.1 数据集和预处理 1.2 概览整个模型 2. 创建模型模块 2.1 在优化循环中梯度裁剪 2.2 采样 3. 构建语言模型 3.1 梯度下降 3.2 训练模型 4. 结论   ...

  4. NKOJ3772 看电影

    问题描述 共有m部电影,编号为1~m,第i部电影的好看值为w[i]. 在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部. 你可以选择l,r(1<=l<=r<= ...

  5. 新建基于STM32F103ZET6的工程-HAL库版本

    1.STM32F103ZET6简介 STM32F103ZET6的FLASH容量为512K,64K的SRAM.按照STM32芯片的容量产品划分,STM32F103ZET6属于大容量的芯片. 2.下载HA ...

  6. Java数组的声明与创建

    今天在刷Java题的时候,写惯了C++发现忘记了Java数组的操作,遂把以前写的文章发出来温习一下. 首先,数组有几种创建方式? Java程序中的数组必须先进行初始化才可以使用,所谓初始化,就是为数组 ...

  7. python 爬虫:学爬虫必学的正则表达式

    文章更新于:2020-03-30 一.语法格式 1.非打印字符 操作符 说明 实例 \cx 匹配由x指明的控制字符 \cM 匹配一个 Control-M 或回车符.x 的值必须为 A-Z 或 a-z ...

  8. Linux系统安装Dos系统(虚拟机里装)

    结合以下两篇优秀的文章就能完成任务. 1.https://www.jb51.net/os/609411.html 2.http://blog.51cto.com/6241809/1687361 所需要 ...

  9. Linux 任务管理篇(一)

    查看网络的联机状态    netstat -a 查看后台执行的程序    ps -aux 将内存中的数据快速写入到硬盘中        sync

  10. Python——详解__str__, __repr__和__format__

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第10篇文章,我们来聊聊Python当中的类. 打印实例 我们先从类和对象当中最简单的打印输出开始讲起,打印一个实例 ...