游戏一般是长连接,自定义协议,不用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
- 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;
-
-
-
-
-
-
-
-
-
- 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;
-
-
-
-
-
-
-
-
- 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;
-
-
-
-
-
-
-
-
-
-
- @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();
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @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)
- .childOption(ChannelOption.SO_KEEPALIVE, true);
- ChannelFuture f = b.bind(port).sync();
- 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;
-
-
-
-
-
-
- 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;
-
-
- 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
- </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 {
-
-
- @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) {
-
- 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 {
-
- 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();
-
-
-
- }
-
- } finally {
- socket.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
测试结果,ok了