2013-07-23 2 views
1

Я использую TCP в своем приложении. Я столкнулся с проблемой потери данных, даже если закрытие сокета изящно. Вот две примеры программы для репликации сценария.Потеря данных, даже если удаленный сокет изящно закрыт в java

// TCP программа Отправитель непрерывно передает данные

public class TCPClient { 

public static void main(String[] args) throws UnknownHostException, IOException { 
    Socket soc = new Socket("xx.xx.xx.xx",9999); 
    OutputStream stream = soc.getOutputStream(); 
    int i=1 ; 
    while(true) { 

     System.out.println("Writing "+ i); 
     stream.write(i++); 
     stream.flush(); 
     try { 
      Thread.sleep(3000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
      break; 
     } 
    } 

    } 

} 

// Сервер TCP/Receiver

public class TCPServer2 { 

public static void main(String[] args) throws IOException { 
    System.out.println("program started"); 
    ServerSocket serverSock = new ServerSocket(9999); 
    Socket socket =serverSock.accept(); 
    System.out.println("client connected"); 
    InputStream stream = socket.getInputStream(); 
    InputStreamReader reader = null; 
     reader = new InputStreamReader(stream); 
     socket.setTcpNoDelay(true); 
     int n=0; 
     int i=1; 
     while (true) { 
     try { 
      n = reader.read(); 
      System.out.println("Msg No.: "+n); 
     } catch (Exception e) { 
      System.out.println(e); 
      e.printStackTrace(); 
      break; 
     } 
     if(i==5) { 
      System.out.println("closing socket"); 
      socket.close(); 
      //client should get exception while sending 6th msg 
      break; 
     }i++; 

    } 
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
     br.readLine(); 

} 

} 

Теперь программа идеально TCPClient должны получить исключение при отправке 6-сообщение, но он получает Исключение при отправке 7-го сообщения. Есть ли способ избежать этой проблемы с потерями данных, помимо использования протоколов высокого уровня и SO_LINGER (задержка помогает в этой программе, но может привести к сбою данных в нескольких других сценариях)?
NB: Эта проблема с потерей сообщения возникает, если мы используем две разные машины Windows. На той же машине он отлично работает.

ответ

2

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

Почему? Чтобы понять, почему нам нужно понимать разницу между неправильным закрытием close to vs.

Чтобы понять это различие, нам нужно посмотреть, что происходит на уровне протокола TCP. Полезно представить установленное TCP-соединение как фактически два отдельных, полунезависимых потока данных. Если два одноранговых узла - A и B, то один поток доставляет данные от A до B, а другой поток от B до A. Упорядоченный выпуск происходит в два этапа. Первая сторона (скажем, A) решает прекратить отправку данных и отправляет сообщение FIN на B. Когда стек TCP со стороны B получает FIN, он знает, что больше данных не поступает из A, и всякий раз, когда B считывает все предыдущие данные сокет, далее читает, вернет значение -1, чтобы указать конец файла. Эта процедура называется закрытием TCP, потому что закрывается только одна половина соединения. Неудивительно, что процедура для другой половины точно такая же. B отправляет сообщение FIN в A, которое в итоге получает -1 после прочтения всех предыдущих данных, отправленных A из сокета.

Напротив, абонентское закрытие использует сообщение RST (Reset). Если какая-либо из сторон выдает RST, это означает, что все соединение прервано, и стек TCP может отбросить любые данные в очереди, которые не были отправлены или получены каким-либо приложением.

Отправка сообщения TCP FIN означает: «Я закончил отправку», тогда как Socket.close() означает «Я закончил отправку и получение». Когда вы вызываете Socket.close(), явно невозможно отправить данные; и, тем не менее, можно получать данные. Итак, что происходит, например, когда A пытается упорядочить выпуск, закрывая сокет, но B продолжает отправлять данные? Это вполне допустимо в спецификации TCP, поскольку в отношении TCP только одна половина соединения была закрыта. Но поскольку сокет A закрыт, некому читать данные, если B следует продолжать отправлять. В этой ситуации стек TCP должен отправить RST для принудительного завершения соединения.

Возможное решение в вашем сценарии: Использовать протокол более высокого уровня.

Источник: https://docs.oracle.com/javase/8/docs/technotes/guides/net/articles/connection_release.html

+0

благодарит @SumitRathi. Ссылка очень полезна, так и ваш ответ. –

0

Интересная проблема. Возможно, попробуйте очистить OutputStream после того, как каждый клиент напишет.

+0

Промывка также не помогает. –

+0

Я не знаю, как добавить комментарий к EJP, но я собирался предложить java.net.Socket.setTcpNoDelay (true) – Kafkaesque

+0

setTcpNoDelay (true) также не работает –

2

клиент должен получить исключение при отправке 6-сообщ

Не совсем. Отправитель получит исключение в конце концов, если он продолжит отправку, но из-за буферизации TCP как для отправителя, так и для получателя, а так как TCP-сообщения являются асинхронными, то определенно не будет произойти на следующей записи после закрытия сверстника. Это произойдет после:

  1. TCP решил отправить свой буфер отправки.
  2. TCP обнаружил входящий ответ RST.
  3. приложение следующее calls send().
+0

«TCP решил отправить свой буфер отправки». - Есть ли способ заставить TCP немедленно отправлять данные на Java? –

+0

@CooperKhan It * is * отправляет его, как можно быстрее, с задержкой по времени и алгоритмом Nagle. Нет операции флеша и нет необходимости в ней. – EJP

1

Ответ EJP описывает, почему ваше текущее решение не будет работать.

Что касается альтернативы, единственный способ сделать то, что вы хотите, - сделать протокол двусторонним. другими словами, вам нужно настроить подтверждение своего сообщения о каждом полученном сообщении. «простой» способ сделать это - немедленно отправить сервер на каждое полученное сообщение. более сложные протоколы могут допускать некоторую периодическую проверку. даже с этим решением все еще есть проблемы. например, клиент может не принять ack (если сокет закрывается досрочно), и поэтому сервер может видеть одно и то же сообщение дважды. Короче говоря, вам нужен какой-то надежный протокол передачи сообщений, и есть много документации для этих уже онлайн.

Смежные вопросы