基於netty遊戲服後臺搭建

項目要轉游戲開發了,所以搭個遊戲服,遊戲一般是長連接,自定義協議,不用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;
	}

	

}

包體,我簡單處理用字符串轉字節碼,一般好多遊戲用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);
	}

}
解碼器

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


/**
 * 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"));
	}

}
服務器

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

}


處理器並分發

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="我收到連接";
		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)
		 
		/* 請求分發*/
 	    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) {
			// TODO Auto-generated catch block
			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 {
				// 向服務器端發送信息的DataOutputStream
				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();
					// 把從控制檯得到的信息傳送給服務器
					// out.writeUTF("客戶端:" + send);
					// 讀取來自服務器的信息
				}

			} finally {
				socket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

測試結果,ok了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章