首先有一个工具类,将释放资源的方法封装:
因为socket和io流都有Closeable接口
public class ChatUtils {
/*用于释放资源的工具类*/
public static void close(Closeable...targets){
for(Closeable target:targets){
try {
if(target!=null){
target.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程实现服务器端:
/*在线聊天室:服务器*/
public class ChatServer {
private static CopyOnWriteArrayList<channel> clientlist = new CopyOnWriteArrayList<>();//定义一个容器,存储所有的客户线程,用CopyOnWrite线程安全
public static void main(String[] args) throws IOException {
System.out.println("-----Server-----");
ServerSocket serverSocket = new ServerSocket(8888);//建立服务器
while (true) {//服务器不关闭
Socket client = serverSocket.accept();//阻塞式监听
System.out.println("一个客户端建立了连接");
channel c = new channel(client);
clientlist.add(c);//客户端加入容器
new Thread(c).start();
}
}
static class channel implements Runnable {//一个channel代表接收一个客户端请求
private Socket client;
private DataInputStream dis;
private DataOutputStream dos;
boolean isrunning;
private String name;//客户端名字
public channel(Socket client) throws IOException {
this.client = client;
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
isrunning = true;
name=receive();//接收客户端构造时传入的名字
sendothers(name+"进入聊天室",true);
}
public String receive() {
//接收客户端消息
String data = "";
try {
data = dis.readUTF();
} catch (IOException e) {
System.out.println("服务器读取数据失败");
release();//直接释放资源
}
return data;
}
public void send(String data) {
//返回响应消息
try {
dos.writeUTF(data);
dos.flush();
} catch (IOException e) {
System.out.println("服务器无响应");
release();
}
}
public void release() {
this.isrunning = false;
ChatUtils.close(dis, dos, client);//封装了释放资源的工具类,直接调用
clientlist.remove(this);
sendothers(this.name+"退出聊天室",true);
}
//群发消息:服务器作为中转站,接收客户端的消息,然后转发出去
//新版本实现私聊功能:遍历数组得到线程名字
private void sendothers(String data,boolean flag) {//flag判断是否为系统消息
boolean isprivate=data.startsWith("@");
if(isprivate){//如果是私聊消息则遍历
int ind=data.indexOf(":");
String datahead=data.substring(1,ind);
String databody=data.substring(ind+1,data.length());
int a=0;
for (channel other : clientlist){
if(datahead.equals(other.name)){
other.send(this.name+"悄悄对你说:"+databody);
a=1;
}
}
if(a==0){
System.out.println("该用户不存在");
send("该用户不存在");
}
}
//群发消息
else { for (channel other : clientlist){
if(other==this){continue;}
if(flag==false){ other.send(this.name+"对所有人说"+data);}
else{other.send("系统:"+data);}
}
}}
@Override
public void run() {
while (isrunning) {
String msg = receive();
System.out.println(msg);
if (!msg.equals("")) {
sendothers(msg,false);
}
}
}
}
}
然后是客户端
public class ChatClient1 {
public static void main(String[] args) throws IOException {
System.out.println("请输入用户名:");
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));//得到客户端名字
String name=br.readLine();
Socket client=new Socket("localhost",8888);
new Thread(new CilentSend(client,name)).start();
new Thread(new CilentReceive(client)).start();
}
}
在上面将客户端的发送与接收功能实现了封装:
发送功能:
/*封装客户端的发送方法*/
public class CilentSend implements Runnable{
private BufferedReader br;
private DataOutputStream dos;
private Socket client;
private String name;
boolean isrunning;
public CilentSend( Socket client,String name){
this.name=name;
br=new BufferedReader(new InputStreamReader(System.in));
try {
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
isrunning=true;
send(name);//将得到的客户端名字发送给服务器
}
@Override
public void run() {
System.out.println("-----对话框-----");
while (isrunning){
String msg = null;
try {
msg = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
send(msg);
}
}
public void send(String msg){
if(!msg.equals("")){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
System.out.println("客户端发送消息失败");
release();
}
}}
public void release(){
this.isrunning=false;
ChatUtils.close(dos,client);//封装了释放资源的工具类,直接调用
}
}
接收功能:
public class CilentReceive implements Runnable{
private Socket client;//插座作为参数
private DataInputStream dis;//当接收的数据为java基本数据类型时选用数据输入流
boolean isrunning;
public CilentReceive(Socket client){
try {
dis=new DataInputStream(client.getInputStream());//实现插座的得到输入流方法
} catch (IOException e) {
e.printStackTrace();
}
isrunning=true;设定一个标志用于run方法循环。
}
@Override
public void run() {
while (isrunning){
String data = null;
try {
data = dis.readUTF();
} catch (IOException e) {
System.out.println("客户端读取数据失败");
release();
}
if(!data.equals(""))
System.out.println(data);
}}
public void release(){
this.isrunning=false;
ChatUtils.close(dis,client);//封装了释放资源的工具类,直接调用
}
}