spring+netty服务器搭建

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

 

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

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

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

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

spring+netty服务器搭建

 

 

首先自定义包头

Header.java

 

 
  1. package com.test.netty.message;

  2.  
  3.  
  4. /**

  5. * Header.java

  6. * 自定义协议包头

  7. * @author janehuang

  8. * @version 1.0

  9. */

  10. public class Header {

  11. private byte tag;

  12. /* 编码*/

  13. private byte encode;

  14. /*加密*/

  15. private byte encrypt;

  16. /*其他字段*/

  17. private byte extend1;

  18. /*其他2*/

  19. private byte extend2;

  20. /*会话id*/

  21. private String sessionid;

  22. /*包的长度*/

  23. private int length = 1024;

  24. /*命令*/

  25. private int cammand;

  26.  
  27. public Header() {

  28.  
  29. }

  30.  
  31. public Header(String sessionid) {

  32. this.encode = 0;

  33. this.encrypt = 0;

  34. this.sessionid = sessionid;

  35. }

  36.  
  37. public Header(byte tag, byte encode, byte encrypt, byte extend1, byte extend2, String sessionid, int length, int cammand) {

  38. this.tag = tag;

  39. this.encode = encode;

  40. this.encrypt = encrypt;

  41. this.extend1 = extend1;

  42. this.extend2 = extend2;

  43. this.sessionid = sessionid;

  44. this.length = length;

  45. this.cammand = cammand;

  46. }

  47.  
  48. @Override

  49. public String toString() {

  50. return "header [tag=" + tag + "encode=" + encode + ",encrypt=" + encrypt + ",extend1=" + extend1 + ",extend2=" + extend2 + ",sessionid=" + sessionid + ",length=" + length + ",cammand="

  51. + cammand + "]";

  52. }

  53.  
  54. public byte getTag() {

  55. return tag;

  56. }

  57.  
  58. public void setTag(byte tag) {

  59. this.tag = tag;

  60. }

  61.  
  62. public byte getEncode() {

  63. return encode;

  64. }

  65.  
  66. public void setEncode(byte encode) {

  67. this.encode = encode;

  68. }

  69.  
  70. public byte getEncrypt() {

  71. return encrypt;

  72. }

  73.  
  74. public void setEncrypt(byte encrypt) {

  75. this.encrypt = encrypt;

  76. }

  77.  
  78. public byte getExtend1() {

  79. return extend1;

  80. }

  81.  
  82. public void setExtend1(byte extend1) {

  83. this.extend1 = extend1;

  84. }

  85.  
  86. public byte getExtend2() {

  87. return extend2;

  88. }

  89.  
  90. public void setExtend2(byte extend2) {

  91. this.extend2 = extend2;

  92. }

  93.  
  94. public String getSessionid() {

  95. return sessionid;

  96. }

  97.  
  98. public void setSessionid(String sessionid) {

  99. this.sessionid = sessionid;

  100. }

  101.  
  102. public int getLength() {

  103. return length;

  104. }

  105.  
  106. public void setLength(int length) {

  107. this.length = length;

  108. }

  109.  
  110. public int getCammand() {

  111. return cammand;

  112. }

  113.  
  114. public void setCammand(int cammand) {

  115. this.cammand = cammand;

  116. }

  117.  
  118.  
  119.  
  120. }


 view pl

 

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

 

Message.java

 

 
  1. package com.test.netty.message;

  2.  
  3. import io.netty.buffer.ByteBuf;

  4. import io.netty.buffer.Unpooled;

  5.  
  6. import java.io.ByteArrayOutputStream;

  7. import java.io.IOException;

  8. import java.io.UnsupportedEncodingException;

  9.  
  10. import com.test.netty.decoder.MessageDecoder;

  11.  
  12. /**

  13. * Message.java

  14. *

  15. * @author janehuang

  16. * @version 1.0

  17. */

  18. public class Message {

  19.  
  20. private Header header;

  21.  
  22. private String data;

  23.  
  24. public Header getHeader() {

  25. return header;

  26. }

  27.  
  28. public void setHeader(Header header) {

  29. this.header = header;

  30. }

  31.  
  32. public String getData() {

  33. return data;

  34. }

  35.  
  36. public void setData(String data) {

  37. this.data = data;

  38. }

  39.  
  40. public Message(Header header) {

  41. this.header = header;

  42. }

  43.  
  44. public Message(Header header, String data) {

  45. this.header = header;

  46. this.data = data;

  47. }

  48.  
  49. public byte[] toByte() {

  50. ByteArrayOutputStream out = new ByteArrayOutputStream();

  51. out.write(MessageDecoder.PACKAGE_TAG);

  52. out.write(header.getEncode());

  53. out.write(header.getEncrypt());

  54. out.write(header.getExtend1());

  55. out.write(header.getExtend2());

  56. byte[] bb = new byte[32];

  57. byte[] bb2 = header.getSessionid().getBytes();

  58. for (int i = 0; i < bb2.length; i++) {

  59. bb[i] = bb2[i];

  60. }

  61.  
  62. try {

  63. out.write(bb);

  64.  
  65. byte[] bbb = data.getBytes("UTF-8");

  66. out.write(intToBytes2(bbb.length));

  67. out.write(intToBytes2(header.getCammand()));

  68. out.write(bbb);

  69. out.write('\n');

  70. } catch (UnsupportedEncodingException e) {

  71. // TODO Auto-generated catch block

  72. e.printStackTrace();

  73. } catch (IOException e) {

  74. // TODO Auto-generated catch block

  75. e.printStackTrace();

  76. }

  77. return out.toByteArray();

  78. }

  79.  
  80. public static byte[] intToByte(int newint) {

  81. byte[] intbyte = new byte[4];

  82. intbyte[3] = (byte) ((newint >> 24) & 0xFF);

  83. intbyte[2] = (byte) ((newint >> 16) & 0xFF);

  84. intbyte[1] = (byte) ((newint >> 8) & 0xFF);

  85. intbyte[0] = (byte) (newint & 0xFF);

  86. return intbyte;

  87. }

  88.  
  89. public static int bytesToInt(byte[] src, int offset) {

  90. int value;

  91. value = (int) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24));

  92. return value;

  93. }

  94.  
  95. public static byte[] intToBytes2(int value) {

  96. byte[] src = new byte[4];

  97. src[0] = (byte) ((value >> 24) & 0xFF);

  98. src[1] = (byte) ((value >> 16) & 0xFF);

  99. src[2] = (byte) ((value >> 8) & 0xFF);

  100. src[3] = (byte) (value & 0xFF);

  101. return src;

  102. }

  103.  
  104. public static void main(String[] args) {

  105. ByteBuf heapBuffer = Unpooled.buffer(8);

  106. System.out.println(heapBuffer);

  107. ByteArrayOutputStream out = new ByteArrayOutputStream();

  108. try {

  109. out.write(intToBytes2(1));

  110. } catch (IOException e) {

  111. // TODO Auto-generated catch block

  112. e.printStackTrace();

  113. }

  114. byte[] data = out.toByteArray();

  115. heapBuffer.writeBytes(data);

  116. System.out.println(heapBuffer);

  117. int a = heapBuffer.readInt();

  118. System.out.println(a);

  119. }

  120.  
  121. }


 

 

 

 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((byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 1, "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了
spring+netty服务器搭建