TCP与应用层协议

科技资讯 投稿 7800 0 评论

TCP与应用层协议

1、小故事与前言

我问:"服务端选什么协议,MQTT或者Websocket?"

我又确认了一遍:"用什么通信协议?"

给我气笑了。最后在我的坚持下,他才勉强以他啥都能做,以我为准的理由妥协了。

    TCP如何处理消息半包,粘包
  1. TCP如何确认应用状态
  2. TCP如何处理服务认证问题

他的回答:

    消息发送,服务端加个标识字符检验,进行数据帧分割
  1. 发个心跳包

2、TCP与应用层协议

MQTT,Websocket属于应用层协议,基于TCP。

一.消息半包、粘包

①介绍

什么是半包与粘包?可以参考我的Netty进阶文章

②演示

服务端代码:

请不要拿readUTF方法进行测试,sendUTF与readUTF是配套使用的,jdk原生进行了半包粘包的处理,对于发送消息添加了额外的消息用于处理问题。

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author  zko0
 * @date  2023/3/5 0:02
 * @description
 */
public class SimpleSocketServer {
     public static void main(String[] args {
          DataInputStream dataInputStream=null;
          ServerSocket serverSocket=null;
          Socket accept=null;
          try {
               serverSocket= new ServerSocket(8081;
               System.out.println("socket服务创建";
               //accept
               accept = serverSocket.accept(;
               System.out.println("client连接";
               //只设置接收缓冲区大小
               accept.setReceiveBufferSize(10;
               InputStream inputStream = accept.getInputStream(;
               dataInputStream= new DataInputStream(inputStream;
               while (true{
                    //创建byte数组用于接收数据
                    byte[] bytes=new byte[10];
                    dataInputStream.read(bytes;
                    System.out.println(new String(bytes;
               }
          } catch (IOException e {
               e.printStackTrace(;
          }finally {
               try {
                    if (dataInputStream!=nulldataInputStream.close(;
                    if (serverSocket!=nullserverSocket.close(;
                    if (accept!=nullaccept.close(;
               } catch (IOException e {
                    e.printStackTrace(;
               }
          }
     }
}

客户端代码:

public class SimpleSocketClient {
    public static void main(String[] args {
        Socket socket =null;
        DataOutputStream dataOutputStream=null;
        try{
            socket=new Socket("127.0.0.1", 8081;
            System.out.println("socket连接";
            OutputStream outputStream= socket.getOutputStream(;
            dataOutputStream= new DataOutputStream(outputStream;
            for (int i = 0; i < 10; i++ {
                dataOutputStream.writeBytes("abc";
            }
            dataOutputStream.flush(;
            while (true;
        }catch (IOException e {
            e.printStackTrace(;
        }
    }
}

测试结果:

很明显,如果服务端直接通过获取的消息进行处理,是存在问题的,对于TCP消息,需要进行半包和粘包的处理,才能使用消息。

③问题解决

readLine(方法

    使用标识符进行消息帧分割

    就比如readLine(使用于读取一行数据。

  1. 这种方式可以解决粘包的问题,但是无法解决半包问题。因为一次发送后,连接就会中断。那么Server获取的数据只会小于单次发送的数据。

  2. 定长帧解码器

    如果发送abc三字节内容,发送数据为:0 0 0 0 3 97 98 99

    你可以参考readUTF(方法,思路相同。

二.心跳

TCP协议是自带心跳的。

在TCP协议里,本身的心跳包机制SO_KEEPALIVE,系统默认设置的是7200秒的启动时间,默认是关闭的

分别表示连接闲置多久才开始发心跳包、连发几次心跳包没有回应表示连接已断开、心跳包之间间隔时间。

Socket中Client关闭,Server能自动感受到连接断开啊?:

FIN数据包给服务器,所以Sevrer能够关闭TCP连接。

FIN数据包的,这时Server就无法感知到Client已经断开了连接。

为什么TCP自带心跳还需要应用层定义心跳:

    TCP心跳机制是传输层实现的,只要当前连接是可用的,对端就会ACK我们的心跳,而对于当前对端应用是否能正常提供服务,TCP层的心跳机制是无法获知的
  1. tcp_keepalive_time参数的设置是秒级,对于极端情况,我们可能想在毫秒级就检测到连接的状态,这个TCP心跳机制就无法办到
  2. 通用性,应用层心跳功能不依赖于传输层协议,如果有一天我们想将传输层协议由TCP改为UDP,那么传输层不提供心跳机制了,应用层的心跳是通用的,此时或许只用修改少量地方代码即可;
  3. 如果连接中存在Sock Proxy或者NAT,他们可能不会处理TCP的Keep Alive包

三.应用报文

在应用层协议中,协议报文都是对应用每个功能定制,且缩小了数据量的。

MQTT第一次报文为CONNETCT报文。在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接 。

    固定头:1--->确定了报文的类型:CONNECT

    • 第6位:用户名标志 User Name Flag
    • 第7位:密码标志 Password Flag
  1. 有效载荷:CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。

3、小结

个人思考后,个人认为将应用层协议分为通用协议特定系统协议俩种。

特定系统协议:如Mysql协议,Redis的RESP协议,都是基于特定应用自己开发的一套网络协议

实在难以想象那种代码水平是否能将Socket自定义协议与消息处理写出基本的骨架出来。希望技术人员专注于技术,共同进步,不要盲目自大。

如果你有较好思考,欢迎指点。

编程笔记 » TCP与应用层协议

赞同 (39) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽