PlainDatagramSocketImpl (IOException: операция не разрешена)

У меня возникла следующая проблема с DatagramSockets:

java.io.IOException: Operation is not permitted.
    at java.net.PlainDatagramSocketImpl.send(Native Method)
    at java.net.DatagramSocket.send(DatagramSocket.java:693)

Исключение возникает случайным образом, и я не вижу закономерности. Это усложняет мне отладку этого.

Тем не менее, я подозреваю, что это происходит чаще, когда я отправляю много данных.

У меня есть несколько потоков, отправляющих через этот сокет, но это не должно быть проблемой, так как я читал, что сокеты Java будут потокобезопасными.

Может ли кто-нибудь сказать мне, когда и при каких условиях может быть выбрано такое исключение?

Вот мой основной сетевой код:

package de.oompf.netwrk;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

class Server implements Runnable {

    private final EventBus bus;
    private final Thread serverThread;
    private final DatagramSocket socket;

    Server(EventBus bus) throws SocketException {
    this.bus = bus;
    serverThread = new Thread(this, "Server Thread");
    socket = getBoundSocket();
    socket.setSoTimeout(2400);
    }

    private static DatagramSocket getBoundSocket() throws SocketException {
    for (int port : Configuration.getPortList()) {
        try {
            return new DatagramSocket(port);
        } catch (SocketException e) {
        }
    }
    return new DatagramSocket(0);
    }

    int getPort() {
    return socket.getLocalPort();
    }

    void start() {
    bus.subscribe(this);
    serverThread.start();
    }

    void stop() {
    serverThread.interrupt();
    socket.close();
    }

    @Override
    public void run() {
    DatagramPacket p = new DatagramPacket(new byte[4096], 4096);
    while (!serverThread.isInterrupted()) {
        try {
            socket.receive(p);
            bus.publish(new IncomingPacket(p.getData(), p.getLength(), p.getAddress(), p.getPort()));
        } catch (IOException e) {
            if (socket.isClosed()) {
                break;
            }
        }
    }
    }

    void send(OutgoingPacket p) {
    try {
        if (p.getData()[0] == 0x03) {
        }
        socket.send(new DatagramPacket(p.getData(), p.getData().length, p.getSocketAddress()));
    } catch (IOException e) {
        if (socket.isClosed()) {
            serverThread.interrupt();
        } else {
            e.printStackTrace();
        }
    }
    }
}

За этим стоит множество классов. Я просто опубликую несколько строк, где заканчивается моя трассировка стека.

private void handleBootstrapRequest(IncomingPacket p) {
if (p.getLength() == 21) {
    byte[] requestNodeBytes = new byte[20];
    System.arraycopy(p.getData(), 1, requestNodeBytes, 0, 20);
    try {
        Node requestNode = new Node(requestNodeBytes);
        if (needsRelay(requestNode)) {
            byte[] forwardPacket = new byte[47];
            forwardPacket[0] = 0x07;
            System.arraycopy(requestNode.getBytes(), 0, forwardPacket, 1, 20);
            System.arraycopy(me.getBytes(), 0, forwardPacket, 21, 20);
            System.arraycopy(p.getAddress().getAddress(), 0, forwardPacket, 41, 4);
            System.arraycopy(ByteBuffer.allocate(2).putShort((short) (p.getPort() - Short.MAX_VALUE)).array(), 0, forwardPacket, 45, 2);
            /* Will send a packet (doing some routing first) */
            relay(forwardPacket, requestNode);
        } else {
            List<Neighbour> references = routing.getClosest(requestNode, 7);
            byte[] answerPacket = new byte[2 + references.size() * 26];
            answerPacket[0] = 0x06;
            answerPacket[1] = (byte) references.size();
            for (int i = 0; i < references.size(); i++) {
                Neighbour n = references.get(i);
                System.arraycopy(n.getBytes(), 0, answerPacket, 2 + i * 26, 20);
                System.arraycopy(n.getAddress().getAddress().getAddress(), 0, answerPacket, 22 + i * 26, 4);
                System.arraycopy(ByteBuffer.allocate(2).putShort((short) (n.getAddress().getPort() - Short.MAX_VALUE)).array(), 0, answerPacket, 26 + i * 26, 2);
            }
            /* That's where my stack trace ends and where the packet gets onto an event bus (100% working properly) */
            bus.publish(new OutgoingPacket(answerPacket, p.getSocketAddress()));
        }

        byte[] quickResponse = new byte[21];
        quickResponse[0] = 0x02;
        System.arraycopy(me.getBytes(), 0, quickResponse, 1, 20);

        /* see last comment */
        bus.publish(new OutgoingPacket(quickResponse, p.getSocketAddress()));
    } catch (InvalidNodeException e) {
    }
}
}

Как я уже сказал, возможно, что несколько исходящих пакетов находятся на шине событий, когда несколько обработчиков пакетов вызываются моими пулами обработчиков пакетов.


person Marcel    schedule 22.01.2015    source источник
comment
Что @EJP, вероятно, означает: опубликуйте код, который показывает нам, как вы отправляете данные. Может быть, также что-то, что показывает нам, как вы создаете/координируете потоки для отправки данных.   -  person Chris    schedule 23.01.2015
comment
Вам нужно воссоздавать DatagramPacket, или, по крайней мере, сбрасывать его длину каждый раз в цикле приема. В противном случае он может продолжать уменьшаться до размера наименьшей полученной до сих пор дейтаграммы. @ Крис Спасибо за бесполезное разъяснение.   -  person user207421    schedule 23.01.2015


Ответы (2)


Просто столкнулся с той же проблемой несколько лет спустя.

В моем случае сбой произошел из-за заполнения conntrack, что не относится к Java и о нем сообщалось в другом месте (1, 2). Один простой способ обойти проблему — сделать так, чтобы весь udp-трафик обходил conntrack:

iptables -I PREROUTING -t raw -p udp -j NOTRACK
iptables -I OUTPUT -t raw -p udp -j NOTRACK

Как вы можете видеть ниже, необработанные цепочки PREROUTING и OUTPUT находятся прямо перед conntrack:

поток сетевых пакетов Linux

Если вы не хотите полностью обходить conntrack, вы можете вместо этого настроить его конфигурацию в sysctl, чтобы разрешить больше подключений.

person Malt    schedule 28.06.2020

Проведя дополнительные исследования, я обнаружил, что моя проблема возникает, когда несколько потоков одновременно обращаются к DatagramSocket.send.

Чтобы решить эту проблему, вам необходимо синхронизировать метод отправки.

Попробуйте этот код. Скорее всего, будет воспроизведено java.io.IOException: операция не разрешена.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;

public class Test {

    public static void main(String[] args) {
        new Test().now();
    }

    public void now() {
        try {
            DatagramSocket sock = new DatagramSocket(0);
            for (int i = 0; i < 32; i++) {
                new Thread(new Sender(sock)).start();
            }
        } catch (SocketException e) {
        }
    }

    public class Sender implements Runnable {

        private final DatagramSocket sock;

        public Sender(DatagramSocket sock) {
            this.sock = sock;
        }

        @Override
        public void run() {
            for(int i=0; i<12; i++) {
                try {
                    sock.send(new DatagramPacket(new byte[20], 20, new InetSocketAddress(InetAddress.getByName("example.com"), 80)));
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
person Marcel    schedule 22.01.2015
comment
Определить «сопоставление друг с другом». - person user207421; 23.01.2015