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

远程服务器DatagramSocket丢包,MulticastSocket正常?VPN无丢包原因排查

问题
  1. 明确如何使用服务器套接字接收远程客户端的数据;
  2. 为何客户端连接VPN时不会出现数据丢失的情况。

背景

我开发了基于UDP套接字的客户端与服务器应用,在LAN环境下测试一切正常,但将服务器部署到远程主机时出现数据丢失问题。而当客户端连接VPN后,客户端与服务器的收发数据功能恢复正常。

客户端代码

var serverConnection = new DatagramSocket();
// 向静态IP发送注册信息(主服务器套接字)
String portStr = String.valueOf(serverConnection.getLocalPort());
DatagramPacket registration = new DatagramPacket(
            portStr.getBytes(),
            portStr.getBytes().length,
            remoteServerIP,
            8560
        );
serverConnection.send(registration);
// 接收服务器上新的"客户端管理器"套接字信息
serverConnection.receive(response);
int port = Integer.parseInt(new String(response.getData()));
var group = new InetSocketAddress(remoteServerIP, port);
// 打开流的代码...
// 发送线程
 DatagramPacket packet = new DatagramPacket(audioBuffer, sampleRate, group);
 while (true) {
     if (micIsOn && micro.available() > 0) {
         micro.read(audioBuffer, 0, audioBuffer.length);
             serverConnection.send(packet);
       }
 }
// 接收线程
  DatagramPacket packet = new DatagramPacket(audioBuffer, outputLine.getBufferSize());
  while (true) {
        try {
            serverConnection.receive(packet);
            outputLine.write(
                 audioBuffer,
                 0,
                 outputLine.getBufferSize()
            );
            outputLine.drain();
            outputLine.flush();
        } catch (IOException e) {
          logger.log(ERROR, "音频输出结束!", e);
        }
  }

LAN环境下正常运行的服务器代码

// 启动时创建主服务器套接字 this.serverAudio = new MulticastSocket(8560);
// 启动线程用于注册客户端并处理后续接收
serverAudio.receive(clientRegistration);
var client = new Client(
    clientRegistration.getAddress(),
    clientRegistration.getPort()
);
DatagramSocket sender = new DatagramSocket(); // 服务器端的客户端管理器,使用DatagramSocket时远程环境不工作
String portStr = String.valueOf(sender.getLocalPort());
serverAudio.send(new DatagramPacket(
    portStr.getBytes(),
    portStr.getBytes().length,
    client.address(),
    client.port()
));
startResend(sender, client, socketGroup);

远程主机上半正常运行的服务器代码(仅替换套接字类型)

// 启动时创建主服务器套接字 this.serverAudio = new MulticastSocket(8560);
// 启动线程用于注册客户端并处理后续接收
serverAudio.receive(clientRegistration);
var client = new Client(
    clientRegistration.getAddress(),
    clientRegistration.getPort()
);
MulticastSocket sender = new MulticastSocket (); // 服务器端的客户端管理器,能接收但向客户端发送时丢包
String portStr = String.valueOf(sender.getLocalPort());
serverAudio.send(new DatagramPacket(
    portStr.getBytes(),
    portStr.getBytes().length,
    client.address(),
    client.port()
));
startResend(sender, client, socketGroup);

重发方法代码

// 重发方法
    private void startResend(
        DatagramSocket sender,
        Client client,
        SocketGroup socketGroup
    )
        throws IOException
    {
        byte[] buffer = new byte[BUFFER_SIZE];
        var packet = new DatagramPacket(
            buffer,
            BUFFER_SIZE // 和客户端一致,为44100
        );
        var toGroup = new DatagramPacket(
            buffer,
            BUFFER_SIZE,
            socketGroup.mcastaddr() // 225.x.x.x
        );
        var toClient = new DatagramPacket(
            buffer,
            BUFFER_SIZE,
            client.address(),
            client.port()
        );
        new Thread(() -> {
            while (true) {
                try {
                    sender.receive(packet); // 使用DatagramSocket时永远收不到包,使用MulticastSocket时可以收到
                    if (packet.getAddress().equals(client.address())
                        && packet.getPort() == client.port()
                    ) {
                        sender.send(toGroup);
                        continue;
                    }
                    sender.send(toClient);
                } catch (IOException e) {
                    log.error("错误", e);
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

问题解答

1. 服务器套接字接收远程客户端数据的正确方式

核心问题出在服务器动态创建的套接字(sender)的NAT穿透逻辑缺陷:

  • 使用new DatagramSocket()时,套接字绑定到随机端口,远程客户端的NAT设备没有对应端口的会话记录,会直接丢弃客户端发往该随机端口的数据包。
  • 切换到MulticastSocket后,虽能接收数据,但依然存在NAT会话问题:服务器通过主套接字发送端口信息,而非后续用于接收的sender套接字,导致客户端NAT未记录sender端口的会话,反向数据无法正常到达。

正确实现步骤:

  • 服务器的动态套接字需绑定固定端口,并在防火墙/云安全组中开放该端口,或复用主套接字端口(通过DatagramSocket构造函数指定端口并设置SO_REUSEADDR)。
  • 服务器必须使用sender套接字向客户端发送第一个数据包(比如端口信息),触发客户端NAT记录会话,确保后续客户端数据能正常转发到该端口。
  • 修改startResend方法:不要复用同一个缓冲区处理接收和发送,避免高并发下数据覆盖,每次接收后复制缓冲区内容再构造发送包。

2. 连接VPN后数据不丢失的原因

VPN会创建虚拟LAN环境:

  • 客户端连接VPN后获得私有网段IP,与服务器处于同一虚拟局域网,绕过了公共网络的NAT限制,双方数据包在VPN私有网络内传输,服务器随机端口可直接被客户端访问。
  • VPN网关会维护端口映射会话,确保双向UDP数据包都能正常穿透,不会被NAT设备丢弃。

内容的提问来源于stack exchange,提问作者NSkul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:29:49