如何通过带有 Java 客户端和 Python 服务器的套接字创建 IPC?

How to create IPC through sockets with a Java client and a Python server?

我试图让两个进程通过本地套接字进行通信:一个 Python server 和一个 Java client.我想在两者之间传递的数据由 Protobuf 对象的 字节 组成,大小可变。我希望与 的连接保持打开状态 并一直使用到程序结束,因为我传递了很多需要处理的对象。

因为 Protobuf 对象的大小可变,所以我在发送包含对象的真实 message/response 之前发送 message/response 的大小。

目前,我正在 Python 端使用来自 socketserver 库的 TCPServer。我实现了以下处理程序:

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def recv_all(self, n):
        # Helper function to recv n bytes or return None if EOF is hit
        data = b''
        while len(data) < n:
            packet = self.request.recv(n - len(data))
            if not packet:
                return None
            data += packet
        return data

    def handle(self):
        logger.debug("Beginning of handle cycle for client: {}.".format(self.client_address))

        while True:
            if True: # please disregard this if condition
                # Receive 4 bytes (1 int) denoting the size of the message
                data_length_bytes: bytes = self.recv_all(4)
                logger.debug('Received data_length: {}'.format(data_length_bytes))

                # If recv read an empty request b'', then client has closed the connection
                if not data_length_bytes:
                    break

                data_length: int = int.from_bytes(data_length_bytes.strip(), byteorder='big')
                data: bytes = self.recv_all(data_length).strip()

                response: bytes = data.upper()

                # Send length of response first
                self.request.sendall(len(response).to_bytes(4, byteorder='big'))
                # Send response
                self.request.sendall(response)

                logger.debug(
                    'Sent response to: {}. Size of response: {} bytes. Response: {}.'.format(self.client_address,
                                                                                             len(response),
                                                                                             response))


        logger.debug("End of handle cycle for client: {}.".format(self.client_address))

以及以下客户:

class SocketClient
{
    private static Socket socket;
    private int port;
    private DataOutputStream out;
    private DataInputStream in;

    SocketClient(int port)
    {
        this.port = port;
        this.createSocket();
    }

    private void createSocket() {
        InetAddress address;
        try {
            address = InetAddress.getByName("localhost");
            socket = new Socket(address, port);
            this.out = new DataOutputStream(socket.getOutputStream());
            this.in = new DataInputStream(socket.getInputStream());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    byte[] sendMessageAndReceiveResponse(byte[] messageToSend){
        try {
            if(true) {  // again, please disregard this condition
                //Send the size of the message to the server
                this.out.writeInt(messageToSend.length);
                out.flush();

                this.out.write(messageToSend);
                out.flush();

                //Get the response message from the server
                int length = in.readInt();                    // read length of incoming message

                byte[] buffer = null;
                if(length>=0) {
                    buffer = new byte[length];
                    in.readFully(buffer, 0, buffer.length); // read the message
                }

                return buffer;
            }
        }
        catch (ConnectException exception) {
            System.out.println("ATTENTION! Could not connect to socket. Nothing was retrieved from the Python module.");
            exception.printStackTrace();
            return null;
        }
        catch (Exception exception)
        {
            exception.printStackTrace();
            return null;
        }
    }

    void close(){
        //Closing the socket
        try
        {
            in.close();
            out.close();
            socket.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

我运行启动Python服务器后进行如下实验:

        SocketClient socketClient = new SocketClient(5000);

        byte[] response;

        // Case 1
        response = socketClient.sendMessageAndReceiveResponse("12345678".getBytes());
        System.out.println(new String(response));

        // Case 2
        response = socketClient.sendMessageAndReceiveResponse("123456781".getBytes());
        System.out.println(new String(response));

        // Case 3
        response = socketClient.sendMessageAndReceiveResponse("12345678123456781".getBytes());
        System.out.println(new String(response));


        socketClient.close();

案例 1 和案例 3 运行良好。但是,当我 运行 case 2,在 Python 服务器端,我得到以下日志:

DEBUG -- [handle()] Received data_length: b'\x00\x00\x00\t' # The '\t' shouldn't be here. A '\x09' should.

然后服务器抛出异常并退出连接。每个 8 < 长度 < 14 的字符串都会发生这种情况。我做错了什么,有没有更简单的方法来实现我想要的?

我明白了为什么我在处理 8 < 长度 < 14 的消息时遇到问题。

当长度等于 9 时,我得到了 \t 个字符。我注意到如果我将长度更改为 10,它将变成 \n。到 13,\r。我意识到没有任何 \t 神奇地出现。 Python 由于某种原因将 \x09 转换为 \t,因为水平制表符 \t 的 ASCII 码等于 9!

当我在这一行中应用 strip() 函数时:

data_length: int = int.from_bytes(data_length_bytes.strip(), byteorder='big')

、Python删了我的\t,其实是我的\x09。我的问题是在 之前记录值 剥离它,所以我花了很长时间才弄清楚我的错误。

因此解决方案就是不使用 strip()。我在这里留下我当前的工作代码(至少是为了我的测试),供其他人使用:

Python 服务器处理程序:

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def recv_all(self, n):
        # Helper function to recv n bytes or return None if EOF is hit
        data = b''
        while len(data) < n:
            packet = self.request.recv(n - len(data))
            if not packet:
                return None
            data += packet
        return data

    def handle(self):
        while True:
            data_length_bytes: bytes = self.recv_all(4)

            # If recv read an empty request b'', then client has closed the connection
            if not data_length_bytes:
                break

            # DON'T DO strip() ON THE DATA_LENGTH PACKET. It might delete what Python thinks is whitespace but
            # it actually is a byte that makes part of the integer.
            data_length: int = int.from_bytes(data_length_bytes, byteorder='big')

            # Don't do strip() on data either (be sure to check if there is some error if you do use)
            data: bytes = self.recv_all(data_length)

            response: bytes = data.upper()

            self.request.sendall(len(response).to_bytes(4, byteorder='big'))
            self.request.sendall(response)

Java 客户端保持不变,但没有我出于调试原因使用的 if(true) 条件。