Java Socket BIO多線程 (beta2)
上一篇的BIO多線程做了基本的框架搭建,這次做了beta2,做了心跳包管理和線程釋放資源的處理
封裝的服務器代碼
-
封裝類(SocketServer),並加了線程池
package com.server; import javax.swing.plaf.nimbus.AbstractRegionPainter; import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class SocketServer extends Thread{ /** * 用戶掃已有的socket處理線程 * 1. 沒有的線程不引用 * 2. 關注是否有心跳 * 3. 關注是否超過登陸時間 */ private ScheduledExecutorService scheduleSocketMonitorExecutor = Executors .newSingleThreadScheduledExecutor(r -> new Thread(r, "socket_monitor_" + r.hashCode())); /** * 存儲只要有socket處理的線程 */ private List<SocketThread> existConnectionThreadList = Collections.synchronizedList(new ArrayList<>()); /** * 中間list,用於遍歷的時候刪除 */ private List<SocketThread> noConnectionThreadList = Collections.synchronizedList(new ArrayList<>()); /** * 存儲當前由用戶信息活躍的的socket線程 */ private ConcurrentMap<String, SocketThread> existSocketMap = new ConcurrentHashMap<>(); private static final int CORE_POOL_SIZE = 5; private static final int MAX_POOL_SIZE = 10; private static final int QUEUE_CAPACITY = 100; private static final Long KEEP_ALIVE_TIME = 1L; // ThreadPoolExecutor executorService = new ThreadPoolExecutor( // CORE_POOL_SIZE, // MAX_POOL_SIZE, // KEEP_ALIVE_TIME, // TimeUnit.SECONDS, // new ArrayBlockingQueue<>(QUEUE_CAPACITY), // new ThreadPoolExecutor.CallerRunsPolicy() // ); //private String host; private int port; //private Socket socket; ServerSocket server; //Map<Integer,SocketThread> socketThreadMap; List<SocketThread> threads = new ArrayList<>(); int count = 0; private boolean isRunning; public SocketServer(int port) { //this.host = host; this.port = port; isRunning = true; } public void Connect() throws IOException { server = new ServerSocket(port); //每隔1s掃一次ThreadList scheduleSocketMonitorExecutor.scheduleWithFixedDelay(() -> { Date now = new Date(); //刪除list中沒有用的thread引用 existConnectionThreadList.forEach(connectionThread -> { if (!connectionThread.isRunning()) { noConnectionThreadList.add(connectionThread); } else { //還在運行的線程需要判斷心跳是否ok以及是否身份驗證了 Date lastOnTime = connectionThread.getConnection().getLastOnTime(); long heartDuration = now.getTime() - lastOnTime.getTime(); if (heartDuration > SocketConstant.HEART_RATE) { //心跳超時,關閉當前線程 System.out.println(connectionThread.getSocketDescribe() + "心跳超時,close it"); connectionThread.Close(); } } }); noConnectionThreadList.forEach(connectionThread -> { existConnectionThreadList.remove(connectionThread); }); noConnectionThreadList.clear(); }, 0, 1, TimeUnit.SECONDS); } @Override public void run() { if(isInterrupted()) return; while (isRunning) { if(server.isClosed()){ isRunning = false; System.out.println("socket server IsClosed......"); break; } try{ System.out.println("Start socket wait......"); Socket socket = server.accept(); //啓動線程 SocketThread socketThread = new SocketThread(socket); count++; socketThread.setName("Thread" + count); existConnectionThreadList.add(socketThread); socketThread.start(); // executorService.submit(socketThread); }catch (IOException e) { e.printStackTrace(); Close(); } } } public void Listener() throws IOException { } public void SendMsg(String data) { } public void Close() { try { //先關閉monitor線程,防止遍歷list的時候 scheduleSocketMonitorExecutor.shutdownNow(); if (server != null && !server.isClosed()) { for (SocketThread currentThread : existConnectionThreadList) { currentThread.Close(); } } //executorService.shutdownNow(); server.close(); //終止線程次 isRunning = false; }catch (Exception e){ e.printStackTrace(); } } }
-
線程類(SocketThread)
package com.server; import java.io.*; import java.net.Inet4Address; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.util.Date; public class SocketThread extends Thread { /** * 封裝的客戶端連接socket */ private Connection connection; private boolean isRunning; private Socket socket = null; private int byteLength = 1024; public String getSocketDescribe() { return socketDescribe; } private String socketDescribe = null; public Connection getConnection() { return connection; } //private InputStream inputStream; public SocketThread(Socket socket) { this.socket = socket; socketDescribe = "Client:" + this.socket.getInetAddress() +"_Port:" +this.socket.getPort(); System.out.println(socketDescribe + " Connected!"); connection = new Connection(socket, this); Date now = new Date(); connection.setCreateTime(now); connection.setLastOnTime(now); isRunning = true; } public boolean isRunning() { return isRunning; } @Override public void run() { if(isInterrupted()) return; while (isRunning) { if(socket.isClosed()) { isRunning = false; System.out.println(socketDescribe + "Client close."); break; } BufferedReader reader; try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String message; while ((message = reader.readLine()) != null) { System.out.println(socketDescribe + "服務端收到消息:" + message); } } catch (IOException e) { System.out.println(socketDescribe + "ConnectionThread.run failed. IOException:{}"+ e.getMessage()); this.Close(); } } } public void Close() { isRunning = false; try { socket.shutdownInput(); socket.shutdownOutput(); }catch (IOException e) { System.out.println(e.toString()); }finally { try { socket.close(); }catch (IOException e) { e.printStackTrace(); } } } }
-
Connect信息記錄函數
package com.server; import java.net.Socket; import java.util.Date; public class Connection { /** * 當前的socket連接實例 */ private Socket socket; /** * 當前連接線程 */ private SocketThread connectionThread; /** * 當前連接是否登陸 */ private boolean isLogin; /** * 存儲當前的user信息 */ private String userId; /** * 創建時間 */ private Date createTime; /** * 最後一次更新時間,用於判斷心跳 */ private Date lastOnTime; public SocketThread getConnectionThread() { return connectionThread; } public String getUserId() { return userId; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getLastOnTime() { return lastOnTime; } public void setLastOnTime(Date lastOnTime) { this.lastOnTime = lastOnTime; } public Connection(Socket socket, SocketThread connectionThread) { this.socket = socket; this.connectionThread = connectionThread; } public void println(String message) { int count = 0; } }
-
常量函數
package com.server; public class SocketConstant { /** * 心跳頻率爲10s */ public static final int HEART_RATE = 10*1000; /** * 最多開2000個socket線程,超過的直接拒絕 */ public static final int MAX_SOCKET_THREAD_NUM = 2000; /** * 重試次數:3 */ public static final int RETRY_COUNT = 3; }
-
主函數調用測試
import com.server.SocketServer; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; //參考文檔 //https://www.jianshu.com/p/cde27461c226 public class Main { public static void main(String[] args) { try{ SocketServer server =new SocketServer(888); BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(System.in,"UTF-8")); while (true) { String str = bufferedReader.readLine(); if(str.equals("quit")) { server.Close(); break; } if(str.equals("start")) { server.Connect(); server.start(); } } }catch (IOException e){ e.printStackTrace(); } System.out.println("Exit"); } }
測試
開了一個SocketTool2(需要的自己網上下載),創建了多個客戶端去連接服務器,可以同時發送信息(回測作爲客戶端發送完畢的結束符),這些信息都會顯示,客戶端斷開後,服務器每10秒輪詢客戶端是否還連接,如果沒有的話服務器對應的socket則斷開,並移除連接列表