/******************************************************************************* * Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.jfastnet.peers.javanet; import com.jfastnet.*; import com.jfastnet.messages.Message; import com.jfastnet.peers.CongestionControl; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; /** @author Klaus Pfeiffer - klaus@allpiper.com */ @Slf4j public class JavaNetPeer implements IPeer { private final Config config; private final State state; private CongestionControl<DatagramPacket> congestionControl; private DatagramSocket socket; private Thread receiveThread; public JavaNetPeer(Config config, State state) { this.config = config; this.state = state; } @Override public boolean start() { try { socket = new DatagramSocket(config.bindPort); socket.setSendBufferSize(config.socketSendBufferSize); socket.setReceiveBufferSize(config.socketReceiveBufferSize); congestionControl = new CongestionControl<>(new ConfigStateContainer(config, state), this::socketSend); receiveThread = new Thread(new MessageReceivingRunnable()); receiveThread.start(); } catch (Exception e) { log.error("Couldn't start server.", e); return false; } return true; } @Override public void stop() { try { if (socket != null) { socket.close(); } if (receiveThread != null) { receiveThread.join(3000); if (receiveThread.isAlive()) { log.error("Receiver thread should be destroyed by now."); } } } catch (Exception e) { log.error("Closing of socket failed.", e); } } @Override public boolean createPayload(Message message) { byte[] data = getByteArray(message); message.payload = data; return data != null; } @Override public boolean send(Message message) { if (message.payload == null) { log.error("Payload of message {} may not be null.", message.toString()); return false; } if (message.socketAddressRecipient == null) { log.error("Recipient of message {} may not be null.", message.toString()); return false; } byte[] payload = (byte[]) message.payload; if (config.trackData) { int frame = 0; config.netStats.getData().add( new NetStats.Line(true, message.getSenderId(), frame, System.currentTimeMillis(), message.getClass(), (payload).length)); } log.trace("Send message: {}", message); log.trace("Payload length: {}", payload.length); DatagramPacket datagramPacket = new DatagramPacket(payload, payload.length, message.socketAddressRecipient); congestionControl.send(message, datagramPacket); return true; } private void socketSend(DatagramPacket datagramPacket) { try { socket.send(datagramPacket); } catch (IOException e) { log.error("Couldn't send message.", e); } } public byte[] getByteArray(Message message) { byte[] data = config.serialiser.serialise(message); log.trace("Message size of {} is {}", message, data != null ? data.length : -1); return data; } public void receive(DatagramPacket packet) { Message message = config.serialiser.deserialise(packet.getData(), packet.getOffset(), packet.getLength()); if (message == null) { return; } log.trace("Received message: {}", message); if (config.debug.simulateLossOfPacket()) { // simulated N % loss rate log.warn("DEBUG: simulated loss of packet: {}", message); return; } trackData(message); message.socketAddressSender = new InetSocketAddress(packet.getAddress(), packet.getPort()); message.setConfig(config); message.setState(state); message.getFeatures().resolve(); message = message.beforeReceive(); // Let the controller receive the message. // Processors are called there. config.internalReceiver.receive(message); } private void trackData(Message message) { if (config.trackData) { int frame = 0; config.netStats.getData().add( new NetStats.Line(false, message.getSenderId(), frame, message.getTimestamp(), message.getClass(), ((byte[]) message.payload).length)); } } @Override public void process() { congestionControl.process(); } private class MessageReceivingRunnable implements Runnable { @Override public void run() { log.info("Start receiver thread {} for senderId {}", Thread.currentThread().getId(), config.senderId); byte[] receiveData = new byte[config.socketReceiveBufferSize]; while(true) { DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); try { if (socket.isClosed()) { log.info("Receiving socket closed."); break; } socket.receive(receivePacket); receive(receivePacket); } catch (Exception e) { if (!socket.isClosed()) { log.warn("Error receiving packet.", e); } else { log.warn("Error receiving packet. Socket is already closed!"); } } } log.info("Receiver thread {} ended", Thread.currentThread().getId()); } } }