前言
閑言少敘,上代碼!
代碼編寫
server服務端
/** * 服務端 */ public class Server { private static ServerSocket server = null; private static Socket ss = null; /** * 客戶端集合 */ private static Map<String, ServerThread> serverThreadMap = new HashMap<String, ServerThread>(); public static void main(String[] args) { server(); } /** * 普通服務器連接 */ private static void server() { try { //建立服務端 server = new ServerSocket(10010); System.out.println("server端已啟動!"); while (true) { //創建接收接口 ss = server.accept(); //啟動新客戶監聽線程 new ServerThread(server, ss).start(); } } catch (IOException e) { e.printStackTrace(); } finally { try { ss.close(); server.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 內部類線程,每連接一個新的客戶端就啟動一個對應的監聽線程 */ @SuppressWarnings("Duplicates") private static class ServerThread extends Thread { ServerSocket server = null; Socket socket = null; InputStream is = null; OutputStream os = null; String clientName = null; boolean alive = true; public ServerThread() { } ServerThread(ServerSocket server, Socket socket) { this.socket = socket; this.server = server; } @Override public void run() { //接收數據 try { is = socket.getInputStream(); //發送 os = socket.getOutputStream(); //緩存區 byte[] b = new byte[1024]; int length = 0; while (alive) { //接收從客戶端發送的消息 length = is.read(b); if (length != -1) { //文本消息 String message = new String(b, 0, length); //JSON字符串轉 HashMap HashMap hashMap = new ObjectMapper().readValue(message, HashMap.class); //消息類型 String type = (String) hashMap.get("type"); //新連接 if ("OPEN".equals(type)) { clientName = (String) hashMap.get("clientName"); //添加客戶端到集合容器中 serverThreadMap.put(clientName, this); System.out.println(clientName + "連接成功!"); System.out.println("當前客戶端數量:" + serverThreadMap.size()); } //關閉 if ("CLOSE".equals(type)) { alive = false; System.err.println(clientName + "退出連接,關閉監聽線程!"); } //文本消息 if ("MESSAGE".equals(type)) { String msg = (String) hashMap.get("message"); String chat = (String) hashMap.get("chat"); //群聊(廣播) if ("GROUP".equals(chat)) { //遍歷容器,給容器中的每個對象轉發消息 for (ServerThread st : serverThreadMap.values()) { //向其他客戶端發送數據 if (st != this) { st.os.write(new String(b, 0, length).getBytes()); } } //后台打印 System.out.println(clientName + "向所有人說:" + msg); } //私聊 if ("PRIVATE".equals(chat)) { String to = (String) hashMap.get("to"); serverThreadMap.get(to).os.write(new String(b, 0, length).getBytes()); //后台打印 System.out.println(clientName + "向" + to + "說:" + msg); } } } } } catch (IOException e) { e.printStackTrace(); System.err.println("與" + clientName + "連接中斷,被迫關閉監聽線程!"); } finally { try { serverThreadMap.remove(clientName); System.out.println("當前客戶端數量:" + serverThreadMap.size()); os.close(); is.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
client客戶端
/** * 客戶端 */ @SuppressWarnings("Duplicates") public class Client { private static String serverInetAddress = "127.0.0.1"; private static int serverPort = 10010; private static Socket client = null; private static OutputStream os = null; private static InputStream is = null; private static String thisName; private static boolean alive = true; /** * 客戶端連接服務器 */ @SuppressWarnings("unused") public static void open(String name) { try { thisName = name; InetAddress inetAddress = InetAddress.getLocalHost(); //建立連接 client = new Socket(serverInetAddress, serverPort); //數據流發送數據 os = client.getOutputStream(); sendMsg("{\"type\":\"OPEN\",\"clientName\":\"" + name + "\"}"); //數據流接收數據 is = client.getInputStream(); byte[] b = new byte[1024]; int length = 0; while (alive) { //接收從服務器發送回來的消息 length = is.read(b); if (length != -1) { onMsg(new String(b, 0, length)); } } } catch (IOException e) { e.printStackTrace(); } finally { try { //關流 os.close(); client.close(); is.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 關閉客戶端 */ public static void close() { sendMsg("{\"type\":\"CLOSE\"}"); alive = false; } /** * 發送消息 */ public static void sendMsg(String msg) { try { //調用發送 os.write(msg.getBytes()); } catch (IOException e) { e.printStackTrace(); } } /** * 收到消息的回調 */ private static void onMsg(String message) { //JSON字符串轉 HashMap HashMap hashMap = null; try { hashMap = new ObjectMapper().readValue(message, HashMap.class); } catch (IOException e) { e.printStackTrace(); } String msg = (String) hashMap.get("message"); String chat = (String) hashMap.get("chat"); String from = (String) hashMap.get("from"); String to = (String) hashMap.get("to"); //群聊 if ("GROUP".equals(chat)) { //后台打印 System.out.println(thisName + "收到(" + to + ")群聊消息:" + msg); } //私聊 if ("PRIVATE".equals(chat)) { //后台打印 System.out.println(thisName + "收到(" + from + ")私聊消息:" + msg); } } /** * 獲取thisName */ public static String getThisName() { return thisName; } }
controller模擬調用客戶端
@RequestMapping("/sendMsg/{chat}/{msg}") public void sendMsg(@PathVariable("chat") String chat, @PathVariable("msg") String msg) { if ("group".equals(chat.toLowerCase())) { //群聊 Client.sendMsg("{\"type\":\"MESSAGE\",\"chat\":\"GROUP\",\"from\":\""+Client.getThisName()+"\",\"to\":\"群號:xxxx\",\"message\":\"" + msg + "\"}"); } else { //私聊 Client.sendMsg("{\"type\":\"MESSAGE\",\"chat\":\"PRIVATE\",\"from\":\""+Client.getThisName()+"\",\"to\":\"" + chat + "\",\"message\":\"" + msg + "\"}"); } } @RequestMapping("/starClient/{name}") public void starClient(@PathVariable("name") String name) { Client.open(name); } @RequestMapping("/closeClient") public void closeClient() { Client.close(); }
效果展示
一個服務端、兩個客戶端(兩個不同的工程、模擬兩個客戶端),注意,要先啟動服務端,再啟動客戶端!
使用controller模擬啟動兩個客戶端:
http://localhost:10086/springboot/user/starClient/張三
http://localhost:10087/starClient/李四
張三發送群聊
http://localhost:10086/springboot/user/sendMsg/group/大家好啊
張三是發送者,server不再轉發此消息給張三
張三向李四發送私聊信息
http://localhost:10086/springboot/user/sendMsg/李四/老表,你好啊
張三是發送者,server不再轉發此消息給張三
李四回復張三私聊信息
李四是發送者,server不再轉發此消息給李四
下線、掉線
張三:http://localhost:10086/springboot/user/closeClient
李四:直接終止客戶端進程
后記
這個例子服務端每次有新的客戶端連接進來,就啟動一個線程去監聽與此客戶端的通信,當有大量客戶端時就不適用了,而且涉及界面時,java socket不能主動給瀏覽器發送消息,界面聊天只能用輪詢的方式實現,不好;多客戶端、涉及有界面的聊天建議使用websocket(猛戳這里 -->WebSocket+Java 私聊、群聊實例)。