I have a very strange behaviour during writing to a NIO socket.
In my mobile client I'm using a NIO socketChannel which is configured as follows:
InetSocketAddress address = new InetSocketAddress(host, port);
socketChannel = SocketChannel.open();
socketChannel.socket().connect(address, 10000);
socketChannel.configureBlocking(false);
then periodically (every 60 seconds) I write some data to this socketChannel (the code here is a little bit simplified):
ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
readBuffer.clear();
int numRead = -1;
numRead = socketChannel.read(readBuffer);
Log.write("Read " + numRead + " bytes" );
if(numRead > 0)
{
processServerResponse(readBuffer);
}
else if(numRead < 0)
{
// ... re-connect etc.
}
// ...
byte[] msg = getNextMessage();
ByteBuffer buffer = ByteBuffer.wrap(msg);
int numOfBytes = 0;
while(buffer.hasRemaining())
{
numOfBytes += socketChannel.write(buffer);
}
Log.write("Written " + numOfBytes + " bytes of " + msg.length );
And it works. But... Sometimes (very infrequently, may 1 or 2 times per day) my server don't receive data. My client log looks like:
Written 10 bytes of 10
Written 20 bytes of 20
Written 30 bytes of 30
Written 40 bytes of 40
Written 50 bytes of 50
and so on. But on the server side it looks like:
Received 10 bytes of 10
Received 50 bytes of 50
20, 30 and 40 bytes data records were not received, despite of fact that on the client side it looks like all data was sent without any exceptions! (In reality the server log is a little bit better than this simplified version. So I can see which data was sent (my sent records contain timestamps etc))
Such gaps can be small (2-3 minutes) which is not very bad, but sometimes they can be very big (1-2 hours = 60-120 cycles) and it is really a problem for my customers.
I really have no idea what can be wrong. The data seems to be sent by client, but it never arrives on the server side. I've checked it also with a proxy.
I would be very grateful for any ideas and tips.
P.S. Maybe it plays some role: the client code runs on an Android mobile device which is moving (it is in a car). The internet connection is established through GPRS.
Related
I am working on a client application that sends transactions to a server over a TLS connection. The applications sends a given number of bytes and receives 1182 bytes as response. It has been working fine until I started increasing the number of transactions per second. After that, some response packets started to be broken and cannot be fully received by the client in only read. When I try to unwrap the packet content, it raises an exception and terminates TLS session.
javax.net.ssl.SSLException: Unrecognized record version (D)TLS-0.0 , plaintext connection?
at java.base/sun.security.ssl.SSLEngineInputRecord.bytesInCompletePacket(SSLEngineInputRecord.java:98)
at java.base/sun.security.ssl.SSLEngineInputRecord.bytesInCompletePacket(SSLEngineInputRecord.java:64)
at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:544)
at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:441)
at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:420)
at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:634)
at MyClass.handleEncryptedTransaction(MyClass.java:297)
I tried to use a buffer to accumulate possible broken packages, however I cannot see the packet content before decripting and can only estimate if it is complete based on its size.
this.peerNetData.clear();
int bytesRead = socketChannel.read(this.peerNetData);
if (bytesRead == DEFAULT_TLS_HANDSHAKE_SIZE) {
handleEncryptedTransaction(socketChannel, engine);
} else if (bytesRead > 0) {
// TLS packet buffering
byte[] justRead = new byte[this.peerNetData.position()];
this.peerNetData.flip();
this.peerNetData.get(justRead);
this.incompleteTransactionsBuffer.put(justRead);
// DEFAULT_TLS_TRANSACTION_SIZE == 1182
if (this.incompleteTransactionsBuffer.position() >= DEFAULT_TLS_TRANSACTION_SIZE) {
this.incompleteTransactionsBuffer.flip(); // flipping to read mode
while (this.incompleteTransactionsBuffer.remaining() >= DEFAULT_TLS_TRANSACTION_SIZE) {
byte[] fullTransaction = new byte[DEFAULT_TLS_TRANSACTION_SIZE];
this.incompleteTransactionsBuffer.get(fullTransaction, 0, fullTransaction.length);
this.peerNetData.clear();
this.peerNetData.put(fullTransaction);
// This method uses this.peerNetData to unwrap data and
handleEncryptedTransaction(socketChannel, engine);
}
this.incompleteTransactionsBuffer.compact(); // wipe out bytes that had been read and free space of the buffer
}
}
Is there anyway to check if a TCP packet over TLS is complete? I tried to read the first 1182 bytes but it doesn't seem to work. Interestingly, this code work when I get multiple packets in the response, such as (N * 1182), where N varies from 2 to 7. Maybe I should wait for another socket read and get another piece of information?
I suppose this problem occurs because of packet retransmissions caused by heavy traffic. Is there any other way to deal with TLS packet retransmissions in low level socket connections in Java?
After getting comments and better understanding TLS protocol, I could work out a solution for the problem by implementing a buffer and getting the exact size of a TLS Record to wait for other TCP reads.
A TLS record "might be split into multiple TCP fragments or TCP fragments might also contain multiple TLS records in full, in half or whatever. These TCP fragments then might even be cut into multiple IP packets although TCP tries hard to avoid this.", from Determine packet size of TLS packet Java/Android. However, that post mentions first 2 bytes and it is not right. According to https://hpbn.co/transport-layer-security-tls/#tls-record-diagram:
Maximum TLS record size is 16 KB
Each record contains a 5-byte header, a MAC (up to 20 bytes for SSLv3,
TLS 1.0, TLS 1.1, and up to 32 bytes for TLS 1.2), and padding if a
block cipher is used.
To decrypt and verify the record, the entire record must be available.
The TLS record size lies on the 3rd and 4th bytes:
The code ended up being like this:
protected synchronized void read(SocketChannel socketChannel, SSLEngine engine) throws IOException {
this.peerNetData.clear();
int bytesRead = socketChannel.read(this.peerNetData);
if (bytesRead > 0) {
// TLS records buffering
this.peerNetData.flip();
byte[] justRead = new byte[this.peerNetData.limit()];
this.peerNetData.get(justRead);
this.tlsRecordsReadBuffer.put(justRead);
byte[] fullTlsRecord;
// Process every TLS record available until buffer is empty or the last record is yet not complete
while ( (fullTlsRecord = this.getFullTlsRecordFromBufferAndDeleteIt()) != null ) {
this.peerNetData.clear();
this.peerNetData.put(fullTlsRecord);
handleEncryptedTransaction(socketChannel, engine);
}
} else if (bytesRead < 0) {
handleEndOfStream(socketChannel, engine);
}
}
private synchronized byte[] getFullTlsRecordFromBufferAndDeleteIt() {
byte[] result = null;
this.tlsRecordsReadBuffer.flip();
if (this.tlsRecordsReadBuffer.limit() > DEFAULT_TLS_HEADER_SIZE) {
// Read only the first 5 bytes (5 = DEFAULT_TLS_HEADER_SIZE) which contains TLS record length
byte[] tlsHeader = new byte[DEFAULT_TLS_HEADER_SIZE];
this.tlsRecordsReadBuffer.get(tlsHeader);
**// read 3rd and 4th bytes to get TLS record length in big endian notation
int tlsRecordSize = ((tlsHeader[3] & 0xff) << 8) | (tlsHeader[4] & 0xff);**
// 0xff IS NECESSARY because it removes one-bit negative representation
// Set position back to the beginning
this.tlsRecordsReadBuffer.position(0);
if (this.tlsRecordsReadBuffer.limit() >= (tlsRecordSize + DEFAULT_TLS_HEADER_SIZE)) {
// Then we have a complete TLS record
result = new byte[tlsRecordSize + DEFAULT_TLS_HEADER_SIZE];
this.tlsRecordsReadBuffer.get(result);
}
}
// remove record and get back to write mode
this.tlsRecordsReadBuffer.compact();
return result;
}
I've write a Client / Server code using Java server socket ( TCP ).
The server is working as a Radio, listening to Mic, and sending the bytes to connected clients.
When i run the code using "localhost" as server name, it works very well, and i can hear the voice in the speakers without any issues.
Now, when i want to expose the localhost to internet using ngrok:
Forwarding tcp://0.tcp.ngrok.io:11049 -> localhost:5000
I start to get below exception in the client side:
java.lang.IllegalArgumentException: illegal request to write non-integral number of frames (1411 bytes, frameSize = 2 bytes)
at com.sun.media.sound.DirectAudioDevice$DirectDL.write(Unknown Source)
at client.Client.Start(Client.java:79)
at client.Receiver.main(Receiver.java:17)
Does any one know why, and how i can fix such problem ?
I tried to change the byte array length.
//server code
byte _buffer[] = new byte[(int) (_mic.getFormat().getSampleRate() *0.4)];
// byte _buffer[] = new byte[1024];
_mic.start();
while (_running) {
// returns the length of data copied in buffer
int count = _mic.read(_buffer, 0, _buffer.length);
//if data is available
if (count > 0) {
server.SendToAll(_buffer, 0, count);
}
}
// client code where exception happens:
_streamIn = _server.getInputStream();
_speaker.start();
byte[] data = new byte[8000];
System.out.println("Waiting for data...");
while (_running) {
// checking if the data is available to speak
if (_streamIn.available() <= 0)
continue; // data not available so continue back to start of loop
// count of the data bytes read
int readCount= _streamIn.read(data, 0, data.length);
if(readCount>0){
_speaker.write(data, 0, readCount); // here throws exception
}
}
should play the sound through the speaker.
I am implementing a simple java udp socket program. Here are the details:
Server side: suppose I create 2500 packets in server side, then inform the client that i'm gonna send 2500 packets, and each packet is packetSize bytes. then in a loop, each packet is created and then sent.
Client side: after being informed of the number of packets, in a for (or while), I wait for 2500 packets to be received.
Here is the problem:
The for loop in client side never ends! that means 2500 packets are never received! Although I checked server side and it has sent them all.
I tried setting the socket's receive buffer size to 10 * packetSize using this:
socket.setReceiveBufferSize(10 * packetSize)
but it does not work.
How do you think I could solve this problem? I know UDP is not reliable but both client and server are running on different ports of the same computer!
Here is the code for server side:
for (int i = 0; i < packets; i++) {
byte[] currentPacket = new byte[size];
byte[] seqnum = intToByteArray(i);
currentPacket[0] = seqnum[0];
currentPacket[1] = seqnum[1];
currentPacket[2] = seqnum[2];
currentPacket[3] = seqnum[3];
for (int j = 0; j < size-4; j++) {
currentPacket[j+4] = finFile[i][j];
}
sendPacket = new DatagramPacket(currentPacket, currentPacket.length, receiverIP, receiverPort);
socket.send(sendPacket);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
and the client side:
int k = 0;
while (true) {
receivedBytes = new byte[size];
receivePacket = new DatagramPacket(receivedBytes, size);
socket.receive(receivePacket);
allBytes.add(receivePacket.getData());
k++;
if (k == packets)
break;
}
allBytes is just a linked list containing received bytes. i use it to reassemble the final file.
P.S. This code works perfect for under 100Mb files.
Thanks
Update:
tl;dr summary: Either packets is not properly initialized or use TCP, or add a sequence number in your UDP packet so that your client knows if it drops a packet and you can write code to handle that (request for a rebroadcast). This essentially makes it a rudimentary TCP anyways.
I have a suspicion that you never initialized packets so you never hit your break. Rewriting your while into a for loop can easily check if this is true. Assuming the first packet you send contains how many packets it will be receiving, and you initialize packets correctly then if your packets are being lost then your client side program will not end since receive() is a blocking method.
If you strongly suspect that your packets are being lost, then debug your client side and see how many received packets are in your LinkedList and compare that against how many are sent on the server side.
for(int i = 0; i < packets; i++) {
receivedBytes = new byte[size];
receivePacket = new DatagramPacket(receivedBytes, size);
socket.receive(receivePacket);
allBytes.add(receivePacket.getData());
}
System.out.println("All " + packet + " received.");
Switching to code to the above will let you know that if you never get to the print statement, then you know that you're losing packets since receive() is a blocking method and it means that your client side is stuck in the for loop. This is because the for loop can't be satisfied since if the server sends 2500 packets but the client only receives 2300 packets, it'll still be in the for loop at the receive() line waiting for 2301, 2302, ... packets and so on.
Since you have a file with upwards of 100MB or more that needs to be assembled I assume you can't tolerate loss, so either use TCP that will fulfill that requirement or handle that possibility in your code by creating your own header with each packet. This header can be as simple as an incrementing sequence number that the client will receive and read, if it skips a number from the previous packet then it will know that a packet was lost. At this point, you can have your client request the server to rebroadcast that specific packet. But at this point you just implemented your own crude TCP.
I've been playing around with transferring data between a test client (written in Java) and a server (written in C#/.NET).
I tried TCP clients and servers, but there has been and current is a problem flushing the stream. I realize flush doesn't always flush the stream, so I'm wondering if there is any way to flush/send a stream without .flush() or in a more reliable way?
Currently, the important part of the client looks like this (message is a string, serverSocket is a Socket object):
OutputStream output = serverSocket.getOutputStream();
byte[] buffer = message.getBytes();
int length = buffer.length;
output.write(ByteBuffer.allocate(4).putInt(length).array());
output.write(buffer);
output.flush();
and the server looks like this:
NetworkStream stream = client.GetStream ();
byte[] sizeBuffer = new byte[4];
int read = stream.Read (sizeBuffer, 0, 4);
int size = BitConverter.ToInt32 (sizeBuffer, 0);
Databaser.log ("recieved byte message denoting size: " + size);
byte[] messageBuffer = new byte[size];
read = stream.Read (messageBuffer, 0, size);
string result = BitConverter.ToString (messageBuffer);
Databaser.log ("\tmessage is as follows: '" + result + "'");
Where, if it's not evident from the code, the client sends 4 bytes, which are combined into a 32 bit integer which is the length of the message. Then I read in the message based on that length and have build in converters translate it into a string.
As I said, I'm wondering how to flush the connection? I know this code isn't perfect, but I can change it back to when I used TCP and UTF exclusive string messaging over the network, but either way, the connection doesn't send anything from the client until the client shuts down or closes the connection.
Maybe the problem is in the byte order. I have an application which send from a tablet (java) to a C# application (Windows Intel), I used similar to what you've done, except in the following
ByteBuffer iLength = ByteBuffer.allocate(4);
iLength.order(ByteOrder.LITTLE_ENDIAN);
iLength.putInt(length);
output.write(iLength.array(), 0, 4);
output.write(buffer);
output.flush();
Java uses BIG-ENDIAN and Intel uses LITTLE-ENDIAN bytes order.
I was thinking about how you would read how much data you send over a Socket. For example if I made a Chat Application and then wanted to find out how much a message would take (in kilobytes or bytes), how would I measure this?
I send a message like "Hello, world!". How do I measure the amount of bandwidth that would take to send?
I know there are programs to monitor how much data is sent and received over the wire and all that, but I wanted to try and do this myself to learn some.
Wrap the socket's output stream in a CountingOutputStream:
CountingOutputStream cos = new CountingOutputStream(socket.getOutputStream());
cos.write(...);
System.out.println("wrote " + cos.getByteCount() + " bytes");
If you send raw string with no header (protocol)
For the strings you have
String hello = "Hello World";
hello.getBytes().length //size of the message
For showing progress to user when sending files you can do this
Socket s = new Socket();
//connect to the client, etc...
//supose you have 5 MB File
FileInputStream f = new FileInputStream( myLocalFile );
//declare a variable
int bytesSent = 0;
int c;
while( (c = f.read()) != -1) {
s.getOutputStream().write(c);
bytesSent++; //One more byte sent!
notifyGuiTotalBytesSent(bytesSent);
}
well, thats just a very simple implementation not using a buffer to read and send the data just for you get the idea.
the method nitify.... would show in the GUI thread (not this one) the bytesSent value