如下所示:
package socket;
/**
* 聊天室服务端
* @author Administrator
*
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
//import java.util.Arrays;
import java.util.List;
public class Server {
/*
* java.net.ServerSocket 运行在服务端的ServerSocket主要有两个作用:
* 1.向系统申请服务端口,客户端就是通过这个端口与服务端建立连接的
* 2.监听服务端口,一旦客户端通过端口建立连接则马上会实例化一个Socket与之对应,服务端就可以通过这个Socket与客户端进行交互了
* 如果把Socket比喻为电话,那么ServerSocket可以想象为总机
*/
private ServerSocket server;
/*
* 用于保存所有客户端输出流的数组,为ClientHandler之间共享各自对应客户端的输出流,从而做到广播消息
*/
//private PrintWriter[] allOut= {};//临界资源在多线程中要考虑并发安全问题,要加入锁机制
private List<PrintWriter> allOut=new ArrayList<>();//此处allOut由数组改成用集合
/**
* 构造方法,用来初始化服务端
*/
public Server() {
try {
/*
* 实例化ServerSocket的同时要指定服务端口 当该端口已经被系统其它程序所使用时,会抛出地址被占用的异常
*/
System.out.println("正在启动服务端...");
server = new ServerSocket(8098);
System.out.println("服务端启动完毕!");
} catch (Exception e) {
e.printStackTrace();
}
}
/** 让服务端开始工作的方法 */
public void start() {
try {
/*
* ServerSocket提供的方法: Socket accept()
* 该方法是一个阻塞方法,执行后服务端开始监听服务端口,直到一个客户端连接为止,
* 此时该方法会返回一个Socket实例,通过该实例即可与客户端进行通讯了
* 每次调用该方法都会导致服务端阻塞,等待一个客户端的连接
*/
while (true) {
System.out.println("等待客户端连接...");
Socket socket = server.accept();// 阻塞方法---程序运行到这里卡着不动了,相当于String
// str=scan.nextLine();//只有用户输入完毕按回车之后才会继续执行
// 启动一个线程来处理该客户端的交互工作
ClientHandler handler = new ClientHandler(socket);// 创建线程的同时把socke传进去获取客户端地址信息
Thread t = new Thread(handler);
t.start();
}
} catch (Exception e) {
// e.printStackTrace();
System.out.println("客户端断开连接!");
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 线程任务 用来处理与指定客户端的交互工作
*
* @author Administrator
*
*/
private class ClientHandler implements Runnable {
/** 声明属性 */
// 当前线程通过这个Socket与对应客户端交互
private Socket socket;
// 客户端地址信息
private String host;
/** 添加构造方法 */
public ClientHandler(Socket socket) {
this.socket = socket;
// 通过socket获取客户端地址信息
InetAddress address = socket.getInetAddress();
host = address.getHostAddress();
System.out.println(host + "上线了!");
}
/** 重写Runnable接口中的run()方法 */
@Override
public void run() {
System.out.println("启动了一个线程!");
PrintWriter pw=null;
try {
// 通过Socket与该客户端交互
/*
* 通过Socket获取输入流,读取客户端发送过来的消息 InputStream getInputStream()
*/
/** 创建输入流 */
InputStream in = socket.getInputStream();
/** 读取客户端发送过来的字符串 */
InputStreamReader isr = new InputStreamReader(in, "utf-8");
BufferedReader br = new BufferedReader(isr);
/*
* 通过socket获取输出流,用于给客户端发消息
*/
OutputStream out=socket.getOutputStream();
OutputStreamWriter osw=new OutputStreamWriter(out, "utf-8");
BufferedWriter bw=new BufferedWriter(osw);
pw=new PrintWriter(bw,true);
/*
*将该客户端发来的消息放到一个数组中(将该客户端对应的输出流存入到共享数组中)
*/
/**为了避免出现并发安全问题(在此处加个锁,让多线程排队干)*/
synchronized (allOut) {
// //1.对allOut数组进行扩容
// allOut=Arrays.copyOf(allOut, allOut.length+1);
// //2.将该输出流存入到数组最后一个位置
// allOut[allOut.length-1]=pw;
allOut.add(pw);//直接往集合中添加元素即可
}
System.out.println(host+"上线了!");
// System.out.println("当前在线人数:"+allOut.length);
System.out.println("当前在线人数:"+allOut.size());//集合中元素的个数
/** 显示客户端发来的消息 */
String message = null;
/*
* br.readLine()读取客户端发送过来的一行字符串的操作中当客户端断开连接时,客户端系统不同这里的表现也不完全一样:
* 通常Windows的客户端断开时,readLine方法会直接抛出异常 Linux的客户端断开时,readLine方法的返回值通常为null
*/
while ((message = br.readLine()) != null) {
System.out.println("客户端"+host+"说:" + message);
// //将消息发送给当前客户端
// pw.println("服务端说:"+message);
/**互斥加锁*---多个线程不能同时执行*/
synchronized (allOut) {
// //将消息发送给所有客户端
// for (int i = 0; i < allOut.length; i++) {
// allOut[i].println(host+"说:"+message);
// }
//用新循环遍历集合
for (PrintWriter printWriter : allOut) {
printWriter.println(host+"说:"+message);
}
}
}
} catch (Exception e) {
// e.printStackTrace();
System.out.println("客户端"+host+"下线了!");
}finally {
//处理当前客户端断开连接后的操作
/**互斥加锁*---多个线程不能同时执行*/
synchronized (allOut) {
// //1.将当前客户端的输出流从allOut中删除
// for (int i = 0; i < allOut.length; i++) {
// if (allOut[i]==pw) {
// allOut[i]=allOut[allOut.length-1];//找到该元素,然后把最后一个元素的值赋给它
// allOut=Arrays.copyOf(allOut, allOut.length-1);//然后把数组缩容一个元素,即把最后一个元素删掉
// break;
// }
// }
allOut.remove(pw);//从集合中删除元素
}
System.out.println(host+"下线了!");
// System.out.println("当前在线人数:"+allOut.length);
System.out.println("当前在线人数:"+allOut.size());
//2.将socket关掉
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}