Google Protobuf

  • 编码和解码的基本介绍
  • Netty 本身的编码解码的机制和问题分析
  • Protobuf
  • Protobuf 快速入门实例
    • pom.xml
    • Student.proto
    • NettyServer
    • NettyServerHandler
    • NettyClient
    • NettyClientHandler
  • Protobuf 快速入门实例 2
    • proto
    • NettyServer
    • NettyServerHandler
    • NettyClient
    • NettyClientHandler

编码和解码的基本介绍

1.编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码[示意图]
2.codec(编解码器)的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据

Netty 本身的编码解码的机制和问题分析

1.Netty 自身提供了一些 codec(编解码器)
2.Netty 提供的编码器

  • 1.StringEncoder:对字符串数据进行编码。
  • 2.ObjectEncoder:对Java对象进行编码。
    3.Netty 提供的解码器
  • 1.StringDecoder,对字符串数据进行解码
  • 2.ObjectDecoder,对 Java 对象进行解码
    4.Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码,底层使用的仍是Java序列化技术,而Java序列化技术本身效率就不高,存在如下问题
    *1.无法跨语言
  • 2.序列化后的体积太大,是二进制编码的5倍多。
  • 3.序列化性能太低
    5.引出新的解决方案[Google 的 Protobuf]

Protobuf

1.Protobuf 基本介绍和使用示意图
2.Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC [远程过程调用 remote procedure call ]数据交换格式目前很多公司 从http + json 转向tcp + protobuf,效率会更高
3.参考文档:https://developers.google.com/protocol-buffers/docs/proto 语言指南
4.Protobuf 是以 message 的方式来管理数据的.
5.支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的](支持目前绝大多数语言,例如 C++、C#、Java、python 等)
6.高性能,高可靠性
7.使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用 .proto 文件进行描述。说明,在 idea 中编写 .proto 文件时,会自动提示是否下载 .ptoto 编写插件.可以让语法高亮。
8.然后通过 protoc.exe 编译器根据 .proto 自动生成 .java 文件
9.protobuf 使用示意图

Protobuf 快速入门实例

编写程序,使用 Protobuf 完成如下功能

客户端可以发送一个 StudentPoJo 对象到服务器(通过 Protobuf 编码)
服务端能接收 StudentPoJo 对象,并显示信息(通过 Protobuf 解码)

pom.xml

<dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.6.1</version>
</dependency>

Student.proto

syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf 使用message 管理数据
message Student { //会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的POJO对象int32 id = 1; // Student 类中有 一个属性 名字为 id 类型为int32(protobuf类型) 1表示属性序号,不是值string name = 2;
}

编译
protoc.exe --java_out=.Student.proto
将生成的 StudentPOJO 放入到项目使用


生成的StudentPOJO代码太长就不贴在这里了
包含的内部类Student如下

NettyServer

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;public class NettyServer {public static void main(String[] args) throws Exception {//创建BossGroup 和 WorkerGroup//说明//1. 创建两个线程组 bossGroup 和 workerGroup//2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成//3. 两个都是无限循环//4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数//   默认实际 cpu核数 * 2EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup(); //8try {//创建服务器端的启动对象,配置参数ServerBootstrap bootstrap = new ServerBootstrap();//使用链式编程来进行设置bootstrap.group(bossGroup, workerGroup) //设置两个线程组.channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现.option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)//给pipeline 设置处理器@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//在pipeline加入ProtoBufDecoder//指定对哪种对象进行解码pipeline.addLast("decoder", new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));pipeline.addLast(new NettyServerHandler());}}); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器System.out.println(".....服务器 is ready...");//绑定一个端口并且同步, 生成了一个 ChannelFuture 对象//启动服务器(并绑定端口)ChannelFuture cf = bootstrap.bind(6668).sync();//给cf 注册监听器,监控我们关心的事件cf.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (cf.isSuccess()) {System.out.println("监听端口 6668 成功");} else {System.out.println("监听端口 6668 失败");}}});//对关闭通道进行监听cf.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}}

NettyServerHandler

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.CharsetUtil;/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler*/
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {//读取数据实际(这里我们可以读取客户端发送的消息)/*1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址2. Object msg: 就是客户端发送的数据 默认Object*/@Overridepublic void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {//读取从客户端发送的StudentPojo.StudentSystem.out.println("客户端发送的数据 id=" + msg.getId() + " 名字=" + msg.getName());}//数据读取完毕@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));}//处理异常, 一般是需要关闭通道@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}

NettyClient

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;public class NettyClient {public static void main(String[] args) throws Exception {//客户端需要一个事件循环组EventLoopGroup group = new NioEventLoopGroup();try {//创建客户端启动对象//注意客户端使用的不是 ServerBootstrap 而是 BootstrapBootstrap bootstrap = new Bootstrap();//设置相关参数bootstrap.group(group) //设置线程组.channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//在pipeline中加入 ProtoBufEncoderpipeline.addLast("encoder", new ProtobufEncoder());pipeline.addLast(new NettyClientHandler()); //加入自己的处理器}});System.out.println("客户端 ok..");//启动客户端去连接服务器端//关于 ChannelFuture 要分析,涉及到netty的异步模型ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();//给关闭通道进行监听channelFuture.channel().closeFuture().sync();}finally {group.shutdownGracefully();}}
}

NettyClientHandler

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;public class NettyClientHandler extends ChannelInboundHandlerAdapter {//当通道就绪就会触发该方法@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//发生一个Student 对象到服务器StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("智多星 吴用").build();//Teacher , Member ,Messagectx.writeAndFlush(student);}//当通道有读取事件时,会触发@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

Protobuf 快速入门实例 2

1.编写程序,使用 Protobuf 完成如下功能
2.客户端可以随机发送 StudentPoJo / WorkerPoJo 对象到服务器(通过 Protobuf 编码)
3.服务端能接收 StudentPoJo / WorkerPoJo 对象(需要判断是哪种类型),并显示信息(通过 Protobuf 解码)

proto

syntax = "proto3";
option optimize_for = SPEED; // 加快解析
option java_package="com.atguigu.netty.codec2";   //指定生成到哪个包下
option java_outer_classname="MyDataInfo"; // 外部类名, 文件名/*
1.protobuf 可以使用message 管理其他的message。最终决定使用哪一个message作为传输对象
2.假设你某个项目需要传输20个对象,你不可能新建20个proto文件吧。此时你就可以
在一个文件里定义20个message,最后再用一个总的message(比方说这里的MyMessage)
来决定在实际传输时真正需要传输哪一个对象
3.因为你实际传输的时候大部分情况传输的都是一个对象,所以下面用oneof进行了限制
4.是否可以传多个对象呢?我个人认为是可以的,比如可以通过map(目前我也不太了解proto的语法)*/
message MyMessage {//定义一个枚举类型,DataType如果是0则表示一个Student对象实例,DataType这个名称自定义enum DataType {StudentType = 0; //在proto3 要求enum的编号从0开始WorkerType = 1;}//用data_type 来标识传的是哪一个枚举类型,这里才真正开始定义MyMessage的数据类型DataType data_type = 1;  //所有后面的数字都只是编号而已/*1.oneof关键字 表示每次枚举类型进行传输时,限制最多只能传输一个对象。dataBody名称也是自定义的2.为什么这里的序号是2呢?因为上面DataType data_type = 1  占了第一个序号了3.MyMessage里真正出现的类型只有两个①DataType类型②Student类型或者Worker类型(这两个在真正传输的时候只会有一个出现)*/oneof dataBody {Student student = 2;  //注意这后面的数字也都只是编号而已Worker worker = 3;}
}
message Student {int32 id = 1;//Student类的属性string name = 2; //
}
message Worker {string name=1;int32 age=2;
}

NettyServer

import com.atguigu.netty.codec.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;public class NettyServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup(); //8try {//创建服务器端的启动对象,配置参数ServerBootstrap bootstrap = new ServerBootstrap();//使用链式编程来进行设置bootstrap.group(bossGroup, workerGroup) //设置两个线程组.channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现.option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)//给pipeline 设置处理器@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//在pipeline加入ProtoBufDecoder//指定对哪种对象进行解码pipeline.addLast("decoder", new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));pipeline.addLast(new NettyServerHandler());}}); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器System.out.println(".....服务器 is ready...");//绑定一个端口并且同步, 生成了一个 ChannelFuture 对象//启动服务器(并绑定端口)ChannelFuture cf = bootstrap.bind(6668).sync();//给cf 注册监听器,监控我们关心的事件cf.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (cf.isSuccess()) {System.out.println("监听端口 6668 成功");} else {System.out.println("监听端口 6668 失败");}}});//对关闭通道进行监听cf.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}}

NettyServerHandler

import com.atguigu.netty.codec.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;//public class NettyServerHandler extends ChannelInboundHandlerAdapter {public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {//读取数据实际(这里我们可以读取客户端发送的消息)/*1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址2. Object msg: 就是客户端发送的数据 默认Object*/@Overridepublic void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {//根据dataType 来显示不同的信息MyDataInfo.MyMessage.DataType dataType = msg.getDataType();if(dataType == MyDataInfo.MyMessage.DataType.StudentType) {MyDataInfo.Student student = msg.getStudent();System.out.println("学生id=" + student.getId() + " 学生名字=" + student.getName());} else if(dataType == MyDataInfo.MyMessage.DataType.WorkerType) {MyDataInfo.Worker worker = msg.getWorker();System.out.println("工人的名字=" + worker.getName() + " 年龄=" + worker.getAge());} else {System.out.println("传输的类型不正确");}}//数据读取完毕@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));}//处理异常, 一般是需要关闭通道@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}

NettyClient

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;public class NettyClient {public static void main(String[] args) throws Exception {//客户端需要一个事件循环组EventLoopGroup group = new NioEventLoopGroup();try {//创建客户端启动对象//注意客户端使用的不是 ServerBootstrap 而是 BootstrapBootstrap bootstrap = new Bootstrap();//设置相关参数bootstrap.group(group) //设置线程组.channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//在pipeline中加入 ProtoBufEncoderpipeline.addLast("encoder", new ProtobufEncoder());pipeline.addLast(new NettyClientHandler()); //加入自己的处理器}});System.out.println("客户端 ok..");//启动客户端去连接服务器端//关于 ChannelFuture 要分析,涉及到netty的异步模型ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();//给关闭通道进行监听channelFuture.channel().closeFuture().sync();}finally {group.shutdownGracefully();}}
}

NettyClientHandler

import com.atguigu.netty.codec.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;import java.util.Random;public class NettyClientHandler extends ChannelInboundHandlerAdapter {//当通道就绪就会触发该方法@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//随机的发送Student 或者 Workder 对象int random = new Random().nextInt(3);MyDataInfo.MyMessage myMessage = null;if(0 == random) { //发送Student 对象myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("玉麒麟 卢俊义").build()).build();} else { // 发送一个Worker 对象myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.WorkerType).setWorker(MyDataInfo.Worker.newBuilder().setAge(20).setName("老李").build()).build();}ctx.writeAndFlush(myMessage);}//当通道有读取事件时,会触发@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

Google Protobuf相关推荐

  1. google ProtoBuf开发者指南

    目录 1   概览 1.1   什么是protocol buffer 1.2   他们如何工作 1.3   为什么不用XML? 1.4   听起来像是为我的解决方案,如何开始? 1.5   一点历史 ...

  2. Google protobuf解析消息逻辑的版本问题

    在分析caffe2源码的过程中,由于caffe2使用protobuf作为网络结构和网络参数序列化和反序列化的机制,想在反序列化之前进行加解密处理,这是反向protouf其实有两个版本的实现来进行消息的 ...

  3. 使用CSharp编写Google Protobuf插件

    什么是 Google Protocol Buffer? Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 ...

  4. Google protobuf使用技巧和经验

    Google protobuf是非常出色的开源工具,在项目中可以用它来作为服务间数据交互的接口,例如rpc服务.数据文件传输等.protobuf为proto文件中定义的对象提供了标准的序列化和反序列化 ...

  5. pythongoogle.probuf.timestamp_数据通信格式:Google Protobuf

    Protobuf是Google开发的序列化结构数据的一套工具,适合用于数据存储,以及不同语言不同应用之间进行通信的数据交换格式.目前Google提供了C++,Python,Java,Go等语言的支持. ...

  6. google protobuf安装与使用

    google protobuf是一个灵活的.高效的用于序列化数据的协议.相比较XML和JSON格式,protobuf更小.更快.更便捷.google protobuf是跨语言的,并且自带了一个编译器( ...

  7. NoClassDefFoundError: com/google/protobuf/RpcCallback

    hbase启动的时候报错: Desktop: Error: A JNI error has occurred, please check your installation and try again ...

  8. Google Protobuf 使用介绍

    直接在 www.google.com.hk 上搜索google protobuf 后下载官方版本. 官方版本支持C++\Java\Python三门语言. 还有很多非官方的语言版本支持,如C\NET(C ...

  9. 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信

    http://blog.csdn.net/whuancai/article/details/11994341 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通 ...

  10. Google Protobuf 开发指南

    为什么80%的码农都做不了架构师?>>>    Google Protobuf开发指南 1.简介 l  它是开源项目:http://code.google.com/p/protobu ...

最新文章

  1. [React Native Android安利系列]搭建React Native Android环境
  2. 创建 桌面、发送到...、快速启动栏、开始菜单、程序菜单、右键菜单 快捷方式...
  3. UNICODE与UTF-8的转换
  4. Windows环境下MySQL 5.7的安装、配置与卸载
  5. 6.OD-Run trace /Hit trace
  6. Myeclipse快捷键总结大全
  7. C语言课后习题(2)
  8. python调用qt动态库_QT开发——动态库(.so文件)的生成与调用
  9. PDF中加入HTML,将PDF导入/嵌入到HTML中(For PDF)
  10. [MCM] MTSP问题的GA求解 多目标优化 (单起点 与 多起点)
  11. 冉宝的每日一题-8月16日回溯法+ 动态规划压缩
  12. 喜讯 | 图扑科技再获厦门数字经济创新创业大赛一等奖
  13. STM32控制步进电机运三种方式控制源码详解:主从定时器+编码器闭环+GPIO模拟(基于【TB6600】【DRV8825】驱动器)
  14. C# Predefined type 'System.Object' is not defined or imported
  15. 第21节--非线性回归(下)
  16. 升压减压以及充电电路设计
  17. Flash Professional / 处理 Flash 文档 XFL(XML格式描述的CS5 FLA)
  18. Leetcode——岛屿问题
  19. AIGC生产工艺流程之games生产流程
  20. 快印客人工智能名片,7个销售新玩法

热门文章

  1. 马云:梭梭树就是企业家精神
  2. 3月19日发布!vivo X27配置揭晓:搭载骁龙710处理器
  3. 带中文字库的12864LCD显示程序
  4. nginx开机自启动
  5. data ajax begin,Ajax.BeginForm()知多少
  6. 条件变量、pthread_cond_init
  7. python3 xpath_「手把手教python3接口自动化」:非结构化数据提取(二)
  8. 输入你的密码来连接到_手机怎样连接WiFi?详细步骤,教你操作
  9. FTP主动模式和被动模式学习笔记
  10. ORA-12505,TNS:listener does not currently know of SID given in connect descriptor(不知道的SID)