spring+netty服务器搭建

2 篇文章 0 订阅

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

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

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

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




首先自定义包头

Header.java

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;  
    }  
  
      
  
}  

  view pl

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

Message.java

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);  
    }  
  
}  



 view plain cop

解码器

MessageDecoder.java

[java] view plain copy
  1. package com.test.netty.decoder;  
  2.   
  3. import io.netty.buffer.ByteBuf;  
  4. import io.netty.channel.ChannelHandlerContext;  
  5. import io.netty.handler.codec.ByteToMessageDecoder;  
  6. import io.netty.handler.codec.CorruptedFrameException;  
  7.   
  8. import java.util.List;  
  9.   
  10. import com.test.netty.message.Header;  
  11. import com.test.netty.message.Message;  
  12.   
  13.   
  14.   
  15. /** 
  16.  * HeaderDecoder.java 
  17.  *  
  18.  * @author janehuang 
  19.  * @version 1.0 
  20.  */  
  21. public class MessageDecoder extends ByteToMessageDecoder {  
  22.     /**包长度志头**/  
  23.     public static final int HEAD_LENGHT = 45;  
  24.     /**标志头**/  
  25.     public static final byte PACKAGE_TAG = 0x01;  
  26.     @Override  
  27.     protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {  
  28.         buffer.markReaderIndex();  
  29.         if (buffer.readableBytes() < HEAD_LENGHT) {  
  30.             throw new CorruptedFrameException("包长度问题");  
  31.         }  
  32.         byte tag = buffer.readByte();  
  33.         if (tag != PACKAGE_TAG) {  
  34.             throw new CorruptedFrameException("标志错误");  
  35.         }  
  36.         byte encode = buffer.readByte();  
  37.         byte encrypt = buffer.readByte();  
  38.         byte extend1 = buffer.readByte();  
  39.         byte extend2 = buffer.readByte();  
  40.         byte sessionByte[] = new byte[32];  
  41.         buffer.readBytes(sessionByte);  
  42.         String sessionid = new String(sessionByte,"UTF-8");  
  43.         int length = buffer.readInt();  
  44.         int cammand=buffer.readInt();  
  45.         Header header = new Header(tag,encode, encrypt, extend1, extend2, sessionid, length, cammand);  
  46.         byte[] data=new byte[length];  
  47.         buffer.readBytes(data);  
  48.         Message message = new Message(header,new String(data,"UTF-8"));  
  49.         out.add(message);  
  50.     }  
  51. }  



编码器

MessageEncoder.java

[java] view plain copy
  1. package com.test.netty.encoder;  
  2.   
  3.   
  4.   
  5. import com.test.netty.decoder.MessageDecoder;  
  6. import com.test.netty.message.Header;  
  7. import com.test.netty.message.Message;  
  8.   
  9. import io.netty.buffer.ByteBuf;  
  10. import io.netty.channel.ChannelHandlerContext;  
  11. import io.netty.handler.codec.MessageToByteEncoder;  
  12.   
  13.   
  14. /** 
  15.  * MessageEncoder.java 
  16.  *  
  17.  * @author janehuang 
  18.  * @version 1.0  
  19.  */  
  20. public class MessageEncoder extends MessageToByteEncoder<Message> {  
  21.   
  22.     @Override  
  23.     protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {  
  24.             Header header = msg.getHeader();  
  25.             out.writeByte(MessageDecoder.PACKAGE_TAG);  
  26.             out.writeByte(header.getEncode());  
  27.             out.writeByte(header.getEncrypt());  
  28.             out.writeByte(header.getExtend1());  
  29.             out.writeByte(header.getExtend2());  
  30.             out.writeBytes(header.getSessionid().getBytes());  
  31.             out.writeInt(header.getLength());  
  32.             out.writeInt(header.getCammand());  
  33.             out.writeBytes(msg.getData().getBytes("UTF-8"));  
  34.     }  
  35.   
  36. }  
服务器

TimeServer.java

[java] view plain copy
  1. package com.test.netty.server;  
  2.   
  3.   
  4. import org.springframework.stereotype.Component;  
  5.   
  6.   
  7. import io.netty.bootstrap.ServerBootstrap;  
  8. import io.netty.buffer.ByteBuf;  
  9. import io.netty.buffer.Unpooled;  
  10. import io.netty.channel.ChannelFuture;  
  11. import io.netty.channel.ChannelInitializer;  
  12. import io.netty.channel.ChannelOption;  
  13. import io.netty.channel.EventLoopGroup;  
  14. import io.netty.channel.nio.NioEventLoopGroup;  
  15. import io.netty.channel.socket.SocketChannel;  
  16. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  17. import io.netty.handler.codec.LineBasedFrameDecoder;  
  18.   
  19.   
  20. import com.test.netty.decoder.MessageDecoder;  
  21. import com.test.netty.encoder.MessageEncoder;  
  22. import com.test.netty.handler.ServerHandler;  
  23.   
  24.   
  25. /** 
  26.  * ChatServer.java 
  27.  *  
  28.  * @author janehuang 
  29.  * @version 1.0 
  30.  */  
  31.   
  32.   
  33. @Component  
  34. public class TimeServer {  
  35.   
  36.     private int port=88888;  
  37.   
  38.   
  39.     public void run() throws InterruptedException {  
  40.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  41.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  42.         ByteBuf heapBuffer = Unpooled.buffer(8);  
  43.         heapBuffer.writeBytes("\r".getBytes());  
  44.         try {  
  45.             ServerBootstrap b = new ServerBootstrap(); // (2)  
  46.             b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class// (3)  
  47.                     .childHandler(new ChannelInitializer<SocketChannel>() { // (4)  
  48.                                 @Override  
  49.                                 public void initChannel(SocketChannel ch) throws Exception {  
  50.                                     ch.pipeline().addLast("encoder"new MessageEncoder()).addLast("decoder"new MessageDecoder()).addFirst(new LineBasedFrameDecoder(65535))  
  51.                                             .addLast(new ServerHandler());  
  52.                                 }  
  53.                             }).option(ChannelOption.SO_BACKLOG, 1024// (5)  
  54.                     .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)  
  55.             ChannelFuture f = b.bind(port).sync(); // (7)  
  56.             f.channel().closeFuture().sync();  
  57.         } finally {  
  58.             workerGroup.shutdownGracefully();  
  59.             bossGroup.shutdownGracefully();  
  60.         }  
  61.     }  
  62.       
  63.     public void start(int port) throws InterruptedException{  
  64.       this.port=port;  
  65.       this.run();  
  66.     }  
  67.   
  68. }  


处理器并分发

ServerHandler.java

[java] view plain copy
  1. package com.test.netty.handler;  
  2.   
  3. import io.netty.channel.ChannelHandlerAdapter;  
  4. import io.netty.channel.ChannelHandlerContext;  
  5.   
  6. import com.test.netty.invote.ActionMapUtil;  
  7. import com.test.netty.message.Header;  
  8. import com.test.netty.message.Message;  
  9.   
  10. /** 
  11.  *  
  12.  * @author janehuang 
  13.  * 
  14.  */  
  15. public class ServerHandler extends ChannelHandlerAdapter {  
  16.       
  17.   
  18.   
  19.     @Override  
  20.     public void channelActive(ChannelHandlerContext ctx) throws Exception {  
  21.         String content="我收到连接";  
  22.         Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0"713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);  
  23.         Message message=new Message(header,content);  
  24.         ctx.writeAndFlush(message);  
  25.           
  26.     }  
  27.   
  28.     @Override  
  29.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  
  30.         cause.printStackTrace();  
  31.         ctx.close();  
  32.     }  
  33.   
  34.     @Override  
  35.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  36.          Message m = (Message) msg; // (1)  
  37.            
  38.         /* 请求分发*/  
  39.         ActionMapUtil.invote(header.getCammand(),ctx, m);  
  40.     }  
  41.       
  42.       
  43. }  

分发工具类

ActionMapUtil.java

[java] view plain copy
  1. package com.test.netty.invote;  
  2.   
  3. import java.lang.reflect.Method;  
  4. import java.util.HashMap;  
  5. import java.util.Map;  
  6.   
  7. public class ActionMapUtil {  
  8.   
  9.     private static Map<Integer, Action> map = new HashMap<Integer, Action>();  
  10.   
  11.     public static Object invote(Integer key, Object... args) throws Exception {  
  12.         Action action = map.get(key);  
  13.         if (action != null) {  
  14.             Method method = action.getMethod();  
  15.             try {  
  16.                 return method.invoke(action.getObject(), args);  
  17.             } catch (Exception e) {  
  18.                 throw e;  
  19.             }  
  20.         }  
  21.         return null;  
  22.     }  
  23.   
  24.     public static void put(Integer key, Action action) {  
  25.         map.put(key, action);  
  26.     }  
  27.   
  28. }  

为分发创建的对象

Action.java

[java] view plain copy
  1. package com.test.netty.invote;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. public class Action {  
  6.       
  7.     private Method method;  
  8.       
  9.     private Object object;  
  10.   
  11.     public Method getMethod() {  
  12.         return method;  
  13.     }  
  14.   
  15.     public void setMethod(Method method) {  
  16.         this.method = method;  
  17.     }  
  18.   
  19.     public Object getObject() {  
  20.         return object;  
  21.     }  
  22.   
  23.     public void setObject(Object object) {  
  24.         this.object = object;  
  25.     }  
  26.       
  27.   
  28. }  
自定义注解,类似springmvc 里面的@Controller

NettyController.java

[java] view plain copy
  1. package com.test.netty.core;  
  2.   
  3. import java.lang.annotation.Documented;  
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Retention;  
  6. import java.lang.annotation.RetentionPolicy;  
  7. import java.lang.annotation.Target;  
  8.   
  9. import org.springframework.stereotype.Component;  
  10.   
  11. @Retention(RetentionPolicy.RUNTIME)  
  12. @Target(ElementType.TYPE)  
  13. @Documented  
  14. @Component  
  15. public @interface NettyController {  
  16.       
  17.         
  18. }  

类型spring mvc里面的@ReqestMapping

ActionMap.java

[java] view plain copy
  1. package com.test.netty.core;  
  2.   
  3. import java.lang.annotation.Documented;  
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Retention;  
  6. import java.lang.annotation.RetentionPolicy;  
  7. import java.lang.annotation.Target;  
  8.   
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. @Target(ElementType.METHOD)  
  11. @Documented  
  12. public @interface ActionMap {  
  13.       
  14.       int key();  
  15.         
  16. }  
加了这些注解是为了spring初始化bean后把这些对象存到容器,此bean需要在spring配置,spring bean 实例化后会调用

ActionBeanPostProcessor.java

[java] view plain copy
  1. package com.test.netty.core;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.springframework.beans.BeansException;  
  6. import org.springframework.beans.factory.config.BeanPostProcessor;  
  7.   
  8. import com.test.netty.invote.Action;  
  9. import com.test.netty.invote.ActionMapUtil;  
  10.   
  11. public class ActionBeanPostProcessor implements BeanPostProcessor  {  
  12.   
  13.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
  14.         return bean;  
  15.     }  
  16.   
  17.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
  18.         Method[] methods=bean.getClass().getMethods();  
  19.         for (Method method : methods) {  
  20.             ActionMap actionMap=method.getAnnotation(ActionMap.class);  
  21.             if(actionMap!=null){  
  22.                 Action action=new Action();  
  23.                 action.setMethod(method);  
  24.                 action.setObject(bean);  
  25.                 ActionMapUtil.put(actionMap.key(), action);  
  26.             }  
  27.         }  
  28.         return bean;  
  29.     }  
  30.   
  31. }  

controller实例

UserController.java

[java] view plain copy
  1. </pre><pre name="code" class="java">package com.test.netty.controller;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4.   
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6.   
  7. import com.test.model.UserModel;  
  8. import com.test.netty.core.ActionMap;  
  9. import com.test.netty.core.NettyController;  
  10. import com.test.netty.message.Message;  
  11. import com.test.service.UserService;  
  12.   
  13.   
  14.   
  15. @NettyController()  
  16. public class UserAction {  
  17.       
  18.       
  19.     @Autowired  
  20.     private UserService userService;  
  21.       
  22.       
  23.     @ActionMap(key=1)  
  24.     public String login(ChannelHandlerContext ct,Message message){  
  25.         UserModel userModel=this.userService.findByMasterUserId(1000001);  
  26.         System.out.println(String.format("用户昵称:%s;密码%d;传人内容%s", userModel.getNickname(),userModel.getId(),message.getData()));  
  27.         return userModel.getNickname();  
  28.     }  
  29.   
  30. }  


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

[html] view plain copy
  1. <bean class="com.test.netty.core.ActionBeanPostProcessor"/>  


测试代码

[java] view plain copy
  1. package test;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.test.netty.server.TimeServer;  
  7.   
  8. public class Test {  
  9.   
  10.       
  11.     public static void main(String[] args) {  
  12.           ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");    
  13.           TimeServer timeServer=  ac.getBean(TimeServer.class);  
  14.           try {  
  15.             timeServer.start(8888);  
  16.         } catch (InterruptedException e) {  
  17.             // TODO Auto-generated catch block  
  18.             e.printStackTrace();  
  19.         }  
  20.             
  21.     }  
  22.       
  23.       
  24. }  

测试开关端

[java] view plain copy
  1. package test;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.OutputStream;  
  5. import java.net.Socket;  
  6. import java.util.Scanner;  
  7.   
  8. import com.test.netty.message.Header;  
  9. import com.test.netty.message.Message;  
  10.   
  11. public class ClientTest {  
  12.   
  13.     public static void main(String[] args) {  
  14.         try {  
  15.             // 连接到服务器  
  16.             Socket socket = new Socket("127.0.0.1"8888);  
  17.   
  18.             try {  
  19.                 // 向服务器端发送信息的DataOutputStream  
  20.                 OutputStream out = socket.getOutputStream();  
  21.                 // 装饰标准输入流,用于从控制台输入  
  22.                 Scanner scanner = new Scanner(System.in);  
  23.                 while (true) {  
  24.                     String send = scanner.nextLine();  
  25.                     System.out.println("客户端:" + send);  
  26.                     byte[] by = send.getBytes("UTF-8");  
  27.                     Header header = new Header((byte1, (byte1, (byte1, (byte1, (byte1"713f17ca614361fb257dc6741332caf2", by.length, 1);  
  28.                     Message message = new Message(header, send);  
  29.                     out.write(message.toByte());  
  30.                     out.flush();  
  31.                     // 把从控制台得到的信息传送给服务器  
  32.                     // out.writeUTF("客户端:" + send);  
  33.                     // 读取来自服务器的信息  
  34.                 }  
  35.   
  36.             } finally {  
  37.                 socket.close();  
  38.             }  
  39.         } catch (IOException e) {  
  40.             e.printStackTrace();  
  41.         }  
  42.     }  
  43. }  

测试结果,ok了
  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值