在使用线程池实现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。