spring+netty服务器搭建

转载自: https://blog.csdn.net/u012930316/article/details/73743305

 

游戏一般是长连接,自定义协议,不用http协议,BIO,NIO,AIO这些我就不说了,自己查资料

我现在用spring+netty搭起简单的游戏服

思路:1自定义协议和协议包;2spring+netty整合;3半包粘包处理,心跳机制等;4请求分发(目前自己搞的都是单例模式)

下个是测试用的,结构如下

 

 

首先自定义包头

Header.java

 

  1. package com.test.netty.message;
    
    /**
    
    * Header.java
    
    * 自定义协议包头
    
    * @author janehuang
    
    * @version 1.0
    
    */
    
    public class Header {
    
    private byte tag;
    
    /* 编码*/
    
    private byte encode;
    
    /*加密*/
    
    private byte encrypt;
    
    /*其他字段*/
    
    private byte extend1;
    
    /*其他2*/
    
    private byte extend2;
    
    /*会话id*/
    
    private String sessionid;
    
    /*包的长度*/
    
    private int length = 1024;
    
    /*命令*/
    
    private int cammand;
    
    public Header() {
    
    }
    
    public Header(String sessionid) {
    
    this.encode = 0;
    
    this.encrypt = 0;
    
    this.sessionid = sessionid;
    
    }
    
    public Header(byte tag, byte encode, byte encrypt, byte extend1, byte extend2, String sessionid, int length, int cammand) {
    
    this.tag = tag;
    
    this.encode = encode;
    
    this.encrypt = encrypt;
    
    this.extend1 = extend1;
    
    this.extend2 = extend2;
    
    this.sessionid = sessionid;
    
    this.length = length;
    
    this.cammand = cammand;
    
    }
    
    @Override
    
    public String toString() {
    
    return "header [tag=" + tag + "encode=" + encode + ",encrypt=" + encrypt + ",extend1=" + extend1 + ",extend2=" + extend2 + ",sessionid=" + sessionid + ",length=" + length + ",cammand="
    
    + cammand + "]";
    
    }
    
    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 getCammand() {
    
    return cammand;
    
    }
    
    public void setCammand(int cammand) {
    
    this.cammand = cammand;
    
    }
    
    }

     

  2.  
  3.  

包体,我简单处理用字符串转字节码,一般好多游戏用probuf系列化成二进制

 

Message.java

 

  1. package com.test.netty.message;
    
    import io.netty.buffer.ByteBuf;
    
    import io.netty.buffer.Unpooled;
    
    import java.io.ByteArrayOutputStream;
    
    import java.io.IOException;
    
    import java.io.UnsupportedEncodingException;
    
    import com.test.netty.decoder.MessageDecoder;
    
    /**
    
    * Message.java
    
    *
    
    * @author janehuang
    
    * @version 1.0
    
    */
    
    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.getCammand()));
    
    out.write(bbb);
    
    out.write('\n');
    
    } catch (UnsupportedEncodingException e) {
    
    // TODO Auto-generated catch block
    
    e.printStackTrace();
    
    } catch (IOException e) {
    
    // TODO Auto-generated catch block
    
    e.printStackTrace();
    
    }
    
    return out.toByteArray();
    
    }
    
    public static byte[] intToByte(int newint) {
    
    byte[] intbyte = new byte[4];
    
    intbyte[3] = (byte) ((newint >> 24) & 0xFF);
    
    intbyte[2] = (byte) ((newint >> 16) & 0xFF);
    
    intbyte[1] = (byte) ((newint >> 8) & 0xFF);
    
    intbyte[0] = (byte) (newint & 0xFF);
    
    return intbyte;
    
    }
    
    public static int bytesToInt(byte[] src, int offset) {
    
    int value;
    
    value = (int) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24));
    
    return value;
    
    }
    
    public 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) {
    
    // TODO Auto-generated catch block
    
    e.printStackTrace();
    
    }
    
    byte[] data = out.toByteArray();
    
    heapBuffer.writeBytes(data);
    
    System.out.println(heapBuffer);
    
    int a = heapBuffer.readInt();
    
    System.out.println(a);
    
    }
    
    }
    
    

     

解码器

 

MessageDecoder.java

 

package com.test.netty.decoder;  
  
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;  
  
import com.test.netty.message.Header;  
import com.test.netty.message.Message;  
  
  
  
/** 
 * HeaderDecoder.java 
 *  
 * @author janehuang 
 * @version 1.0 
 */  
public class MessageDecoder extends ByteToMessageDecoder {  
    /**包长度志头**/  
    public static final int HEAD_LENGHT = 45;  
    /**标志头**/  
    public static final byte PACKAGE_TAG = 0x01;  
    @Override  
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {  
        buffer.markReaderIndex();  
        if (buffer.readableBytes() < HEAD_LENGHT) {  
            throw new CorruptedFrameException("包长度问题");  
        }  
        byte tag = buffer.readByte();  
        if (tag != PACKAGE_TAG) {  
            throw new CorruptedFrameException("标志错误");  
        }  
        byte encode = buffer.readByte();  
        byte encrypt = buffer.readByte();  
        byte extend1 = buffer.readByte();  
        byte extend2 = buffer.readByte();  
        byte sessionByte[] = new byte[32];  
        buffer.readBytes(sessionByte);  
        String sessionid = new String(sessionByte,"UTF-8");  
        int length = buffer.readInt();  
        int cammand=buffer.readInt();  
        Header header = new Header(tag,encode, encrypt, extend1, extend2, sessionid, length, cammand);  
        byte[] data=new byte[length];  
        buffer.readBytes(data);  
        Message message = new Message(header,new String(data,"UTF-8"));  
        out.add(message);  
    }  
}  



 

 

编码器

MessageEncoder.java

 

package com.test.netty.encoder;  
  
  
  
import com.test.netty.decoder.MessageDecoder;  
import com.test.netty.message.Header;  
import com.test.netty.message.Message;  
  
import io.netty.buffer.ByteBuf;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.handler.codec.MessageToByteEncoder;  
  
  
/** 
 * MessageEncoder.java 
 *  
 * @author janehuang 
 * @version 1.0  
 */  
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.getCammand());  
            out.writeBytes(msg.getData().getBytes("UTF-8"));  
    }  
  
}  

服务器

 

TimeServer.java

 

package com.test.netty.server;  
  
  
import org.springframework.stereotype.Component;  
  
  
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 com.test.netty.decoder.MessageDecoder;  
import com.test.netty.encoder.MessageEncoder;  
import com.test.netty.handler.ServerHandler;  
  
  
/** 
 * ChatServer.java 
 *  
 * @author janehuang 
 * @version 1.0 
 */  
  
  
@Component  
public class TimeServer {  
  
    private int port=88888;  
  
  
    public void run() throws InterruptedException {  
        EventLoopGroup bossGroup = new NioEventLoopGroup();  
        EventLoopGroup workerGroup = new NioEventLoopGroup();  
        ByteBuf heapBuffer = Unpooled.buffer(8);  
        heapBuffer.writeBytes("\r".getBytes());  
        try {  
            ServerBootstrap b = new ServerBootstrap(); // (2)  
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)  
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)  
                                @Override  
                                public void initChannel(SocketChannel ch) throws Exception {  
                                    ch.pipeline().addLast("encoder", new MessageEncoder()).addLast("decoder", new MessageDecoder()).addFirst(new LineBasedFrameDecoder(65535))  
                                            .addLast(new ServerHandler());  
                                }  
                            }).option(ChannelOption.SO_BACKLOG, 1024) // (5)  
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)  
            ChannelFuture f = b.bind(port).sync(); // (7)  
            f.channel().closeFuture().sync();  
        } finally {  
            workerGroup.shutdownGracefully();  
            bossGroup.shutdownGracefully();  
        }  
    }  
      
    public void start(int port) throws InterruptedException{  
      this.port=port;  
      this.run();  
    }  
  
}  



处理器并分发

 

ServerHandler.java

 

package com.test.netty.handler;  
  
import io.netty.channel.ChannelHandlerAdapter;  
import io.netty.channel.ChannelHandlerContext;  
  
import com.test.netty.invote.ActionMapUtil;  
import com.test.netty.message.Header;  
import com.test.netty.message.Message;  
  
/** 
 *  
 * @author janehuang 
 * 
 */  
public class ServerHandler extends ChannelHandlerAdapter {  
      
  
  
    @Override  
    public void channelActive(ChannelHandlerContext ctx) throws Exception {  
        String content="我收到连接";  
        Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0, "713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);  
        Message message=new Message(header,content);  
        ctx.writeAndFlush(message);  
          
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  
        cause.printStackTrace();  
        ctx.close();  
    }  
  
    @Override  
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
         Message m = (Message) msg; // (1)  
           
        /* 请求分发*/  
        ActionMapUtil.invote(header.getCammand(),ctx, m);  
    }  
      
      
}  


分发工具类

 

ActionMapUtil.java

 

package com.test.netty.invote;  
  
import java.lang.reflect.Method;  
import java.util.HashMap;  
import java.util.Map;  
  
public class ActionMapUtil {  
  
    private static Map<Integer, Action> map = new HashMap<Integer, Action>();  
  
    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.test.netty.invote;  
  
import java.lang.reflect.Method;  
  
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;  
    }  
      
  
}  

自定义注解,类似springmvc 里面的@Controller
 

 

NettyController.java

 

package com.test.netty.core;  
  
import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
import org.springframework.stereotype.Component;  
  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
@Documented  
@Component  
public @interface NettyController {  
      
        
}  


类型spring mvc里面的@ReqestMapping

 

ActionMap.java

 

package com.test.netty.core;  
  
import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
@Documented  
public @interface ActionMap {  
      
      int key();  
        
}  

加了这些注解是为了spring初始化bean后把这些对象存到容器,此bean需要在spring配置,spring bean 实例化后会调用

 

ActionBeanPostProcessor.java

 

package com.test.netty.core;  
  
import java.lang.reflect.Method;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanPostProcessor;  
  
import com.test.netty.invote.Action;  
import com.test.netty.invote.ActionMapUtil;  
  
public class ActionBeanPostProcessor implements BeanPostProcessor  {  
  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
        return bean;  
    }  
  
    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;  
    }  
  
}  


controller实例

 

UserController.java

 

package com.test.netty.controller;  
  
import io.netty.channel.ChannelHandlerContext;  
  
import org.springframework.beans.factory.annotation.Autowired;  
  
import com.test.model.UserModel;  
import com.test.netty.core.ActionMap;  
import com.test.netty.core.NettyController;  
import com.test.netty.message.Message;  
import com.test.service.UserService;  
  
  
  
@NettyController()  
public class UserAction {  
      
      
    @Autowired  
    private UserService userService;  
      
      
    @ActionMap(key=1)  
    public String login(ChannelHandlerContext ct,Message message){  
        UserModel userModel=this.userService.findByMasterUserId(1000001);  
        System.out.println(String.format("用户昵称:%s;密码%d;传人内容%s", userModel.getNickname(),userModel.getId(),message.getData()));  
        return userModel.getNickname();  
    }  
  
}  



applicationContext.xml配置文件记得加入这个

 

<bean class="com.test.netty.core.ActionBeanPostProcessor"/>  

 

 

测试代码

 

package test;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
import com.test.netty.server.TimeServer;  
  
public class Test {  
  
      
    public static void main(String[] args) {  
          ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");    
          TimeServer timeServer=  ac.getBean(TimeServer.class);  
          try {  
            timeServer.start(8888);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
            
    }  
      
      
}  


测试开关端

 

package test;  
  
import java.io.IOException;  
import java.io.OutputStream;  
import java.net.Socket;  
import java.util.Scanner;  
  
import com.test.netty.message.Header;  
import com.test.netty.message.Message;  
  
public class ClientTest {  
  
    public static void main(String[] args) {  
        try {  
            // 连接到服务器  
            Socket socket = new Socket("127.0.0.1", 8888);  
  
            try {  
                // 向服务器端发送信息的DataOutputStream  
                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, "713f17ca614361fb257dc6741332caf2", by.length, 1);  
                    Message message = new Message(header, send);  
                    out.write(message.toByte());  
                    out.flush();  
                    // 把从控制台得到的信息传送给服务器  
                    // out.writeUTF("客户端:" + send);  
                    // 读取来自服务器的信息  
                }  
  
            } finally {  
                socket.close();  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  


测试结果,ok了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值