package network; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import network.protocol.Packet; import utilities.Signal; public class Socket { private final DatagramSocket socket; private final List handlers; private final Thread readThread; private final PacketPool packetPool; public final Signal onClose = new Signal(); /** * Construct a UDP Socket to connect to a server * @throws SocketException */ public Socket() throws SocketException { this.socket = new DatagramSocket(); this.handlers = new ArrayList<>(); this.packetPool = new PacketPool(this); this.readThread = new Thread(this::readLoop); this.readThread.start(); } /** * Construct a UDP Socket to listen to connexion at the specified port * @param listeningPort the port to listen to * @throws SocketException */ public Socket(int listeningPort) throws SocketException { this.socket = new DatagramSocket(listeningPort); this.handlers = new ArrayList<>(); this.packetPool = new PacketPool(this); this.readThread = new Thread(this::readLoop); this.readThread.start(); } public void addHandler(PacketHandler handler) { this.handlers.add(handler); } /** * @return this.socket.getLocalPort() */ public int getLocalPort() { return this.socket.getLocalPort(); } // needs to be accessible by PacketPool /** * Send a packet to the specified address * @param packet the packet to send * @param address the address to send to * @throws IOException */ void sendPacket(ReliablePacket packet, InetSocketAddress address) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(stream); oos.writeObject(packet); oos.flush(); byte[] data = stream.toByteArray(); DatagramPacket dataPacket = new DatagramPacket(data, data.length, address.getAddress(), address.getPort()); this.socket.send(dataPacket); } /** * Try to recieve packets and send them to the PacketPool. * This method blocks until something has been read * @throws IOException * @throws ClassNotFoundException */ private void recievePacketReliable() throws IOException, ClassNotFoundException { byte[] buffer = new byte[65535]; DatagramPacket dataPacket = new DatagramPacket(buffer, buffer.length); socket.receive(dataPacket); InetSocketAddress address = new InetSocketAddress(dataPacket.getAddress(), dataPacket.getPort()); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(dataPacket.getData())); ReliablePacket packet = (ReliablePacket) ois.readObject(); this.packetPool.onPacketReceived(packet, address); } /** * Send a packet to the specified address * @param packet the packet to send * @param address the address to send to * @throws IOException */ public void sendPacket(Packet packet, InetSocketAddress address) throws IOException { this.packetPool.sendPacket(packet, address); } /** * Recieve packet in a reliable way (packets are in the right order). * This method is blocking * @throws IOException * @throws ClassNotFoundException */ private void recievePacket() throws IOException, ClassNotFoundException { this.recievePacketReliable(); var entry = this.packetPool.getNextPacket(); while (entry != null) { this.handlePacket(entry.getKey(), entry.getValue()); entry = this.packetPool.getNextPacket(); } } /** * Close the socket */ public void close() { this.socket.close(); this.packetPool.close(); this.readThread.interrupt(); this.onClose.emit(); } /** * Dispatch the packet * @param packet the packet to dispatch * @param address the incoming address */ void handlePacket(Packet packet, InetSocketAddress address) { for (PacketHandler handler : this.handlers) { handler.handlePacket(packet, address); } } /** * Loop to read all packets */ private void readLoop() { try { while (!Thread.interrupted()) { recievePacket(); } } catch (IOException | ClassNotFoundException e) { // e.printStackTrace(); } } }