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<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
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了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 常用java正则表达式的工具类
下一篇: Java字符串详解的实例介绍