1. 安装开发环境
1.1 Netty环境
这里我使用Netty5.0.0版本 到这里下载即可http://netty.io/ 下载netty-all-5.0.0.Alpha2.jar 这个jar包简单配置一下即可使用。
1.2 Protobuf环境
这个就比较麻烦了,这里说一下我的做法。 可以在这里下载最新版https://github.com/google/protobuf 或者使用 v2.6.1稳定版 https://github.com/google/protobuf/tree/v2.6.1
也可以在这里下载http://pkgs.fedoraproject.org/repo/pkgs/protobuf/protobuf-2.6.1.tar.bz2/
在这里下载对应的Protobuf-java.jar http://central.maven.org/maven2/com/google/protobuf/protobuf-java/
http://mvnrepository.com/artifact/com.google.protobuf/protobuf-java
1.3 Protoc 工具
Linux和Windows都差不多,编译源代码即可。
以Windows为例,打开protobuf-2.6.1vsprojectsprotobuf.sln
这样生成解决方案。
在Debug里面这些文件是有用的
2. protobuf初始化
SubscribeReq.proto
1 package netty; 2 option java_package = "com.jieli.nettytest.protobuf"; 3 option java_outer_classname = "SubscribeReqProto"; 4 5 message SubscribeReq{ 6 required int32 subReqID = 1; 7 required string userName = 2; 8 required string productName = 3; 9 repeated string address = 4; 10 }
SubscribeResq.proto
1 package netty; 2 option java_package = "com.jieli.nettytest.protobuf"; 3 option java_outer_classname = "SubscribeResqProto"; 4 5 message SubscribeResq{ 6 required int32 subReqID = 1; 7 required int32 respCode = 2; 8 required string desc = 3; 9 }
用protobuf.exe进行编译
1 protoc.exe --java_out=. --cpp_out=. SubscribeReq.proto 2 protoc.exe --java_out=. --cpp_out=. SubscribeResq.proto
3. Protobuf 测试
TestSubscribeReqProto.java
1 package com.jieli.nettytest.protobuf; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import com.google.protobuf.InvalidProtocolBufferException; 6 7 public class TestSubscribeReqProto { 8 9 private static byte[] encode(SubscribeReqProto.SubscribeReq req){ 10 return req.toByteArray(); 11 } 12 13 private static SubscribeReqProto.SubscribeReq decode(byte[] body) 14 throws InvalidProtocolBufferException { 15 return SubscribeReqProto.SubscribeReq.parseFrom(body); 16 } 17 18 private static SubscribeReqProto.SubscribeReq createSubscribeReq(){ 19 SubscribeReqProto.SubscribeReq.Builder builder = 20 SubscribeReqProto.SubscribeReq.newBuilder(); 21 builder.setSubReqID(1); 22 builder.setUserName("Lilinfeng"); 23 builder.setProductName("netty book"); 24 List<String> address = new ArrayList<>(); 25 address.add("NanJing YuHuaTai"); 26 address.add("beijin lilili"); 27 address.add("asdfasdf"); 28 builder.addAllAddress(address); 29 return builder.build(); 30 } 31 32 public static void main(String[] args) { 33 try { 34 SubscribeReqProto.SubscribeReq req = createSubscribeReq(); 35 System.out.println("befor encode:" + req.toString()); 36 SubscribeReqProto.SubscribeReq req2 = decode(encode(req)); 37 System.out.println("After decode :"+req.toString()); 38 System.out.println("assert equal : ==>" + req2.equals(req)); 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } 42 } 43 }
运行结果
项目目录结构
4. java-java通信例子(跟书本上是差不多一样的)
SubReqServer.java
1 package com.jieli.nettytest.protobuf; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioServerSocketChannel; 11 import io.netty.handler.codec.protobuf.ProtobufDecoder; 12 import io.netty.handler.codec.protobuf.ProtobufEncoder; 13 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 14 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 15 import io.netty.handler.logging.LogLevel; 16 import io.netty.handler.logging.LoggingHandler; 17 18 public class SubReqServer { 19 20 public void bind(int port){ 21 EventLoopGroup bossGroup = new NioEventLoopGroup(); 22 EventLoopGroup workerGroup = new NioEventLoopGroup(); 23 try { 24 ServerBootstrap b = new ServerBootstrap(); 25 b.group(bossGroup, workerGroup) 26 .channel(NioServerSocketChannel.class) 27 .option(ChannelOption.SO_BACKLOG, 100) 28 .handler(new LoggingHandler(LogLevel.INFO)) 29 .childHandler(new ChannelInitializer<SocketChannel>() { 30 @Override 31 protected void initChannel(SocketChannel ch) throws Exception { 32 ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); 33 //与c++通信时这里的varint32要注释掉,因为默认的protobuf是没有32位对齐的,如果要实现自动分包,那么要在C++客户端进行组装 34 ch.pipeline().addLast(new ProtobufDecoder( 35 SubscribeReqProto.SubscribeReq.getDefaultInstance())); 36 ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); 37 ch.pipeline().addLast(new ProtobufEncoder()); 38 ch.pipeline().addLast(new SubReqServerHandler()); 39 } 40 }); 41 42 ChannelFuture f = b.bind(port).sync(); 43 44 f.channel().closeFuture().sync(); 45 } catch (Exception e) { 46 e.printStackTrace(); 47 } finally { 48 bossGroup.shutdownGracefully(); 49 workerGroup.shutdownGracefully(); 50 } 51 } 52 53 public static void main(String[] args) { 54 new SubReqServer().bind(7777); 55 } 56 }
SubReqServerHandler.java
1 package com.jieli.nettytest.protobuf; 2 3 import io.netty.channel.ChannelHandlerAdapter; 4 import io.netty.channel.ChannelHandlerContext; 5 6 public class SubReqServerHandler extends ChannelHandlerAdapter{ 7 @Override 8 public void channelRead(ChannelHandlerContext ctx, Object msg) 9 throws Exception { 10 SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg; 11 if("Lilinfeng".equalsIgnoreCase(req.getUserName())){ 12 System.out.println("Service accept client subscribe req:["+req.toString()+"]"); 13 //ctx.writeAndFlush(resp(req.getSubReqID())); 14 } 15 } 16 17 private SubscribeResqProto.SubscribeResq resp(int subReqID){ 18 SubscribeResqProto.SubscribeResq.Builder builder = 19 SubscribeResqProto.SubscribeResq.newBuilder(); 20 builder.setSubReqID(subReqID); 21 builder.setRespCode(0); 22 builder.setDesc("Netty book order success.."); 23 return builder.build(); 24 } 25 26 @Override 27 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 28 throws Exception { 29 cause.printStackTrace(); 30 } 31 }
SubReqClient.java
1 package com.jieli.nettytest.protobuf; 2 3 4 import io.netty.bootstrap.Bootstrap; 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelInitializer; 7 import io.netty.channel.ChannelOption; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.SocketChannel; 11 import io.netty.channel.socket.nio.NioSocketChannel; 12 import io.netty.handler.codec.protobuf.ProtobufDecoder; 13 import io.netty.handler.codec.protobuf.ProtobufEncoder; 14 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 15 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 16 import io.netty.handler.logging.LogLevel; 17 import io.netty.handler.logging.LoggingHandler; 18 19 public class SubReqClient { 20 21 public void connect(int port, String host){ 22 EventLoopGroup group = new NioEventLoopGroup(); 23 try { 24 Bootstrap b = new Bootstrap(); 25 b.group(group) 26 .channel(NioSocketChannel.class) 27 .option(ChannelOption.TCP_NODELAY, true) 28 .handler(new ChannelInitializer<SocketChannel>() { 29 @Override 30 protected void initChannel(SocketChannel ch) throws Exception { 31 ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); 32 ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); 33 ch.pipeline().addLast(new ProtobufDecoder( 34 SubscribeResqProto.SubscribeResq.getDefaultInstance())); 35 ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); 36 ch.pipeline().addLast(new ProtobufEncoder()); 37 ch.pipeline().addLast(new SubReqClientHandler()); 38 } 39 }); 40 41 ChannelFuture f = b.connect(host, port).sync(); 42 43 f.channel().closeFuture().sync(); 44 } catch (Exception e) { 45 e.printStackTrace(); 46 } finally { 47 group.shutdownGracefully(); 48 } 49 } 50 51 public static void main(String[] args) { 52 new SubReqClient().connect(7777, "localhost"); 53 } 54 }
SubReqClientHandler.java
1 package com.jieli.nettytest.protobuf; 2 3 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import io.netty.channel.ChannelHandlerAdapter; 8 import io.netty.channel.ChannelHandlerContext; 9 10 public class SubReqClientHandler extends ChannelHandlerAdapter{ 11 public SubReqClientHandler() { 12 } 13 14 @Override 15 public void channelActive(ChannelHandlerContext ctx) throws Exception { 16 for(int i=0; i<10; i++){ 17 ctx.write(subReq(i)); 18 } 19 ctx.flush(); 20 } 21 22 private SubscribeReqProto.SubscribeReq subReq(int i){ 23 SubscribeReqProto.SubscribeReq.Builder builder = 24 SubscribeReqProto.SubscribeReq.newBuilder(); 25 builder.setSubReqID(i); 26 builder.setUserName("Lilinfeng"); 27 builder.setProductName("Netty Book.."); 28 List<String> address = new ArrayList<>(); 29 address.add("NanJin LLLLLL"); 30 address.add("beijin lllllll"); 31 address.add("shenzhen jjjjjj"); 32 builder.addAllAddress(address); 33 return builder.build(); 34 } 35 36 @Override 37 public void channelRead(ChannelHandlerContext ctx, Object msg) 38 throws Exception { 39 System.out.println("Receive server response:["+msg+"]"); 40 } 41 42 @Override 43 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 44 ctx.flush(); 45 } 46 47 }
服务器运行结果
客户端运行结果
5. (C/C++)-java通信例子
本来想用mingw来实现的但是,试了几次,总是编译不过。就放弃了,使用VS2008来编译了。
新建一个控制台程序,配置下属性页, 下面这个图配置到protobuf源代码的src目录
下面这个图配置到用vs编译编译产生的中间文件,包含几个lib包的目录
将经过protoc.exe产生的*.h和*.cc文件放到对应的项目中
main.cpp代码
1 #include <iostream> 2 #include <windows.h> 3 #include "SubscribeReq.pb.h" 4 #include "SubscribeResq.pb.h" 5 6 #pragma comment(lib, "ws2_32.lib") 7 #pragma comment(lib,"libprotobuf.lib") 8 #pragma comment(lib,"libprotobuf-lite.lib") 9 10 using namespace std; 11 using namespace netty; 12 13 14 //打开连接 15 SOCKET open_msg(char *host, int port) 16 { 17 //初始化Socket dll 18 WSADATA wsaData; 19 WORD socketVersion = MAKEWORD(2,0); 20 if(WSAStartup(socketVersion,&wsaData)!=0) 21 { 22 printf("Init socket dll error!"); 23 return -1; 24 } 25 //创建socket 26 SOCKET s = socket(AF_INET, SOCK_STREAM, 0); //tcp 27 if (SOCKET_ERROR == s) 28 { 29 printf("Create Socket Error!"); 30 return -1; 31 } 32 //指定服务端的地址 33 sockaddr_in server_addr; 34 server_addr.sin_family = AF_INET; 35 server_addr.sin_addr.S_un.S_addr = inet_addr(host); 36 server_addr.sin_port = htons(port); 37 38 char opt = 1; 39 int ret = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(char)); 40 if(ret == -1) 41 { 42 printf("ERROR "); 43 return -1; 44 } 45 46 //连接 47 if (SOCKET_ERROR == connect(s, (LPSOCKADDR)&server_addr, sizeof(server_addr))) 48 { 49 printf("Can Not Connect To Server IP! "); 50 return -1; 51 } 52 return s; 53 } 54 //获取信息 55 int recv_msg(SOCKET s,char *msg,int size) 56 { 57 int ret = recv(s,msg,size,0); 58 if(ret == SOCKET_ERROR) 59 { 60 printf("Recv Error. "); 61 return -1; 62 } 63 return ret; 64 } 65 //发送信息 66 int send_msg(SOCKET s,char *msg,int size) 67 { 68 int ret = send(s,msg,size,0); 69 if(ret == SOCKET_ERROR) 70 { 71 printf("Send Error. "); 72 return -1; 73 } 74 return ret; 75 } 76 //关闭连接 77 int close_msg(SOCKET s) 78 { 79 closesocket(s); 80 return 0; 81 } 82 83 84 int main() 85 { 86 SubscribeReq req ; 87 req.set_username("Lilinfeng"); 88 req.add_address("asdf"); 89 req.set_subreqid(2); 90 req.set_productname("laskjdfk111"); 91 92 93 SOCKET s = open_msg("127.0.0.1", 7777); 94 95 char msg[1024] = {0}; 96 req.SerializePartialToArray(msg, 1024); 97 98 cout<<req.GetCachedSize()<<endl; 99 send_msg(s, msg, req.GetCachedSize()); 100 101 close_msg(s); 102 103 system("pause"); 104 return 0; 105 }
然后编译运行就可以发送protobuf对象到java服务器端,运行后服务器出现这个结果
找了很久原因,原来是服务器SubReqServer.java中的ProtobufVarint32***解码器对Protobuf包进行处理,导致格式不一致,解决的办法是注释掉这两行,不过这样又会产生书本上说到的问题,会出现粘包。我能想到的办法是1.在C++客户端中进行修改,使之对应到Java中对齐格式,这个要看源代码。 2.发送的包前面增加包头,然后包头信息描述Protobuf大小。
参考资料
Netty权威指南 – 第八章 Google Protobuf 编解码
http://blog.csdn.net/majianfei1023/article/details/45371743
http://www.cnblogs.com/lidabo/p/3911456.html