/* Copyright 2013 RobustNet Lab, University of Michigan. All Rights Reserved.
*
* 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.udpmeasurement;
import java.io.*;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
/**
* @author Hongyi Yao (hyyao@umich.edu)
* The main receiver thread for UDP Burst Server.
* The thread continually receives the packet from client. If the packet
* contains uplink data, it records the packet's information and send a
* response when the uplink is finished. Or if the packet is a downlink
* request, it generates another handler thread to send downlink burst.
* Otherwise it replies with a error message
*/
public class UDPReceiver implements Runnable {
public DatagramSocket socket;
private DatagramPacket receivedPacket;
private byte[] receivedBuffer;
private HashMap<ClientIdentifier, ClientRecord> clientMap;
public UDPReceiver(int port) throws MeasurementError {
try {
socket = new DatagramSocket(port);
} catch (SocketException e) {
throw new MeasurementError("Failed opening and binding socket!");
}
receivedBuffer = new byte[Config.BUFSIZE];
receivedPacket = new DatagramPacket(receivedBuffer, receivedBuffer.length);
clientMap = new HashMap<ClientIdentifier, ClientRecord>();
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
* Main receiving iteration
*/
@Override
public void run() {
System.out.println("Receiver thread is running...");
while ( true ) {
try {
// get client's request
socket.setSoTimeout(Config.GLOBAL_TIMEOUT);
socket.receive(receivedPacket);
ClientIdentifier clientId = new ClientIdentifier(
receivedPacket.getAddress(), receivedPacket.getPort());
Config.logmsg("Received message from " + clientId.toString());
// processing message
try {
MeasurementPacket packet = new MeasurementPacket(
clientId, receivedPacket.getData());
processPacket(packet);
} catch (MeasurementError e) {
Config.logmsg("Error processing message: " + e.getMessage());
}
} catch (IOException e) {
// Timeout, clean all unfinished record and send response to each client
try {
removeOldRecord();
} catch (MeasurementError e1) {
Config.logmsg("Error sending response when timeout: " + e.getMessage());
}
}
}
}
/**
* The thread continually receives the packet from client. If the packet
* contains uplink data, it records the packet's information and send a
* response when the uplink is finished. Or if the packet is a downlink
* request, it generates another handler thread to send downlink burst.
* Otherwise it replies with a error message
* @param packet received packet
* @throws MeasurementError
*/
private void processPacket(final MeasurementPacket packet)
throws MeasurementError {
if ( packet.type == Config.PKT_REQUEST ) {
// Create a new thread to burst udp packets
Config.logmsg("Receive packet request");
ClientRecord clientRecord = new ClientRecord();
clientRecord.seq = packet.seq;
clientRecord.burstCount = packet.burstCount;
clientRecord.packetSize = packet.packetSize;
clientRecord.udpInterval = packet.udpInterval;
// TODO(Hongyi): setup similar check in the client side
if ( clientRecord.burstCount <= 0 ) {
throw new MeasurementError("Burst count should be positive, not " +
clientRecord.burstCount);
}
if ( clientRecord.burstCount > Config.MAX_BURSTCOUNT ) {
throw new MeasurementError("Burst count should be not bigger than " +
Config.MAX_BURSTCOUNT + ", not " + clientRecord.burstCount);
}
if ( clientRecord.packetSize < Config.MIN_PACKETSIZE ) {
throw new MeasurementError("Request packet size " +
clientRecord.packetSize + " shorter than min packet size " +
Config.MIN_PACKETSIZE);
}
if ( clientRecord.packetSize > Config.MAX_PACKETSIZE ) {
throw new MeasurementError("Request packet size " +
clientRecord.packetSize + " longer than max packet size " +
Config.MAX_PACKETSIZE);
}
if ( clientRecord.udpInterval < 0 ) {
throw new MeasurementError("Request interval " +
clientRecord.udpInterval + " should be not negtive, not " +
clientRecord.udpInterval);
}
if ( clientRecord.udpInterval > Config.MAX_INTERVAL ) {
throw new MeasurementError("Request interval " +
clientRecord.udpInterval + " longer than max interval " +
Config.MAX_INTERVAL);
}
// Create a new thread for downlink burst. Otherwise the uplink burst
// at the same time may be blocked and lead to wrong delay estimation
RequestHandler respHandle = new RequestHandler(socket,
packet.clientId, clientRecord);
new Thread(respHandle).start();
}
else if ( packet.type == Config.PKT_DATA ) {
// Look up the client map to find the corresponding recorder
// , or create a new one. Then record the packet's content
// After received all the packets in a burst or timeout,
// send a request back
ClientRecord clientRecord;
if ( clientMap.containsKey(packet.clientId) ) {
clientRecord = clientMap.get(packet.clientId);
int seq = packet.seq;
// seq must stay the same for one burst
if ( seq == clientRecord.seq ) {
long timeNow = System.currentTimeMillis();
clientRecord.addPacketInfo(packet.packetNum,
timeNow - packet.timestamp, timeNow);
}
else {
Config.logmsg("client sent a different sequence number! old " +
clientRecord.seq + " => " + "new " + seq);
sendPacket(Config.PKT_ERROR, packet.clientId, null);
clientMap.remove(packet.clientId);
throw new MeasurementError( packet.clientId.toString() +
" send a new seq " + seq + " different from current seq " +
clientRecord.seq);
}
}
else { // Receive the first UDP packet from a new client
long timeNow = System.currentTimeMillis();
clientRecord = new ClientRecord();
clientRecord.burstCount = packet.burstCount;
clientRecord.packetSize = packet.packetSize;
clientRecord.seq = packet.seq;
clientRecord.addPacketInfo(packet.packetNum,
timeNow - packet.timestamp, timeNow);
clientRecord.timeoutChecker = new Thread(new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
* Check whether this client is timeout. If so, send result back
* and remove it from client map.
*/
@Override
public void run() {
long timeToSleep = Config.DEFAULT_TIMEOUT;
while ( true ) {
try {
Thread.sleep(timeToSleep);
} catch (InterruptedException e1) {
Config.logmsg(e1.getMessage());
}
ClientRecord clientRecord = clientMap.get(packet.clientId);
if ( clientRecord == null
|| clientRecord.packetCount == clientRecord.burstCount ) {
// UDP burst finished. No need to handle timeout
return;
}
timeToSleep = clientRecord.lastTimestamp + Config.DEFAULT_TIMEOUT
- System.currentTimeMillis();
if ( timeToSleep <= 0 ) {
Config.logmsg("Client " + packet.clientId.toString() + " timeouted");
try {
sendPacket(Config.PKT_RESPONSE, packet.clientId, clientRecord);
return;
} catch (MeasurementError e) {
Config.logmsg(e.getMessage());
return;
} finally {
clientMap.remove(packet.clientId);
}
}
}
}
});
clientRecord.timeoutChecker.start();
clientMap.put(packet.clientId, clientRecord);
}
Config.logmsg("Receive data packet s:" + clientRecord.seq + " b:" +
clientRecord.burstCount + " p:" + packet.packetNum);
if (clientRecord.packetCount == clientRecord.burstCount) {
try {
sendPacket(Config.PKT_RESPONSE, packet.clientId, clientRecord);
return;
} catch (MeasurementError e) {
throw e;
} finally {
clientMap.remove(packet.clientId);
}
}
}
else {
// Not data or request packet, send error packet back
Config.logmsg("Received malformed packet! Type " + packet.type);
sendPacket(Config.PKT_ERROR, packet.clientId, null);
}
}
/**
* Send packet according to the type and clientRecord
* @param type the type of the packet to be sent
* @param clientId the corresponding client identifier
* @param clientRecord the other information needed in creating packet
* @throws MeasurementError
*/
private void sendPacket(int type, ClientIdentifier clientId,
ClientRecord clientRecord) throws MeasurementError {
MeasurementPacket packet = new MeasurementPacket(clientId);
if ( type == Config.PKT_ERROR ) {
MeasurementPacket errorPacket = packet;
errorPacket.type = Config.PKT_ERROR;
errorPacket.packetSize = Config.MIN_PACKETSIZE;
}
else if ( type == Config.PKT_DATA ) {
MeasurementPacket dataPacket = packet;
dataPacket.type = Config.PKT_DATA;
dataPacket.burstCount = clientRecord.burstCount;
dataPacket.packetNum = clientRecord.packetReceived;
dataPacket.timestamp = System.currentTimeMillis();
dataPacket.packetSize = clientRecord.packetSize;
dataPacket.seq = clientRecord.seq;
}
else if ( type == Config.PKT_RESPONSE ) {
MeasurementPacket responsePacket = packet;
responsePacket.type = Config.PKT_RESPONSE;
responsePacket.burstCount = clientRecord.burstCount;
responsePacket.outOfOrderNum = clientRecord.calculateOutOfOrderNum();
// Store jitter in the field timestamp
responsePacket.timestamp = clientRecord.calculateJitter();
responsePacket.packetNum = clientRecord.packetCount;
responsePacket.packetSize = clientRecord.packetSize;
responsePacket.seq = clientRecord.seq;
}
byte[] sendBuffer = packet.getByteArray();
DatagramPacket sendPacket = new DatagramPacket(
sendBuffer, sendBuffer.length, clientId.addr, clientId.port);
try {
socket.send(sendPacket);
} catch (IOException e) {
throw new MeasurementError(
"Fail to send UDP packet to " + clientId.toString());
}
Config.logmsg("Sent response to " + clientId.toString() + " type:" + type
+ " b:" + packet.burstCount + " p:" + packet.packetNum + " out_of_order:"
+ packet.outOfOrderNum + " j:" + packet.timestamp + " s:"
+ packet.packetSize);
}
private void removeOldRecord() throws MeasurementError {
for(Map.Entry<ClientIdentifier, ClientRecord> entry : clientMap.entrySet()){
sendPacket(Config.PKT_RESPONSE, entry.getKey(), entry.getValue());
}
clientMap.clear();
}
}