在使用线程池实现TCP服务端的时候,线程池的大小是个很重要的因素:
如果创建的线程太多(空闲线程太多),则会消耗系统资源,如果创建的线程太少,客户端还是有可能等很长时间才能获得服务。因此,线程池的大小设置需要根据负载情况进行调整,以使客户端连接的时间最短,理想的情况是有一个调度的工具,可以在系统负载增加时扩展线程池的大小(低于大上限值),负载减轻时缩减线程池的大小。这就可以使用Java中的Executor接口
Executor接口代表了一个根据某种策略来执行Runnable实例的对象,其中可能包括了排队和调度等细节,或如何选择要执行的任务,它提供了一个更高级的工具来关闭服务器,包括正常的关闭和突然的关闭。通过调用Executors类的各种静态工厂方法来获取ExecutorService实例,而后通过调用execute()方法来为需要处理的任务分配线程,它首先会尝试使用已有的线程,但如果有必要,它会创建一个新的线程来处理任务,另外,如果一个线程空闲了60秒以上,则将其移出线程池,而且任务是在Executor的内部排队,而不像之前的服务器那样是在网络系统中排队,因此,使用比使用Executor要比线程,和线程池这两种方式来实现的TCP服务器效率要高。

实现代码如下:

客户端:

public static void tcpClient(String hostname, int port) throws IOException {
        String serverResponse = null;
        //与端口进行连接
        Socket tcpClient = new Socket(hostname, port);
        tcpClient.setSoTimeout(6000);
        //获取键盘输入
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        //输出流
        PrintStream out = new PrintStream(tcpClient.getOutputStream());
        //输入流 ,获取服务器消息
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(tcpClient.getInputStream()));

        //使用循环主要是为了更好的展示线程池,如果只是建立一次连接就结束,没有必要使用循环
        boolean flag = true;
        while (flag) {
            System.out.print("输入需要发送的信息:");
            String msg = input.readLine();
            //发送数据到服务端
            out.println(msg);
            try {
                serverResponse = bufferedReader.readLine();
                System.out.println("服务端返回:"+serverResponse);
            } catch (SocketTimeoutException e) {
                System.out.println("连接超时,无响应");
            }
            if ("bye".equals(msg)) {
                flag = false;
            }
        }
        if (tcpClient != null) {
            tcpClient.close(); //只关闭socket,其关联的输入输出流也会被关闭
        }
    }

服务端线程类:

public class TcpServerThread implements Runnable {
    private Socket client = null;

    public TcpServerThread(Socket client) {
        this.client = client;
    }

    public static void executeClient(Socket client) {
        String msg = "你好,客户端";
        try {
            //获取Socket的输出流,用来向客户端发送数据
            PrintStream out = new PrintStream(client.getOutputStream());
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            boolean flag = true;
            while (flag) {
                String str = bufferedReader.readLine();
                if (str == null || "".equals(str)) {
                    flag = false;
                } else {
                    out.println("服务端消息:" + msg);
                    System.out.println("接受到客户端消息客户端消息:" + str);
                }
            }
            out.close();
            bufferedReader.close();
            client.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void run() {
        executeClient(client);

    }
}

服务端代码

public static void tcpServerByExecutor(int serverPort)throws Exception{
        ServerSocket serverSocket=new ServerSocket(20006);
        Socket socketClient=null;
        //通过调用Executors类的静态方法,创建一个ExecutorService实例
        Executor service= Executors.newCachedThreadPool();
        boolean flag=true;
        while (flag){
            //等待客户端的连接
            socketClient=serverSocket.accept();
            //调用execute()方法时,首先会尝试使用已有的线程,如果已有线程都在使用中,会创建一个新的线程来处理任务
            //对于空闲的线程,如果一个线程空闲60秒以上,则将其移除线程池;
            service.execute(new TcpServerThread(socketClient));
        }
        serverSocket.close();
    }

需要说明的是,基于线程池,和基于EXECUTOR实现的TCP通讯,其客户端和服务端线程类的代码都是一样的,不一样的地方在与服务端代码的实现,一个是使用人工定义的线程池,一个是使用Executor 来创建一个CachedThreadPool。