java 聊天室 私聊_Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊

本文介绍了如何使用Java基于TCP实现一个聊天室,包括群聊和私聊功能。服务端通过多线程处理客户端连接,客户端通过多线程实现发送和接收消息。群聊消息发送给所有用户,私聊消息通过指定用户名进行发送。代码中包含了IO流的使用以及异常处理。
摘要由CSDN通过智能技术生成

分析:

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

服务端负责转发消息。

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

消息分类:

群聊消息:发送除自己外所有人

私聊消息:只发送@的人

系统消息:根据情况分只发送个人和其他人

技术方面:

客户端和服务端收发消息,需要使用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();

}

}

运行服务端和客户端:

18ef1002e1e6c95027c2acb3c16908e7.png

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

实现群聊:

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

添加容器:

public class Chat {

private static CopyOnWriteArrayList all = new CopyOnWriteArrayList();

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);

}

运行测试:

a28ec0bdbf09774d6e390c91d7c82764.png

实现私聊:

通过判断用户输入信息是否包含“@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);

}

}

}

}

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

运行测试一下:

2292b95488fbec8b5051b8f9d627dc8a.png

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值