基本的思想
在线聊天室:服务器端和客户端
- 目标是实现多个客户可以收发多条信息,加入多线程,可以开启多个用户端
如果直接用lamda写入线程可能存在问题:
线程代码太多,不好维护
客户端读写没有分开,必须先写后读。
所以将线程代码放入一个类中,进行封装
群聊:
加入容器实,将创建的客户端都加入client容器中,在进行发送消息的时候,遍历整个容器,依次将消息发给各个客户端。
私聊:
规定一种私聊的方式,例如@xxx,然后遍历容器,找到目标客户,将信息发出:
服务器端
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class THChat3 {
//线程安全的容器,允许在用容器的时候进行修改,保证数据的一致性
private static CopyOnWriteArrayList<Channal> all=new CopyOnWriteArrayList<Channal>();
public static void main(String[] args) throws IOException {
System.out.println("------Server------");
//1.指定端口。使用ServerSocket创建服务器
ServerSocket server=new ServerSocket(8888);
// 2.阻塞式等待连接accept
//创建一个客户端就进行连接
while(true) {
Socket client=server.accept();
System.out.println("一个客户端建立了");
Channal c=new Channal(client);
all.add(c);//管理所有的客户端成员
new Thread(c).start();
}
}
static class Channal implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private Socket client;
private boolean isRunnable;
private String name;
public Channal(Socket client) {
super();
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
isRunnable=true;
//获取名称:
name=receive();
this.send("欢迎加入聊天室");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("初始化出错");
release();//只要出错了就停止
}
}
//接受消息
private String receive() {
String msg ="";//避免空指针
try {
msg = dis.readUTF();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("接受出错");
release();
}
return msg;
}
//发送消息,
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("发送出错");
release();
}
}
//发送消息,群聊,将自己的消息发给其他人
//私聊:约定数据格式:@xxx:msg;
private void sendOther(String msg) {
boolean isPrivate=msg.startsWith("@");
if(isPrivate) {//私聊
//获取目标和数据
int index=msg.indexOf(":");
String targetName=msg.substring(1,index);
msg=msg.substring(index+1);
for(Channal others:all) {
if(others.name.equals(targetName)) {
others.send(this.name+"私聊你说"+msg);
}
}
}else {//群聊
for(Channal others:all) {
if(others==this) {//如果是自己,则表示是不发
continue;
}
else {
others.send(this.name+"说"+msg);
}
}
}
}
//释放资源
private void release() {
this.isRunnable=false;
chatUtils.close(dis,dos,client);
all.remove(this);
sendOther(this.name+"退出聊天室");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(isRunnable) {
//读
String msg=receive();
if(!msg.equals("")) {
//发送
//send(msg);
sendOther(msg);
}
}
}
}
}
客户端
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class THClient3 {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("------client------");
BufferedReader console=new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名:");
String name=console.readLine();
// 1.建立连接:使用Socket创建客户端,指定服务器的地址和端口
Socket client=new Socket("localhost",8888);//指定本机的服务器,端口号
//客户端发送消息,接收消息
//利用线程实现收发信息的同步
new Thread(new Send(client,name)).start();
new Thread(new Receive(client)).start();
}
}
封装的发送端和接受端
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class Receive implements Runnable{
private BufferedReader console;
private Socket client;
private DataInputStream dis;
private boolean isRunning=true;
public Receive(Socket client) {
this.client=client;
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("recive");
release();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
String msg=receive();
if(!msg.equals("")) {
System.out.println(msg);
}
}
}
//接受消息
private String receive() {
String msg ="";//避免空指针
try {
msg = dis.readUTF();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("接受出错");
release();
}
return msg;
}
private void release() {
this.isRunning=false;
chatUtils.close(this.dis,client);
}
}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class Send implements Runnable{
private BufferedReader console;
private Socket client;
private DataOutputStream dos;
private boolean isRunning;
private String name;
public Send(Socket client,String name) {
this.client=client;
this.name=name;
console=new BufferedReader(new InputStreamReader(System.in));
try {
dos=new DataOutputStream(this.client.getOutputStream());
isRunning=true;
//只要管道一建立好就将名字发送过去。
send(name);
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("send");
release();
}
}
//从控制台获取消息
private String getStrFromConsole() {
try {
return console.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "";
}
@Override
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
String msg=getStrFromConsole();
if(!msg.equals("")) {
send(msg);
}
}
}
//发送消息
private void send(String msg) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("发送出错");
release();
}
}
private void release() {
this.isRunning=false;
chatUtils.close(this.dos,client);
}
}
用于释放资源的工具类
/**
*
* 工具类:释放资源实现了Closeable接口
*
*
* @author lsy
*
*/
import java.io.Closeable;
public class chatUtils {
//IO中都实现了Closeable接口,Socket也实现了
public static void close(Closeable... targets) {
for(Closeable target:targets) {
try {
if(null!=target) {
target.close();
}
}catch(Exception e) {
}
}
}
}
本文介绍了一个基于Java的在线聊天室的实现方案,包括服务器端和客户端的设计,通过多线程支持多个用户同时在线交流。文章详细讲解了如何使用CopyOnWriteArrayList作为线程安全的容器来管理所有客户端,以及如何实现群聊和私聊功能。
3万+

被折叠的 条评论
为什么被折叠?



