I am working on a device which would work to measure some readings through sensors. Device is operated by an Android app. I have to take readings from TCP layer. This is the code to send data on TCP
TcpClient.java
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
* Created by shahbaz on 25/4/17.
*/
public class TcpClient {
public static final String SERVER_IP = "192.168.1.76"; //server IP address
public static final int SERVER_PORT = 1800;
// message to send to the server
private String mServerMessage;
// sends message received notifications
private OnMessageReceived mMessageListener = null;
// while this is true, the server will continue running
private boolean mRun = false;
// used to send messages
private PrintWriter mBufferOut;
// used to read messages from the server
private BufferedReader mBufferIn;
/**
* Constructor of the class. OnMessagedReceived listens for the messages received from server
*/
public TcpClient(OnMessageReceived listener) {
mMessageListener = listener;
}
/**
* Sends the message entered by client to the server
*
* #param message text entered by client
*/
public void sendMessage(String message) {
if (mBufferOut != null && !mBufferOut.checkError()) {
mBufferOut.println(message);
mBufferOut.flush();
}
}
/**
* Close the connection and release the members
*/
public void stopClient() {
mRun = false;
if (mBufferOut != null) {
mBufferOut.flush();
mBufferOut.close();
}
mMessageListener = null;
mBufferIn = null;
mBufferOut = null;
mServerMessage = null;
}
public void run() {
mRun = true;
try {
//here you must put your computer's IP address.
InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
Log.e("TCP Client", "C: Connecting...");
//create a socket to make the connection with the server
Socket socket = new Socket(serverAddr, SERVER_PORT);
try {
//sends the message to the server
mBufferOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
//receives the message which the server sends back
mBufferIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//in this while the client listens for the messages sent by the server
while (mRun) {
mServerMessage = mBufferIn.readLine();
if (mServerMessage != null && mMessageListener != null) {
//call the method messageReceived from MyActivity class
mMessageListener.messageReceived(mServerMessage);
}
}
Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + mServerMessage + "'");
} catch (Exception e) {
Log.e("TCP", "S: Error", e);
} finally {
//the socket must be closed. It is not possible to reconnect to this socket
// after it is closed, which means a new socket instance has to be created.
socket.close();
}
} catch (Exception e) {
Log.e("TCP", "C: Error", e);
}
}
//Declare the interface. The method messageReceived(String message) will must be implemented in the MyActivity
//class at on asynckTask doInBackground
public interface OnMessageReceived {
public void messageReceived(String message);
}
}
Packet Structure
Packet format contains,
While communicating with the device on TCP, boundaries between packets are not identified, in this case if the packets are out of sequence or if any of the packets is missed one can identify a new packet using ‘header start’.
So, first 2 bytes in the packet represent the start of the packet.
Header start: Two-byte field that indicates the start of every packet. 0x55AA is a 2 bytes number used as header start.
Protocol version: One-byte field to specify the version of the protocol in use. Version specified in the payload will decide the payload structure. At any given moment a device will support single protocol version. Present protocol version is ‘1’.
DSN: Sequence number is 1-byte field which will identify the packet uniquely. Requester of the packet will have to fill this field in request payload; responder has to fill the same unique identifier in the response payload.
Request Id: One-byte field specifies the command id. The parsing of the payload will be done on the basis of the command id. In case of request payload this field will be non zero and in case of response it will be zero.
Payload length: Two-byte field specifies the length of the payload in bytes. It specifies the number of bytes followed payload length field. In the payload length, header length and CRC is not included. Currently, Max payload length supported by gateway device is 512 (bytes).
CRC: 1 byte field which will be calculated by XORing all the bytes and add the XOR count of 0.
And it is working. But according to docs I have to send packet using binary communication protocol. Including header start, payload data, etc. How can I send these params in packet structure? How can I create packet?
Any help is appreciated.
The main mistake was that I was not thinking much about size of primitive data types.
byte = 1 byte
short = 2 bytes
int = 4 bytes
long = 8 bytes
float = 4 bytes
double = 8 bytes
char = 2 byte
After referencing the size of primitive datatypes I realised we should track the size and index of packet because we are dealing with byte array.
TcpPacket.java
public class TcpPacket {
private static int header_start = 0x55AA;
private static int protocol_version = 1;
private PacketUtils packetUtils = new PacketUtils();
public byte[] getHandshakePacket()
{
int request_id = 1;
byte[] header_data = packetUtils.ItoBA2(header_start);
byte[] payload_data = packetUtils.ItoBA4(packetUtils.getDateTime());
byte[] payload_length = packetUtils.ItoBA2(4);
byte[] a_data = new byte[]{header_data[0], header_data[1], (byte) protocol_version, packetUtils.getDSN(), (byte) request_id, payload_length[0], payload_length[1],
payload_data[0], payload_data[1], payload_data[2], payload_data[3]};
byte[] b_data = new byte[]{ packetUtils.getCRC(a_data)};
byte[] packet_data = packetUtils.concatBytes(a_data,b_data);
return packet_data;
}
}
PacketUtils.java
public class PacketUtils {
public byte[] ItoBA4(int value) { // integer to bytes function (return byte array of 4 bytes)
return new byte[] {
(byte)(value >>> 24),
(byte)(value >>> 16),
(byte)(value >>> 8),
(byte)value};
}
public byte[] ItoBA2(int value) { // integer to bytes function (return byte array of 2 bytes)
return new byte[] {
(byte)(value >>> 8),
(byte)value};
}
public byte getDSN() // return one byte random number
{
char[] chars = "1234567890".toCharArray();
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 1; i++) {
char c = chars[random.nextInt(chars.length)];
sb.append(c);
}
byte output = Byte.valueOf(sb.toString());
return output;
}
public byte getCRC(byte[] packet) // required CRC function (return byte)
{
try
{
if (packet == null)
{
//Logger.Error("empty packet received");
return (byte)0;
}
byte XORCheckSum = 0;
byte zeroCount = 0;
byte FFCount = 0;
for (int i = 0; i < packet.length; i++)
{
XORCheckSum ^= packet[i];
if (packet[i] == (byte) 0)
{
zeroCount++;
continue;
}
if (packet[i] == (byte)255)
{
FFCount++;
continue;
}
}
XORCheckSum ^= zeroCount;
XORCheckSum ^= FFCount;
return XORCheckSum;
}
catch (Exception ex)
{
//Logger.Error(ex);
return (byte)0;
}
}
byte[] concatBytes(byte[]...arrays) // concatenate byte arrays
{
// Determine the length of the result array
int totalLength = 0;
for (int i = 0; i < arrays.length; i++)
{
totalLength += arrays[i].length;
}
// create the result array
byte[] result = new byte[totalLength];
// copy the source arrays into the result array
int currentIndex = 0;
for (int i = 0; i < arrays.length; i++)
{
System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length);
currentIndex += arrays[i].length;
}
return result;
}
public int getDateTime()
{
int dateInSec = (int) (System.currentTimeMillis() / 1000);
return dateInSec;
}
}
Related
I have a client-server program trying to send a jpg file.
I'm trying to add an ACKNOWLEDGE byte and the DATA's BlockNumber byte to a UDP packet. I'm having trouble in this part of the code (this section of the code is just the DATA Block handling code from the Client program):
if(pType == TftpUtil.DATA){ //If byte 0 = 2 meaning it's a DATA block.
private int blockNumber = 1;
byte[] bn = intToBytes(blockNumber);
ByteBuffer buff = ByteBuffer.allocate(8); // For 2 ints, an int is 4 bytes long
buff.put(TftpUtil.ACK); // This is an "ACKNOWLEDGE byte", at position zero.
buff.put(bn); // This is supposed to be a byte containing the integer "blockNumber" meaning, what DATA block in the series it is.
buff.rewind(); // Not sure what this does. What does this do? Saw it in the code I copied.
DatagramPacket dataPacket = new DatagramPacket(buff.array(), buff.limit(), echoServerIP, 69); //Create datagramPacket
System.out.println("TftpClient sending a ACK on port " + clientSocket.getLocalPort());
clientSocket.send(dataPacket);
blockNumber++;
}
The above code doesn't work with the server I have, and the server was made by the Teacher. I've checked the server code, looks great.
For some reason it hangs on the part where it's supposed to send an ACKNOWLEDGE byte that contains the integer, "3" meaning "Acknowledged". And it's supposed to contain that ACK byte in position zero of the buff.array(). But the server says there's no acknowledge byte being received.
TftpUtil starts off like this:
public class TftpUtil {
//read request packet
public static final byte RRQ = 1;
//data packet
public static final byte DATA = 2;
//ack packet
public static final byte ACK = 3;
//error packet
public static final byte ERROR = 4;
//the maximum number of resent
public static final int MAX_RESEND = 5;
//data buffer size: 512
public static int DATA_BUFFER_SIZE = 512;
//packet buffer size: 2 bytes header + 512 bytes file data;
public static int PACKET_BUFFER_SIZE = DATA_BUFFER_SIZE + 2;
}
What the server's doing is it's sending a .jpg file, in DATA blocks of 512 bytes. The server picks up that there's a connection made, and tries to send the file. It sends the first DATA block, which seems to come through because when I check the folder, there is an asd.jpg file of 512 bytes. But when the client goes to send the ACK byte back, the server says it's incorrect and times out.
Can you see what's wrong with the code I've pasted or should I paste more code?
Thanks.
UPDATE full server code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class server_v004 {
private InetAddress serverAddress;
private int serverPort;
private DatagramSocket serverSocket;
private static final ExecutorService exec = Executors.newFixedThreadPool(10);
public void startServer()
{
try {
String serverIpAddress = "192.168.1.32";
InetAddress echoServerIP = InetAddress.getByName(serverIpAddress);
serverSocket = new DatagramSocket(serverPort, echoServerIP);
System.out.println("TftpServer on port " + serverSocket.getLocalPort());
while(true) {
byte[] buf = new byte[1472];
DatagramPacket p = new DatagramPacket(buf, 1472);
System.out.println("waiting for connection ....");
serverSocket.receive(p);
server_v004ServerWorker worker = new server_v004ServerWorker(p);
exec.execute(worker);
}
}
catch(Exception e) {
System.err.println("Exception: " + e);
}
serverSocket.close();
return;
}
public static void main(String[] args) {
server_v004 tftpServer = new server_v004();
//tftpServer.checkArgs(args);
try{
tftpServer.parseArgsAndInit(args);
tftpServer.startServer();
}
catch(Exception e){
e.printStackTrace();
}
}
private void checkArgs(String[] args){
if(args.length <0) { //<1 //<2
System.out.println("Usage: TftpServer"); //server_port server_ip
System.exit(0);
}
}
private void parseArgsAndInit(String[] args) throws Exception{
//serverAddress = InetAddress.getByName(args[0]);
serverPort = 12345; //Integer.parseInt(args[1]);
}
}
class server_v004ServerWorker implements Runnable
{
//use to store RRQ request
private DatagramPacket req;
//client address who sent the RRQ request
private SocketAddress clientAddress;
//socket used to send the file data packets to client
private DatagramSocket sendfileSocket;
//byte buffer to store the file data
private byte[] dataBuffer = new byte[TftpUtil.DATA_BUFFER_SIZE];
//the first block sequence
private byte currentBlockSeq = 1;
//use to retrieve the ack packet, only two bytes long
private DatagramPacket ackDP = new DatagramPacket(new byte[2], 2);
private int TIME_OUT = 1000; //1 second
public void run(){
try{
sendfileSocket = new DatagramSocket(69);
System.out.println("TftpServer sending a file on port " + sendfileSocket.getLocalPort());
byte pType = TftpUtil.checkPacketType(req);
clientAddress = req.getSocketAddress();
//checking if the first packet from client is a RRQ packet
if(pType == TftpUtil.RRQ){
String filename = getFileName(req);
System.out.println("Requested file name:" + filename);
//if the file doesn't exist, send ERROR packet and close socket
if(!(new File(filename)).exists()) {
DatagramPacket errorDP = TftpUtil.packErrorPacket(filename);
errorDP.setSocketAddress(clientAddress);
sendfileSocket.send(errorDP);
}
else{
//the file does exist, send file
sendfile(filename);
}
}// end if
}catch(Exception e){
e.printStackTrace();
}
sendfileSocket.close();
return;
}
private void sendfile(String filename) throws Exception{
FileInputStream fileInput = new FileInputStream(filename);
while(true){
int rec = fileInput.read(dataBuffer);
//the file size is a multiple of 512, send empty packet
if(rec == -1){
sendDataPacket(new byte[0],0);
System.out.println("The last packet [0 byte in size]:#"+currentBlockSeq);
break;
}
//send a file data packet
boolean successed = sendDataPacket(dataBuffer,rec);
//tried five times
if (!successed) {
System.out.println("Tried five times, give up");
System.exit(0);
}
// the last packet (the file size if not a multiple of 512)
if (rec < 512 && rec > 0 ) {
System.out.println("The last packet ["+rec+" bytes in size]:#"+currentBlockSeq);
break;
}
currentBlockSeq++;
}//while
fileInput.close();
}
//
private boolean sendDataPacket(byte[] databuffer,int length) throws Exception{
int resendCount = 0;
DatagramPacket dataPacket = packFileDataPacket(databuffer,length);
//try five times
while(resendCount < TftpUtil.MAX_RESEND){
try{
sendfileSocket.send(dataPacket);
sendfileSocket.setSoTimeout(TIME_OUT);
System.out.println("sent data block #"+currentBlockSeq+", waiting for ack #" + currentBlockSeq);
//ack arrives
sendfileSocket.receive(ackDP);
byte ackedBlockseq = TftpUtil.extractACKNumber(ackDP);
System.out.println("received ack #" + ackedBlockseq);
if(ackedBlockseq != currentBlockSeq) {
//the acked block seq is not the seq of block sent
//ignore this ack and resend
resendCount++;
continue;
}
//this data packet has been acked, return
return true;
}//end of try
catch(SocketTimeoutException ste){
resendCount++;
System.out.println("timeout #" + resendCount );
}
}//end of while
return false;
}
private DatagramPacket packFileDataPacket(byte[] dataBuffer, int length){
int packetLength = 2 + length;//type (1) + block seq (1) + data length
ByteBuffer byteBuffer = ByteBuffer.allocate(packetLength);
byteBuffer.put(TftpUtil.DATA);//type
byteBuffer.put(currentBlockSeq);//block seq
byteBuffer.put(dataBuffer,0,length);//data
DatagramPacket dataPacket = new DatagramPacket(byteBuffer.array(), packetLength);
dataPacket.setSocketAddress(clientAddress);
return dataPacket;
}
private String getFileName(DatagramPacket dataDP){
byte[] data = dataDP.getData();
int dataLength = dataDP.getLength();
ByteBuffer byteBuffer = ByteBuffer.allocate(dataLength-1);
//remove the packet type (RRQ)
byteBuffer.put(data,1,dataLength-1);
return new String(byteBuffer.array());
}
public server_v004ServerWorker(DatagramPacket req)
{
this.req = req;
}
}
and full TftpUtil code:
import java.net.DatagramPacket;
import java.nio.ByteBuffer;
public class TftpUtil {
//read request packet
public static final byte RRQ = 1;
//data packet
public static final byte DATA = 2;
//ack packet
public static final byte ACK = 3;
//error packet
public static final byte ERROR = 4;
//the maximum number of resent
public static final int MAX_RESEND = 5;
//data buffer size: 512
public static int DATA_BUFFER_SIZE = 512;
//packet buffer size: 2 bytes header + 512 bytes file data;
public static int PACKET_BUFFER_SIZE = DATA_BUFFER_SIZE + 2;
//return the type (RRQ, DATA, ACK or ERROR) of a packet
public static byte checkPacketType(DatagramPacket dataDP){
byte[] payload = dataDP.getData();
return payload[0];
}
//return a RRQ packet
public static DatagramPacket packRRQDatagramPacket(byte[] filename) throws Exception{
return packDatagramPacket(RRQ, filename);
}
//return a "file not found" error packet
public static DatagramPacket packErrorPacket(String filename) throws Exception{
String errorMessage = filename + " not found";
return packDatagramPacket(ERROR, errorMessage.getBytes());
}
/*
* utility method that wrap a packet type, data into a DatagramPacket
*/
private static DatagramPacket packDatagramPacket(byte type, byte[] payload) throws Exception{
int dataLength = 1 + payload.length;
ByteBuffer byteBuffer = ByteBuffer.allocate(dataLength);
byteBuffer.put(type);
byteBuffer.put(payload);
return new DatagramPacket(byteBuffer.array(), dataLength);
}
//return the ack number of a ACK packet
public static byte extractACKNumber(DatagramPacket ackDP){
byte[] payload = ackDP.getData();
return payload[1];
}
//print the string content of a ERROR packet
public static void printErrorString(DatagramPacket p){
byte[] data = p.getData();
int dataLength = p.getLength();
ByteBuffer byteBuffer = ByteBuffer.allocate(dataLength-1);
//ignore the packet type
byteBuffer.put(data,1, dataLength-1);
System.out.print(new String(byteBuffer.array()));
}
//return the block sequence of a data packet
public static byte extractBlockSeq(DatagramPacket dataDP){
byte[] payload = dataDP.getData();
if(payload.length <=1) return -1; //-1: no block sequence in data
int type = payload[0];
if(type == DATA){
return payload[1];
}
return -1; //-1: not a data packet
}
}
I tried to get MOTD of the remote server, but I can't get colors. When MOTD is colored, the plugin isn't working.
I know why, but I don't know how to resolve it.
public PingServer(String host, int port) {
this.host = host;
this.port = port;
try {
socket.connect(new InetSocketAddress(host, port));
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write(0xFE);
int b;
StringBuffer str = new StringBuffer();
while ((b = in.read()) != -1) {
if (b != 0 && b > 16 && b != 255 && b != 23 && b != 24) {
str.append((char) b);
}
}
data = str.toString().split("§");
data[0] = data[0].substring(1, data[0].length());
} catch (IOException e) {
e.printStackTrace();
}
}
According to the specification, the plugin will get response like this: MOTD§ONLINEPLAYERS§MAXPLAYERS, which should be split on § to get the different portions. However, § is also used for chat messages, and I'm not sure how to differentiate between the two. How can I work around this?
You're currently using the legacy server list ping, designed for beta 1.8 to 1.3. That one is triggered via sending just FE to the server. While current servers still support this ping, it's very old and has several flaws (including the one you found).
You should instead perform the current ping. While this is slightly more complicated, you don't need to implement much of the protocol to actually perform it.
There's only one complicated portion of the protocol you need to know about: VarInts. These are somewhat complicated because they take a varying number of bytes depending on the value. And as such, you have a packet length that can be somewhat hard to calculate.
/** See http://wiki.vg/Protocol_version_numbers. 47 = 1.8.x */
private static final int PROTOCOL_VERSION_NUMBER = 47;
private static final int STATUS_PROTOCOL = 1;
private static final JsonParser PARSER = new JsonParser();
/** Pings a server, returning the MOTD */
public static String pingServer(String host, int port) {
this.host = host;
this.port = port;
try {
socket.connect(new InetSocketAddress(host, port));
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
byte[] hostBytes = host.getBytes("UTF-8");
int handshakeLength =
varIntLength(0) + // Packet ID
varIntLength(PROTOCOL_VERSION_NUMBER) + // Protocol version number
varIntLength(hostBytes.length) + hostBytes.length + // Host
2 + // Port
varIntLength(STATUS_PROTOCOL); // Next state
writeVarInt(handshakeLength, out);
writeVarInt(0, out); // Handshake packet
writeVarInt(PROTOCOL_VERSION_NUMBER, out);
writeVarInt(hostBytes.length, out);
out.write(hostBytes);
out.write((port & 0xFF00) >> 8);
out.write(port & 0xFF);
writeVarInt(STATUS_PROTOCOL, out);
writeVarInt(varIntLength(0));
writeVarInt(0); // Request packet (has no payload)
int packetLength = readVarInt(in);
int payloadLength = readVarInt(in);
byte[] payloadBytes = new int[payloadLength];
int readLength = in.read(payloadBytes);
if (readLength < payloadLength) {
throw new RuntimeException("Unexpected end of stream");
}
String payload = new String(payloadBytes, "UTF-8");
// Now you need to parse the JSON; this is using GSON
// See https://github.com/google/gson
// and http://www.javadoc.io/doc/com.google.code.gson/gson/2.8.0
JsonObject element = (JsonObject) PARSER.parse(payload);
JsonElement description = element.get("description");
// This is a naive implementation; it assumes a specific format for the description
// rather than parsing the entire chat format. But it works for the way the
// notchian server impmlements the ping.
String motd = ((JsonObject) description).get("text").getAsString();
return motd;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static int varIntLength(int value) {
int length = 0;
do {
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
length++;
} while (value != 0);
}
public static void writeVarInt(int value, OutputStream out) {
do {
byte temp = (byte)(value & 0b01111111);
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
if (value != 0) {
temp |= 0b10000000;
}
out.write(temp);
} while (value != 0);
}
public static int readVarInt(InputStream in) {
int numRead = 0;
int result = 0;
int read;
do {
read = in.read();
if (read < 0) {
throw new RuntimeException("Unexpected end of stream");
}
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 5) {
throw new RuntimeException("VarInt is too big");
}
} while ((read & 0b10000000) != 0);
return result;
}
The current ping does use JSON, which means you need to use GSON. Also, this implementation makes some assumptions about the chat format; this implementation could break on custom servers that implement chat more completely, but it'll work for servers that embed § into the motd instead of using the more complete chat system (this includes the Notchian server implementation).
If you need to use the legacy ping, you can assume that the 2nd-to-last § marks the end of the MOTD (rather than the 1st §). Something like this:
String legacyPingResult = str.toString();
String[] data = new String[3];
int splitPoint2 = legacyPingResult.lastIndexOf('§');
int splitPoint1 = legacyPingResult.lastIndexOf('§', splitPoint2 - 1);
data[0] = legacyPingResult.substring(0, splitPoint1);
data[1] = legacyPingResult.substring(splitPoint1 + 1, splitPoint2);
data[2] = legacyPingResult.substring(splitPoint2 + 1);
However, I still don't recommend using the legacy ping.
I'm new to java network programming.
I try to send more than one instance of an object named Packet over UDP.
Sender: changes String to charArray, then sends each char in an object.
Receiver: receives the Datagrampacket and readObject then prints it out.
Sender is ok, but the receiver just takes the first instance of the object, and prints it as many times as the length of the charArray. Here is my code:
Receiver
public class Receiver {
public static void main(String args[]) throws Exception
{
DatagramSocket rcvSocket;
DatagramPacket rcvPacket;
ByteArrayInputStream bis;
ObjectInputStream ois;
byte[] rcvData;
Packet SnWPacket;
rcvSocket = new DatagramSocket(7777);
rcvData = new byte[1024];
rcvPacket = new DatagramPacket(rcvData, rcvData.length);
while(true)
{
rcvSocket.receive(rcvPacket);
byte[] in = rcvPacket.getData();
bis = new ByteArrayInputStream(in);
ois = new ObjectInputStream(bis);
SnWPacket = (Packet) ois.readObject();
System.out.println("RECIEVED: " + SnWPacket);
ois.close();
bis.close();
}
}
}
Sender
public class Sender {
public static void main(String args[]) throws Exception
{
DatagramPacket sndPacket;
DatagramSocket sndSocket;
ByteArrayOutputStream bos;
ObjectOutputStream oos;
InetAddress addr;
Scanner sc;
char[] charArray;
int seqNo = 0;
byte sndData[];
Packet SnWPacket;
addr = InetAddress.getByName("localhost");
sndSocket = new DatagramSocket();
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
sc = new Scanner(System.in);
System.out.println("Input: ");
String in = sc.nextLine();
charArray = in.toCharArray();
for (int i = 0; i < charArray.length; i++) {
SnWPacket = new Packet("data", seqNo, charArray[i]);
oos.writeObject(SnWPacket);
oos.flush();
sndData = bos.toByteArray();
bos.flush();
sndPacket = new DatagramPacket(sndData, sndData.length, addr, 7777);
sndSocket.send(sndPacket);
System.out.println("SENT: " + SnWPacket);
seqNo += 1;
}
oos.close();
bos.close();
sndSocket.close();
}
}
Packet
public class Packet implements Serializable{
private String type;
private int seqNo;
private char data;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getSeqNo() {
return seqNo;
}
public void setSeqNo(int seqNo) {
this.seqNo = seqNo;
}
public char getData() {
return data;
}
public void setData(char data) {
this.data = data;
}
public Packet(String type, int seqNo, char data)
{
this.type = type;
this.seqNo = seqNo;
this.data = data;
}
public Packet()
{
this.type = "";
this.seqNo = 0;
this.data = ' ';
}
#Override
public String toString() {
return "Packet: \tType: " + this.type + "\tseqNo: " + this.seqNo + "\tdata: " + this.data;
}
}
Assume that I send: "abc"
Sender:
SENT: Packet: Type: data seqNo: 0 data: a
SENT: Packet: Type: data seqNo: 1 data: b
SENT: Packet: Type: data seqNo: 2 data: c
Receiver:
RECIEVED: Packet: Type: data seqNo: 0 data: a
RECIEVED: Packet: Type: data seqNo: 0 data: a
RECIEVED: Packet: Type: data seqNo: 0 data: a
Problem is that receiver prints three "a". Please help me figure it out. Thanks!
To debug this sort of thing, it helps to use tcpdump or similar to track what is actually going on the network:
$ tcpdump -i lo port 7777
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
19:53:20.818460 IP zoo.59305 > zoo.7777: UDP, length 83
19:53:20.822292 IP zoo.59305 > zoo.7777: UDP, length 100
19:53:20.822410 IP zoo.59305 > zoo.7777: UDP, length 117
This tells you you're sending the 3 packets you expect, but each packet gets 17 bytes larger than the previous (instead of all being the same size). Examining your sending code carefully explains this -- you use a single ObjectOutputStream to construct the packets, and you don't reset it between Packet objects, so the first packet sent contains the first Packet object, while the second contains TWO packet objects (a copy of the first one, and then the scond one), and the third one contains all three.
On the receiver side, you only decode the first Packet object in the recieved packet and print it, so you print the first one three times...
Im having a problem that i can't solve by my own. I think my approach with splitting and adding to a array list and reassembling the message pieces in the end works great if (1) the MSG > BUFFER & message rate is 1msg/second. But the problems appear when i send more than 1 message/second and i have to split a large/small message. Yes the approach may be inefficient in the long run, but this is an assignment so i just want to get it working as i wanted and i'm fine by that.
I am pretty sure the problem is that it of course send every message pieces rate times. And the output is like this in my console:
--------------------------------
| UDP Echo Client
| Configuration:
| server name: localhost
| port: 4950
| buffer: 8
| rate: 5
| message size: 15
--------------------------------
Original: [HelloHe, lloHell, o]
Received: [HelloHe]
MESSAGE IS NOT EQUAL!
Received: [HelloHe, HelloHe]
MESSAGE IS NOT EQUAL!
Received: [HelloHe, HelloHe, HelloHe]
MESSAGE IS NOT EQUAL!
Can anyone please try to help me ? What is the best way to fix this ?
UDP Client:
import java.io.IOException;
import java.net.*;
import java.util.*;
/*
UDP Echo client. Sends a echo message of a size to the server and gets it back.
It checks so that the message wasn't lost or anything has happened to it.
by jv222dp
Rate works perfectly when MSG.length <= MY_BUFFER.
When the BUFFER is smaller then the MSG it works great if rate is 1
*/
public class UDPEchoClient {
private static final String MSG = "HelloHelloHello";
private static int MY_PORT;
private static int RATE;
private static int MY_BUFFER;
private static String HOST_NAME;
private static byte[] buf;
private static int packages;
private static int chars;
private static List<String> originalMsg;
private static List<String> receivedString = new ArrayList<>(packages);
private static DatagramPacket sendPacket;
public static void main(String[] args) {
if (!isCorrect(args)) {
System.exit(1);
} else {
try {
/* Configuration printout */
System.out.println("--------------------------------" +
"\n| UDP Echo Client" +
"\n| Configuration: " +
"\n| server name: " + HOST_NAME +
"\n| port: " + MY_PORT +
"\n| buffer: " + MY_BUFFER +
"\n| rate: " + RATE +
"\n| message size: "+MSG.length()+
"\n--------------------------------");
/* Sets the buffer */
buf = new byte[MY_BUFFER];
/* Create socket */
DatagramSocket socket = new DatagramSocket(null);
/* Create local endpoint using bind() */
SocketAddress localBindPoint = new InetSocketAddress(0);
socket.bind(localBindPoint);
socket.setSoTimeout(2000);
/* Create remote endpoint */
SocketAddress remoteBindPoint = new InetSocketAddress(HOST_NAME,
(MY_PORT));
/* Sends and reads the echo message */
sendEchoPackets(socket, remoteBindPoint);
} catch (SocketException se) {
System.err.println("Host unreachable!" +
"Wrong port or host offline");
}
}
}
public static void sendEchoPackets(DatagramSocket socket, SocketAddress remoteBindPoint) {
System.out.println("Original: "+originalMsg.toString());
/* For each string in the List of message parts */
for (String message : originalMsg) {
/* Create datagram packet for sending message */
sendPacket = new DatagramPacket(
message.getBytes(),
message.length(),
remoteBindPoint);
Timer timer = new Timer();
TimerTask rate = new TimerTask() {
#Override
public void run() {
try {
if (RATE == 0 || RATE == 1) {
for (int i = 0; i < RATE; i++) {
socket.send(sendPacket);
timer.cancel();
}
} else {
for (int i = 0; i < RATE; i++) {
socket.send(sendPacket);
timer.cancel();
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
};
timer.scheduleAtFixedRate(rate, 0, 1000);
readEchoPacket(socket);
}
}
public static void readEchoPacket(DatagramSocket socket){
try {
/* Create datagram packet for receiving echoed message */
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
socket.receive(receivePacket);
String receivedEcho = new String(
receivePacket.getData(),
receivePacket.getOffset(),
receivePacket.getLength());
receivedString.add(receivedEcho);
/* Compares if the message is the same as the one that was sent */
compareEchoMessage(receivedString);
}
catch (IOException e) {
System.out.println(e.getMessage());
}
}
public static void compareEchoMessage(List<String> receivedMsg){
StringBuilder sb = new StringBuilder();
for (String str : receivedMsg) {
sb.append(str);
}
System.out.println("Received: "+receivedMsg.toString());
if (sb.toString().compareTo(MSG) == 0){
System.out.printf("%s bytes sent and received!",sb.length());
}
else{
System.out.println("MESSAGE IS NOT EQUAL!");
}
}
/* Splits the message equally */
private static ArrayList<String> splitMessage(String message, int chunks) {
/* */
ArrayList<String> packages = new ArrayList<>(
(message.length() + chunks) - 1 / chunks);
for (int i = 0; i < message.length(); i += chunks){
packages.add(message.substring(i, Math.min(message.length(),
i + chunks)));
}
return packages;
}
public static boolean isCorrect(String[] args) {
/* Make sure all arguments are present */
if (args.length != 4 && args.length == 0) {
printUsage();
return false;
}
else
try {
HOST_NAME = args[0];
MY_PORT = Integer.parseInt(args[1]);
MY_BUFFER = Integer.parseInt(args[2]);
RATE = Integer.parseInt(args[3]);
/* Ensures RATE is not too high with a tested limit of 3000 */
if (RATE > 3000) {
System.err.println("Rate value is too large!");
return false;
}
/* Make sure the host is valid */
if (!isValidHost(HOST_NAME)) {
System.err.println("Host address is not valid!" +
"\nRequires a valid IP address or just localhost");
return false;
}
/* Make sure the port number is in the valid range */
if (MY_PORT <= 0 || MY_PORT >= 65536) {
System.err.println("Port value must be in (0 -> 65535)!");
return false;
}
/* Make sure the buffer is at least 2, not lower */
if (MY_BUFFER < 2){
System.err.println("Buffer must be higher or equal to 2!");
return false;
}
/* Split the message if bigger than buffer to appropriate packages */
if (MSG.length() > MY_BUFFER) {
packages = (int) Math.ceil((double) MSG.length() / MY_BUFFER);
chars = (MSG.length() / packages);
originalMsg = splitMessage(MSG, chars);
}
/* Else adds whole message to array list */
else {
packages = (int) Math.ceil( (double)MSG.length() / MY_BUFFER);
chars = (MSG.length() / packages);
originalMsg = splitMessage(MSG, chars);
}
}
catch (IndexOutOfBoundsException e) {
printUsage();
System.exit(1);
}
catch (NumberFormatException n) {
System.err.println("Invalid arguments!");
printUsage();
System.exit(1);
}
/* Everything is valid */
return true;
}
private static boolean isValidHost(String host) {
/* Check if the string is valid */
if (host == null || host.length() < 7 || host.length() > 15){
return false;
}
else
/* Host is valid "localhost" */
if (host.equals("localhost")){
return true;
}
/* Check the host string, should be in x.x.x.x format */
StringTokenizer token = new StringTokenizer(host,".");
if (token.countTokens() != 4)
return false;
while (token.hasMoreTokens()) {
/* Get current token and convert to an integer value */
String ip = token.nextToken();
try {
int ipVal = Integer.valueOf(ip).intValue();
if ( ipVal < 0 || ipVal > 255)
return false;
}
catch (NumberFormatException ex) {
return false;
}
}
/* IP Address looks valid */
return true;
}
private static void printUsage() {
System.err.println("Input arguments did not match expected arguments!" +
"\nUsage: \"<host_name> <port> <message_buffer> <message_rate>\"");
}
}
UDP Server:
/*
UDPEchoServer.java
A simple echo server with no error handling
*/
import java.io.IOException;
import java.net.*;
public class UDPEchoServer {
public static final int BUFSIZE = 1024;
public static final int MYPORT = 4950;
public static boolean running = true;
public static void main(String[] args) {
byte[] buf = new byte[BUFSIZE];
try{
/* Create socket */
DatagramSocket socket = new DatagramSocket(null);
/* Create local bind point */
SocketAddress localBindPoint = new InetSocketAddress(MYPORT);
socket.bind(localBindPoint);
System.out.println("---------------------------------"+
"\n| UDP Echo Server"+
"\n| Configuration: "+
"\n| port: "+MYPORT+
"\n---------------------------------");
while (running) {
/* Create datagram packet for receiving message */
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
/* Receiving message */
socket.receive(receivePacket);
/* Create datagram packet for sending message */
DatagramPacket sendPacket =
new DatagramPacket(receivePacket.getData(),
receivePacket.getLength(),
receivePacket.getAddress(),
receivePacket.getPort());
String echo = new String(receivePacket.getData(),
receivePacket.getOffset(), receivePacket.getLength());
System.out.printf("UDP echo request from %s", receivePacket.getAddress().getHostAddress());
System.out.printf(" using port %d\n", receivePacket.getPort());
System.out.println("Received: "+echo);
/* Send message*/
socket.send(sendPacket);
}
}
catch (SocketException s){
System.err.println(s.getMessage());
}
catch (IOException e){
System.err.println(e.getMessage());
}
}
}
Let's see what happens when your rate is 5:
This is the timer body:
if (RATE == 0 || RATE == 1) {
for (int i = 0; i < RATE; i++) {
socket.send(sendPacket);
timer.cancel();
}
} else {
for (int i = 0; i < RATE; i++) {
socket.send(sendPacket);
timer.cancel();
}
}
So, the if condition is false, because the rate is neither 0 nor 1. We go to the else:
for (int i = 0; i < RATE; i++) {
socket.send(sendPacket);
timer.cancel();
}
For RATE = 5, this is like writing:
socket.send(sendPacket);
timer.cancel();
socket.send(sendPacket);
timer.cancel();
socket.send(sendPacket);
timer.cancel();
socket.send(sendPacket);
timer.cancel();
socket.send(sendPacket);
timer.cancel();
Of course, cancelling the timer five times doesn't have any effect, but it is sending the same packet 5 times one-by-one. It will then send the next part 5 times, and the third part 5 times, because you are creating three separate timers for the parts.
I think if you want to send at a rate of 5 datagrams per second (is that the meaning of the rate?), you should not create as many timers as there are parts. You should create one timer, give it a list of datagrams to send, and set its schedule period to 1000L / rate (make sure rate is not zero!). The timer should pop the next datagram from the list and send it. If there is no datagram left in the list, it should cancel itself.
One loop to fill the list with datagrams
Assign list to final variable which can be used from anonymous class, or to a field.
Create timer and run it with 1000L / rate schedule time.
Run second loop for reading and comparing the received echo datagrams.
Note the two separate loops!
About re-assembling the datagrams
First, please note that the DatagramPacket that you receive from the server is not the same DatagramPacket that you sent to it even if the contents are the same! They are two different objects, and the equals() method from Object is not overridden, meaning that for any two objects a and b of type DatagramPacket, a.equals(b) is equivalent to a == b.
This means that the only thing you can compare is the datagram content, not the datagram object.
Since UDP does not guarantee that the packets will be sent in any particular order, you have to take care of this yourself. This usually means that you have to include more information in the datagram payload than just the string content. A good place to start is adding a byte that represents the number of the part.
For example, suppose you want to send the message "ABCDEF" in two packets containing "ABC" and "DEF". What you are doing now is sending something like:
┌──┬──┬──┐
│65│66│67│
└──┴──┴──┘
┌──┬──┬──┐
│68│69│70│
└──┴──┴──┘
Now you might get that back as
┌──┬──┬──┐
│68│69│70│
└──┴──┴──┘
┌──┬──┬──┐
│65│66│67│
└──┴──┴──┘
And you have no way to know that, and you'll re-assemble it and it will be DEFABC.
But if you sent another byte that gives the order:
┌─┬──┬──┬──┐
│0│65│66│67│
└─┴──┴──┴──┘
┌─┬──┬──┬──┐
│1│68│69│70│
└─┴──┴──┴──┘
you'd get the first byte, convert it to integer, and convert the rest of the bytes to string. Then you can put it in the list using the index, and they will come out in the proper order, no matter if you got the 1 packet first or second.
In a real world situation, you'll also send the size (number of packets) and an identifying number (so that if you get some rogue datagrams that belong to an old communication that only just arrived, they won't get mixed into your re-assembled payload.
After browsing some other threads regarding my problem I think I've understood that I need to re-design my application. But just for clarification: I have a single TCP/IP connection between a client and a server. On the client side there are a number of threads running concurrently. Randomly one or more of these threads use the TCP/IP connection to communicate with the server. I've found out that, e. g. While a long running file transfer is active, using the connection with another thread concurrently might lead to errors. Though I've preceeded each message with a specific header including the data length it appears to me that the IP stack sometimes delivers a mix of more than one messages to my program, which means that though one message has net yet been delivered completely, part of another message is delivered to my read method. Is this a correct observation which matches the intended TCP/IP behaviour? Thanks in advance - Mario
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
For anybody who's interested: following is the source code of my test program. You may play with various values for the BUFFER_SIZE and the number of THREADS used to bombard the server socket with concurrent TCP/IP sends using the same socket. I've left out some error handling and removed a more sophisticated termination including the closing of the sockets. Test with a BUFFER_SIZE greater than 64KB always leads to errors on my machine.
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
public class TCPTest
{
private final static String INPUT_FILE = "c:/temp/tcptest.in";
private final static int BUFFER_SIZE = 64 * 1024 - 8; //65536;
private final static int MESSAGE_SIZE = 512 * 64 * 1024;
private final static int THREADS = 3;
private final static int SIZE_OF_INT = 4;
private final static int LENGTH_SIZE = SIZE_OF_INT;
private final static int ID_SIZE = SIZE_OF_INT;
private final static int HEADER_SIZE = LENGTH_SIZE + ID_SIZE;
private final static String NEW_LINE = System.getProperty("line.separator");
private ServerSocket m_serverSocket = null;
private Socket m_clientSocket = null;
private int m_iThreadCounter;
public static void main(String[] args)
{
new TCPTest();
} // main
public TCPTest()
{
final String id = "ReaderThread[*]";
// start a new thread creating a server socket waiting for connections
new Thread(new Runnable()
{
public void run()
{
try
{
// create server socket and accept client requests
m_serverSocket = new ServerSocket(12345);
m_clientSocket = m_serverSocket.accept();
// client request => prepare and read data
long startTime = System.currentTimeMillis();
byte[] buffer = new byte[BUFFER_SIZE];
ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
int iTotalBytesRead = 0;
boolean fTerminate = false;
int iBytesRead;
// get hold of socket's input stream
InputStream clientInputStream = m_clientSocket.getInputStream();
// loop
while (false == fTerminate)
{
// loop to read next header
for (int i = 0; i < HEADER_SIZE; i++)
clientInputStream.read(header.array(), i, 1);
header.rewind();
// get information of interest
int iLength = header.getInt();
int iId = header.getInt();
int iLengthSoFar = 0;
int iBytesLeft = iLength;
int iBytesToRead;
// any length given?
if ((0 < iLength) && (BUFFER_SIZE >= iLength))
{
// that's the case => read complete message
while (iLengthSoFar < iLength)
{
// calculate number of bytes left
iBytesLeft = iLength - iLengthSoFar;
// calculate maximum number of bytes to read
if (iBytesLeft > BUFFER_SIZE)
iBytesToRead = BUFFER_SIZE;
else
iBytesToRead = iBytesLeft;
// read next portion of bytes
if ((iBytesRead = clientInputStream.read(buffer, 0, iBytesToRead)) != -1)
{
// maintain statistics
iTotalBytesRead += iBytesRead;
iLengthSoFar += iBytesRead;
} // if
else
{
// finish => print message
System.out.println("==> "+id+": ERROR length=<-1> received " +
"for id=<"+iId+">");
fTerminate = true;
break;
} // else
} // while
} // if
else
{
System.out.println("==> "+id+": ERROR data length <= 0 for id=<"+iId+">");
dump(header, 0, HEADER_SIZE / SIZE_OF_INT, "Error header");
} // else
} // while
System.out.println("==> "+id+": "+ iTotalBytesRead + " bytes read in "
+ (System.currentTimeMillis() - startTime) + " ms.");
} // try
catch (IOException e)
{
e.printStackTrace();
} // catch
} // run
}).start();
// create the socket writer threads
try
{
// ensure server is brought up and request a connection
Thread.sleep(1000);
System.out.println("==> "+id+": just awoke");
Socket socket = new Socket("localhost", 12345);
OutputStream socketOutputStream = socket.getOutputStream();
System.out.println("==> "+id+": socket obtained");
// create some writer threads
for (int i = 0; i < THREADS; i++)
// create a new socket writer and start the thread
(new SocketWriter(socket,
(i+1),
BUFFER_SIZE,
new String("WriterThread["+(i+1)+"]"),
socketOutputStream)).start();
} // try
catch (Exception e)
{
e.printStackTrace();
} // catch
} // TCPTestEx
private final static void dump(ByteBuffer bb, int iOffset, int iInts, String header)
{
System.out.println(header);
bb.rewind();
for (int i = 0; i < iInts; i++)
System.out.print(" " + Integer.toHexString(bb.getInt()).toUpperCase());
System.out.print(NEW_LINE);
} // dump
private class SocketWriter extends Thread
{
Socket m_socket;
int m_iId;
int m_iBufferSize;
String m_id;
OutputStream m_os;
protected SocketWriter(Socket socket, int iId, int iBufferSize, String id, OutputStream os)
{
m_socket = socket;
m_iId = iId;
m_iBufferSize = iBufferSize;
m_id = id;
m_os = os;
// increment thread counter
synchronized (m_serverSocket)
{
m_iThreadCounter++;
} // synchronized
} // SocketWriter
public final void run()
{
try
{
long startTime = System.currentTimeMillis();
ByteBuffer buffer = ByteBuffer.allocate(m_iBufferSize + HEADER_SIZE);
int iTotalBytesRead = 0;
int iNextMessageSize = 512 * m_iBufferSize;
int iBytesRead;
// open input stream for file to read and send
FileInputStream fileInputStream = new FileInputStream(INPUT_FILE);
System.out.println("==> "+m_id+": file input stream obtained");
// loop to read complete file
while (-1 != (iBytesRead = fileInputStream.read(buffer.array(), HEADER_SIZE, m_iBufferSize)))
{
// add length and id to buffer and write over TCP
buffer.putInt(0, iBytesRead);
buffer.putInt(LENGTH_SIZE, m_iId);
m_os.write(buffer.array(), 0, HEADER_SIZE + iBytesRead);
// maintain statistics and print message if so desired
iTotalBytesRead += iBytesRead;
if (iNextMessageSize <= iTotalBytesRead)
{
System.out.println("==> "+m_id+": <"+iTotalBytesRead+"> bytes processed");
iNextMessageSize += MESSAGE_SIZE;
} // if
} // while
// close my file input stream
fileInputStream.close();
System.out.println("==> "+m_id+": file input stream closed");
System.out.println("==> "+m_id+": <"+ iTotalBytesRead + "> bytes written in "
+ (System.currentTimeMillis() - startTime) + " ms.");
// decrement thread counter
synchronized (m_serverSocket)
{
m_iThreadCounter--;
// last thread?
if (0 >= m_iThreadCounter)
// that's the case => terminate
System.exit(0);
} // synchronized
} // try
catch (Exception e)
{
e.printStackTrace();
} // catch
} // run
} // SocketWriter
} // TCPTest
Yer. TCP is a byte oriented stream protocol. That means that the application receives an (undelimited) stream of bytes. The concept of "message" should be provided by the application (or use a message oriented protocol instead).