JavaSE实现简易版聊天室服务端代码

如下所示:

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