基于netty游戏服后台搭建

来源:互联网 发布:淘宝海外买手 编辑:程序博客网 时间:2024/05/02 04:42

项目要转游戏开发了,所以搭个游戏服,游戏一般是长连接,自定义协议,不用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;}@Overridepublic 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;}}

包体,我简单处理用字符串转字节码,一般好多游戏用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 blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.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 blocke.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;@Overrideprotected 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> {@Overrideprotected 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 */@Componentpublic 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)@Overridepublic 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 {    @Overridepublic 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();    }@Overridepublic 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@Componentpublic @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)@Documentedpublic @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

</pre><pre name="code" class="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 {@Autowiredprivate 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 blocke.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 {// 向服务器端发送信息的DataOutputStreamOutputStream 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 0