Games usually have long connections and custom protocols, and do not use http protocols. I won’t talk about BIO, NIO, AIO, etc., check the information by yourself.
I'm using spring+netty to set up a simple game server
Ideas: 1. Custom protocol and protocol package; 2.spring+netty integration; 3.half-packet sticking processing, heartbeat mechanism, etc.; 4.request distribution (currently, I am doing singleton mode)
Next is for testing, the structure is as follows
First, customize the package header
Header.java
package com.test.netty.message; /** * Header.java * Custom protocol header* @author janehuang * @version 1.0 */ public class Header { private byte tag; /* Encoding*/ private byte encode; /* Encryption*/ private byte encrypt; /*Other fields*/ private byte extend1; /*Other 2*/ private byte extend2; /*Sessionid*/ private String sessionid; /*Package length*/ private int length = 1024; /*Command*/ 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 encrypt, byte extend1, byte extend2, String sessionid, int length, int bullet) { 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 campmand; } public void setCammand(int campmand) { this.cammand = campmand; } } I simply process the use of string to bytecode. Generally, many games use probuf series to transform into binary
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); } } Decoder
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 { /** Package length header**/ public static final int HEAD_LENGHT = 45; /** Flag header**/ 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("package length issue"); } byte tag = buffer.readByte(); if (tag != PACKAGE_TAG) { throw new CorruptedFrameException("flag error"); } 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); } } Encoder
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")); } } server
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(); } } Processor and distribute
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="I received the connection"; 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) /* Request distribution*/ ActionMapUtil.invote(header.getCammand(),ctx, m); } } Distribution tool category
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); } } Objects created for distribution
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; } }Custom annotations, similar to @Controller in springmvc
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 { } @ReqestMapping in type spring mvc
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(); } These annotations are added to store these objects in the container after spring initialization beans. This bean needs to be configured in spring. The spring bean will be called after instantiation.
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 instance
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("Using nickname:%s; Password %d; Transmitter content %s", userModel.getNickname(), userModel.getId(), message.getData())); return userModel.getNickname(); } } Remember to add this to the applicationContext.xml configuration file
<bean/>
Test code
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(); } } } Test switch end
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 { // Connect to the server Socket socket = new Socket("127.0.0.1", 8888); try { // DataOutputStream that sends information to the server OutputStream out = socket.getOutputStream(); // Decorate the standard input stream, used to input from the console Scanner scanner = new Scanner(System.in); while (true) { String send = scanner.nextLine(); System.out.println("Client:" + send); byte[] by = send.getBytes("UTF-8"); Header header = new Header((byte) 1, (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(); // Send the information obtained from the console to the server// out.writeUTF("Client:" + send); // Read the information from the server} } finally { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } }Test results, OK
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.