黑马全套Java教程(九):网络编程(三)+https://developer.aliyun.com/article/1556507
37.4 线程池优化
ServerDemo2.java
package d8_socket4; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; //目标:实现服务端可以同时处理多个客户端的消息 public class ServerDemo2 { //使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { try { System.out.println("=====服务端启动成功====="); //1. 注册端口 ServerSocket serverSocket = new ServerSocket(6666); //定义一个死循环由主线程负责不断的接收客户端的Socket管道连接 while (true) { //2. 每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + ":它来了,上线了!"); //任务对象负责读取消息 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (IOException e) { e.printStackTrace(); } } }
ClientDemo1.java
package d8_socket4; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; //目标:使用线程池优化,实现通信 public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("=====客户端启动成功====="); //1. 创建Socket通信管道请求有服务端的连接 //public Socket(String host, int port) //参数一:服务器的IP //参数二:服务端的端口 Socket socket = new Socket("127.0.0.1",6666); //2. 从socket通信管道中得到一个字节输出流,负责发送数据 OutputStream os = socket.getOutputStream(); //3. 把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { //4. 发送消息 System.out.println("请说:"); String msg = sc.nextLine(); ps.println(msg); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } } }
ServerReaderRunnable.java
package d8_socket4; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class ServerReaderRunnable implements Runnable{ private Socket socket; public ServerReaderRunnable(Socket socket){ this.socket = socket; } @Override public void run(){ try { //3. 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); //4. 把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //5. 按照行读取消息 String msg; while((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg); } } catch (IOException e) { System.out.println(socket.getRemoteSocketAddress() + ":下线了!"); } } }
案例:即时通信
即时通信的含义,要怎么设计:
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一个消息要推送给其他管道
ServerReaderThread.java
package d9_tcp_sms; import d8_socket4.ServerReaderRunnable; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; //目标:实现服务端可以同时处理多个客户端的消息 public class ServerDemo2 { //定义一个静态的List集合存储当前全部在线的socket管道 public static List<Socket> allOnlineSockets = new ArrayList<>(); public static void main(String[] args) throws Exception{ System.out.println("=====服务端启动成功====="); //1. 注册端口 ServerSocket serverSocket = new ServerSocket(6666); //定义一个死循环由主线程负责不断的接收客户端的Socket管道连接 while (true) { //2. 每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + ":上线了!"); //任务对象负责读取消息 Runnable target = new ServerReaderRunnable(socket); allOnlineSockets.add(socket); //上线完成 //3. 创建一个独立的线程来单独处理这个socket管道 new ServerReaderThread(socket).start(); } } } class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { //3. 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); //4. 把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //5. 按照行读取消息 String msg; while ((msg = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + "发来了:" + msg); //把这个消息进行端口转发给全部客户端socket管道 sendMsgToAll(msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + ":下线了!"); ServerDemo2.allOnlineSockets.remove(socket); } } private void sendMsgToAll(String msg) throws Exception{ for(Socket socket : ServerDemo2.allOnlineSockets){ PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(msg); ps.flush(); } } }
ClientDemo1.java
package d9_tcp_sms; import java.io.*; import java.net.Socket; import java.util.Scanner; public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动成功====="); //1. 创建Socket通信管道请求有服务端的连接 Socket socket = new Socket("127.0.0.1", 6666); //创建一个独立的线程专门负责这个客户端的读消息(服务端随时可能转发消息过来) new ClientReaderThread(socket).start(); //2. 从socket通信管道中得到一个字节输出流,负责发送数据 OutputStream os = socket.getOutputStream(); //3. 把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); //4. 发送消息 Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); ps.println(msg); ps.flush(); } } } class ClientReaderThread extends Thread { private Socket socket; public ClientReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { //3. 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); //4. 把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //5. 按照行读取消息 String msg; while ((msg = br.readLine()) != null) { System.out.println("收到消息:" + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + ":服务端把你踢出去了!"); } } }
案例:模拟BS系统
package d10_bs; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; //目标:实现服务端可以同时处理多个客户端的消息 public class BSserverDemo { public static void main(String[] args) throws Exception{ //1. 注册端口 ServerSocket serverSocket = new ServerSocket(8080); //2. 创建一个循环接收多个客户端的请求 while (true) { Socket socket = serverSocket.accept(); //3. 创建一个独立的线程来单独处理这个socket管道 new ServerReaderThread(socket).start(); } } } class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { //浏览器 已经与本线程建立了Socket管道 //响应消息给浏览器显示 PrintStream ps = new PrintStream(socket.getOutputStream()); //必须响应HTTP协议格式数据,否则浏览器不认识消息 ps.println("HTTP/1.1 200 OK"); //协议类型和版本 响应成功的消息! ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页 ps.println(); //必须发送一个空行 //才可以响应数据回去给浏览器 ps.println("<span style='color:red;font-size:90px'>《Java大法好》 </span>"); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }
例2:利用线程池优化
package d10_bs; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; //目标:实现服务端可以同时处理多个客户端的消息 public class BSserverDemo { //使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) throws Exception{ //1. 注册端口 ServerSocket serverSocket = new ServerSocket(8080); //2. 创建一个循环接收多个客户端的请求 while (true) { Socket socket = serverSocket.accept(); //3. 创建一个独立的线程来单独处理这个socket管道 pool.execute(new ServerReaderThread(socket)); } } } class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { //浏览器 已经与本线程建立了Socket管道 //响应消息给浏览器显示 PrintStream ps = new PrintStream(socket.getOutputStream()); //必须响应HTTP协议格式数据,否则浏览器不认识消息 ps.println("HTTP/1.1 200 OK"); //协议类型和版本 响应成功的消息! ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页 ps.println(); //必须发送一个空行 //才可以响应数据回去给浏览器 ps.println("<span style='color:red;font-size:90px'>《Java大法好》 </span>"); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }