TCP沾包问题

面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;

  • 调用write时, 数据会先写入发送缓冲区中;
  • 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;

沾包问题

  • 粘包问题中的 “包” , 是指的应用层的数据包;
  • 在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段;
  • 站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中;
  • 站在应用层的角度, 看到的只是一串连续的字节数据;
  • 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包.

正常情况
在这里插入图片描述
沾包
在这里插入图片描述
半包
在这里插入图片描述

代码实现

客户端发送10条消息给服务器端。

//服务器端
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerError {
    //端口号
    private static final int port = 9005;
    //内容长度
    private static final int len = 1024;

    public static void main(String[] args) throws IOException {
        //创建 TCP 服务端
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务器已启动");
        //得到客户端连接
        Socket socket = serverSocket.accept();

        System.out.println(String.format("已连接,IP:%s,Port:%d",socket.getInetAddress().getHostAddress(),socket.getPort()));

        try(InputStream inputStream = socket.getInputStream()) {
            while(true) {
                byte[] bytes = new byte[len];
                //将内容读取到数组中
                int result = inputStream.read(bytes,0,len);
                if(result > 0) {
                    String msg = new String(bytes);
                    System.out.println("收到消息:" + msg);
                }
            }
        }
    }
}


//客户端
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClientError {
    //IP
    private  static final String IP = "127.0.0.1";
    //端口号
    private  static final int Port = 9005;
    public static void main(String[] args) throws IOException {
        //创建Socket 连接服务器
        Socket socket = new Socket(IP,Port);
        String msg = "hello world!";
        //得到发送对象
        try(OutputStream outputStream = socket.getOutputStream()) {
            for (int i = 0; i < 10; i++) {
                //发消息给服务器端
                outputStream.write(msg.getBytes());
                outputStream.flush();

            }
        }
    }
}

结果
在这里插入图片描述

解决方案1

1.使用 \n 作为流的结束符,这样流就有边界了,就能正常的收、发消息了。

代码实现

//服务器端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerError {
    //端口号
    private static final int port = 9005;
    //内容长度
    private static final int len = 1024;

    public static void main(String[] args) throws IOException {
        //创建 TCP 服务端
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务器已启动");
        //得到客户端连接
        Socket socket = serverSocket.accept();

        System.out.println(String.format("已连接,IP:%s,Port:%d",socket.getInetAddress().getHostAddress(),socket.getPort()));


        //解决方案1
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            while(true) {
                //按行分割读取内容
                String msg = reader.readLine();
                if(msg != null && !msg.equals(" ")) {
                    System.out.println("收到消息:" + msg);
                }
            }
        }
    }
}

//客户端
import java.io.*;
import java.net.Socket;

public class TCPClientError {
    //IP
    private  static final String IP = "127.0.0.1";
    //端口号
    private  static final int Port = 9005;
    public static void main(String[] args) throws IOException {
        //创建Socket 连接服务器
        Socket socket = new Socket(IP,Port);
        //字符串末尾加\n
        String msg = "hello world!\n";
        //得到发送对象
        try(OutputStream outputStream = socket.getOutputStream()) {
            for (int i = 0; i < 10; i++) {
                //发消息给服务器端
                outputStream.write(msg.getBytes());
                outputStream.flush();
            }
        }
    }
}

打印结果
在这里插入图片描述



解决方案2

2.每次发送固定大小的流信息,这样也能确定每个数据边界,正常收、发消息了。

代码实现

//服务器端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerError {
    //端口号
    private static final int port = 9005;
    //内容长度
    private static final int len = 1024;

    public static void main(String[] args) throws IOException {
        //创建 TCP 服务端
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务器已启动");
        //得到客户端连接
        Socket socket = serverSocket.accept();

        System.out.println(String.format("已连接,IP:%s,Port:%d",socket.getInetAddress().getHostAddress(),socket.getPort()));

        //解决方案2
        try(InputStream inputStream = socket.getInputStream()) {
            while(true) {
                byte[] bytes = new byte[len];
                //将内容读取到数组中
                int result = inputStream.read(bytes,0,len);
                if(result > 0) {
                    String msg = new String(bytes);
                    System.out.println("收到消息:" + msg.trim());
                }
            }
        }
    }
}


//客户端
import java.io.*;
import java.net.Socket;

public class TCPClientError {
    //IP
    private  static final String IP = "127.0.0.1";
    //端口号
    private  static final int Port = 9005;
    public static void main(String[] args) throws IOException {
        //创建Socket 连接服务器
        Socket socket = new Socket(IP,Port);
        //字符串末尾加\n
        String msg = "hello world!\n";
        //得到发送对象
        try(OutputStream outputStream = socket.getOutputStream()) {
            //规定流的大小就为1024
            byte[] bytes = new byte[1024];
            int index = 0;
            for(Byte item : msg.getBytes()) {
                bytes[index++] = item;
            }
            for (int i = 0; i < 10; i++) {
                //发消息给服务器端
                outputStream.write(bytes);
                outputStream.flush();
            }
        }
    }
}

执行结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值