Java Socket通訊實現私聊、群聊
前言
閒言少敘,上程式碼!
程式碼編寫
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(猛戳這裡 --> ofollow,noindex" target="_blank">WebSocket+Java 私聊、群聊例項 )。