跟孙哥学java
什么是协议?
-
网络传输数据过程中的数据格式,一种约定。
-
协议是分层的
- 不同的协议 端口是可以冲突的?
TCP:80 和 UPD:80 不会冲突 若Http :80 pop3 :80 会端口冲突
-
共有协议/私有协议 :::info 共有协议(Public Protocol)和私有协议(Private Protocol)是指在计算机通信中的两种协议类型。
-
共有协议(Public Protocol): 共有协议是一种公开标准或规范,通常由行业组织、标准制定机构或开放社区制定和维护。这些协议是公开的,任何人都可以访问其规范,并且在实现和使用时具有普适性。共有协议通常用于确保不同系统和设备之间的互操作性,以促进信息交换和通信。 例子:
- HTTP/HTTPS: 超文本传输协议,用于在网络上传输超文本。
- TCP/IP: 传输控制协议/互联网协议,用于在互联网上进行数据传输。
- SMTP/POP3/IMAP: 用于电子邮件传输和接收的协议。
- 私有协议(Private Protocol): 私有协议是由个体、组织或公司独立定义的通信协议,通常没有公开的规范。这些协议可能是特定于某个应用、服务或产品的定制协议。私有协议在特定的环境中使用,可能不被外部系统或应用程序支持。 例子:
- 企业内部通信协议: 一些公司可能会为其内部系统和服务定义专有的通信协议,以满足其特定需求。
- 特定硬件设备通信协议: 一些硬件制造商可能使用特定于其产品的私有协议,用于设备间的通信。
- 定制化的通信协议: 某些应用程序或服务可能选择使用私有协议,以满足其独特的功能和性能要求
基于自己软件定义的一种数据传输的格式,只限定于自己的软件网络通信过程中使用。
Netty聊天室 协议 私有协议
Dubbo dubbo协议 私有
自研的RPC 私有的协议
jdbc 私有的协议
传输层 多
:::
- 通信组合
应用层通信+应用层协议 ---> 应用层通信 【Http协议+文本---> springcloud体系 传输效率 低 应用广泛性 高
传输层通信+私有协议 ----> 传输层通信 【私有协议+二进制】---> dobbo 传输效率 高 应用广泛性 低
grpc 应用层协议(Http2+二进制)
协议和序列化
序列化的目的:更加方便的在网络中传输数据,有利于编程的处理。
常见的序列化
- JSON字符串
Object--->json json-->User 好处:可读性好 存在问题: 体量大 同等数据体量的情况下: 字符串比二进制(byte)大 {name="zhangsan",age=10}
- 二进制形式
体量小,可读性差 JDK 序列化,Hanssian序列化,Thrift序列化,Protobuf 序列化 数据体量大小 从左到右,越来越小,Prorobuf最小,JDK序列化最大
底层传输
二进制/字符串 在底层传输的都是二进制
常见的序列化方式
网络传输数据把 Object---序列化成 网络传输数据的个数
- 二进制
- JDK
- 序列化:ObjectOutputStream
- 反序列化: ObjectInputStream
- Hessain [Dubbo]
- 序列化 Hessian2Output
- 反序列化 Hessian2Input
- Thrfit
- Protobuf
- JDK
- 字符串
- JSON
jdk序列化
@Test
public void test3() throws IOException, ClassNotFoundException {
User user=new User("LISI",19);
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
byte[] byteArray = outputStream.toByteArray();
System.out.println("byteArray.length"+byteArray.length);
ByteArrayInputStream inputStream=new ByteArrayInputStream(byteArray);
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
Object o = objectInputStream.readObject();
System.out.println((User)o);
}
序列化的大小为79
public class TestJDK {
@Test
public void test1() throws IOException {
//序列化 Object--->数据格式
// 反序列化 数据格式--->Object
User user=new User("zhangsan",11);
FileOutputStream fileOutputStream=new FileOutputStream("test");
ObjectOutputStream outputStream=new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(user);
}
@Test
public void test2() throws IOException, ClassNotFoundException {
//序列化 Object--->数据格式
// 反序列化 数据格式--->Object
User user=new User("zhangsan",11);
FileInputStream fileInputStream=new FileInputStream("test");
ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);
Object o = objectInputStream.readObject();
System.out.println((User)o);
}
}
hessian 序列化
引入依赖
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
@Test
public void test1() throws IOException {
User user=new User("LISI",19);
//序列化
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
Hessian2Output hessian2Output=new Hessian2Output(outputStream);
hessian2Output.writeObject(user);
//Hessian 必须要flush
hessian2Output.flush();
byte[] byteArray = outputStream.toByteArray();
System.out.println("byteArray.length="+byteArray.length);
//反序列化
ByteArrayInputStream inputStream=new ByteArrayInputStream(byteArray);
Hessian2Input hessian2Input=new Hessian2Input(inputStream);
User newUser = (User) hessian2Input.readObject(User.class);
System.out.println(newUser);
}
Hessian序列化的大小为32 比JDK序列化的大小小了一倍
Thrift序列化
引入依赖
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.13.0</version>
</dependency>
namespace java com.suns.thrift
struct User{
1:string name,
2:i32 age
}
生成java代码 thrift --gen java test.thrift
maven 插件 maven-thrift-plug
<build>
<plugins>
<plugin>
<groupId>org.apache.thrift.tools</groupId>
<artifactId>maven-thrift-plugin</artifactId>
<version>0.1.11</version>
<configuration>
<thriftSourceRoot>${project.basedir}/src/main/thrift</thriftSourceRoot>
<outputDirectory>${project.build.directory}/generated-sources/thrift</outputDirectory>
<generator>java</generator>
</configuration>
</plugin>
</plugins>
</build>
Thrift 网络传输的时候不认可我们自定义的Object,只认可他在IDL创建的结构
- IDL定义 通过Thrfit 命令生成 User代码
@Test
public void test1() throws TException {
com.suns.User user=new User("LISI",19);
//序列化
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
TBinaryProtocol outProtocol=new TBinaryProtocol(new TIOStreamTransport(outputStream));
user.write(outProtocol);
byte[] byteArray = outputStream.toByteArray();
System.out.println("byteArray.length="+byteArray.length);
//反序列化
ByteArrayInputStream inputStream=new ByteArrayInputStream(byteArray);
TBinaryProtocol inProtocol=new TBinaryProtocol(new TIOStreamTransport(inputStream));
com.suns.User newUser=new User();
newUser.read(inProtocol);
System.out.println(newUser);
}
序列化的数据 才19
Protobuf
依赖
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.51.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
插件
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.52.1:exe:${os.detected.classifier}</pluginArtifact>
<!--修改生成位置-->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--生成文件不清空之前的文件-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
编写proto文件
syntax="proto3";
package com.suns;
option java_multiple_files=false;
option java_package="com.suns";
option java_outer_classname="HelloProto";
message HelloRequest{
string name=1;
int32 age=2;
}
生成java文件
public class TestProtobuf {
@Test
public void test1() throws InvalidProtocolBufferException {
HelloProto.HelloRequest helloRequest = HelloProto.HelloRequest.newBuilder().setName("LISI").setAge(19).build();
//序列化
byte[] byteArray = helloRequest.toByteArray();
System.out.println("byteArray.length="+byteArray.length);
//反序列化
HelloProto.HelloRequest helloRequest1 = HelloProto.HelloRequest.parseFrom(byteArray);
System.out.println("helloRequest1.getName()="+helloRequest1.getName()+"helloRequest1.getAge()="+helloRequest1.getAge());
}
}
与前面的序列化对比,protobuf序列化的数据最小
JSON
引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
@Test
public void test1(){
User1 user1=new User1("LISI",19);
//序列化
String jsonStr = JSONUtil.toJsonStr(user1);
byte[] bytes = jsonStr.getBytes(StandardCharsets.UTF_8);
System.out.println("bytes.length="+bytes.length);
//反序列化
User1 newUser = JSONUtil.toBean(jsonStr, User1.class);
System.out.println(newUser);
}
这个序列化的的数据大小为什么比 二进制序列化方式: jdk序列化和hessian序列化小??
因为这个User对象的属性太少了
对于属性较多的情况下,hessian序列化的大小不会改变很多,而json会增加较多
增加字段
Hessian
json
序列化方式的封装
public interface Serializar {
//1. 序列化
public byte[] serializar(Object obj) throws Exception;
//2.反序列化
public Object deserializar(byte[] bytes) throws Exception;
}
public class HessainSerializarImpl implements Serializar {
@Override
public byte[] serializar(Object obj) throws Exception {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
Hessian2Output hessian2Output=new Hessian2Output(outputStream);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return outputStream.toByteArray();
}
@Override
public Object deserializar(byte[] bytes) throws Exception {
ByteArrayInputStream inputStream=new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input=new Hessian2Input(inputStream);
return hessian2Input.readObject();
}
}
public class JDKSerializarImpl implements Serializar {
@Override
public byte[] serializar(Object obj) throws Exception {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream( outputStream);
objectOutputStream.writeObject(obj);
return outputStream.toByteArray();
}
@Override
public Object deserializar(byte[] bytes) throws Exception {
ByteArrayInputStream inputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
return objectInputStream.readObject();
}
}
public class JSONSerializarImpl implements Serializar {
@Override
public byte[] serializar(Object obj) throws Exception {
String jsonStr = JSONUtil.toJsonStr(obj);
return jsonStr.getBytes(Charset.forName("UTF-8"));
}
@Override
public Object deserializar(byte[] bytes) throws Exception {
String s = new String(bytes);
return JSONUtil.toBean(s, Object.class);
}
}
public class ProtoBufSerializarImpl implements Serializar {
@Override
public byte[] serializar(Object obj) throws Exception {
//TODO User1写死了
User1 user1=(User1) obj;
HelloProto.HelloRequest helloRequest= HelloProto.HelloRequest.newBuilder().setName(user1.getName()).setAge(user1.getAge()).build();
return helloRequest.toByteArray();
}
@Override
public Object deserializar(byte[] bytes) throws Exception {
HelloProto.HelloRequest helloRequest= HelloProto.HelloRequest.parseFrom(bytes);
User1 user1=new User1(helloRequest.getName(),helloRequest.getAge());
return user1;
}
}
public class ThriftSerializarImpl implements Serializar {
@Override
public byte[] serializar(Object obj) throws Exception {
//TODO User1写死了
User1 user1=(User1) obj;
com.suns.User u=new com.suns.User();
u.setName(user1.getName());
u.setAge(user1.getAge());
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
TBinaryProtocol outProtocol=new TBinaryProtocol(new TIOStreamTransport(outputStream));
u.write(outProtocol);
return outputStream.toByteArray();
}
@Override
public Object deserializar(byte[] bytes) throws Exception {
ByteArrayInputStream inputStream=new ByteArrayInputStream(bytes);
TBinaryProtocol inProtocol=new TBinaryProtocol(new TIOStreamTransport(inputStream));
com.suns.User u=new com.suns.User();
u.read(inProtocol);
User1 user1=new User1();
user1.setName(u.getName());
user1.setAge(u.getAge());
return user1;
}
}
序列化与网络传输的集成
网路通信的开发: BIO,NIO,Netty
Netty 如何与 序列化方式 进行集成?
序列化--->编码 反序列化-->解码
Netty 编解码---> Handler ByteToMessageDecode 编码 MessageToByteEncode 解码 上述两个都是偏底层的编解码器
我们主要用 MessageToMessageDecoder 反序列化 MessageToMessageEncoder 序列化
注意:应用的层的编解码器 不会解决封帧的问题 所以我们在前置handler使用封帧解码器 解决半包 粘包的问题 LengthFieldBaseFrameDecoder
Netty与自定义编解码的整合 (Client ,Server)
服务端
通过Netty的ServerBootstrap 开发服务端:
public class RPCServer {
public static void main(String[] args) {
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(new NioEventLoopGroup());
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//封帧
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,0));
//日志输出
pipeline.addLast(new LoggingHandler());
//解码器
pipeline.addLast(new RPCMessageToMessageCodec());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
//处理信息
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
User user=(User) msg;
System.out.println("user="+user);
super.channelRead(ctx, msg);
}
});
}
});
serverBootstrap.bind(8000);
}
}
客户端
通过Bootstrap开发客户端:调用服务端
public class RPCClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventLoopGroup=new NioEventLoopGroup();
Bootstrap bootstrap=new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(eventLoopGroup);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,0));
pipeline.addLast(new LoggingHandler());
pipeline.addLast(new RPCMessageToMessageCodec());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(new User("xiaohei",19));
}
});
}
});
ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));
connect.sync();
}
}
其中解码,编码handler为:
/**
* MessageToMessageCodec<ByteBuf, User>
* 第一个泛型: 解码器 接收的数据类型
* 网络传输的格式[{@link ByteBuf} ] --> java {@link Object}
* 第二个泛型: 编码器 接收的数据类型
* java object--->网络传输的格式 {@link ByteBuf}
*/
@Slf4j
public class RPCMessageToMessageCodec extends MessageToMessageCodec<ByteBuf, User> {
/**
* java object---> 网络传输的格式
* 1. java object msg 参数获得
* 2. 序列化 自己编写 {@link com.suns.serilizazar.Serializar}
* 3. 把序列化后的byte[] 交给netty进行传输
* byte[] --->ByteBuf --->out.add()
*/
@Override
protected void encode(ChannelHandlerContext ctx, User msg, List<Object> out) throws Exception {
log.debug("编码器运行......");
ByteBufAllocator alloc = ctx.alloc();
ByteBuf buffer = alloc.buffer();
Serializar serializar=new JDKSerializarImpl();
byte[] bytes = serializar.serializar(msg);
//数据的长度
buffer.writeInt(bytes.length);
buffer.writeBytes(bytes);
out.add(buffer);
}
/**
*解码器
* 网络传输的格式---> java object
* 反序列化
* 1.网络传输的格式 ByteBuf msg ----> 解码 object
* 2. 反序列化 {@link Serializar} jdk
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
int protocolLength= msg.readInt();
byte[] bytes=new byte[protocolLength];
msg.readBytes(bytes);
Serializar serializar=new JDKSerializarImpl();
User user = (User) serializar.deserializar(bytes);
out.add(user);
}
}