【问题标题】:Java Server SSL Socket and Python Client SSL socket - problems when server send messagesJava 服务器 SSL 套接字和 Python 客户端 SSL 套接字 - 服务器发送消息时的问题
【发布时间】:2012-08-02 23:55:08
【问题描述】:

我正在尝试与服务器 SSL 套接字 Java 和客户端 SSL 套接字 Python 通信。发送的第一条消息是可以的,但是当服务器发送另一条消息时,客户端会收到分成两部分的消息。例如:如果服务器发送消息“abcdefghij”,客户端会先收到“a”,然后是“bcdefghij”。

有人知道为什么在第一次收到消息后分为两部分吗?问候。

客户端代码:

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

【问题讨论】:

    标签: java python ssl client


    【解决方案1】:

    您似乎希望始终能够从另一侧读取您在一个块中发送的任何数量的数据。

    这是一个常见错误,并非特定于 SSL/TLS,也与普通 TCP 通信有关。

    您应该始终循环并阅读您打算阅读的任何内容。您还应该定义您的协议(或使用现有协议)以考虑命令和请求/响应终止符。

    例如,HTTP 使用空行来指示标头的结尾,并使用 Content-Length 标头或分块传输编码来告诉接收者何时停止读取正文。

    SMTP 使用行分隔命令和单个. 作为邮件结尾。

    【讨论】:

    • 这在一般意义上都是正确的,但正是 naggle/ack 交互导致了他所看到的特定行为。如果您的要求之一是性能,它还会导致两个数据包之间出现约 40 毫秒的延迟,这是一个严重的问题。有没有见过两个人站在 10 英尺远的地方,每个人都在等待另一个人的到来?就是那个。直到你在数据包嗅探器中看到它之前,你都很难找到它。 (如果你从不发送小块数据,你就看不到它)。
    • @BrianRoach,您的回答解释了这一点,但人们一般不应该依赖这一点(您不能总是知道远程方是否使用 Naggle 算法)。 OP 的期望与尝试仅使用读取来检测连接关闭(另一个常见错误)密切相关。
    • 公平点。我认为我们两个答案的组合几乎涵盖了所有基础。我根本不是 python 人 - 他的 recv() 在调用时返回 TCP 缓冲区中的任何内容但如果没有则阻塞?
    • 我根据您的 cmets 更改了服务器和客户端的代码,但总是在第一次传输第一个字节之后发送消息,其余消息稍后到达。感谢您的帮助,必须更改为识别消息而组装的协议。
    • 但是不要认为问题是因为使用Python作为客户端和服务器,或者Java作为客户端和服务器的实现没有发生,也用C客户端和服务器测试,问题没有发生.使用 C 和 Python 作为客户端和服务器的测试没有发生。只有当我使用Java和C,或者Java和Python时才描述。
    【解决方案2】:

    它是服务器端的 Naggle 算法和客户端 TCP 堆栈中的延迟 ACK 的组合。您还会发现两个数据包之间的延迟约为 40 毫秒。

    在服务器端禁用 Naggle 算法以进行补救:

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

    更多关于为什么会出现这种情况的信息:http://www.stuartcheshire.org/papers/NagleDelayedAck/

    编辑添加:请注意下面布鲁诺的回答。虽然这描述了您在此处看到的具体原因,但不能保证您期望来自服务器的数据的方式。

    【讨论】:

    • +1 用于解释这里的原因,但这并不能解决根本问题。
    • @BrianRoach 我根据您的 cmets 更改了服务器和客户端的代码,但总是在第一次传输第一个字节之后发送消息,其余消息稍后到达。感谢您的帮助,必须更改为识别消息而组装的协议。
    【解决方案3】:

    我尝试通过两种方式解决我的问题,消息总是在第一个字符处中断,所以我决定在每条消息的开头和结尾添加三个空格。因此,当客户收到它们时,请对消息进行修剪。 我发现的另一种方法是使用 DataOutputStream,该方法逐字节传递 writeBytes,在服务器上使用它,客户端在接收数据时发生 q 变化,消息必须构建客户端处理才能最终完成我想要的内容消息的结尾。感谢讨论!

    【讨论】:

    • 您似乎不明白的是,TCP 连接会为您提供连续的数据流。您在一端写入的块不一定是您在另一端读取的块。当所有块连接在一起时,TCP 保证的只是整个序列的顺序。使用空格作为分隔符是可行的,但您总是需要连接缓冲区并继续循环,直到您读取这些空格。
    • 抱歉,我在解决方案中采用了第二种方法。然而,只是有点奇怪,因为从我读到的 TCP 数据包的大小是 1460 字节,所以对我来说,遇到这 1460 字节的情况与我在一端写的完全一样。当然,我的读取缓冲区应该已经预定了这个大小,如果不是被破坏的话。我感谢您的解释,并理解我必须先处理收到的消息然后处理它。再次感谢。
    • 1460 字节的限制可能与 MTU 相关。再次,不要担心。使用 TCP,您应该不断地读取缓冲区,无论它需要多少次才能获得所需的消息,直到您定义的分隔符为止。期望数据以正确大小的片段出现是错误的方法。
    猜你喜欢
    • 2021-07-29
    • 2020-10-12
    • 2011-11-28
    • 1970-01-01
    • 2014-02-14
    • 1970-01-01
    • 2016-08-31
    • 1970-01-01
    相关资源
    最近更新 更多