spring+netty服务器搭建的方法

网友投稿 217 2023-02-21


spring+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 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 {

@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.enhttp://coder.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() { // (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 map = new HashMap();

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

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配置文件记得加入这个

测试代码

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了


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序之多文件下载的简单封装示例
下一篇:新网互联api接口文档(新网互联科技有限公司)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~