因为设备的通信协议准备采用protobuf,所以准备这篇protobuf的使用入门,golang作为客户端,java作为服务端,这才能真正体现出protobuf的无关语言特性。

本文采用protobuf2,注重于如何快速入门使用,并不会涉及到具体的细节知识点。


整体结构说明

golang作为客户端,java作为服务端,protobuf2为两者的通信协议格式。


protobuf2文件

  • protobuf2简介

    详细说明

  • helloworld.proto

    syntax = "proto2";
    
    package proto;
    
    message ProtocolMessage {
    message SearchRequest{
    required string name = 1;
    optional int32 search = 2 ;
    } message ActionRequest{
    required string name = 1;
    optional int32 action = 2 ;
    } message SearchResponse{
    required string name = 1;
    optional int32 search = 2 ;
    } message ActionResponse{
    required string name = 1;
    optional int32 action = 2 ;
    } optional SearchRequest searchRequest = 1;
    optional ActionRequest actionRequest = 2;
    optional SearchResponse searchResponse = 3;
    optional ActionResponse actionResponse = 4;
    }
    • SearchRequestSearchResponse为对应的请求和相应message;
    • ActionRequestActionResponse为对应的请求和相应message;
    • 由于服务端使用netty框架,限制了只能接受一个message进行编码解码,所以把SearchRequestSearchResponseActionRequestActionResponse都内嵌到ProtocolMessage中,通过对ProtocolMessage编码解码进行数据交互。

golang客户端

目录结构

client_proto/
├── api
│ ├── proto # 存放proto协议文件以及生产的pd.go文件
│ ├── helloworld.pb.go
│ └── helloworld.proto
├── cmd
│ ├── main.go
│ ├── util
│ └── util.go

采用go mod 进行开发

生成pb.go文件

  • 安装proto

    自行百度......

  • 在.proto文件处,输入protoc --go_out=./ helloworld.proto

  • 即可生成helloworld.pb.go文件

main.go

package main

import (
"github.com/gin-gonic/gin"
proto "grpc/api/grpc_proto"
"grpc/cmd/demo3/util"
"net/http"
"time"
) func init() {
util.InitTransfer()
} func main() {
router := gin.Default()
// search 测试
router.GET("/search", func(c *gin.Context) {
name := "search"
search := int32(12)
message := &proto.ProtocolMessage{
SearchRequest:&proto.ProtocolMessage_SearchRequest{
Name:&name,
Search:&search,
},
} if err := util.G_transfer.SendMsg(message); err != nil {
c.JSON(500, gin.H{
"err": err.Error(),
})
return
} if err := util.G_transfer.ReadResponse(message); err != nil {
c.JSON(500, gin.H{
"err": err.Error(),
}) return
} c.JSON(200, gin.H{
"message": message.SearchResponse.Name,
})
}) // action测试
router.GET("/action", func(c *gin.Context) {
name := "action"
action := int32(34)
message := &proto.ProtocolMessage{
ActionRequest: &proto.ProtocolMessage_ActionRequest{
Name: &name,
Action: &action,
},
} if err := util.G_transfer.SendMsg(message); err != nil {
c.JSON(500, gin.H{
"err": err.Error(),
})
} if err := util.G_transfer.ReadResponse(message); err != nil {
c.JSON(500, gin.H{
"err": err.Error(),
})
} c.JSON(200, gin.H{
"message": message.ActionResponse.Name,
})
}) ReadTimeout := time.Duration(60) * time.Second
WriteTimeout := time.Duration(60) * time.Second s := &http.Server{
Addr: ":8090",
Handler: router,
ReadTimeout: ReadTimeout,
WriteTimeout: WriteTimeout,
MaxHeaderBytes: 1 << 20,
} s.ListenAndServe()
}

util.go

package util

import (
"encoding/binary"
"errors"
"github.com/gogo/protobuf/proto"
grpc_proto "grpc/api/grpc_proto"
"net"
)
var (
G_transfer *Transfer
) func InitTransfer() {
var (
pTCPAddr *net.TCPAddr
conn net.Conn
err error
)
if pTCPAddr, err = net.ResolveTCPAddr("tcp", "127.0.0.1:3210"); err != nil {
return
} if conn, err = net.DialTCP("tcp", nil, pTCPAddr); err != nil {
return
} // 定义 Transfer 指针变量
G_transfer = &Transfer{
Conn: conn,
}
} // 声明 Transfer 结构体
type Transfer struct {
Conn net.Conn // 连接
Buf [1024 * 2]byte // 传输时,使用的缓冲
} // 获取并解析服务器的消息
func (transfer *Transfer) ReadResponse(response *grpc_proto.ProtocolMessage) (err error) {
_, err = transfer.Conn.Read(transfer.Buf[:4])
if err != nil {
return
} // 根据 buf[:4] 转成一个 uint32 类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(transfer.Buf[:4])
//根据pkglen 读取消息内容
n, err := transfer.Conn.Read(transfer.Buf[:pkgLen])
if n != int(pkgLen) || err != nil {
return
} if err = proto.Unmarshal(transfer.Buf[:pkgLen], response); err != nil {
return
}
return
} // 发送消息到服务器
func (transfer *Transfer) SendMsg(action *grpc_proto.ProtocolMessage) (err error) {
var (
sendBytes []byte
readLen int
)
//sendBytes, ints := action.Descriptor()
if sendBytes, err = proto.Marshal(action); err != nil {
return
} pkgLen := uint32(len(sendBytes))
var buf [4]byte
binary.BigEndian.PutUint32(buf[:4],pkgLen) if readLen, err = transfer.Conn.Write(buf[:4]); readLen != 4 && err != nil {
if readLen == 0 {
return errors.New("发送数据长度发生异常,长度为0")
}
return
}
// 发送消息
if readLen, err = transfer.Conn.Write(sendBytes); err != nil {
if readLen == 0 {
return errors.New("检查到服务器关闭,客户端也关闭")
}
return
}
return
}
  • 这里发送消息和读取消息都需要先发送/解析数据的长度,然后发送/解析数据本身;
  • 这里与服务端怎么样解析/发送数据有关,这是由于netty框架中定义的编码解码器决定的。

java服务端

目录结构

server_proto/
├── src
│ ├── main
│ ├── java
│ ├── com
│ ├── dust
│ ├── proto_server
│ ├── config
│ └── NettyConfig.java
│ ├── netty
│ └── NettyServerListener.java
│ └── SocketServerHandler.java
│ ├── proto
│ └── Helloworld.java
│ └── helloworld.proto # proto配置文件
│ └── Application.java # 启动配置类
│ ├── resources
│ └── application.yml #配置文件
│ ├── test
└── pom.xml # maven配置文件

采用springBoot+netty+maven开发

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dust</groupId>
<artifactId>proto_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>proto_server</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!-- protobuf依赖-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.8.0</version>
</dependency> <dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.2</version>
</dependency> <dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.19.Final</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
  • 注意:protobuf-java的版本为3.8.0,必须和安装proto.exe的版本保持一致。

application.yml

# netty配置
netty:
# 端口号
port: 3210
# 最大线程数
maxThreads: 1024
# 数据包的最大长度
max_frame_length: 65535

NettyConfig.java

package com.dust.proto_server.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; @Data
@Component
@ConfigurationProperties(prefix = "netty")
public class NettyConfig {
private int port;
}

生成Helloworld.java

  • 在.proto文件处,输入protoc --java_out=./ helloworld.proto
  • 即可生成Helloworld.java文件

SocketServerHandler.java

package com.dust.proto_server.netty;

import com.dust.proto_server.proto.Helloworld;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; @Component
@ChannelHandler.Sharable
public class SocketServerHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(SocketServerHandler.class); public ChannelGroup CHANNEL_GROUP = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override
public void handlerAdded(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
LOGGER.info(channel.id().toString()+"加入");
CHANNEL_GROUP.add(channel);
} @Override
public void handlerRemoved(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
LOGGER.info(channel.id().toString()+"退出");
CHANNEL_GROUP.remove(channel);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
//
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
LOGGER.info("开始读取客户端发送过来的数据");
Helloworld.ProtocolMessage protocolMessage = (Helloworld.ProtocolMessage) msg;
Helloworld.ProtocolMessage.Builder builder = Helloworld.ProtocolMessage.newBuilder(); if (protocolMessage.getSearchRequest().getSerializedSize() != 0) {
Helloworld.ProtocolMessage.SearchRequest searchRequest = protocolMessage.getSearchRequest();
LOGGER.info("searchRequest--{}",searchRequest); Helloworld.ProtocolMessage.SearchResponse searchResponse = Helloworld.ProtocolMessage.SearchResponse.newBuilder().setName("i am SearchResponse").setSearch(45).build();
builder.setSearchResponse(searchResponse); } else if (protocolMessage.getActionRequest().getSerializedSize() != 0) {
Helloworld.ProtocolMessage.ActionRequest actionRequest = protocolMessage.getActionRequest();
LOGGER.info("actionRequest--{}",actionRequest); Helloworld.ProtocolMessage.ActionResponse actionResponse = Helloworld.ProtocolMessage.ActionResponse.newBuilder().setName("i am ActionResponse").setAction(67).build();
builder.setActionResponse(actionResponse);
} Helloworld.ProtocolMessage message = builder.build();
// 发送数据长度
ctx.channel().writeAndFlush(message.toByteArray().length);
// 发送数据本身
ctx.channel().writeAndFlush(message);
}
}

NettyServerListener.java

package com.dust.proto_server.netty;

import com.dust.proto_server.config.NettyConfig;

import com.dust.proto_server.proto.Helloworld;
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.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import javax.annotation.PreDestroy;
import javax.annotation.Resource; @Component
public class NettyServerListener {
/**
* NettyServerListener 日志输出器
*
*/
private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerListener.class);
/**
* 创建bootstrap
*/
ServerBootstrap serverBootstrap = new ServerBootstrap();
/**
* BOSS
*/
EventLoopGroup boss = new NioEventLoopGroup();
/**
* Worker
*/
EventLoopGroup work = new NioEventLoopGroup(); @Resource
private SocketServerHandler socketServerHandler; /**
* NETT服务器配置类
*/
@Resource
private NettyConfig nettyConfig; /**
* 关闭服务器方法
*/
@PreDestroy
public void close() {
LOGGER.info("关闭服务器....");
//优雅退出
boss.shutdownGracefully();
work.shutdownGracefully();
} /**
* 开启及服务线程
*/
public void start() {
// 从配置文件中(application.yml)获取服务端监听端口号
int port = nettyConfig.getPort();
serverBootstrap.group(boss, work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 负责通过4字节Header指定的Body长度将消息切割
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
// 负责将frameDecoder处理后的完整的一条消息的protobuf字节码转成ProtocolMessage对象
pipeline.addLast("protobufDecoder",
new ProtobufDecoder(Helloworld.ProtocolMessage.getDefaultInstance()));
// 负责将写入的字节码加上4字节Header前缀来指定Body长度
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
// 负责将ProtocolMessage对象转成protobuf字节码
pipeline.addLast("protobufEncoder", new ProtobufEncoder()); pipeline.addLast(socketServerHandler);
} }).option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO));
try {
LOGGER.info("netty服务器在[{}]端口启动监听", port);
ChannelFuture f = serverBootstrap.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
LOGGER.info("[出现异常] 释放资源");
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
  • 这个类就定义服务端是怎么样处理接受和发送数据的;

  • frameDecoderprotobufDecoder对应的handler用于解码Protobuf package数据包,他们都是Upstream Handles:先处理长度,然后再处理数据本身;

  • frameEncoderprotobufEncoder对应的handler用于编码Protobuf package数据包,他们都是Downstream Handles;

  • 此外还有一个handler,是一个自定义的Upstream Handles,用于开发者从网络数据中解析得到自己所需的数据socketServerHandler;

  • 上例Handles的执行顺序为

    upstream:frameDecoder,protobufDecoder,handler   //解码从Socket收到的数据
    downstream:frameEncoder,protobufEncoder //编码要通过Socket发送出去的数据

Application.java

package com.dust.proto_server;

import com.dust.proto_server.netty.NettyServerListener;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import javax.annotation.Resource; @SpringBootApplication
public class Application implements CommandLineRunner { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
} @Resource
private NettyServerListener nettyServerListener; @Override
public void run(String... args) throws Exception {
nettyServerListener.start();
}
}

测试

  • 先启动服务端,再启动客户端

  • search测试

  • action测试

java和golang通过protobuf协议相互通信的更多相关文章

  1. linux 网络编程:客户端与服务器通过TCP协议相互通信 + UDP

    1.TCP编程的客户端一般步骤: 1.创建一个socket,用函数socket(): 2.设置socket属性,用函数setsockopt():* 可选: 3.绑定IP地址.端口等信息到socket上 ...

  2. Java和C# RSA加解密相互通信和使用公钥加密传输

    关于JAVA和C#加解密通讯的话,可以用这个BouncyCastle插件,会帮助你解决很多问题 http://www.bouncycastle.org/ //c#使用java给的公钥进行rsa加密 p ...

  3. Java实例练习——基于UDP协议的多客户端通信

    昨天学习了UDP协议通信,然后就想着做一个基于UDP的多客户端通信(一对多),但是半天没做出来,今天早上在参考了很多代码以后,修改了自己的代码,然后运行成功,在这里分享以下代码,也说一下自己的认识误区 ...

  4. 开源项目SMSS开源项目(三)——protobuf协议设计

    本文的第一部分将介绍protobuf使用基础以及如何利用protobuf设计通信协议.第二部分会给出smss项目的协议设计规范和源码讲解. 一.Protobuf使用基础 什么是protobuf pro ...

  5. JAVA基础知识之网络编程——-基于UDP协议的通信例子

    UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramS ...

  6. Protobuf协议的Java应用例子

    Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态. Pro ...

  7. Protobuf协议--java实现

    Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态.  pr ...

  8. 保姆级别的RabbitMQ教程!一看就懂!(有安装教程,送安装需要的依赖包,送Java、Golang两种客户端教学Case)

    保姆级别的RabbitMQ教程!一看就懂!(有安装教程,送安装需要的依赖包,送Java.Golang两种客户端教学Case)   目录 什么是AMQP 和 JMS? 常见的MQ产品 安装RabbitM ...

  9. protobuf 协议 windows 下 C++ 环境搭建

    1. 下载protobuf https://code.google.com/p/protobuf/downloads/list Protocol Buffers 2.5.0 full source - ...

随机推荐

  1. Screensiz.es站收集整理了移动端的相关尺寸。

    Screensiz.es站收集整理了移动端的相关尺寸. Screensiz.es 彩蛋爆料直击现场 Screensiz.es站收集整理了移动端的相关尺寸.

  2. Qt在Windows下的三种编程环境搭建(图文并茂,非常清楚)good

    尊重作者,支持原创,如需转载,请附上原地址:http://blog.csdn.net/libaineu2004/article/details/17363165 从QT官网可以得知其支持的平台.编译器 ...

  3. x64内联汇编调用API(需intel编译器,vc不支持x64内联汇编)

    #include "stdafx.h" #include <windows.h> STARTUPINFOW StartInfo  = {0}; PROCESS_INFO ...

  4. elasticsearch local debug环境搭建

    最近计划看看elasticsearch的源码,首先得把local debug环境搞定. 下载源码.因为公司产线是5.6.5,所以就下载了5.6.5的代码. 源码编译.先进入到/elasticsearc ...

  5. 点菜网---Java开源生鲜电商平台-系统架构图(源码可下载)

    点菜网---Java开源生鲜电商平台-系统架构图(源码可下载) 1.点菜网-生鲜电商平台的价值与定位. 生鲜电商平台是一家致力于打造全国餐饮行业智能化.便利化.平台化与透明化服务的创新型移动互联网平台 ...

  6. 【Java源码】集合类-JDK1.8 哈希表-红黑树-HashMap总结

    JDK 1.8 HashMap是数组+链表+红黑树实现的,在阅读HashMap的源码之前先来回顾一下大学课本数据结构中的哈希表和红黑树. 什么是哈希表? 在存储结构中,关键值key通过一种关系f和唯一 ...

  7. java常用基础(一)

    Java常用基础(一) 原文写于2017-12-02 输入输出 //输入 Scanner in = new Scanner(new BufferedInputStream(System.in)); i ...

  8. CSS3过渡与动画

    一.CSS3 过渡 transition-property 规定过渡效果的 CSS 属性名 -webkit-transition-property: none / all / property; -m ...

  9. 大白话5分钟带你走进人工智能-第31节集成学习之最通俗理解GBDT原理和过程

    目录 1.前述 2.向量空间的梯度下降: 3.函数空间的梯度下降: 4.梯度下降的流程: 5.在向量空间的梯度下降和在函数空间的梯度下降有什么区别呢? 6.我们看下GBDT的流程图解: 7.我们看一个 ...

  10. C# 连接数据库等

    SqlConnection连接池:可以通过连接字符串配置连接池.对象池技术:HttpApplication :Asp.Net生产者 消费者. 线程.应用程序跟数据连接非常耗时,而且连接使用非常频繁,使 ...