You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多同步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();
                }
            }
        }
    }
}

问题根源分析

  1. 未同步noEnd状态:客户端绘制线条依赖Point2的noEnd字段判断是否连续绘制,但发送数据时完全没传递这个状态,导致接收端所有点默认noEnd=true,无法正确判断线条的起止。
  2. 绘制逻辑缺陷:paintComponent只绘制最后两个点的连线,没有遍历整个点列表重绘所有历史线条,新接收的点无法形成完整轨迹。
  3. 冗余数据传输:每次拖拽都发送整个点列表,不仅浪费带宽,还会导致接收端重复添加大量重复点,干扰绘制逻辑。
  4. 鼠标移动的结束状态未同步:本地设置最后一个点的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 22:47:11