2012-08-02 2 views
0

Я пытаюсь передать сервер SSL-сокет Java и клиентский SSL-сокет Python. Первое отправленное сообщение одобрено, но когда сервер отправляет другие сообщения, клиент получает сообщения, делящиеся на 2 части. Например: если сервер отправляет сообщение «abcdefghij», клиент получает сначала «a», а после «bcdefghij».Java Server SSL Socket и Python Client SSL-сокет - проблемы при отправке сообщений сервера

Кто-нибудь знает, почему после первого получения сообщений в двух частях? С уважением.

Код клиента:

import socket, ssl, pprint 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
ssl_sock = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_NONE) 
ssl_sock.connect(('localhost', 7000)) 
pprint.pprint(ssl_sock.getpeercert()) 

while(1): 
    print "Waiting" 
    data = ssl_sock.recv() 
    print "Received:", data 
    data = "" 

ssl_sock.close() 

Серверный код:

import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.FileInputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.security.KeyStore; 

import javax.net.ssl.KeyManagerFactory; 
import javax.net.ssl.SSLContext; 
import javax.net.ssl.SSLServerSocket; 
import javax.net.ssl.SSLServerSocketFactory; 
import javax.net.ssl.SSLSession; 
import javax.net.ssl.SSLSocket; 


public class SslReverseEchoer { 

    public static void main(String[] args) { 
     char ksPass[] = "123456".toCharArray(); 
     char ctPass[] = "123456".toCharArray(); 

     try { 
      KeyStore ks = KeyStore.getInstance("JKS"); 
      ks.load(new FileInputStream("keystore.jks"), ksPass); 
      KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 
      kmf.init(ks, ctPass); 
      SSLContext sc = SSLContext.getInstance("TLS"); 
      sc.init(kmf.getKeyManagers(), null, null); 
      SSLServerSocketFactory ssf = sc.getServerSocketFactory(); 
      SSLServerSocket s = (SSLServerSocket) ssf.createServerSocket(7000); 
      printServerSocketInfo(s); 
      SSLSocket c = (SSLSocket) s.accept(); 
      printSocketInfo(c); 
      BufferedWriter w = new BufferedWriter(new OutputStreamWriter(c.getOutputStream())); 
      BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream())); 
      //1th time 
      String m = "abcdefghj1234567890"; 
      w.write(m, 0, m.length()); 
      w.newLine(); 
      w.flush(); 
      //2th time 
      String m2 = "#abcdefghj1234567890"; 
      w.write(m2, 0, m2.length()); 
      w.newLine(); 
      w.flush(); 
      //3th time 
      String m3 = "?abcdefghj1234567890"; 
      w.write(m3, 0, m3.length()); 
      w.newLine(); 
      w.flush(); 
      while ((m = r.readLine()) != null) { 
       if (m.equals(".")) 
        break; 
       char[] a = m.toCharArray(); 
       int n = a.length; 
       for (int i = 0; i < n/2; i++) { 
        char t = a[i]; 
        a[i] = a[n - 1 - i]; 
        a[n - i - 1] = t; 
       } 
       w.write(a, 0, n); 
       w.newLine(); 
       w.flush(); 
      } 
      w.close(); 
      r.close(); 
      c.close(); 
      s.close(); 
     } catch (Exception e) { 
      System.err.println(e.toString()); 
     } 
    } 

    private static void printSocketInfo(SSLSocket s) { 
     System.out.println("Socket class: " + s.getClass()); 
     System.out.println(" Remote address = " + s.getInetAddress().toString()); 
     System.out.println(" Remote port = " + s.getPort()); 
     System.out.println(" Local socket address = " + s.getLocalSocketAddress().toString()); 
     System.out.println(" Local address = " + s.getLocalAddress().toString()); 
     System.out.println(" Local port = " + s.getLocalPort()); 
     System.out.println(" Need client authentication = " + s.getNeedClientAuth()); 
     SSLSession ss = s.getSession(); 
     System.out.println(" Cipher suite = " + ss.getCipherSuite()); 
     System.out.println(" Protocol = " + ss.getProtocol()); 
    } 

    private static void printServerSocketInfo(SSLServerSocket s) { 
     System.out.println("Server socket class: " + s.getClass()); 
     System.out.println(" Socker address = " + s.getInetAddress().toString()); 
     System.out.println(" Socker port = " + s.getLocalPort()); 
     System.out.println(" Need client authentication = " + s.getNeedClientAuth()); 
     System.out.println(" Want client authentication = " + s.getWantClientAuth()); 
     System.out.println(" Use client mode = " + s.getUseClientMode()); 
    } 
} 

ответ

1

Это сочетание алгоритма Naggle на стороне сервера и задержки ACK в стек TCP клиента. Вы также найдете задержку ~ 40 мс между двумя пакетами.

Отключение Naggle Algo на стороне сервера, чтобы исправить:

SSLSocket c = (SSLSocket) s.accept(); 
c.setTcpNoDelay(true); 

Больше информации о том, почему это происходит здесь: http://www.stuartcheshire.org/papers/NagleDelayedAck/

Изменить, чтобы добавить: Примечание ответ Бруно ниже. Хотя это описывает конкретную причину того, что вы видите здесь, то, как вы ожидаете данные с сервера, не гарантируется.

+0

+1 для объяснения причины здесь, но это не устраняет фундаментальную проблему. – Bruno

+0

@BrianRoach Я изменил код с сервера и клиента в соответствии с комментариями от вас, но все же всегда после первой передачи первого байта отправляются следующие сообщения, а остальная часть сообщения поступает позже. Протокол, который собирается для идентификации сообщений, должен быть изменен благодаря помощи. –

2

Кажется, что вы ожидаете, что всегда сможете читать любое количество данных, отправленных в одном блоке с другой стороны.

Это распространенная ошибка, не связанная с SSL/TLS, но также имеющая отношение к простой TCP-связи.

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

HTTP, например, использует пустые строки для указания конца заголовков и заголовка Content-Length или кодирования с передачей по каналу, чтобы сообщить получателю, когда прекратить чтение тела.

SMTP использует команды с разделителями строк и один . для завершения сообщения.

+0

Это все верно в общем смысле, но это взаимодействие нагг/ак вызывает специфическое поведение, которое он видит.Это также вызывает задержку ~ 40 мс между двумя пакетами, что является серьезной проблемой, если одно из ваших требований - производительность. Вы когда-нибудь видели двух людей, стоящих на расстоянии 10 футов друг от друга, каждый из которых ожидал прибытия другого человека? Это все. На самом деле раздражает маленького педераста, чтобы выследить его, пока вы не увидите его в пакете сниффера. (Если вы никогда не отправляете небольшие фрагменты данных, вы их не видите). –

+1

@BrianRoach, ваш ответ объясняет этот конкретный момент, но люди не должны полагаться на это вообще (вы не всегда можете знать, использует ли удаленная сторона алгоритм Naggle algo). Ожидания OP идут рука об руку с попыткой обнаружить замыкания на соединение, используя только чтения (другая распространенная ошибка). – Bruno

+0

Справедливая точка. Я думаю, что сочетание двух наших ответов в значительной степени охватывает все базы. Я вообще не парень-питон - его 'recv()' возвращает все, что есть в буфере TCP во время вызова, но блокирует, если нет ничего? –

0

Я попытался решить мою проблему двумя способами: сообщение всегда прерывается первым символом, поэтому я решил добавить три пробела в начале каждого сообщения и завершить. Поэтому, когда клиент получает их, обрезает сообщение. Другим способом, который я нашел, был использование DataOutputStream, метод, который поставляет байты byBytes by byte, использовал его на сервере, и клиент изменил q, когда он получил данные, сообщение должно было построить обработку на стороне клиента, чтобы, наконец, сделать то, что я хочу дом конец сообщения. Спасибо за обсуждение!

+0

Что вы, похоже, не понимаете, так это то, что TCP-соединение дает вам непрерывный поток данных. Куски, которые вы пишете на одном конце, не обязательно являются кусками, которые вы читаете на другом конце. Какие гарантии TCP являются только порядком общей последовательности, когда все куски объединены вместе. Использование пробелов в качестве разделителей может работать, но вам всегда нужно объединить буферы и продолжить цикл до тех пор, пока вы не прочитаете эти пробелы. – Bruno

+0

Извините, что я сделал во втором подходе в своем решении. Тем не менее, только немного странно, потому что из того, что я читал, TCP-пакет имеет размер 1460 байт, поэтому для меня это должно было найти эти 1460 байт точно так же, как я писал на одном конце.И, конечно, мой буфер чтения должен был забронировать этот размер, если не сломаться. Я ценю объяснение и понимаю, что я должен ехать к полученному сообщению, а затем обработать его. Еще раз спасибо. –

+0

Ограничение на 1460 байтов, вероятно, связано с чем-то, связанным с MTU. Опять же, не беспокойтесь об этом. С помощью TCP вы должны продолжать читать свой буфер, но сколько раз требуется, чтобы получить нужное сообщение, до определенного разделителя. Ожидание появления данных в фрагментах нужного размера - это просто неправильный подход. – Bruno