基于netty的springboot游戏服务后台搭建

基于netty的游戏服务后台springboot项目搭建

    • 项目目录结构
    • 自定义协议头 Header.java
    • 包体结构 Message.java
    • 消息编解码代码 MessageDecoder.java MessageEncoder.java
    • 服务器代码
    • 处理请求并分发到指定的Controller
    • 业务Controller
    • 启动类
    • 测试类

项目目录结构

一直都是做web应用后端的,好奇游戏服务器后端是怎样搭建的,看了下网上的资料,将springboot和netty4.1.36.Final结合起来搭建了一个简单的游戏服务器,下面是目录结构
基于netty的springboot游戏服务后台搭建_第1张图片

自定义协议头 Header.java

package com.butterfly.game.netty.message;

/**
 * Created by  on 2019/10/14.
 * 自定义协议头
 * @author 
 */
public class Header {
    //标识
    private byte tag;
    //编码
    private byte encode;
    //加密
    private byte encrypt;
    //其他字段1
    private byte extend1;
    //其他字段2
    private byte extend2;
    //会话id
    private String sessionid;
    //包长度
    private int length;
    //指令
    private int command;

    public Header(){

    }

    public Header(byte tag, byte encode, byte encrypt, byte extend1, byte extend2, String sessionid, int length, int command) {
        this.tag = tag;
        this.encode = encode;
        this.encrypt = encrypt;
        this.extend1 = extend1;
        this.extend2 = extend2;
        this.sessionid = sessionid;
        this.length = length;
        this.command = command;
    }

    public byte getTag() {
        return tag;
    }

    public void setTag(byte tag) {
        this.tag = tag;
    }

    public byte getEncode() {
        return encode;
    }

    public void setEncode(byte encode) {
        this.encode = encode;
    }

    public byte getEncrypt() {
        return encrypt;
    }

    public void setEncrypt(byte encrypt) {
        this.encrypt = encrypt;
    }

    public byte getExtend1() {
        return extend1;
    }

    public void setExtend1(byte extend1) {
        this.extend1 = extend1;
    }

    public byte getExtend2() {
        return extend2;
    }

    public void setExtend2(byte extend2) {
        this.extend2 = extend2;
    }

    public String getSessionid() {
        return sessionid;
    }

    public void setSessionid(String sessionid) {
        this.sessionid = sessionid;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getCommand() {
        return command;
    }

    public void setCommand(int command) {
        this.command = command;
    }
}

包体结构 Message.java

package com.butterfly.game.netty.message;

import com.butterfly.game.netty.decoder.MessageDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * Created by  on 2019/10/14.
 * 包体
 *
 * @author 
 */
public class Message {
    private Header header;
    private String data;

    public Header getHeader() {
        return header;
    }

    public void setHeader(Header header) {
        this.header = header;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Message(Header header) {
        this.header = header;
    }

    public Message(Header header, String data) {
        this.header = header;
        this.data = data;
    }

    public byte[] toByte() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(MessageDecoder.PACKAGE_TAG);
        out.write(header.getEncode());
        out.write(header.getEncrypt());
        out.write(header.getExtend1());
        out.write(header.getExtend2());
        byte[] bb = new byte[32];
        byte[] bb2 = header.getSessionid().getBytes();
        for (int i = 0; i < bb2.length;i++){
            bb[i] = bb2[i];
        }

        try {
            out.write(bb);
            byte[] bbb = data.getBytes("UTF-8");
            out.write(intToBytes2(bbb.length));
            out.write(intToBytes2(header.getCommand()));
            out.write(bbb);
            out.write('\n');
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }

    private static byte[] intToBytes2(int value) {
        byte[] src = new byte[4];
        src[0] = (byte) ((value >> 24) & 0xFF);
        src[1] = (byte) ((value >> 16) & 0xFF);
        src[2] = (byte) ((value >> 8) & 0xFF);
        src[3] = (byte) (value & 0xFF);
        return src;
    }

    public static void main(String[] args) {
        ByteBuf heapBuffer = Unpooled.buffer(8);
        System.out.println(heapBuffer);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            out.write(intToBytes2(1));
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] data = out.toByteArray();
        heapBuffer.writeBytes(data);
        System.out.println(heapBuffer);
        int a = heapBuffer.readInt();
        System.out.println(a);

    }
}

消息编解码代码 MessageDecoder.java MessageEncoder.java

package com.butterfly.game.netty.decoder;

import com.butterfly.game.netty.message.Header;
import com.butterfly.game.netty.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;

import java.util.List;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
public class MessageDecoder extends ByteToMessageDecoder {
    public static final int HEAD_LENGTH = 45;

    public static final byte PACKAGE_TAG = 0x01;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        in.markReaderIndex();
        if (in.readableBytes() < HEAD_LENGTH) {
            throw new CorruptedFrameException("包长度问题");
        }
        byte tag = in.readByte();
        if (tag != PACKAGE_TAG) {
            throw new CorruptedFrameException("标识问题");
        }
        byte encode = in.readByte();
        byte encrypt = in.readByte();
        byte extend1 = in.readByte();
        byte extend2 = in.readByte();
        byte sessionByte[] = new byte[32];
        in.readBytes(sessionByte);
        String sessionid = new String(sessionByte, "UTF-8");
        int length = in.readInt();
        int command = in.readInt();
        Header header = new Header(tag, encode, encrypt, extend1, extend2, sessionid, length, command);
        byte[] data = new byte[length];

        in.readBytes(data);
        Message message = new Message(header, new String(data, "UTF-8"));
        out.add(message);
    }
}

package com.butterfly.game.netty.encoder;

import com.butterfly.game.netty.decoder.MessageDecoder;
import com.butterfly.game.netty.message.Header;
import com.butterfly.game.netty.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
public class MessageEncoder extends MessageToByteEncoder<Message>{
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        Header header = msg.getHeader();
        out.writeByte(MessageDecoder.PACKAGE_TAG);
        out.writeByte(header.getEncode());
        out.writeByte(header.getEncrypt());
        out.writeByte(header.getExtend1());
        out.writeByte(header.getExtend2());
        out.writeBytes(header.getSessionid().getBytes());
        out.writeInt(header.getLength());
        out.writeInt(header.getCommand());
        out.writeBytes(msg.getData().getBytes("UTF-8"));
    }
}

服务器代码

package com.butterfly.game.netty.server;

import com.butterfly.game.netty.decoder.MessageDecoder;
import com.butterfly.game.netty.encoder.MessageEncoder;
import com.butterfly.game.netty.handler.ServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.LineBasedFrameDecoder;
import org.springframework.stereotype.Component;

/**
 * Created by on 2019/10/14.
 *
 * @author 
 */
@Component
public class TimeServer {
    private int port = 88888;

    public void run() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGrgoup = new NioEventLoopGroup();
        ByteBuf heapBuffer = Unpooled.buffer(8);
        heapBuffer.writeBytes("\r".getBytes());
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGrgoup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("encoder", new MessageEncoder())
                                    .addLast("decoder", new MessageDecoder())
                                    //防止TCP粘包/拆包
                                    .addFirst(new LineBasedFrameDecoder(65535))
                                    //ServerHandler实现了业务逻辑
                                    .addLast(new ServerHandler());
                        }
                    })
                    //服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。
                    // 可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是7200s即2小时。Netty默认关闭该功能。
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //绑定服务器,等待绑定完成,调用sync()的原因是当前线程阻塞
            ChannelFuture f = b.bind(port).sync();
            //关闭channel和块,直到它被关闭
            f.channel().closeFuture().sync();
        } finally {
            //关闭EventLoopGroup,释放所有资源(包括所有创建的线程)
            workerGrgoup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public void start(int port) throws InterruptedException {
        this.port = port;
        this.run();
    }
}

处理请求并分发到指定的Controller

ActionMapUtil.java

package com.butterfly.game.netty.invote;

import com.butterfly.game.netty.message.Message;
import io.netty.channel.ChannelHandlerContext;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by  on 2019/10/14.
 *  请求分发器
 * @author 
 */
public class ActionMapUtil {
    private static Map<Integer, Action> map = new HashMap<>();

    public static Object invote(Integer key, Object... args) throws Exception {
        Action action = map.get(key);
        if (action != null) {
            Method method = action.getMethod();
            try {
                return method.invoke(action.getObject(), args);
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }

    public static void put(Integer key, Action action) {
        map.put(key, action);
    }
}

Action.java

package com.butterfly.game.netty.invote;

import java.io.ObjectOutput;
import java.lang.reflect.Method;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
public class Action {
    private Method method;
    private Object object;

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

请求分发器ServerHandler

package com.butterfly.game.netty.handler;

import com.butterfly.game.netty.invote.ActionMapUtil;
import com.butterfly.game.netty.message.Header;
import com.butterfly.game.netty.message.Message;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * Created by  on 2019/10/14.
 * 处理并分发
 *
 * @author 
 */
public class ServerHandler extends SimpleChannelInboundHandler {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message m = (Message) msg;
        Header header = m.getHeader();
        /* 请求分发*/
        ActionMapUtil.invote(header.getCommand(), ctx, m);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        super.exceptionCaught(ctx, cause);
    }
}

ActionBeanPostProcessor 项目初始化时将所有请求方法放到ActionMapUtil里

package com.butterfly.game.netty.core;

import com.butterfly.game.netty.invote.Action;
import com.butterfly.game.netty.invote.ActionMapUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
@Component
public class ActionBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            ActionMap actionMap = method.getAnnotation(ActionMap.class);
            if (actionMap != null) {
                Action action = new Action();
                action.setMethod(method);
                action.setObject(bean);
                ActionMapUtil.put(actionMap.key(), action);
            }
        }
        return bean;
    }
}

自定义注解ActionMap,类似于mvc的@RequestMapping

package com.butterfly.game.netty.core;

import java.lang.annotation.*;

/**
 * Created by  on 2019/10/14.
 * 类似于mvc的@RequestMapping
 * @author 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ActionMap {
    int key();
}

自定义注解NettyController,类似于mvc的@Controller注解

package com.butterfly.game.netty.core;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * Created by  on 2019/10/14.
 * 类似于mvc的Controller注解
 * @author 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Component
public @interface NettyController {
}

业务Controller

package com.butterfly.game.netty.controller;

import com.butterfly.game.netty.core.ActionMap;
import com.butterfly.game.netty.core.NettyController;
import com.butterfly.game.netty.message.Message;
import io.netty.channel.ChannelHandlerContext;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
@NettyController
public class UserAction {
    @ActionMap(key = 1)
    public String login(ChannelHandlerContext ct, Message message){
        System.out.println(message.getData());
        return null;
    }
}

启动类

package com.butterfly.game;

import com.butterfly.game.netty.server.TimeServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class GameApplication {

	public static void main(String[] args) throws InterruptedException {
		ConfigurableApplicationContext context = SpringApplication.run(GameApplication.class, args);
		TimeServer server = context.getBean(TimeServer.class);
		server.start(8888);
	}

}

测试类

package com.butterfly.game.netty;

import com.butterfly.game.netty.message.Header;
import com.butterfly.game.netty.message.Message;
import com.sun.org.apache.xpath.internal.SourceTree;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * Created by  on 2019/10/14.
 *
 * @author 
 */
public class TestClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8888);
        try {
            OutputStream out = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            while(true){
                String send = scanner.nextLine();
                System.out.println("客户端"+send);
                byte[] by = send.getBytes("UTF-8");
                Header header = new Header((byte)1,(byte)1,(byte)1,(byte)1,(byte)1,"6ebf17wee14361fbxf5dwd741587fhk2",by.length,1);
                Message message = new Message(header,send);
                out.write(message.toByte());
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }
}

测试结果
客户端打印
在这里插入图片描述
服务端打印
基于netty的springboot游戏服务后台搭建_第2张图片

你可能感兴趣的:(游戏服务器)