Чтение сокета по одному байту за раз, как это изменить, оптимизация - PullRequest
2 голосов
/ 10 сентября 2011

Мне нужна помощь по оптимизации. Я пытаюсь улучшить этот игровой сервер с открытым исходным кодом, созданный с помощью JAVA. У каждого игрока есть свой поток, и каждый поток выглядит примерно так:

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 = "";
    }
}

Мне столько раз говорили, что этот код глуп, и я согласен, потому что при его профилировании я вижу, что чтение каждого байта и добавление его с помощью packet += "" просто глупо и медленно.

Я хочу улучшить это, но я не знаю как ... Я уверен, что смогу что-то найти, но я боюсь, что это будет даже медленнее, чем это, потому что я должен разделить пакеты на основе '\u0000 ', '\n' или '\r' для их анализа. И я знаю, что разделение 3 раза очень медленно.

Может кто-нибудь дать мне идею? Или кусок кода для этого? Это сделает мой день.

Если вы собираетесь объяснить, пожалуйста, используйте очень простые слова с примерами кода, я только начинающий JAVA. Спасибо в

Ответы [ 3 ]

2 голосов
/ 10 сентября 2011

Нет значительных проблем с производительностью при чтении из BufferedReader как большими кусками, так и даже одним символом за раз.

Если ваше профилирование не определило метод BufferedReader.read() как определенную горячую точку вВаш код, лучшее, что вы можете сделать, это сделать код простым и читаемым, а не тратить время на его оптимизацию.


Для вашего конкретного случая:

  • да, этот кодэто немного неубедительно, но
  • нет, вряд ли это будет иметь большое значение с точки зрения производительности.

Реальным узким местом производительности, скорее всего, является сама сеть.Для решения этой проблемы вы можете сделать что-то на уровне приложения, но в конечном итоге вы можете отправлять / получать данные только со скоростью, поддерживаемой сквозным сетевым соединением.


Мой результат профилирования говорит, что он исходит из: BufferedReader.read ().Что это значит на самом деле?

Вы уверены, что время действительно тратится в методе чтения Socket?Если это так, то реальная проблема заключается в том, что потоки вашего приложения тратят много времени на ожидание сетевых пакетов.Если это так, то единственное, что вы можете сделать, - это уменьшить количество сбросов на стороне клиента и сервера, чтобы сети не приходилось иметь дело с таким большим количеством маленьких пакетов.В зависимости от вашего приложения это может быть неосуществимо.

Я бы написал ваш код следующим образом:

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

StringBuilder packet = new StringBuilder();
int ch;

while ((ch = _in.read()) != 1) {
    if (ch != '\u0000' && ch != '\n' && ch != '\r') {
        packet.append((char) ch);
    } else if (!packet.isEmpty()) {
        parsePlayerPacket(packet.toString());
        packet = new StringBuilder();
    }
}

Но я не думаю, что это будет многоразница в производительности ... если только "пакеты" обычно не длиннее сотни символов.(Реальная цель моих настроек - уменьшить количество временных строк, создаваемых при чтении пакета. Я не думаю, что есть простой способ сделать так, чтобы он тратил меньше реального времени на вызовы read.)

1 голос
/ 10 сентября 2011

Возможно, вам следует обратиться к методу readLine () в BufferedReader.Похоже, что вы читаете строки, вызов BufferedReader.readLine () дает вам следующую строку (без новой строки / перевода строки).

Примерно так:

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

Так же, как выДля реализации readLine () будет блокироваться до тех пор, пока либо не будет закрыт поток, либо пока не появится символ новой строки / перевода строки.

РЕДАКТИРОВАТЬ: да, это не разделит '\ 0'.Лучше всего делать ставку на PushbackReader, читая в некотором буфере символов (как предлагает Дэвид Оливан Убието)

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);
}

Я не отлаживал это, но вы поняли идею.

Обратите внимание, что при использовании String.split ('\ u0000') возникает проблема, при которой пакет, заканчивающийся на \ u0000, не будет обработан до тех пор, пока через поток не будет передан перевод новой строки / перевода строки.Поскольку вы пишете какую-то игру, я предполагаю, что важно обрабатывать входящий пакет, как только вы его получите.

0 голосов
/ 10 сентября 2011

Считайте как можно больше байтов, используя большой буфер (1 КБ). Проверьте, найден ли терминатор (\ u0000, \ n, \ r). Если нет, скопируйте во временный буфер (больше, чем используется для чтения сокета), прочитайте снова и копируйте во временный буфер, пока не будет найден терминатор. Когда у вас есть все необходимые байты, скопируйте временный буфер в любой «конечный» буфер и обработайте его. Оставшиеся байты должны рассматриваться как «следующее» сообщение и копироваться в начало временного буфера.

...