Java Socket多线程服务器向指定客户端回发消息实现问询
嘿,我来帮你搞定这两个Socket和Swing的问题!你已经搭好了基础的多线程服务端和客户端消息发送功能,接下来咱们一步步解决这两个需求:
问题1:服务端点击JLabel向指定客户端发送消息
要实现这个功能,核心是把每个客户端的连接信息和对应的PCNUM绑定存储,这样点击订单JLabel时,就能通过PCNUM找到对应的客户端Socket并发送消息。具体步骤如下:
1. 新增全局客户端连接存储
在服务端创建一个线程安全的集合,用来保存PCNUM和对应客户端Socket的映射。推荐用ConcurrentHashMap,因为多线程环境下操作更安全:
// 在ThreadedEchoServer类里添加全局静态集合 import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ThreadedEchoServer { static final int PORT = 1978; // 存储PCNUM -> 对应客户端Socket public static final Map<String, Socket> clientSockets = new ConcurrentHashMap<>(); // 原startServer方法不变... }
2. 在EchoThread中解析PCNUM并维护连接集合
当客户端发送消息时,解析出消息里的PCNUM,把当前Socket存入集合;当客户端断开连接时,从集合中移除:
public class EchoThread extends Thread { protected Socket socket; private String clientPcNum; // 存储当前客户端的PCNUM public EchoThread(Socket clientSocket) { this.socket = clientSocket; } public void run() { InputStream inp = null; BufferedReader brinp = null; try { inp = socket.getInputStream(); brinp = new BufferedReader(new InputStreamReader(inp)); String line; while ((line = brinp.readLine()) != null) { if (line.equalsIgnoreCase("QUIT")) { break; } // 解析消息中的PCNUM(你的消息格式是item/PCNUM) String[] parts = line.split("/"); if (parts.length == 2) { clientPcNum = parts[1]; // 将当前客户端Socket存入全局集合 ThreadedEchoServer.clientSockets.put(clientPcNum, socket); // 这里处理你的订单逻辑,比如更新JLabel图标... } } } catch (IOException e) { e.printStackTrace(); } finally { // 客户端断开连接,从集合中移除 if (clientPcNum != null) { ThreadedEchoServer.clientSockets.remove(clientPcNum); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } // 新增向指定客户端发送消息的工具方法 public static void sendMessageToClient(String pcNum, String message) { Socket clientSocket = ThreadedEchoServer.clientSockets.get(pcNum); if (clientSocket == null || clientSocket.isClosed()) { System.out.println("客户端" + pcNum + "已断开连接"); return; } try (OutputStream os = clientSocket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); BufferedWriter bw = new BufferedWriter(osw)) { bw.write(message); bw.newLine(); // 记得加换行,因为客户端用readLine()读取 bw.flush(); } catch (IOException e) { e.printStackTrace(); // 发送失败,移除无效连接 ThreadedEchoServer.clientSockets.remove(pcNum); } } }
3. 服务端JLabel点击事件调用发送方法
在你的服务端GUI中,给订单JLabel添加鼠标点击事件,获取该订单对应的PCNUM,然后调用发送方法:
// 假设你把订单对应的PCNUM存在JLabel的name属性里 orderLabel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { JLabel source = (JLabel) e.getSource(); String pcNum = source.getName(); String replyMessage = "订单已收到,正在处理!"; EchoThread.sendMessageToClient(pcNum, replyMessage); } });
问题2:客户端多JPanel环境下弹出JOptionPane
这里要注意两个核心点:
- 客户端需要保持长连接,才能接收服务端主动发送的消息(你现在的客户端发送完就关闭Socket,服务端没法主动发消息过来);
- Swing组件必须在**事件调度线程(EDT)**中操作,否则会出现UI异常。
1. 修改客户端为长连接并监听服务端消息
创建一个客户端Socket管理类,初始化长连接并启动监听线程,收到消息后在EDT中弹出对话框:
package questorderingsystem.engine; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; public class ClientSocketManager { private static Socket socket; private static BufferedReader br; // 初始化长连接并启动监听线程 public static void initConnection() throws UnknownHostException, IOException { String host = "192.168.55.151"; int port = 1978; socket = new Socket(host, port); // 启动后台监听线程 new Thread(() -> { try { br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String serverMessage; while ((serverMessage = br.readLine()) != null) { final String msg = serverMessage; // 在EDT中弹出对话框,保证UI线程安全 SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(null, msg, "服务端通知", JOptionPane.INFORMATION_MESSAGE); }); } } catch (IOException e) { e.printStackTrace(); } finally { // 断开连接时清理资源 try { if (br != null) br.close(); if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } // 修改发送方法,不再关闭Socket public static void sendToServer(String item) throws IOException { if (socket == null || socket.isClosed()) { initConnection(); // 如果连接断开,重新初始化 } String ipadr = socket.getLocalAddress().toString(); String PCNUM = ipadr.substring(ipadr.length() - 2); String oneLine = item.replace("\n", "%"); String sendMessage = oneLine + "/" + PCNUM; try (var osw = new java.io.OutputStreamWriter(socket.getOutputStream()); var bw = new java.io.BufferedWriter(osw)) { bw.write(sendMessage); bw.newLine(); // 加换行,服务端用readLine()读取 bw.flush(); } } }
2. 在客户端启动时初始化连接
比如在客户端主窗口的构造函数中调用初始化方法:
public class ClientMainFrame extends JFrame { public ClientMainFrame() { try { ClientSocketManager.initConnection(); } catch (IOException e) { JOptionPane.showMessageDialog(this, "连接服务端失败!", "错误", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } // 其他UI初始化逻辑... } }
这样不管客户端当前显示哪个JPanel,只要收到服务端消息,都会在EDT中弹出JOptionPane,不会出现UI线程安全问题。
内容的提问来源于stack exchange,提问作者Aleksandar Stojanovic
相关产品推荐
相关产品推荐

