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(猛戳这里 -->WebSocket+Java 私聊、群聊实例)。
版权声明
作者:huanzi-qch
若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.
捐献、打赏
请注意:相应的资金支持能更好的持续开源和创作,如果喜欢这篇文章,请随意打赏!
支付宝
微信
交流群
有事请加群,有问题进群大家一起交流!