使用套接字通过tcp进行自定义协议

时间:2017-12-02 15:46:55

标签: java android sockets tcp kotlin

我的协议如下:
[size: UInt16][Channel: Uint16][Protobuff packet]
问题是,我的理解是tcp并不能保证用户发送的整个数据包都会全部到达。因此,技术上可能会出现这样的情况,即我不能立即获取整个消息,甚至是使用此消息发送另一条消息的一部分。

当我假设我将立即获取整个消息时,这很容易,因为我只是从流中读取前2个字节,创建了一个具有适当大小的缓冲区并调用read(buffer)但是,如果我开始考虑切断的可能性,我不知道如何处理它。我尝试了以下方法:

    val input = clientSocket.getInputStream()
    while (!isStopped) {
        val bufferedInputStream: BufferedInputStream = BufferedInputStream(input)
        val dataInputStream: DataInputStream = DataInputStream(bufferedInputStream)
        var messageSize: Int? = null
        var channel: Int? = null
        while (true) {
            if (messageSize == null) {
                messageSize = dataInputStream.readUnsignedShort()
            }
            if (channel == null) {
                channel = dataInputStream.readUnsignedShort()
            }
            var bytesRead = 0
            var didReachEnd = false
            val buffer = ByteArray(messageSize - 4)
            while (bytesRead != -1 && bytesRead != messageSize) {
                val temp = dataInputStream.read(buffer)
                if (temp == -1) {
                    didReachEnd = true
                } else {
                    bytesRead += temp
                }
                Logger.debug(this::class.java, "bytes read: $bytesRead out of $messageSize")
            }
            val packet = ChatChannel.Packet.parseFrom(buffer)
            print(packet.chatMessage.messageText)
        }
    }

我尝试通过从客户端套接字执行以下操作来模仿消息拆分:

        val chatPacket = ChatChannel.Packet.newBuilder().setChatMessage(chatChannel).build()
        val sendPacket = Packet(0,chatPacket.toByteArray())
        val sendPacketByteArray = sendPacket.toByteArray()


        val halfPoint = sendPacketByteArray.size/2
        val firstSlice = sendPacketByteArray.sliceArray(IntRange(0,halfPoint))
        val secondSlice = sendPacketByteArray.sliceArray(IntRange(halfPoint,halfPoint+(halfPoint%2)))

        s.getOutputStream().write(firstSlice)
        sleep(200)
        s.getOutputStream().write(secondSlice)

但是我的实现只是在第二次运行时挂起在以下行:

bytesRead = dataInputStream.read(buffer,0,buffer.size)

我有一种感觉我不知道套接字是如何工作的,但是我看到的每个实现都假定当用户完成发送时他会关闭连接,但就我而言,它是一个聊天程序,所以用户只有在完成聊天时才会关闭连接。

我想要实现的目标:

  • 读取尺寸
  • 读取频道
  • 读取字节,直到size的缓冲区已满
  • 解析消息
  • 通知
  • 回到第0步

编辑: 经过进一步的研究,我发现使用Java中的protobuf的MessageLite库似乎有一些名为writeDelimitedTo(outputStream)和parseDelimitedFrom(inputStream)的方法,所以看起来大部分工作都是为我完成的。

1 个答案:

答案 0 :(得分:0)

两件事:

while (bytesRead != -1 && bytesRead != messageSize) {
                bytesRead = dataInputStream.read(buffer,0,buffer.size)

对我来说似乎很奇怪。你没有增加bytesRead。也就是说,如果messageSize为100并且您在第一次读取时读取了90个字节,则在第二次读取时将读取10个字节。 你不能简单地说' bytesRead + = ...'因为它可能会返回-1。所以你需要另一个变量。检查返回值是否为非负值,然后将增量转换为byteCounter。需要将此byteCounter与messageSize进行比较。

另外:如果您所做的只是为Google protobuf消息提供多个频道,请不要这样做!创建包含protobuf消息和通道变量的protobuf消息。这应该会让生活变得更轻松。