手写一个聊天室
主要功能:
1.接收消息
2.发送消息
3.自动释放资源
4.多人聊天与私聊
涉及知识点:
- 多线程:runnable,thread
- 遍历容器:迭代器
- 基于TCP的简单编程
- IO流的对接操作
- 面向对象封装思想
利用封装的思想:
1.封装服务对象(每个客户端(Channel)即一个对象)
*接收消息
*发送消息
*私聊与多人聊天
*借助工具类释放资源
2.封装一个工具类
*有一个释放资源的方法
多人聊天实现思路: 将所有客户端装入容器,在封装的sendOthers()方法,判断是否多人,若是遍历容器后将消息发送给除自己以外的其他所有人,使用send()方法。
私聊实现思路: 约定以 (@用户名:消息内容) 形式只对该用户发送消息,在封装的sendOthers()方法中判断是否私人,若是使用send()方法
容器选择:CopyOnWriteArrayList,可以同时读写。
工作方法:简单来说,就是平时查询的时候,都不需要加锁,随便访问,只有在写入/删除的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。
共有五个文件
服务端
package com.lijing.chat05;
import com.lijing.chat05.lijingUtils;
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 Chat {
private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();
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("一个客户端建立了连接");
Channel c = new Channel(client);
all.add(c);//管理所有的成员
new Thread(c).start();
}
}
//一个客户代表一个Channel
static class Channel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private Socket client;
private boolean isRunning;
private String name;
public Channel(Socket client){
this.client = client;
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
isRunning = true;
//获取名称
this.name =receive();
//欢迎你的到来
this.send("欢迎你的到来");
sendOthers(this.name+"来到了聊天室",true);
} catch (IOException e) {
System.out.println("-------1-------");
release();
}
}
//接收消息
private String receive(){
String msg="";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("-------2-------");
release();
}
return msg;
}
/*群聊: 获取自己的消息,发给其他人
私聊: 约定数据格式:@xxx:msg
*/
private void sendOthers(String msg,boolean isSys){
boolean isPrivate = msg.startsWith("@");
//System.out.println(isPrivate);
//System.out.println("hjhhhghghghhghkhhgkhg");
if(isPrivate){//私聊
//获取目标和数据
int idx = msg.indexOf(":");
String targetName = msg.substring(1,idx);
msg = msg.substring(idx+1);
for(Channel other:all) {
if(other.name.equals(targetName)){//目标
other.send(this.name +"悄悄地对您说:"+msg);
}
}
} else{
for(Channel other:all) {
if (other == this) {//自己
continue;
}
if (!isSys) {
other.send(this.name + "对所有人说:" + msg);
} else {
other.send("系统提示:" + msg);
}
}
}
}
//发送消息
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("-------3-------");
release();
}
}
//释放资源
private void release(){
this.isRunning = false;
lijingUtils.close(dis,dos,client);
//推出
all.remove(this);
sendOthers(this.name+"离开了",true);
}
public void run() {
while (isRunning){
String msg = receive();
if(!msg.equals("")){
sendOthers(msg,false);
}
}
}
}
}
客户端
package com.lijing.chat05;
import java.io.*;
import java.net.Socket;
/*
在线聊天室:客户端
*/
public class Client {
public static void main(String[] args)throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名");
String name = br.readLine();
System.out.println("----------Client-----------");
//1、建立连接:使用Socket创建+服务的地址端口
Socket client = new Socket("localhost",8888);
//2、客户端发送消息
new Thread(new Send(client,name)).start();
new Thread(new Receive(client)).start();
}
}
工具类
package com.lijing.chat05;
import java.io.Closeable;
/*
工具类
*/
public class lijingUtils {
public static void close(Closeable... targets){
for (Closeable target:targets){
try {
if(null!=target)
target.close();
}
catch (Exception e){
}
}
}
}
使用多线程封装的接收端
package com.lijing.chat05;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.PrivateKey;
/*
使用多线程封装了接收端
1、接受消息
2、释放资源
3、重写Run
*/
public class Receive implements Runnable{
private DataInputStream dis;
private Socket client;
private boolean isRunning;
public Receive(Socket client){
this.client=client;
try {
dis = new DataInputStream(client.getInputStream());
isRunning = true;
} catch (IOException e) {
System.out.println("====2====");
release();
}
}
//接收消息
private String receive(){
String msg="";
try {
msg = dis.readUTF();
} catch (IOException e) {
System.out.println("====4=====");
release();
}
return msg;
}
@Override
public void run() {
while (isRunning){
String msg = receive();
if(!msg.equals("")){
System.out.println(msg);
}
}
}
//释放资源
private void release(){
this.isRunning = false;
lijingUtils.close(dis,client);
}
}
使用多线程封装的发送端
package com.lijing.chat05;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/*
使用多线程封装了发送端
1、发送消息
2、从控制台获取消息
3、释放资源
4、重写Run
*/
public class Send implements Runnable{
private BufferedReader console;
private DataOutputStream dos;
private Socket client;
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(client.getOutputStream());
//发送名称
send(name);
isRunning = true;
} catch (IOException e) {
System.out.println("====1====");
this.release();
}
}
@Override
public void run() {
while (isRunning){
String msg = getStrFromConsole();
if(!msg.equals("")){
send(msg);
}
}
}
//发送消息
private void send(String msg){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("====3====");
release();
}
}
//从控制台获取消息
private String getStrFromConsole(){
try {
return console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
//释放资源
private void release(){
this.isRunning = false;
lijingUtils.close(dos,client);
}
}
试运行: