netty学习之路(一) netty+protocolbuffer 实现简单的消息发送

学习netty之前,我们要知道netty是什么,Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,主要提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
你可以把netty当作一个建立通信的工具,凡是牵扯到网络通信的,你都可以使用它,与netty一样的还有mina。
protocolbuffer是一个google的一种数据交换的格式,它独立于语言,独立于平台,google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。可以把protobuf当作一个封装数据的载体。
这里附上protobuf的语法地址

http://blog.csdn.net/sylar_d/article/details/51325987

其实真的有用到的就是域了
我们在定义Message的时候protobuf提供了3种可选域:
required: message中必须至少包含一个required域,并且在序列化之前必须被赋值。
optional: message中需要包含0个以上optional域。
repeated:这个域用来保存一些要重复设置的变量,这些变量可以设置0次到多次。并且顺序保存。(用于设置数组)。
我们可以把一个message看成java的一个对象,其中required和option可以看成常量属性,required不能为空,option可以为空,而repeated可以看成一个链表。
我们使用一个简单的例子吧
首先创建protobuf文件 User.proto

package test;

option java_package = "com.zengame.proto.user";
option java_outer_classname = "UserProto";

message Req_UpdateSecurityPassword{
    required string securityPassword = 1;
    optional string oldSecurityPassword = 2[default = ""];//老密码
}

message Ans_RecommendServer{
    required int32 recommendSvrID = 1;//推荐serverID 玩家可能在其他服有信息
}

message Req_User{
    required string username = 1;
}

这是proto文件,之后使用java的编译器生成代码,window下的指定如下(注意:必须要在同一目录下):

protoc.exe -I=./ --java_out=./proto  User.proto
pause

我们在使用proto时会将其序列化和反序列化,所以我创建了一个RPCMessage作为传递消息的对象,代码如下:RPCMessage.java

//记得加上这个标签,依旧实现Serializable接口
@Message
public class RPCMessage implements Serializable{
    private static final long serialVersionUID = 1L;
    private int cmd; //用于存储协议号
    private byte[] data;//用于存储protobuf的字节数组
    private int seq; //消息队列位置
    private byte type;//消息类型
    private transient Object obj;//这个暂时没点用,所以加上transient让他不需要序列化

    public int getCmd() {
        return cmd;
    }
    public void setCmd(int cmd) {
        this.cmd = cmd;
    }
    public byte[] getData() {
        return data;
    }
    public void setData(byte[] data) {
        this.data = data;
    }
    public int getSeq() {
        return seq;
    }
    public void setSeq(int seq) {
        this.seq = seq;
    }
    public byte getType() {
        return type;
    }
    public void setType(byte type) {
        this.type = type;
    }
    public Object getObj() {
        return obj;
    }
    public void setObj(Object obj) {
        this.obj = obj;
    }

    @Override
    public String toString() {
        return "RPCMessage [cmd=" + cmd + ", seq=" + seq + ", type=" + type + "]";
    }

}

服务器代码 NettyServer.java

ServerBootstrap bootStrap = new ServerBootstrap(
                new NioServerSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));
        bootStrap.setPipelineFactory(new ChannelPipelineFactory() {

            @Override
            public ChannelPipeline getPipeline() throws Exception {
                // TODO Auto-generated method stub
                ChannelPipeline pipeline = Channels.pipeline();
                ObjectDecoder objectDecoder = new ObjectDecoder(1024 * 1024,
                ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader()));
                //pipeline.addLast("decoder", new ProtobufDecoder(UserProto.Ans_RecommendServer.getDefaultInstance()));
                pipeline.addLast("decoder", objectDecoder); // 传输对象需要使用对象解码
                pipeline.addLast("encoder", new ObjectEncoder()); //传输对象需要使用对象加码
                //pipeline.addLast("decoder", new ProtobufDecoder(UserProto.Req_UpdateSecurityPassword.getDefaultInstance()));
                //pipeline.addLast("encoder",new ProtobufEncoder());
                pipeline.addLast("handler", new ObjectServerHandler()); //事件处理器 需要自己重写
                return pipeline;
            }
        });

        bootStrap.bind(new InetSocketAddress(8000));

服务器处理类 ObjectServerHandler.java

public class ObjectServerHandler extends SimpleChannelHandler{

    //客户端连接服务器时,会执行
    @Override
    public void channelConnected(
            ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        //将channel存储起来
        PlayerManager.addSession(100175, ctx.getChannel());
    }

    //服务器收到消息时会执行
    @Override
    public void messageReceived(
            ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        RPCMessage message = (RPCMessage)e.getMessage();
        switch (message.getCmd()) {
        case 10001:
            Req_UpdateSecurityPassword ans = Req_UpdateSecurityPassword.parseFrom(message.getData());
            System.out.println(message);
            System.out.println("SecurityPassword = "+ans.getSecurityPassword()+",OldSecurityPassword = "+ans.getOldSecurityPassword());
            Ans_RecommendServer req = Ans_RecommendServer.newBuilder()
                                     .setRecommendSvrID(10001)
                                     .build();
            PlayerManager.sendMessage(message.getCmd(),req.toByteArray(), 100175);
            break;
        case 10002:
            Req_User rsp = Req_User.parseFrom(message.getData());
            System.out.println(message);
            System.out.println("username=" + rsp.getUsername());
            req = Ans_RecommendServer.newBuilder()
                     .setRecommendSvrID(10002)
                     .build();
            PlayerManager.sendMessage(message.getCmd(),req.toByteArray(), 100175);
            break;
        default:
            break;
        }
    }
}

客户端代码 NettyClient.java

ClientBootstrap bootStarp = new ClientBootstrap(
                new NioClientSocketChannelFactory(
                        Executors.newCachedThreadPool(), 
                        Executors.newCachedThreadPool()));
        bootStarp.setPipelineFactory(new ChannelPipelineFactory() {

            @Override
            public ChannelPipeline getPipeline() throws Exception {
                // TODO Auto-generated method stub
                ChannelPipeline pipeline = Channels.pipeline();
                ObjectDecoder objectDecoder = new ObjectDecoder(1024 * 1024,
                        ClassResolvers.weakCachingConcurrentResolver(this
                                .getClass().getClassLoader()));
                //pipeline.addLast("decoder", new ProtobufDecoder(UserProto.Ans_RecommendServer.getDefaultInstance()));
                pipeline.addLast("decoder", objectDecoder);
                pipeline.addLast("encoder",new ObjectEncoder());
                pipeline.addLast("handler", new ObjectClientHandler()); //客户端处理类
                return pipeline;
            }
        });

        bootStarp.connect(new InetSocketAddress("127.0.0.1",8000));

客户端处理类 ObjectClientHandler.java

public class ObjectClientHandler extends SimpleChannelHandler {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {
        RPCMessage message = (RPCMessage) e.getMessage();
        switch (message.getCmd()) {
        case 10001:
            Ans_RecommendServer ans = Ans_RecommendServer.parseFrom(message.getData());
            System.out.println(message);
            System.out.println("recommendSvrID = " + ans.getRecommendSvrID());
            break;
        case 10002:
            ans = Ans_RecommendServer.parseFrom(message.getData());
            System.out.println(message);
            System.out.println("recommendSvrID = " + ans.getRecommendSvrID());
            break;
        default:
            break;
        }
    }

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
            throws Exception {
        PlayerManager.addSession(100175, ctx.getChannel());
    }
}

客户端发送消息代码

int op = 0;
        Scanner in = new Scanner(System.in);
        StringBuffer sb = new StringBuffer();
        boolean flag = true;
        do{
            System.out.println("请输入操作: 1 发送消息 2 断开连接");
            op = in.nextInt();
            switch (op) {
            case 1:
                System.out.print("请输入发送消息的内容:");
                sb.append(in.next()+" "+in.next());
                String[] value = sb.toString().split(" ");
                Req_UpdateSecurityPassword req = Req_UpdateSecurityPassword.newBuilder()
                                                .setSecurityPassword(value[0])
                                                .setOldSecurityPassword(value[1])
                                                .build();
                PlayerManager.sendMessage(10001,req.toByteArray(), 100175);
                break;
            case 2:
                System.out.print("请输入发送消息的内容:");
                sb.setLength(0);
                sb.append(in.next());
                Req_User username = Req_User.newBuilder()
                               .setUsername(sb.toString())
                               .build();
                PlayerManager.sendMessage(10002,username.toByteArray(), 100175);
                break;
            default:
                PlayerManager.close(100175);
                System.out.println("连接关闭!");
                flag = !flag;
                break;
            }
        }while(flag);
        System.out.println("系统关闭");

这里我还多做了一步操作,存储channel,文件名为PlayerManager.java

public class PlayerManager {

    private static Map<Integer,Channel> sessionMap = new ConcurrentHashMap<Integer, Channel>();

    public static void addSession(int playerId ,Channel channel){
        if(sessionMap.containsKey(playerId)){
            if(sessionMap.get(playerId) == channel){
                return ;
            }
        }
        sessionMap.put(playerId, channel);
    }

    public static void removeSession(int playerId){
        if(sessionMap.containsKey(playerId)){
            sessionMap.remove(playerId);
        }
    }

    public static Channel getSession(int playerId){
        if(!sessionMap.containsKey(playerId)){
            return null;
        }
        return sessionMap.get(playerId);
    }

    public static void sendMessage(int cmd, byte[] data, int playerId){
        if(sessionMap.containsKey(playerId)){
            RPCMessage message = new RPCMessage();
            message.setCmd(cmd);
            message.setSeq(-1);
            message.setType((byte) -1);
            message.setData(data);
            sessionMap.get(playerId).write(message);
            System.out.println("发送消息成功.");
        }
    }

    public static void close(int playerId){
        if(sessionMap.containsKey(playerId)){
            sessionMap.get(playerId).close();
        }
    }
}

运行结果
client结果
server结果
好吧,整个大概的使用应该会了,有了这些,我感觉就可以实现一个简单的聊天室系统了,哈哈
在这里我也要说声抱歉了,因为我对netty以及NIO了解的也不是很深,不过我可以推荐大家这篇文章,是讲解NIO,个人觉得讲解的非常好!!

https://zhuanlan.zhihu.com/p/23488863

总结:个人觉得自己对于netty只是会初步使用,里面的底层实现什么的我还不是很了解,学习之路永不停息。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值