pb的知识点:
1.协议文件
.proto文件,这是协议原始文件,通过它,可以转换成对应平台的代码,java中直接转成java类,cpp中转成.h和.cc文件。.proto文件中有几个关键字:optional的字段表示可用可不用,required表示必须要用,repeated的作用类似于数组了。转换后的文件中,包含协议中的字段,还包含各种对应的函数。
2.编码要点
pb中的编码是比较特殊的,常规的设计中 int32占4个字节,填入的数据,不管多大,都占4个字节;而pb中的int32是一种特殊编码,称作varint,如果数据小,比如<128,则只占一个字节,这是比较特殊的地方,联想到其他类型,基本上都是这种情况。另外负数情况(因为负数在计算机一般是很大的数据),pb中使用了zigzag方式来处理这种情况,依然使字节可保持的很小。另外pb中的字节序是小端。
3.简单使用
pb2.6.0,服务端java+netty,客户端cpp vs2017
首先找到pb2.6.0,下载对应的lib库和一个编译好的protoc.exe如图
配置cpp平台:使用vs2017,解压上面的protobuf-2.6.0.zip后,如图
打开vsprojects文件夹后,打开protobuf.sln,然后使用release方式编译全部项目
期间可能出现的问题:
1.右击 libprotobuf,属性->c/c++预处理器->预处理器定义中加入:_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
编译成功后会生成几个用到的lib。这是在改解决方案中创建一个测试项目,这里我创建了一个控制台项目:
右击改测试项目,属性->c/c++->附加包含目录中填入H:\protobuf-2.6.0\src,这里我把那个上面的zip解压到了H盘。
接着,链接器->附加包含目录中填入H:\protobuf-2.6.0\vsprojects\Release,这个release是之前编译生成的。
定义一个协议
最后在cmd中,找到前面说到的protoc.exe文件夹,进入,使用 protoc.exe ./GCToLS.proto --java_out=./ 生成java的目标文件;再使用 protoc.exe ./GCToLS.proto --cpp_out=./ 生成cpp的目标文件。
在cpp测试项目中:
// ConsoleApplication1.cpp: 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include "GCToLS.pb.h"
#include <winsock2.h>
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include <google/protobuf/io/coded_stream.h>
#define BUFSIZE 4096 /*缓冲区大小*/
#pragma comment(lib, "libprotobuf.lib")
#pragma comment(lib, "libprotoc.lib")
#pragma comment(lib, "WS2_32")
using namespace std;
int main()
{
GCToLS::AskLogin ask;
ask.set_platform(321);
ask.set_sessionid("888");
ask.set_uin("555");
cout << ask.platform() << endl;
int length = ask.ByteSize()+4;
char *msg = new char[length];
google::protobuf::io::ArrayOutputStream arrayOut(msg, length);
google::protobuf::io::CodedOutputStream codedOut(&arrayOut);
codedOut.WriteVarint32(ask.ByteSize());
ask.SerializeToCodedStream(&codedOut);
//ask.SerializePartialToArray(msg, length);
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(10001);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
send(sclient, msg, length, 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
while (true) {};
return 0;
}
在java中:
创建加入protocol的jar,2.6.0,创建一个netty简单项目,
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import GCToLS.GCToLSOut;
public class ProtoBufServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(GCToLSOut.AskLogin.getDefaultInstance()));
ch.pipeline().addLast(new ProtoBufServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
System.out.println("init start");
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 10001;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new ProtoBufServer().bind(port);
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.List;
import GCToLS.GCToLSOut;
import GCToLS.GCToLSOut.AskLogin;
public class ProtoBufServerHandler extends ChannelInboundHandlerAdapter {
int count=0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(count++);
GCToLSOut.AskLogin req = (AskLogin) msg;
System.out.println("平台:" + req.getPlatform() + ","+req.getUin() + "," + req.getSessionid());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
亲测,一切ok。
另外对上述几个说明:
将
google::protobuf::io::ArrayOutputStream arrayOut(msg, length);
google::protobuf::io::CodedOutputStream codedOut(&arrayOut);
codedOut.WriteVarint32(ask.ByteSize());
ask.SerializeToCodedStream(&codedOut);
替换成
ask.SerializePartialToArray(msg, length);
那么将java中的ProtobufVarint32FrameDecoder与new ProtobufDecoder(GCToLSOut.AskLogin.getDefaultInstance()去掉,在ProtoBufServerHandler中直接获取字节,在用
AskLogin req = GCToLSOut.AskLogin.parseFrom(target);来获取对应的内容。