Java server socket communication is VERY slow - java

Local on Linux. It's about 10 seconds for a 20k message. My guess is my Java is bad and Python is fine.
py client:
def scan(self, msg):
try:
print 'begin scan'
HOST = 'localhost'
PORT = 33000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT));
s.sendall(msg)
data = s.recv(1024)
s.close()
print 'Received', repr(data)
except Exception, e:
print "error: " + str(e)
Java server:
ServerSocket service = new ServerSocket(33000);
while(true) {
debug("Begin waiting for connection");
//this spins
Socket connection = service.accept();
debug("Connection received from " + connection.getInetAddress().getHostName());
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
ScanResultsHeader results = new ScanResultsHeader();
Scanner scanner = new Scanner();
results = scanner.scan("scannerfake#gmail.com", "123", in);
and
public ScanResultsHeader scan (String userEmail,
String imapRetrievalId,
BufferedInputStream mimeEmail)
throws IOException, FileNotFoundException, MimeException, ScannerException {
//how fast would it be to just slurp up stream?
debug("slurp!");
String slurp = IOUtils.toString(mimeEmail);
debug("slurped " + slurp.length() + " characters");
slurp = slurp.toLowerCase();
debug("lc'ed it");
//...
My guess is I'm juggling the input streams wrong. One catch is the "BufferedInputStream mimeEmail" signature is required by the library API scan is using, so I'll need to get to that form eventually. But I noticed the simple act of slurping up a string takes ludicrously long so I'm already doing something incorrect.

Revising my answer....
If you are reading efficiently, and it appears you are, it will only be taking a lot time because either
You are creating a new connection every time you send a message which can be very expensive.
You are not sending the data as fast as you think.
The message is very large (unlikely but it could be)
There are plenty of examples on how to do this and a good library you can use is IOUtils which makes it simpler.
You should be able to send about 200K/s messages over a single socket in Java.
If you have a sends X bytes protocol using Big Endian you can do this.
DataInputStream dis = new DataInputStream( ...
int len = dis.readInt();
byte[] bytes = new byte[len];
dis.readFully(bytes);
String text = new String(bytes, "UTF-8");

Original problem was that the client isn't sending an end-of-input so the "slurp" operation keeps waiting for more stuff to cross the connection.
Solution was to implement an application-layer protocol to send the size of the message in advance, then stop listening for more message after that many bytes. I would have preferred a standard library -- something like, FiniteInputStream extends BufferedInputStream and takes a size as an argument, but wrote my own.

Related

Accessing byte array values from socket inputStream

I am attempting to retrieve the byte values from an InputStream which is being sent to the socket. I have used many ways but it always prints me the address of the byte array instead of its contents.
Below is my code for Client and Server. When a packet is sent from the client to the server, the server instantiates a new Thread to handle the connection. So slaveSocket is the socket I want to use for this.
public class TCPClient {
public static void main(String[] args) throws IOException{
Socket socket;
String address;
int port;
String userInput;
String serverResponse;
PrintWriter out;
BufferedReader in;
//read characters from user
BufferedReader stdIn;
if (args.length != 2) {
System.err.println("Usage: java EchoClient <address> <port>");
System.exit(1);
}
byte[] mode = "octet".getBytes(Charset.forName("UTF-8"));
address = args[0];
port = Integer.parseInt(args[1]);
try{
//connect socket to server
socket = new Socket(address, port);
//Construct PrintWriter to write objects to the socket
out = new PrintWriter(socket.getOutputStream(), true);
//Construct BufferedReader to read input from the socket
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//Another reader to read characters typed by the user
stdIn = new BufferedReader(new InputStreamReader(System.in));
//scanner for menu option
Scanner scanner = new Scanner(System.in);
int menuOption;
System.out.println("Press 1 to read from file or 2 to write to file");
menuOption = scanner.nextInt();
if (menuOption == 1){
String filename = "";
String text = "";
System.out.println("Enter file name");
filename = scanner.next();
byte[] packet = new byte[512];
//Constructing the RRQ Packet
//Ading the OPCODE
packet[0] = 1;
//adding the filename
filename.getBytes(Charset.forName("UTF-8"));
byte[] filenameB = filename.getBytes(Charset.forName("UTF-8"));
System.arraycopy(filenameB,0,packet,1, filenameB.length);
//adding a 0
packet[filenameB.length +1] = 0;
//adding the mode
System.arraycopy(mode,0,packet,1+filenameB.length+1,mode.length);
//adding the last 0
packet[1+filenameB.length+1+mode.length+1] = 0;
out.println(packet);
}else if(menuOption == 2){
}
socket.close();
}catch(UnknownHostException e){
System.err.println("Dont know about host" + address);
System.exit(1);
}catch(IOException e){
System.err.println("Couldnt get I/O for the connection to " + address);
System.exit(1);
}
}
}
public class TCPServer {
public static void main(String[] args) throws IOException{
//port of the server
int port = 10000;
//Socket objects
ServerSocket masterSocket;
Socket slaveSocket;
//instantiate the server socket
masterSocket = new ServerSocket(port);
System.out.println("Server Started");
boolean flag1 = true;
while(true){
slaveSocket = masterSocket.accept();
System.out.println("Accepted TCP connection from: " +
slaveSocket.getInetAddress() + ", " + slaveSocket.getPort() + "...");
System.out.println("Initialising new Thread...");
new TCPServerThread(slaveSocket).start();
}
}
}
public class TCPServerThread extends Thread{
private Socket slaveSocket = null;
public TCPServerThread(Socket socket){
super("TCPServerThread");
this.slaveSocket = socket;
}
public void run(){
byte[] ClientPacket = new byte[512];
PrintWriter socketOutput;
InputStream socketInput;
try{
//send packet to client
socketOutput = new PrintWriter((slaveSocket.getOutputStream()), true);
//read packet from client
socketInput = new DataInputStream(slaveSocket.getInputStream());
ClientPacket = socketInput.readAllBytes();
System.out.println(new String(ClientPacket, StandardCharsets.UTF_8));
}catch (IOException e){
System.err.println(e);
}
}
}
You've hopelessly overengineered this.
Writer and Reader do character input and output. InputStream and OutputStream do byte input and output.
You turn byte-based stuff (and in the end, network ports are byte based, not character based) into character based stuff in dangerous ways and then are attempting to read and write bytes into and out of the char-based things.
The solution is simple. Just stop doing that. You have byte-based stuff, there is absolutely no need to involve Reader and Writer.
A bunch of lines that cause problems:
out.println(packet);
PrintStreams are debug aids. You can't use them for any of this. For example, this line will print newlines (definitely not something you'd want in a byte based stream system!), and will print 'objects' - it does that by invoking the .toString() method, and the toString method of arrays are mostly useless. That explains why you see what you see. This is not how you send bytes. You cannot send bytes to a PrintStream (which is a confused mess, as it tries to let you send characters to a byte based system. As I said, you use it for debugging and nothing else. You should not be using it here at all).
new InputStreamReader(socket.getInputStream())
This is dangerous. You're turning a byte based system (InputStream) into a char-based one (Reader) and this always means somebody is making an explicit, 'out of band' (not based on the data in that stream) decision about charset encoding. In this case, as per the docs of InputStreamReader, you get the 'platform default'. Starting with JDK18, it's guaranteed to be UTF-8 fortunately, but before that, who knows what it is. You never want to call this constructor to avoid the confusion. new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8).
Mostly, though, don't make a reader in the first place. You have no interest whatsoever in reading streams of characters, you just want bytes.
If you have smallish strings and the information about where they 'end' is done 'out of band' (example: The size in bytes (not characters) is sent first, then X bytes that are the string, UTF_8 encoded), you can just read that in as bytes, and then make a string off of that, bypassing any need for Readers and Writers. Reader and Writer is useful only if the entire stream is all character based, or if you have huge strings (hundreds of megabytes) where their end can only be surmised by interpreting the data as characters first. (Mostly, those are horrible protocols that shouldn't be used).
//Construct PrintWriter to write objects to the socket
No, you can't write objects to sockets. Objects aren't bytes. You can write bytes to a socket; some objects will let themselves be turned into bytestreams but this is decidedly not a trivial job, and PrintWriter can't do it at all.
catch (IOException e) { System.err.println(e);
Most code has no reasonable route to 'deal' with them, but the solution to that is to throw them onwards. Not to catch the exception, print a note of despair, and just keep going on like nothing happened. Doing it right is also less code, so, win-win.
stdIn = new BufferedReader(new InputStreamReader(System.in));
//scanner for menu option
Scanner scanner = new Scanner(System.in);
You're making 2 different ways to read standard input. That makes no sense. Pick one.
I tried to fix it for you:
public class TCPClient {
public static void main(String[] args) throws Exception { // always throw Exception from `main`.
if (args.length != 2) {
System.err.println("Usage: java EchoClient <address> <port>");
System.exit(1);
return; // Always return after System.exit.
}
byte[] mode = "octet".getBytes(Charset.forName("UTF-8"));
String address = args[0];
int port = Integer.parseInt(args[1]);
Scanner scanner = new Scanner(System.in);
scanner.useDelimiter("\\R"); // split on newlines, not spaces. So much more logical.
// resources need to be safe-closed - use try-with!
try (var socket = new Socket(address, port);
var out = new BufferedOutputStream(socket.getOutputStream());
var in = socket.getInputStream()) {
System.out.println("Press 1 to read from file or 2 to write to file");
int menuOption = scanner.nextInt();
if (menuOption == 1) {
System.out.println("Enter file name");
String filename = scanner.next();
//Constructing the RRQ Packet
//Adding the OPCODE
out.write(1);
out.write(filename.getBytes(StandardCharsets.UTF_8));
out.write(0);
// The above is dangerous; NUL (0) is actually a valid char.
// A proper way to send strings is to send length in bytes
// first. I'll leave it to you to fix your protocol.
// If it can't be fixed, scan for `\0` chars and get rid of em.
//adding the mode
out.write(mode);
out.write(0);
}else if (menuOption == 2) {
}
}
}
Sending bytes one at a time can be slow (as it ends up sending an entire packet) but can also be useful - the data is just sent, instead of waiting perhaps for a long time for more data. In your case, you send it all in one go, so sending it all off very quickly is not a good idea. Hence, why the outputstream is wrapped in a BufferedOutputStream, which fixes that. You can always use flush() to force sending now, in case you want to keep the connection open (close(), naturally, also flushes).
It's fine if you want to use a byte[] packet instead, but it seems convoluted and unneccessary here. out.write(someByteArray), where out is an OutputStream of some sort, works fine. out.println(byteArray), where out is a Writer of some sort, or a PrintStream - doesn't work at all. (It would take the array, call toString() on it which isn't useful, then convert those bytes using some unknown charset and send that, and none of that is what you want).
You'll need to similarly eliminate PrintStream and the like from your server code.

C# to Java UTF8 string over TCP

I'm unable to send a UTF-8 string from a C# server to a Java client due to an EOF error in the client. How do I properly configure the C# server? I assume the error lies there because this client works with the Java server shown below.
Java client's receive function does this (this also works if I receive from a Java server, shown below):
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); //The constructor initialises a field, using the socket object.
StringBuilder inputMessage = new StringBuilder();
inputMessage.append((String) dataInputStream.readUTF());
Desired C# server:
static async Task Main(string[] args)
{
TcpListener server = new TcpListener(IPAddress.Any, 34567);
server.Start();
byte[] bytes = new byte[4096];
byte[] responseBytes;
using (var client = await server.AcceptTcpClientAsync()){
using(var tcpStream = client.GetStream())
{
await tcpStream.ReadAsync(bytes, 0, bytes.Length);
var playerNumber = Encoding.UTF8.GetString(bytes);
Console.WriteLine("Player " + playerNumber + " connected."); //java client to server works.
StringBuilder outputMessage = new StringBuilder("Some output");
responseBytes = Encoding.UTF8.GetBytes(outputMessage.ToString());
await tcpStream.WriteAsync(responseBytes, 0, responseBytes.Length); //This doesn't work...
}
server.Stop();
}
}
The error:
java.io.EOFException
at java.base/java.io.DataInputStream.readFully(DataInputStream.java:201)
at java.base/java.io.DataInputStream.readUTF(DataInputStream.java:613)
at java.base/java.io.DataInputStream.readUTF(DataInputStream.java:568)
at Client.Connection.Receive(Connection.java:26)
at Client.Main.lambda$main$0(Main.java:30)
at com.sun.javafx.application.PlatformImpl.lambda$startup$5(PlatformImpl.java:271)
at com.sun.glass.ui.Application.invokeAndWait(Application.java:464)
at com.sun.javafx.tk.quantum.QuantumToolkit.runToolkit(QuantumToolkit.java:366)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$startup$10(QuantumToolkit.java:280)
at com.sun.glass.ui.Application.lambda$run$1(Application.java:153)
Interestingly, a Java server doing this works:
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
StringBuilder outputMessage = new StringBuilder("Some output");
dataOutputStream.writeUTF(outputMessage.toString());
dataOutputStream.flush();
EDIT
This is received from the working Java server. The "bytearr" contains 100 bytes that I am using for my message and 100 bytes that are 0 (they come after my message). The "chararr" correctly sees the first 100 bytes as something meaningful and the last 200 bytes as '\u0000':
This is received form the non-working C# server. It seems to start two bytes in compared to the correct version and also it's "chararr" contains only thousands of '\u0000':
DataInputStream's readUTF reads a special data format, it is not a general purpose method for reading a sequence of UTF-8 bytes. Most notably, it expects an initial sequence of bytes specifying the length of the stream.
I found the answer here. Changing the way the Java client reads to this, works:
byte[] buff = dataInputStream.readAllBytes();
String str = new String(buff, "UTF-8");

Use writeUTF and readUTF for http requests in Java

This is a a Java method that tries to crawl a designated web page. I am using writeUTF and readUTF for socket communications to a server.
static void get_html(String host, String page, int port) throws IOException {
Socket sock = new Socket(host, port);
String msg = MessageFormat.format("GET {0} HTTP/1.1\r\nHost: {1}\r\n\r\n", page, host);
DataOutputStream outToServer = new DataOutputStream(sock.getOutputStream());
DataInputStream inFromServer = new DataInputStream(sock.getInputStream());
InputStream stream = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
BufferedReader buf = new BufferedReader(new InputStreamReader(stream));
String outMsg;
while ((outMsg = buf.readLine()) != null) {
System.out.println("Sending message: " + outMsg);
outToServer.writeUTF(outMsg);
String inMsg;
try {
inMsg = inFromServer.readUTF();
} catch (EOFException eof) {
break;
}
System.out.println(inMsg);
}
sock.close();
}
The reason I am writing it this way was to mimic the c code, where you have a while loop of send() making all deliveries from a buffer, and another while loop of recv() from a buffer untill it hits 'null'. When execute my code, it just hangs there, I suspect that is due to a call of readUTF before I finished sending all my messages. If this is the case, is there any way to fix it?
You can't do this. HTTP is defined as text lines. writeUTF() does not write text, it writes a special format starting with a 16-bit binary length word. Similarly the HTTP server won't reply with that format into your readUTF() call. See the Javadoc.
You have to use binary streams and the write() method, with \r\n as the line terminator. Depending on the output format you may or may not be able to use readLine(). Best not, then you don't have to write two pieces of code: use binary streams again.
In fact you should throw it all away and use HttpURLConnection. Implementing HTTP is not as simple as may hastily be supposed.

Reading all string from a server socket which has not been closed

I have a problem with socket programming in Java.
There is a server which has been written in python like this which I shouldn't not change.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send('from server\nnewline\0')
data = s.recv(BUFFER_SIZE)
s.close()
Now I want to write a code in Java which read the string from a server. Something like this:
public static String readStr(Socket client) throws IOException {
InputStreamReader inStream = new InputStreamReader(
client.getInputStream());
BufferedReader inBuff = new BufferedReader(inStream);
String answer = new String();
String str = inBuff.readLine();
while (str!=null) {
answer = answer.concat(str + "\n");
str = inBuff.readLine();
}
answer = answer.substring(0, answer.length() - 1);
System.out.println("answer:\n "+answer);
return answer;
}
But it seems that it blocks at line str = inBuff.readLine(); at the last line of the message. I tried the read() method but it was blocked too.
When designing a protocol over tcp, the best way is to include some kind of framer. This is done in the current protocol by the usage of a NUL byte.
When reading the data from the socket, you should first divide it into blocks/frames by some operations, before parsing the individual blocks.
A crude way to divide the packets into blocks is reading until you find a NUL byte, then returning that block as a byte array. (This is not the most efficient implementation)
public byte[] readPacket(InputStream in) throws IOException {
ByteArrayOutputStream tempStr = new ByteArrayOutputStream();
int read;
read=in.read();
while(read > 0){
tempStr.write(read);
read=in.read();
}
if(read == -1)
throw new EOFException();
return tempStr.toByteArray();
}
Because you now have proper frames for your data, you can now easily read the data:
byte[] frame = readPacket(in);
String[] lines = new String(frame, StandardCharsets.UTF8).split("\n");
// Do something with the lines
This is probably because the last line sent by the server does not end and readLine() method only returns when it reaches end of the line. Since you change the server's code. I recommend you use another method for reading from the stream. You may also use InputStreamReader class.
Apart from already mentioned inconsistent message/line ending - once with \n, second with \0, at the server there is no detection of end of the message. So the server will loop as long as the socket is not closed (or shut down for writing) at the client side. And as you have this line before closing the socket:
data = s.recv(BUFFER_SIZE)
in other words the client is waiting for some response from the server. But the server is stuck forever reading the message from the client in a loop.
So you need to either close the socket prior to that recv call or send some message (like empty line) and detect in on the server and eventually exit the loop.

IOUtils copy and write issues

I'm trying to send strings to a server from a client but it doesn't seem to be reading from the input stream.
Client
Scanner scanner = new Scanner(System.in);
Socket connection = new Socket("localhost", 13)
OutputStream out = connection.getOutputStream();
while(true) {
String message = scanner.nextLine();
IOUtils.write(message, out, "UTF-8");
out.flush();
}
Server
ServerSocket server = new ServerSocket(localhost,13);~
Socket connection = server.accept();
InputStream in = connection.getInputStream();
StringWriter writer = new StringWriter();
while(true) {
try {
IOUtils.copy(in, writer);
System.out.println(writer.toString());
} catch(IOException io) {}
}
It reads if I close the stream from the client's outputstream but I am trying to send multiple messages from the client to the server. Could someone please help
You seem to think that each time you call flush() at client-side, the server will know it and be able to know that this is the end of a message. That's not the case. IOUtils.copy() reads everything from a stream of bytes. While the stream end hasn't been reached, copy() won't return.
You can see streams as two sides of a long tube. If you pour 10 buckets of water in the at the end of the tube, all you'll get at the other side is a continuous flow of water.
If you need multiple separate messages, then you need to design a protocol allowing to separate messages, and read until the end of a message has been reached. It could be based on separators for example. Or you could send the length of the message followed by the message itself, to let the server know how many bytes it must read to get the next message.

Categories

Resources