Java原生Socket实现ProtocolBuf的例子

一、开发环境

1.Protocol版本:3.9(可用最新)下载地址
2.Protocol协议版本:3
3.普通java环境即可,源码就2个类。

这里主要是使用Java原生的Socket API实先前后端的即时通讯。然后通过ProtocolBuf产生二进制数据(Bytes
)进行网络交互,代码简单易懂,适合对即时通讯的入门。真正项目会采用nio之类的成熟框架来实先这个通讯底层,但是底层原理是相同的。(比如使用Netty)

二、protocol协议文件

前端的请求协议文件

syntax = "proto3";

option java_package = "com.asframe.pb3demo.proto";
option java_outer_classname = "LoginRequest";

message LoginReq
{
   //登录类型
   int32 type = 1;
   //名字
   string name = 2;
   //密码
   string pass = 3;
}

服务端的返回协议文件

syntax = "proto3";

option java_package = "com.asframe.pb3demo.proto";
option java_outer_classname = "LoginResponse";

message LoginRep 
{
   //登录结果
   int32 result = 1;
   string msg = 2;
}

注意,其实Protocolbuf的协议结构体没有一定分前后端。这里也只是根据命名来做前后协议的区别,这样好管理。你要合成一个文件也没问题。
执行生成对应的结构体指令:

protoc -I=proto --java_out=src login_rep_msg.proto login_req_msg.proto

根据协议文件生成对应的java代码

三、代码

代码比较简单,注释也比较清晰,就直接贴代码了。
心急的同学也可以先直接下载源码

下载源码

前端代码:

/**
 * 客户端连接测试例子
 * @author sodaChen
 * @date 2019.07.06
 */
public class Client {

    public static void main(String[] args) throws Exception {
        //前端socket链接
        Socket socket = new Socket("localhost", 19000);
        // 读取服务器端传过来信息的DataInputStream
        DataInputStream in = new DataInputStream(socket.getInputStream());
        // 向服务器端发送信息的DataOutputStream
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());

        //构造一个LoginReq对象
        LoginRequest.LoginReq loginReq = LoginRequest.LoginReq.newBuilder()
                .setType(1)
                .setName("soda")
                .setPass("123456")
                .build();
        //转成字节,计算发送的pb协议大小
        byte[] bodyBytes = loginReq.toByteArray();

        //定义消息体头(消息体内容+上版本号的大小)
        out.writeShort(bodyBytes.length + 4);
        //写版本号
        out.writeShort(1);
        //消息指令,消息指令是唯一的,返回指令比发送指令多10000,也就是20001
        out.writeShort(10001);
        //写消息体
        out.write(bodyBytes);
        out.flush();

        //这里接受服务器数据.解析服务器的发送协议流程,基本上客户端发送给服务端类似
        //先收消息长度大小
        short msgLength = in.readShort();
        //消息指令
        short cmd = in.readShort();
        //剩下的字节都是消息体
        byte[] bytes = new byte[msgLength - 2];
        in.read(bytes);
        //这里规定服务器返回是多1w的协议号。实际项目这里应该做好架构,动态处理
        if(cmd == 20001)
        {
            LoginResponse.LoginRep loginRep = LoginResponse.LoginRep.parseFrom(bytes);
            System.out.println("服务器返回数据:" + loginRep);
        }
        //闭流
        out.close();
        System.out.println("client close");
    }
}

服务端代码的解析过程,其实就是按照前端发送的格式来解析。这是所有通讯的基础。具体的规则其实是可以根据项目的需求进行制定的。

/**
 * protocolbuf3的服务端处理
 * @author sodaChen
 * @date 2019.07.06
 */
public class Server
{
    public static void main(String[] args) throws Exception
    {
        //建立Socket服务器
        ServerSocket serverSocket = new ServerSocket(19000);
        System.out.println("服务器socket启动.");
        while (true)
        {
            //监听客户端的连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("有一个客户端连接上来");
            // 读取客户端传过来信息的DataInputStream
            DataInputStream in = new DataInputStream(clientSocket.getInputStream());
            // 向客户端发送信息的DataOutputStream
            DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
            //先收消息长度大小
            short msgLength = in.readShort();
            //定义消息的版本,目前用不上
            short version = in.readShort();
            //消息指令,这个指令很重要,用来识别后面的消息体是什么东西来的
            short cmd = in.readShort();
            //剩下的字节都是消息体(减去已经度的4个字节)
            byte[] bytes = new byte[msgLength - 4];
            //直接读
            in.read(bytes);
            //这里直接判断cmd的值,实际做项目的话,这里的代码会进行底层封装,应该是自动解析,而不是手动判断
            if(cmd == 10001)
            {
                //转换成登录请求对象
                LoginRequest.LoginReq loginReq = LoginRequest.LoginReq.parseFrom(bytes);
                System.out.printf("loginReq:" + loginReq);
                //
                LoginResponse.LoginRep.Builder builder = LoginResponse.LoginRep.newBuilder();
                if(loginReq.getName().equals("soda"))
                {
                    builder.setResult(1);
                    builder.setMsg("登录成功");
                }
                else
                {
                    builder.setResult(0);
                    builder.setMsg("登录失败");
                }
                //创建登录返回对象
                LoginResponse.LoginRep loginRep = builder.build();
                //封装消息体发送给前端
                //计算发送的pb协议大小
                byte[] bodyBytes = loginRep.toByteArray();

                //定义消息体头(消息体内容+cmd的大小),服务器不需要返回版本号
                out.writeShort(bodyBytes.length + 2);
                //消息指令,服务器返回20001
                out.writeShort(20001);
                //写消息体
                out.write(bodyBytes);
                out.flush();
            }
        }
    }
}

你可能感兴趣的:(Java游戏服务开发)