Search code examples
javasocketsbuffering

Reading socket one byte a time, how to change this, optimisation


I need some help about optimisation. I am trying to improve this open-source game server made with JAVA. Each player has its own thread, and each thread goes something like this:

BufferedReader _in = new BufferedReader(new InputStreamReader(_socket.getInputStream()));

String packet = "";
char charCur[] = new char[1];

while(_in.read(charCur, 0, 1)!=-1)
{
    if (charCur[0] != '\u0000' && charCur[0] != '\n' && charCur[0] != '\r')
    {
        packet += charCur[0];

    }else if(!packet.isEmpty())
    {
        parsePlayerPacket(packet);
        packet = "";
    }
}

I have been told so many times that this code is stupid, and I agree because when profiling it I see that reading each byte and appending it using packet += "" is just stupid and slow.

I want to improve this but I don't know how.. I'm sure I can find something, but I'm afraid it will be even slower than this, because I have to split packets based on the '\u0000', '\n', or '\r' to parse them. And I know that splitting 3 times is verry slow.

Can someone give me an idea? Or a piece of code for this? It will make my day.

If you're going to explain, please, please use verry simple words, with code examples, I'm just a JAVA beginner. Thank's


Solution

  • Perhaps you should look into the readLine() method of BufferedReader. Looks like you're reading Strings, calling BufferedReader.readLine() gives you the next line (sans the newline/linefeed).

    Something like this:

    String packet = _in.readLine();
    while(packet!=null) {
        parsePlayerPacket(packet);
        packet = _in.readLine();
    }
    

    Just like you're implementation, readLine() will block until either the stream is closed or there's a newline/linefeed.

    EDIT: yeah, this isn't going to split '\0'. You're best bet is probably a PushbackReader, read in some buffer of chars (like David Oliván Ubieto suggests)

    PushbackReader _in = new PushbackReader(new InputStreamReader(_socket.getInputStream()));
    StringBuilder packet = new StringBuilder();
    char[] buffer = new char[1024];
    // read in as much as we can
    int bytesRead = _in.read(buffer);
    
    while(bytesRead > 0) {
        boolean process = false;
        int index = 0;
        // see if what we've read contains a delimiter
        for(index = 0;index<bytesRead;index++) {
            if(buffer[index]=='\n' ||
               buffer[index]=='\r' ||
               buffer[index]=='\u0000') {
                process = true;
                break;
            }
        }
        if(process) {
             // got a delimiter, process entire packet and push back the stuff we don't care about
             _in.unread(buffer,index+1,bytesRead-(index+1));  // we don't want to push back our delimiter
             packet.append(buffer,0,index);
             parsePlayerPacket(packet);
             packet = new StringBuilder();
        }
        else {
             // no delimiter, append to current packet and read some more
             packet.append(buffer,0,bytesRead);
        }
        bytesRead = _in.read(buffer);
    }
    

    I didn't debug that, but you get the idea.

    Note that using String.split('\u0000') has the problem where a packet ending with '\u0000' won't get processed until a newline/linefeed is sent across the stream. Since you're writing some kind of game, I assume it's important to process an incoming packet as soon as you get it.