搞懂Socket通信(四)

411 阅读3分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

搞懂Socket通信(四)

上篇文章讲解了基本的Socket TCP长链接,这篇文章是对Socket TCP的进阶。探索它的高级用法和遇到的疑难杂症。

一、分包粘包如何处理

原因

TCP 是流的形式,在某些条件下对数据进行了优化

场景

当前发送方发送了两个包,两个包的内容如下:
123456789
ABCDEFGH

我们希望接收方的情况是:收到两个包,第一个包为:123456789,第二个包为:ABCDEFGH。但是在粘包和分包出现的情况就达不到预期情况。

粘包表现

两个包在很短的时间间隔内发送,比如在0.1秒内发送了这两个包,如果包长度足够的话,那么接收方只会接收到一个包,如下:

123456789ABCDEFGH

粘包解决方法

  1. 约定结束符,遇到结束符时分割数据
  2. 约定数据长度,根据数据长度截取

这里最简单的方法是
在发送数据的时候末尾拼上换行符,在接收数据时使用BufferedReaderrd.readLine()进行接收。

readLine 代表读取了一行的内容。

    @Override
    public void run() {
        while (true) {
            try {
                InputStream inputStream = socket.getInputStream();
                out = socket.getOutputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                String str = reader.readLine();
                if (str == null) continue;
                if (listener != null) {
                    listener.onReceive(this, str);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

分包情况

假设包的长度最长设置为5字节(较极端的假设,一般长度设置为1000到1500之间),那么在没有粘包的情况下,接收方就会收到4个包,如下:

12345
6789
ABCDE
FGH

分包解决方法

在粘包场景中,是需要分割数据。
在分包场景中,需要将多个包合在一起。

解决方法
约定一个分隔符,当该条数据中包含该分割符时进行分割,不包含分隔符时,就合并下一条数据,包含分割符时就进行分割,提取数据。

二、如何传输byte和String

2.1 发送

我们平时是怎么发送byte和String的呢?举例代码:

String str = "hello world"+ "\n";
byte[] bytes = 图片字节流;
// 获取socket输出流
OutputStream out = socket.getOutputStream();
// 发送字符串
out.write(str.getBytes());
// 发送byte数组
out.write(bytes);

发送代码部分很简单,即使是String也是转了byte发送的。那客户端在接收的时候如何做区分呢,客户端怎么知道接收的byte是要转字符串,还是转图片或其它类型呢?

那么就需要改进代码了,在发送端增加一个标识,客户端在接收到消息的时候,根据标识进行区分,这样接收端就知道消息该怎么转了。

修改之后的发送端代码举例:

// 定义两个标识
final int SOCKET_STRING = 1;
final int SOCKET_BYTE = 2;

public void sendTcpPacket(final byte[] packet, final SendCallback sendCallback) {
        ThreadPool.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    OutputStream outputStream = mSocket.getOutputStream();
                    outputStream.write("标识");
                    outputStream.write(packet);
                    if (sendCallback != null)
                        sendCallback.success();
                } catch (Exception e) {
                    if (sendCallback != null)
                        sendCallback.failed();
                }
            }
        });
    }
   

2.2 接收

接收到字节,判断第一个标识位是什么,则对应相应的解析操作。

 @Override
    public void run() {
        while (true) {
            try {
                InputStream inputStream = socket.getInputStream();
                DataInputStream input = new DataInputStream(inputStream);

                byte[] buffer = new byte[2048];
                //消息长度
                int realLength = input.read(buffer, 0, 2048);
                System.out.println("接收的消息长度:" + realLength);
                //传输的实际byte[]
                byte[] buffer1 = new byte[realLength];
                for (int i = 0; i < buffer1.length; i++) {
                    buffer1[i] = buffer[i];
                }

                if (listener != null) {
                    listener.onReceive(this, buffer1);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }