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

Java下SCTP报文UDP封装实现方案咨询(适配NAT网络)

问题

我需要用SCTP协议传输文件,已编写Java代码,但在4G热点这类重度NAT网络环境下无法正常运行。了解到SCTP UDP封装的相关规范文档,现寻求能将SCTP报文封装进UDP并通过UDP通道发送、实现NAT穿越的Java方案。以下是当前的文件发送代码:

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.*;

import com.sun.nio.sctp.MessageInfo;
import com.sun.nio.sctp.SctpChannel;
import com.sun.nio.sctp.SctpServerChannel;

public class Main {
    SctpChannel connectionChannelPrimary;
    SctpChannel connectionChannelSecondary;
    InetSocketAddress serverSocketAddressPrimary;
    InetSocketAddress serverSocketAddressSecondary;

    String directoryPath;

    public Main() {
        serverSocketAddressPrimary = new InetSocketAddress(6002);
        serverSocketAddressSecondary = new InetSocketAddress(6003);
    }

    public void setDirectoryPath(String directoryPath) {
        this.directoryPath = directoryPath;
    }

    public String getDirectoryPath() {
        return directoryPath;
    }

    public void establishConnection(int connId) throws IOException {
        SctpServerChannel sctpServerChannel = SctpServerChannel.open();
        if (connId == 0) {
            sctpServerChannel.bind(serverSocketAddressPrimary);
            connectionChannelPrimary = sctpServerChannel.accept();
            System.out.println("connection established for primary");
        } else {
            sctpServerChannel.bind(serverSocketAddressSecondary);
            connectionChannelSecondary = sctpServerChannel.accept();
            System.out.println("connection established for helper");
        }

    }

    ArrayList<String> getAllFiles() {
        File directory = new File(this.directoryPath);
        ArrayList<String> fileNames = new ArrayList<>();
        for (File fileEntry : Objects.requireNonNull(directory.listFiles())) {
            if (fileEntry.isFile()) {
                fileNames.add(fileEntry.getName());
            }
        }
        Collections.sort(fileNames);
        return fileNames;
    }

    public byte[] readFile(String filename) throws IOException {
        String extraString = "\n\n\n\nNRL\n\n\n";
        File file = new File(filename);
        FileInputStream fl = new FileInputStream(file);
        ByteBuffer finalBuffer = ByteBuffer.allocate((int) (file.length() + extraString.length()));
        byte[] arr = new byte[(int) file.length()];
        int res = fl.read(arr);
        if (res < 0) {
            System.out.println("Error in reading file");
            fl.close();
            return null;
        }
        fl.close();
        finalBuffer.put(arr);
        finalBuffer.put(extraString.getBytes());
        byte[] tmp = new byte[extraString.length()];
        finalBuffer.position((int) (file.length() - 1));
        finalBuffer.get(tmp, 0, tmp.length);
        return finalBuffer.array();
    }

    public void sendBytes(String filename, int connId) throws IOException {
        byte[] message = readFile(filename);
        assert message != null;
        System.out.println(message.length);
        int tmp = 0;
        int cntIndex = 60000;
        int prevIndex = 0;
        boolean isBreak = false;
        while (!isBreak) {
            byte[] slice;
            if (prevIndex + 60000 >= message.length) {
                slice = Arrays.copyOfRange(message, prevIndex, message.length);
                isBreak = true;
            } else {
                slice = Arrays.copyOfRange(message, prevIndex, cntIndex);
                prevIndex = cntIndex;
                cntIndex = cntIndex + 60000;
            }
            final ByteBuffer byteBuffer = ByteBuffer.allocate(64000);
            final MessageInfo messageInfo = MessageInfo.createOutgoing(null, 0);
            byteBuffer.put(slice);
            byteBuffer.flip();
            tmp += slice.length;
            try {
                if (connId == 0) connectionChannelPrimary.send(byteBuffer, messageInfo);
                else connectionChannelSecondary.send(byteBuffer, messageInfo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println(tmp);
    }

    public static void main(String[] args) throws IOException {
        String bgFilePath = "/home/iiitd/Desktop/background/";
        String fgFilePath = "/home/iiitd/Desktop/foreground/";
        Main myObj = new Main();
        myObj.setDirectoryPath("/home/iiitd/Desktop/tmp/");
        myObj.establishConnection(1);
        myObj.establishConnection(0);
        ArrayList<String> files = myObj.getAllFiles();

        for (String tmpFile : files) {
            String cntFilePath = myObj.getDirectoryPath() + tmpFile;
            
            myObj.sendBytes(cntFilePath,0);
        }
    }
}
SCTP UDP封装的Java实现方案

核心思路是把完整的SCTP报文作为UDP的数据载荷发送,同时添加一个极简的封装头部(包含SCTP的源/目的端口),以此绕过NAT对SCTP的限制。

1. 封装核心规则

UDP数据包结构 = [4字节封装头部] + [原始SCTP报文]

  • 封装头部:2字节源SCTP端口 + 2字节目的SCTP端口,用于两端识别内部SCTP会话

2. 代码实现

2.1 UDP封装发送器

替换原代码中SctpChannel的发送逻辑,把SCTP报文封装后通过UDP发送:

import java.net.*;
import java.nio.ByteBuffer;

public class SctpOverUdpSender {
    private DatagramSocket udpSocket;
    private InetSocketAddress remoteUdpAddr;
    private int localSctpPort;
    private int remoteSctpPort;

    public SctpOverUdpSender(int localUdpPort, String remoteHost, int remoteUdpPort, int localSctpPort, int remoteSctpPort) throws SocketException {
        this.udpSocket = new DatagramSocket(localUdpPort);
        this.remoteUdpAddr = new InetSocketAddress(remoteHost, remoteUdpPort);
        this.localSctpPort = localSctpPort;
        this.remoteSctpPort = remoteSctpPort;
    }

    // 封装并发送SCTP报文
    public void sendSctpPacket(ByteBuffer sctpPacket) throws Exception {
        // 构建封装头部
        ByteBuffer encapsHeader = ByteBuffer.allocate(4);
        encapsHeader.putShort((short) localSctpPort);
        encapsHeader.putShort((short) remoteSctpPort);
        encapsHeader.flip();

        // 合并头部与SCTP报文
        ByteBuffer udpData = ByteBuffer.allocate(4 + sctpPacket.remaining());
        udpData.put(encapsHeader);
        udpData.put(sctpPacket);
        udpData.flip();

        // 发送UDP数据包
        DatagramPacket packet = new DatagramPacket(udpData.array(), udpData.limit(), remoteUdpAddr);
        udpSocket.send(packet);
    }

    public void close() {
        udpSocket.close();
    }
}

2.2 UDP解封装接收器

负责接收UDP数据包,剥离封装头部后还原为SCTP报文:

import java.net.*;
import java.nio.ByteBuffer;

public class SctpOverUdpReceiver implements Runnable {
    private DatagramSocket udpSocket;
    private int bufferSize = 65535;
    private OnSctpPacketReceivedListener listener;

    public interface OnSctpPacketReceivedListener {
        void onSctpPacketReceived(ByteBuffer sctpPacket);
    }

    public SctpOverUdpReceiver(int localUdpPort, OnSctpPacketReceivedListener listener) throws SocketException {
        this.udpSocket = new DatagramSocket(localUdpPort);
        this.listener = listener;
    }

    @Override
    public void run() {
        byte[] buffer = new byte[bufferSize];
        while (!udpSocket.isClosed()) {
            try {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                udpSocket.receive(packet);

                // 剥离4字节封装头部,得到原始SCTP报文
                ByteBuffer sctpPacket = ByteBuffer.wrap(buffer, 4, packet.getLength() - 4);
                listener.onSctpPacketReceived(sctpPacket);
            } catch (Exception e) {
                if (!udpSocket.isClosed()) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void close() {
        udpSocket.close();
    }
}

2.3 适配原代码的关键修改

Java自带的com.sun.nio.sctpAPI无法直接获取原始SCTP报文,需要改用第三方SCTP库(比如jSCTP)来生成原始报文。修改原代码的sendBytes方法:

// 初始化UDP发送器(示例配置,根据实际场景修改)
SctpOverUdpSender udpSender = new SctpOverUdpSender(6004, "目标IP", 6005, 6002, 6002);

// 替换原SctpChannel.send逻辑:通过jSCTP生成原始SCTP报文后发送
// ByteBuffer sctpPayload = 生成的原始SCTP报文
udpSender.sendSctpPacket(sctpPayload);

3. 注意事项

  • NAT会话保持:两端定期发送UDP心跳包,避免NAT会话超时被销毁
  • 报文大小控制:UDP报文不能超过MTU(通常1500字节),需对SCTP报文分片后再封装
  • 库依赖:必须引入第三方SCTP库来获取原始报文,Java原生API无法满足需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 20:05:13