多同步Paint绘图板仅同步鼠标点问题排查求助
多画板同步绘图问题排查
我需要创建多个可同步的Paint应用,实现任意画板绘制的内容在所有其他画板上完全同步显示。当前遇到的问题是:在一个画板绘图时,其他画板仅显示鼠标点,无法同步完整绘制线条。
客户端代码(DrawingBoard.java)
package drawing; import java.awt.BasicStroke; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.util.StringTokenizer; import java.util.Vector; import javax.swing.JFrame; import javax.swing.JPanel; class Point2 { int x, y; boolean noEnd = true; public Point2(int x, int y) { this.x = x; this.y = y; } } public class DrawingBoard extends JFrame { final static int ServerPort = 5002; DataInputStream is; DataOutputStream os; int x, y; Vector<Point2> list = new Vector<>(); Point2 oldP=new Point2(0, 0), newP; class MyPanel extends JPanel { public MyPanel() { addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent event) { x = event.getX(); y = event.getY(); newP = new Point2(x, y); list.add(newP); repaint(); oldP = newP; try { String update = ""; for (Point2 point : list) { update += point.x + "," + point.y + ","; } os.writeUTF(update); os.flush(); } catch (IOException e) { e.printStackTrace(); } } public void mouseMoved(MouseEvent event) { if(list.size() != 0) list.elementAt(list.size()-1).noEnd = false; } }); } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(5)); if(list.size()>=2 && list.elementAt(list.size()-2).noEnd) g2.drawLine(list.elementAt(list.size()-2).x, list.elementAt(list.size()-2).y, list.elementAt(list.size()-1).x, list.elementAt(list.size()-1).y); } } public DrawingBoard() throws IOException { InetAddress ip = InetAddress.getByName("localhost"); Socket s = new Socket(ip, ServerPort); is = new DataInputStream(s.getInputStream()); os = new DataOutputStream(s.getOutputStream()); Thread thread2 = new Thread(new Runnable() { @Override public void run() { while (true) { try { String update = is.readUTF(); processDrawingUpdate(update); } catch (IOException e) { e.printStackTrace(); } } } }); thread2.start(); setSize(600, 450); setTitle("My Paint"); add(new MyPanel()); setVisible(true); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private void processDrawingUpdate(String update) { StringTokenizer tokenizer = new StringTokenizer(update, ","); while (tokenizer.hasMoreTokens()) { int newX = Integer.parseInt(tokenizer.nextToken()); int newY = Integer.parseInt(tokenizer.nextToken()); Point2 receivedPoint = new Point2(newX, newY); list.add(receivedPoint); } repaint(); } public static void main(String[] args) throws IOException { DrawingBoard p = new DrawingBoard(); } }
服务端代码(Server.java)
package drawing; import java.io.*; import java.util.*; import java.net.*; public class Server { static ArrayList<ServerThread> list = new ArrayList<>(); static int clientCount = 0; public static void main(String[] args) throws IOException { ServerSocket ssocket = new ServerSocket(5002); Socket s; while (true) { s = ssocket.accept(); DataInputStream is = new DataInputStream(s.getInputStream()); DataOutputStream os = new DataOutputStream(s.getOutputStream()); ServerThread thread = new ServerThread(s, "client " + clientCount, is, os); list.add(thread); thread.start(); clientCount++; } } } class ServerThread extends Thread { Scanner scn = new Scanner(System.in); private String name; final DataInputStream is; final DataOutputStream os; Socket s; boolean active; public ServerThread(Socket s, String name, DataInputStream is, DataOutputStream os) { this.is = is; this.os = os; this.name = name; this.s = s; this.active = true; } @Override public void run() { String message; while (true) { try { message = is.readUTF(); System.out.println(message); if (message.equals("logout")) { this.active = false; this.s.close(); break; } sendDrawingUpdate(message); } catch (IOException e) { e.printStackTrace(); break; } } try { this.is.close(); this.os.close(); } catch (IOException e) { e.printStackTrace(); } } private void sendDrawingUpdate(String update) { for (ServerThread t : Server.list) { if (t != this) { try { t.os.writeUTF(update); t.os.flush(); } catch (IOException e) { e.printStackTrace(); } } } } }
问题根源分析
- 未同步
noEnd状态:客户端绘制线条依赖Point2的noEnd字段判断是否连续绘制,但发送数据时完全没传递这个状态,导致接收端所有点默认noEnd=true,无法正确判断线条的起止。 - 绘制逻辑缺陷:
paintComponent只绘制最后两个点的连线,没有遍历整个点列表重绘所有历史线条,新接收的点无法形成完整轨迹。 - 冗余数据传输:每次拖拽都发送整个点列表,不仅浪费带宽,还会导致接收端重复添加大量重复点,干扰绘制逻辑。
- 鼠标移动的结束状态未同步:本地设置最后一个点的
noEnd=false后,没有把这个状态通知给其他客户端,导致其他客户端认为线条还在继续。
修复方案
客户端关键代码修改
1. 修改鼠标拖拽的发送逻辑(只传当前点+状态)
public void mouseDragged(MouseEvent event) { x = event.getX(); y = event.getY(); newP = new Point2(x, y); list.add(newP); repaint(); oldP = newP; try { // 发送格式:x坐标,y坐标,noEnd状态 String update = x + "," + y + "," + newP.noEnd; os.writeUTF(update); os.flush(); } catch (IOException e) { e.printStackTrace(); } }
2. 同步鼠标移动的结束状态
public void mouseMoved(MouseEvent event) { if(list.size() != 0) { Point2 lastPoint = list.elementAt(list.size()-1); lastPoint.noEnd = false; try { // 发送结束状态通知 String update = lastPoint.x + "," + lastPoint.y + "," + false; os.writeUTF(update); os.flush(); } catch (IOException e) { e.printStackTrace(); } } }
3. 修改绘制逻辑(重绘所有历史线条)
public void paintComponent(Graphics g) { super.paintComponent(g); // 必须调用父方法清空画布,避免残影 Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(5)); // 遍历所有点,连续绘制线条 for(int i = 1; i < list.size(); i++) { Point2 prevPoint = list.get(i-1); Point2 currPoint = list.get(i); if(prevPoint.noEnd) { g2.drawLine(prevPoint.x, prevPoint.y, currPoint.x, currPoint.y); } } }
4. 修改接收逻辑(解析noEnd状态)
private void processDrawingUpdate(String update) { StringTokenizer tokenizer = new StringTokenizer(update, ","); if(tokenizer.countTokens() >= 3) { int newX = Integer.parseInt(tokenizer.nextToken()); int newY = Integer.parseInt(tokenizer.nextToken()); boolean noEnd = Boolean.parseBoolean(tokenizer.nextToken()); if(!noEnd && list.size() > 0) { // 如果是结束状态,更新最后一个点的状态 list.get(list.size()-1).noEnd = false; } else { // 否则添加新点并设置状态 Point2 receivedPoint = new Point2(newX, newY); receivedPoint.noEnd = noEnd; list.add(receivedPoint); } repaint(); } }
内容的提问来源于stack exchange,提问作者Hưng Nguyễn Thành
相关产品推荐
相关产品推荐

