【问题标题】:Java - tcp packet issuesJava - tcp 数据包问题
【发布时间】:2015-10-12 02:23:03
【问题描述】:

我正在尝试连接到使用Steam RCON Protocol 的 Steam 游戏服务器的 RCON。我用谷歌搜索了一下,发现有人为这个确切目的做了一段时间的以下代码。在检查了一段时间的代码后,它确实看起来应该可以工作,但由于某种原因它没有。

代码如下:

class RconSteam {

    /*
     * The constants are the packet types.
     */
    static final int EXECUTE_COMMAND_PACKET = 2;
    static final int AUTHORIZATION_PACKET = 3;
    static final int AUTHORIZATION_RESPONSE = 2;
    static final SocketAddress serverAddress = new InetSocketAddress("IP ADDRESS", PORT);
    static final String rconPassword = "password";
    static InputStream inputStream;
    static OutputStream outputStream;

    /**
     * Starts the application.
     * 
     * @param args
     *            command-line arguments
     * @throws IOException
     */
    public static void main(String[] args) {
        try {
            /*
             * Prepare the socket and retrieve the streams.
             */
            Socket socket = new Socket();
            socket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
            socket.connect(serverAddress);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();

            /*
             * Authorize the user and then send commands. Multiple commands can
             * be sent, but the user may need to be reauthorized after some
             * time.
             */
            boolean auth = sendAuthorizationPacket();
            if (auth) {
                if(sendCommand("serverchat HELLO"))
                {
                    System.out.println("WINNER");
                } else {
                    System.out.println("FAILED");
                }
            } else {
                System.out.println("OH NOES");
            }
            /*
             * Close the socket once its done being used.
             */
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a command to the server. The user must be authorized for the server
     * to execute the commands.
     * 
     * @param command
     *            the command
     * @throws IOException
     */
    static boolean sendCommand(String command) throws IOException {
        byte[] packet = createPacket(2000, EXECUTE_COMMAND_PACKET, command);
        outputStream.write(packet);

        /*
         * The server responds to command execution packets as well, but those
         * responses are not very important. However, it may be worthwhile to
         * check if the server actually responded with text. If a response
         * contains no textual message, that usually means that the user needs
         * to be reauthorized with the server. Another way to check if the user
         * needs to be reauthorized is simply by joining the server and seeing
         * if the commands sent have any effect.
         */
        ByteBuffer response = parsePacket();

        int type = response.getInt(8);
        int id = response.getInt(4);

        System.out.println(type);
        System.out.println(id);

        return ((type == EXECUTE_COMMAND_PACKET) && (id) == 2000);
    }

    /**
     * Sends an authorization packet to the server.
     * 
     * @return whether or not the user was authorized by the server
     * @throws IOException
     */
    static boolean sendAuthorizationPacket() throws IOException {
        /*
         * Create the authorization packet and send it.
         */
        byte[] packet = createPacket(1000, AUTHORIZATION_PACKET, rconPassword);
        outputStream.write(packet);

        /*
         * Read the response (the first packet is a junk one) and check if the
         * server authorized the user. The user is authorized if the server
         * responds with the same packet ID as the one sent, which is 1000 in
         * this case.
         */
        ByteBuffer response = parsePacket();
        return (response.getInt(8) == AUTHORIZATION_RESPONSE) && (response.getInt(4) == 1000);
    }

    /**
     * Creates an RCON packet.
     * 
     * @param id
     *            the packet ID (not an opcode)
     * @param type
     *            the type
     * @param command
     *            the command
     * @return the bytes representing the packet
     */
    static byte[] createPacket(int id, int type, String command) {
        ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
        packet.order(LITTLE_ENDIAN);
        packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
        return packet.array();
    }

    /**
     * Parses the next packet from the socket's input stream.
     * 
     * @return the next packet
     * @throws IOException
     */
    static ByteBuffer parsePacket() throws IOException {
        /*
         * Read the length of the packet.
         */
        byte[] length = new byte[4];
        inputStream.read(length);

        /*
         * Create a buffer to contain the packet's payload.
         */
        ByteBuffer packet = ByteBuffer.allocate(4120);
        packet.order(LITTLE_ENDIAN);
        packet.put(length);

        /*
         * Read the payload.
         */
        for (int i = 0; i < packet.getInt(0); i++) {
            packet.put((byte) inputStream.read());
        }

        return packet;
    }
}

程序在程序中运行没有错误,但是它似乎只在第一次发送数据包时从服务器获得响应。我可以成功地对服务器进行身份验证并从服务器获得响应,但是第二个命令似乎没有得到正确响应并关闭连接(?)。

我尝试测试以立即跳到serverchat HELLO 命令,但我确实从服务器收到了我未通过身份验证的响应。

我想看看 Wireshark 发生了什么,但我不知道该怎么做。我有限的wireshark经验告诉我发送第二个数据包后出现了问题。

此时,我不完全确定问题是否出在此代码中,或者是否与服务器或网络相关(如果是,抱歉在 SO 上发布)。不过其他工具似乎工作正常,所以我猜它与代码有关。

任何帮助表示赞赏!

编辑:如果屏幕太小,请链接到屏幕截图:http://i.imgur.com/5CZXLaT.png

编辑 2:更新后的代码:

class RconSteam {

    /*
     * The constants are the packet types.
     */
    static final int EXECUTE_COMMAND_PACKET = 2;
    static final int AUTHORIZATION_PACKET = 3;
    static final int AUTHORIZATION_RESPONSE = 2;
    static final SocketAddress serverAddress = new InetSocketAddress("IP HERE", PORT);
    static final String rconPassword = "password\0";
    static InputStream inputStream;
    static OutputStream outputStream;

    /**
     * Starts the application.
     * 
     * @param args
     *            command-line arguments
     * @throws IOException
     */
    public static void main(String[] args) {
        try {
            /*
             * Prepare the socket and retrieve the streams.
             */
            Socket socket = new Socket();
            socket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
            socket.connect(serverAddress);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();

            /*
             * Authorize the user and then send commands. Multiple commands can
             * be sent, but the user may need to be reauthorized after some
             * time.
             */
            boolean auth = sendAuthorizationPacket();
            if (auth) {
                if(sendCommand("serverchat HELLO"))
                {
                    System.out.println("WINNER");
                } else {
                    System.out.println("FAILED");
                }
            } else {
                System.out.println("OH NOES");
            }
            /*
             * Close the socket once its done being used.
             */
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a command to the server. The user must be authorized for the server
     * to execute the commands.
     * 
     * @param command
     *            the command
     * @throws IOException
     */
    static boolean sendCommand(String command) throws IOException {
        byte[] packet = createPacket(2000, EXECUTE_COMMAND_PACKET, command+'\0');
        outputStream.write(packet);

        /*
         * The server responds to command execution packets as well, but those
         * responses are not very important. However, it may be worthwhile to
         * check if the server actually responded with text. If a response
         * contains no textual message, that usually means that the user needs
         * to be reauthorized with the server. Another way to check if the user
         * needs to be reauthorized is simply by joining the server and seeing
         * if the commands sent have any effect.
         */
        ByteBuffer response = parsePacket();

        int type = response.getInt(8);
        int id = response.getInt(4);

        System.out.println(type);
        System.out.println(id);

        return ((type == EXECUTE_COMMAND_PACKET) && (id) == 2000);
    }

    /**
     * Sends an authorization packet to the server.
     * 
     * @return whether or not the user was authorized by the server
     * @throws IOException
     */
    static boolean sendAuthorizationPacket() throws IOException {
        /*
         * Create the authorization packet and send it.
         */
        byte[] packet = createPacket(1000, AUTHORIZATION_PACKET, rconPassword);
        outputStream.write(packet);

        /*
         * Read the response (the first packet is a junk one) and check if the
         * server authorized the user. The user is authorized if the server
         * responds with the same packet ID as the one sent, which is 1000 in
         * this case.
         */
        ByteBuffer response = parsePacket();
        return (response.getInt(8) == AUTHORIZATION_RESPONSE) && (response.getInt(4) == 1000);
    }

    /**
     * Creates an RCON packet.
     * 
     * @param id
     *            the packet ID (not an opcode)
     * @param type
     *            the type
     * @param command
     *            the command
     * @return the bytes representing the packet
     */
    static byte[] createPacket(int id, int type, String command) {
        ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
        packet.order(LITTLE_ENDIAN);
        //packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
        packet.putInt(command.length() + 14).putInt(id).putInt(type).put(command.getBytes()).put((byte)0).put((byte)0);
        return packet.array();
    }

    /**
     * Parses the next packet from the socket's input stream.
     * 
     * @return the next packet
     * @throws IOException
     */
    static ByteBuffer parsePacket() throws IOException {
        /*
         * Read the length of the packet.
         */
        byte[] length = new byte[4];
        inputStream.read(length);

        /*
         * Create a buffer to contain the packet's payload.
         */
        ByteBuffer packet = ByteBuffer.allocate(4120);
        packet.order(LITTLE_ENDIAN);
        packet.put(length);

        /*
         * Read the payload.
         */
        for (int i = 0; i < packet.getInt(0); i++) {
            packet.put((byte) inputStream.read());
        }

        return packet;
    }
}

【问题讨论】:

    标签: java sockets networking tcp packet


    【解决方案1】:

    问题似乎出在您创建数据包的方式上。

    static byte[] createPacket(int id, int type, String command) {
      ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
      packet.order(ByteOrder.LITTLE_ENDIAN);
      packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
      return packet.array();}
    

    一个症状是您分配了额外的 16 个字节,但长度仅增加了 12 个。

    从 Steam RCON 协议页面,数据包结构如下:

    Size            32-bit little-endian Signed Integer
    ID              32-bit little-endian Signed Integer
    Type            32-bit little-endian Signed Integer
    Body            Null-terminated ASCII String
    Empty String    Null-terminated ASCII String (0x00)
    

    请注意您的实施中缺少的几件事:

    1. 您的命令不是以 NULL 结尾的字符串(Java 不会以 null 终止 String。但 Steam 协议需要它。您必须在放置后添加一个 零字节命令进入ByteBuffer

    2. 您正在使用零值整数而不是单个零字节来终止数据包。

    3. 您的大小应等于:
      3 个整数 + 命令长度 + 1 字节 + 1 字节 == 命令长度 + 14

    因此您的数据包创建应该是:

    packet.putInt(command.length() + 14).putInt(id).putInt(type).put(command.getBytes()).put(0).put(0);
    

    希望这会有所帮助。

    【讨论】:

    • 感谢您的回复。我尝试了您的建议,但问题仍然存在。也许我错过了什么?我在 OP 底部添加了编辑后的代码。
    • 我应该注意到,对服务器的身份验证(又名第一个数据包)在您提出建议之前和之后都有效。这只是第二个永远无法工作的数据包。
    猜你喜欢
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    • 2012-03-01
    • 1970-01-01
    相关资源
    最近更新 更多