Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊
分析:
聊天室需要多个客户端和一个服务端。
服务端负责转发消息。
客户端可以发送消息、接收消息。
消息分类:
群聊消息:发送除自己外所有人
私聊消息:只发送@的人
系统消息:根据情况分只发送个人和其他人
技术方面:
客户端和服务端收发消息,需要使用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 实现聊天室 群聊 私聊的更多相关文章
- Java 网络编程 -- 基于TCP 模拟多用户登录
Java TCP的基本操作参考前一篇:Java 网络编程 – 基于TCP实现文件上传 实现多用户操作之前先实现以下单用户操作,假设目前有一个用户: 账号:zs 密码:123 服务端: public c ...
- Java WebSocket实现网络聊天室(群聊+私聊)
1.简单说明 在网上看到一份比较nice的基于webSocket网页聊天项目,准备看看学习学习,如是有了这篇文章!原博主博客:http://blog.csdn.net/Amayadream/artic ...
- Java 网络编程 -- 基于TCP实现文件上传
Java TCP 操作基本流程 一.创建服务器 1.指定端口, 使用serverSocket创建服务器 2.阻塞式连接 accept 3.操作:输入流 输出流 4.释放资源 二.创建客户端 1.使用S ...
- JAVA基础知识之网络编程——-基于TCP通信的简单聊天室
下面将基于TCP协议用JAVA写一个非常简单的聊天室程序, 聊天室具有以下功能, 在服务器端,可以接受客户端注册(用户名),可以显示注册成功的账户 在客户端,可以注册一个账号,并用这个账号发送信息 发 ...
- java 25 - 5 网络编程之多线程实现聊天室
平时聊天都是在同一个窗口的,所以,这个窗口同时实现发送数据和接收数据,这时就需要多线程实现. 建立一个类: 把聊天的发送端和接收端放在同一个类,启动一个窗口 public class CharRoom ...
- java 网络编程 UDP TCP
网络编程 网络编程主要用于解决计算机与计算机(手机.平板..)之间的数据传输问题. 网络编程: 不需要基于html页面就可以达到数据之间的传输. 比如: feiQ , QQ , 微信....网页编程: ...
- java网络编程基础——TCP网络编程一
基于TCP协议的网络编程 TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路. Java使用Socket对象来代表两端的通信端口,并通过 ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- Java网络编程以及简单的聊天程序
网络编程技术是互联网技术中的主流编程技术之一,懂的一些基本的操作是非常必要的.这章主要讲解网络编程,UDP和Socket编程,以及使用Socket做一个简单的聊天软件. 全部代码下载:链接 1.网络编 ...
随机推荐
- HDU 4497 GCD and LCM 素因子分解+ gcd 和 lcm
题意: 给两个数,lll 和 ggg,为x , y , z,的最小公倍数和最大公约数,求出x , y , z 的值有多少种可能性 思路: 将x , y , z进行素因子分解 素因子的幂次 x a1 a ...
- django中 对Mysql数据库的建表
Django操作Mysql数据库: 1.1 在settings中,配置数据库相关参数,所以无需修改,这里我们看一下: DATABASES = { 'default': { # 这里可以指定使用的数据库 ...
- HTML特殊转义字符——特殊符号
干货,见下图: 后期我会陆续更一些JavaScript的文章,大家可以一起学习交流.
- vulnhub~Djinn:2
这道题挺难的,和Djinn:1相比,正如作者所言,有许多相似的地方.仍然开放着端口 可以看到5个端口开放着,1337是web端口,这里面如djinn1一样,write your wish,但是send ...
- 2019NYIST计科第七次周赛总结
2019NYIST计科第七次周赛总结 文章目录 2019NYIST计科第七次周赛总结 [秤取物体重量( 二进制枚举法)](https://blog.csdn.net/qq_34261446/artic ...
- Appium自动化(2) - appium环境安装常见问题的解决方案
如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 问题1:adb检测不到设备 解决 ...
- PTA数据结构与算法题目集(中文) 7-31
PTA数据结构与算法题目集(中文) 7-31 7-31 笛卡尔树 (25 分) 笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2.首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有 ...
- flask-migrate的基本使用
Flask-migrate 在实际开发环境中,经常会发生数据库修改的行为.一般我们修改数据库不会手动的去修改,而是去修改orm对应的模型, 然后再把模型映射到数据库中.这时候如果有一个工具能专门做这种 ...
- NS网络仿真,小白起步版,模拟仿真之间注意的事项
FTP是基于TCP的,所以FTP应用不可以绑定UDP发送代理 FTP和CBR属于应用流,他们用来绑定TCP和UDP发送代理 TCP用于发送代理时,接收代理为TCPSink,可以绑定FTP应用.CBR流 ...
- 【Selenium02篇】python+selenium实现Web自动化:鼠标操作和键盘操作!
一.前言 最近问我自动化的人确实有点多,个人突发奇想:想从0开始讲解python+selenium实现Web自动化测试,请关注博客持续更新! 这是python+selenium实现Web自动化第二篇博 ...