เกมมักจะมีการเชื่อมต่อที่ยาวนานและโปรโตคอลที่กำหนดเองและอย่าใช้โปรโตคอล HTTP ฉันจะไม่พูดถึง Bio, Nio, AIO ฯลฯ ตรวจสอบข้อมูลด้วยตัวเอง
ฉันใช้ Spring+Netty เพื่อตั้งค่าเซิร์ฟเวอร์เกมง่ายๆ
แนวคิด: 1. แพ็คเกจโปรโตคอลและโปรโตคอลที่กำหนดเอง; 2. การรวม SPRING+NETTY; 3. การแปรรูปการติดแพ็คเก็ต Half, กลไกการเต้นของหัวใจ, ฯลฯ ; 4. ขอให้มีการกระจาย (ปัจจุบันฉันกำลังทำโหมดซิงเกิลตัน)
ถัดไปสำหรับการทดสอบโครงสร้างมีดังนี้
ขั้นแรกให้ปรับแต่งส่วนหัวแพ็คเกจ
head.java
แพ็คเกจ com.test.netty.message; / ** * Header.java * ส่วนหัวโปรโตคอลที่กำหนดเอง * @author Janehuang * @version 1.0 */ ส่วนหัวคลาสสาธารณะ {แท็กไบต์ส่วนตัว; /* การเข้ารหัส*/ ไบต์ส่วนตัวเข้ารหัส; /* การเข้ารหัส*/ การเข้ารหัสไบต์ส่วนตัว; /*ฟิลด์อื่น ๆ*/ ไบต์ส่วนตัวขยาย 1; /*อื่น ๆ 2*/ ไบต์ส่วนตัวขยาย 2; /*SessionId*/ String ส่วนตัว SessionId; /*ความยาวแพ็คเกจ*/ ความยาว int ส่วนตัว = 1024; /*คำสั่ง*/ ส่วนตัว int cammand; ส่วนหัวสาธารณะ () {} ส่วนหัวสาธารณะ (String SessionId) {this.encode = 0; this.encrypt = 0; this.sessionId = SessionId; } ส่วนหัวสาธารณะ (แท็กไบต์, ไบต์เข้ารหัส, การเข้ารหัสไบต์, ไบต์เข้ารหัส, ไบต์ Extend1, BYTE Extend2, String SessionId, ความยาว int, int bullet) {this.tag = tag; this.encode = encode; this.encrypt = เข้ารหัส; this.extend1 = Extend1; this.extend2 = Extend2; this.sessionId = SessionId; this.length = ความยาว; this.cammand = cammand; } @Override สตริงสาธารณะ toString () {return "header [tag =" + tag + "encode =" + encode + ", encrypt =" + encrypt + ", extend1 =" + exend1 + ", extend2 =" + extend2 + ", sessionid =" + sessionid + ", ความยาว =" + " +" } ไบต์สาธารณะ getTag () {tag return; } โมฆะสาธารณะ settag (แท็กไบต์) {this.tag = tag; } ไบต์สาธารณะ getEncode () {return encode; } โมฆะสาธารณะ setEncode (ไบต์เข้ารหัส) {this.encode = encode; } ไบต์สาธารณะ getEncrypt () {return encrypt; } โมฆะสาธารณะ setenCrypt (BYTE Encrypt) {this.encrypt = เข้ารหัส; } ไบต์สาธารณะ getExtend1 () {return extend1; } โมฆะสาธารณะ setExtend1 (BYTE Extend1) {this.extend1 = Extend1; } ไบต์สาธารณะ getExtend2 () {return extend2; } โมฆะสาธารณะ setExtend2 (BYTE Extend2) {this.extend2 = Extend2; } สตริงสาธารณะ getSessionId () {return sessionId; } โมฆะสาธารณะ setSessionId (String sessionId) {this.sessionId = sessionId; } public int getLength () {ความยาวคืน; } โมฆะสาธารณะ setLength (ความยาว int) {this.length = ความยาว; } public int getCammand () {return campmand; } โมฆะสาธารณะ setCammand (int campmand) {this.cammand = campmand; - ฉันเพียงแค่ประมวลผลการใช้สตริงไปยังไบต์ โดยทั่วไปเกมหลายเกมใช้ซีรีส์ Probuf เพื่อเปลี่ยนเป็นไบนารี
message.java
แพ็คเกจ com.test.netty.message; นำเข้า io.netty.buffer.bytebuf; นำเข้า io.netty.buffer.unpooled; นำเข้า Java.io.ByTeArrayOutputStream; นำเข้า java.io.ioException; นำเข้า java.io.unsupportencodingexception; นำเข้า com.test.netty.decoder.messagedecoder; / ** * message.java * * @author janehuang * @version 1.0 */ ข้อความระดับสาธารณะ {ส่วนหัวส่วนบุคคลส่วนตัว; ข้อมูลสตริงส่วนตัว ส่วนหัวสาธารณะ getheader () {return header; } โมฆะสาธารณะ setheader (ส่วนหัวส่วนหัว) {this.header = ส่วนหัว; } สตริงสาธารณะ getData () {ส่งคืนข้อมูล; } โมฆะสาธารณะ setData (ข้อมูลสตริง) {this.data = ข้อมูล; } ข้อความสาธารณะ (ส่วนหัวส่วนหัว) {this.header = ส่วนหัว; } ข้อความสาธารณะ (ส่วนหัวส่วนหัว, ข้อมูลสตริง) {this.header = ส่วนหัว; this.data = ข้อมูล; } ไบต์สาธารณะ [] 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 ()); ไบต์ [] bb = ไบต์ใหม่ [32]; ไบต์ [] bb2 = header.getSessionId (). getBytes (); สำหรับ (int i = 0; i <bb2.length; i ++) {bb [i] = bb2 [i]; } ลอง {out.write (bb); ไบต์ [] bbb = data.getBytes ("UTF-8"); out.write (inttobytes2 (bbb.length)); out.write (inttobytes2 (header.getCammand ())); out.write (bbb); out.write ('/n'); } catch (unsupportencodingexception e) {// toDo บล็อก catch ที่สร้างโดยอัตโนมัติ E.PrintStackTrace (); } catch (ioexception e) {// todo บล็อก catch block ที่สร้างอัตโนมัติ e.printstacktrace (); } return out.tobytearray (); } ไบต์คงที่สาธารณะ [] inttobyte (int newint) {byte [] intbyte = byte ใหม่ [4]; intbyte [3] = (ไบต์) ((newint >> 24) & 0xff); intbyte [2] = (ไบต์) ((newint >> 16) & 0xff); intbyte [1] = (ไบต์) ((newint >> 8) & 0xff); intbyte [0] = (ไบต์) (newint & 0xff); กลับ intbyte; } สาธารณะคงที่ int bytestoint (byte [] src, int offset) {ค่า int; value = (int) ((SRC [Offset] & 0xff) | ((SRC [Offset + 1] & 0xff) << 8) | (SRC [Offset + 2] & 0xff) << 16) | (SRC [Offset + 3] & 0xff) << 24)); ค่าส่งคืน; } ไบต์คงที่สาธารณะ [] inttobytes2 (ค่า int) {byte [] src = byte ใหม่ [4]; src [0] = (ไบต์) ((ค่า >> 24) & 0xff); src [1] = (ไบต์) ((ค่า >> 16) & 0xff); src [2] = (ไบต์) ((ค่า >> 8) & 0xff); src [3] = (ไบต์) (ค่า & 0xff); กลับ src; } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {bytebuf heapbuffer = unpooled.buffer (8); System.out.println (heapbuffer); ByTeArrayOutputStream out = new ByteArrayOutputStream (); ลอง {out.write (inttobytes2 (1)); } catch (ioexception e) {// todo บล็อก catch block ที่สร้างอัตโนมัติ e.printstacktrace (); } byte [] data = out.tobyteArray (); HEAPBUFFER.WRITEBYTES (ข้อมูล); System.out.println (heapbuffer); int a = heapbuffer.readint (); System.out.println (a); - ตัวถอดรหัส
Messagedecoder.java
แพ็คเกจ com.test.netty.decoder; นำเข้า io.netty.buffer.bytebuf; นำเข้า io.netty.channel.channelhandlerContext; นำเข้า io.netty.handler.codec.bytetomessagedecoder; นำเข้า io.netty.handler.codec.corruptedFrameException; นำเข้า java.util.list; นำเข้า com.test.netty.message.header; นำเข้า com.test.netty.message.message; / *** headerDecoder.java** @author Janehuang* @version 1.0*/ คลาสสาธารณะ MessageDecoder ขยาย Bytetomessagedecoder {/ ** ส่วนหัวความยาวแพ็คเกจ **/ สาธารณะคงที่ int สุดท้าย int head_lenght = 45; / ** ส่วนหัวธง **/ สาธารณะคงที่ไบต์สุดท้าย BYTE Package_tag = 0x01; @Override Void Decode (ChannelHandlerContext CTX, บัฟเฟอร์ ByTebuf, รายการ <Ojrop> ออก) โยนข้อยกเว้น {buffer.markreaderIndex (); if (buffer.readableBytes () <head_lenght) {โยน corruptedFrameException ใหม่ ("ปัญหาความยาวแพ็คเกจ"); } byte tag = buffer.readbyte (); if (tag! = package_tag) {โยน corruptedFrameException ใหม่ ("Flag Error"); } byte encode = buffer.readByte (); byte encrypt = buffer.readbyte (); BYTE Extend1 = buffer.readByte (); BYTE Extend2 = buffer.readByte (); byte sessionByte [] = byte ใหม่ [32]; buffer.readbytes (SessionByte); String sessionId = สตริงใหม่ (SessionByte, "UTF-8"); ความยาว int = buffer.readint (); int cammand = buffer.readint (); ส่วนหัวส่วนหัว = ส่วนหัวใหม่ (แท็ก, เข้ารหัส, เข้ารหัส, Extend1, Extend2, SessionId, ความยาว, Cammand); ไบต์ [] data = ไบต์ใหม่ [ความยาว]; buffer.readbytes (ข้อมูล); ข้อความข้อความ = ข้อความใหม่ (ส่วนหัว, สตริงใหม่ (ข้อมูล, "UTF-8")); out.add (ข้อความ); - เครื่องเข้ารหัส
MessageEncoder.java
แพ็คเกจ com.test.netty.encoder; นำเข้า com.test.netty.decoder.messagedecoder; นำเข้า com.test.netty.message.header; นำเข้า com.test.netty.message.message; นำเข้า io.netty.buffer.bytebuf; นำเข้า io.netty.channel.channelhandlerContext; นำเข้า io.netty.handler.codec.messagetobyteencoder; / ** * MessageEncoder.java * * @author Janehuang * @version 1.0 */ คลาสสาธารณะ MessageEncoder ขยาย Messagetobyteencoder <Sessage> {@Override Void Encode (ChannelHandlerContext CTX 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
แพ็คเกจ com.test.netty.server; นำเข้า org.springframework.stereotype.component; นำเข้า io.netty.bootstrap.serverbootstrap; นำเข้า io.netty.buffer.bytebuf; นำเข้า io.netty.buffer.unpooled; นำเข้า io.netty.channel.channelfuture; นำเข้า io.netty.channel.channelinitializer; นำเข้า io.netty.channel.channeloption; นำเข้า io.netty.channel.eventloopgroup; นำเข้า io.netty.channel.nio.nioeVentloopGroup; นำเข้า io.netty.channel.socket.socketChannel; นำเข้า io.netty.channel.socket.nio.nioserversocketChannel; นำเข้า io.netty.handler.codec.linebasedframedecoder; นำเข้า com.test.netty.decoder.messagedecoder; นำเข้า com.test.netty.encoder.messageencoder; นำเข้า com.test.netty.handler.serverhandler; / ** * chatserver.java * * @author janehuang * @version 1.0 */ @component คลาสสาธารณะ Timeserver {private int port = 88888; Public Void Run () พ่น InterruptedException {EventLoopGroup BossGroup = ใหม่ nioEventLoopGroup (); EventLoopGroup WorkerGroup = ใหม่ nioEventLoopGroup (); bytebuf heapbuffer = unpooled.buffer (8); HEAPBUFFER.WRITEBYTES ("/R" .getBytes ()); ลอง {serverbootstrap b = ใหม่ serverbootstrap (); // (2) B.Group (Bossgroup, WorkerGroup). Channel (nioserversocketChannel.class) // (3) .childhandler (ช่องใหม่ channelInitializer <Ocketchannel> () {// (4) @Override สาธารณะ MessageEncoder ()). addLast ("ตัวถอดรหัส", messagedecoder ใหม่ ()). addfirst (ใหม่ lineBasedFrameCoder (65535)) .Addlast (ใหม่ ServerHandler ()); // (6) channelelfure f = b.bind (พอร์ต) .sync (); // (7) f.Channel (). closeFuture (). sync (); } ในที่สุด {workerGroup.shutdowngracefully (); bossgroup.shutdowngracefully (); }} การเริ่มต้นโมฆะสาธารณะ (พอร์ต int) พ่น InterruptedException {this.port = พอร์ต; this.run (); - โปรเซสเซอร์และแจกจ่าย
serverhandler.java
แพ็คเกจ com.test.netty.handler; นำเข้า io.netty.channel.channelhandleradapter; นำเข้า io.netty.channel.channelhandlerContext; นำเข้า com.test.netty.invote.actionmaputil; นำเข้า com.test.netty.message.header; นำเข้า com.test.netty.message.message; / ** * * @author janehuang * */ คลาสสาธารณะคลาส ServerHandler ขยาย channelhandlerAdapter {@Override โมฆะสาธารณะเป็นโมฆะ channelactive (channelhandlercontext ctx) โยนข้อยกเว้น {สตริงเนื้อหา = "ฉันได้รับการเชื่อมต่อ"; ส่วนหัวส่วนหัว = ส่วนหัวใหม่ ((ไบต์) 0, (ไบต์) 1, (ไบต์) 1, (ไบต์) 1, (ไบต์) 0, "713F17CA614361FB257DC6741332CAF2", getBytes ("UTF-8") ข้อความข้อความ = ข้อความใหม่ (ส่วนหัวเนื้อหา); ctx.writeandflush (ข้อความ); } @Override void public excendcaught (channelhandlercontext ctx, สาเหตุที่สามารถจดจำได้) {cause.printstacktrace (); ctx.close (); } @Override โมฆะสาธารณะ ChannelRead (channelHandlerContext CTX, Object msg) โยนข้อยกเว้น {ข้อความ m = (ข้อความ) msg; // (1)/* การแจกจ่ายคำขอ*/ actionMaputil.invote (header.getCammand (), ctx, m); - หมวดหมู่เครื่องมือการกระจาย
ActionMaputil.java
แพ็คเกจ com.test.netty.invote; นำเข้า java.lang.reflect.method; นำเข้า java.util.hashmap; นำเข้า java.util.map; Public Class ActionMaputil {แผนที่คงที่ส่วนตัว <จำนวนเต็ม, การกระทำ> MAP = ใหม่ HashMap <จำนวนเต็ม, การกระทำ> (); วัตถุสาธารณะคงที่ INVOTE (คีย์จำนวนเต็ม, วัตถุ ... args) โยนข้อยกเว้น {action action = map.get (คีย์); if (action! = null) {method method = action.getMethod (); ลอง {return method.invoke (action.getObject (), args); } catch (exception e) {โยน e; }} return null; } โมฆะคงที่สาธารณะใส่ (คีย์จำนวนเต็ม, การดำเนินการ) {map.put (คีย์, การกระทำ); - วัตถุที่สร้างขึ้นเพื่อการกระจาย
Action.java
แพ็คเกจ com.test.netty.invote; นำเข้า java.lang.reflect.method; การดำเนินการในชั้นเรียนสาธารณะ {วิธีการส่วนตัว วัตถุวัตถุส่วนตัว; วิธีการสาธารณะ getMethod () {วิธีการส่งคืน; } โมฆะสาธารณะ setMethod (วิธีการ) {this.method = วิธี; } วัตถุสาธารณะ getObject () {return object; } โมฆะสาธารณะ setObject (วัตถุวัตถุ) {this.Object = Object; -คำอธิบายประกอบที่กำหนดเองคล้ายกับ @Controller ใน SpringMVC
nettycontroller.java
แพ็คเกจ com.test.netty.core; นำเข้า java.lang.annotation.documented; นำเข้า java.lang.annotation.ElementType; นำเข้า java.lang.annotation.retention; นำเข้า java.lang.annotation.RetentionPolicy; นำเข้า java.lang.annotation.target; นำเข้า org.springframework.stereotype.component; @retention (RetentionPolicy.runtime) @Target (ElementType.type) @Documented @component สาธารณะ @Interface NetTyController {} @reqestmapping ในประเภทสปริง MVC
actionmap.java
แพ็คเกจ com.test.netty.core; นำเข้า java.lang.annotation.documented; นำเข้า java.lang.annotation.ElementType; นำเข้า java.lang.annotation.retention; นำเข้า java.lang.annotation.RetentionPolicy; นำเข้า java.lang.annotation.target; @Retention (RetentionPolicy.Runtime) @Target (ElementType.Method) @Documented สาธารณะ @Interface ActionMap {int key (); - คำอธิบายประกอบเหล่านี้จะถูกเพิ่มเพื่อเก็บวัตถุเหล่านี้ไว้ในภาชนะหลังจากถั่วเริ่มต้นฤดูใบไม้ผลิ ถั่วนี้ต้องได้รับการกำหนดค่าในฤดูใบไม้ผลิ ถั่วฤดูใบไม้ผลิจะถูกเรียกหลังจากอินสแตนซ์
ActionBeanPostProcessor.java
แพ็คเกจ com.test.netty.core; นำเข้า java.lang.reflect.method; นำเข้า org.springframework.beans.beansexception; นำเข้า org.springframework.beans.factory.config.beanpostprocessor; นำเข้า com.test.netty.invote.action; นำเข้า com.test.netty.invote.actionmaputil; Public Class ActionBeanPostProcessor ใช้ beanpostProcessor {วัตถุสาธารณะ postprocessbeforeinitialization (Object Bean, String Beanname) พ่น beansexception {return bean; } วัตถุสาธารณะ postprocessafterinitialization (Object Bean, String Beanname) พ่น beansexception {method [] methods = bean.getClass (). getMethods (); สำหรับ (วิธีการ: วิธีการ) {ActionMap ActionMap = Method.getAnnotation (ActionMap.Class); if (actionMap! = null) {action action = new action (); action.setMethod (วิธีการ); action.setObject (ถั่ว); ActionMaputil.put (actionmap.key (), การกระทำ); }} return bean; - อินสแตนซ์คอนโทรลเลอร์
usercontroller.java
แพ็คเกจ com.test.netty.controller; นำเข้า io.netty.channel.channelhandlerContext; นำเข้า org.springframework.beans.factory.annotation.autowired; นำเข้า com.test.model.usermodel; นำเข้า com.test.netty.core.actionmap; นำเข้า com.test.netty.core.nettycontroller; นำเข้า com.test.netty.message.message; นำเข้า com.test.service.userservice; @NetTyController () ผู้ใช้ระดับสาธารณะ {@AutoWired Userservice Userservice; @ActionMap (key = 1) การเข้าสู่ระบบสตริงสาธารณะ (channelHandlerContext CT, ข้อความข้อความ) {USERMODEL USERMODEL = this.USERSERVICE.FINDBYMASTERUSERID (1000001); System.out.println (string.format ("การใช้ชื่อเล่น: %s; รหัสผ่าน %d; เนื้อหาตัวส่งสัญญาณ %s", usermodel.getNickName (), usermodel.getId (), message.getData ())); ส่งคืน usermodel.getNickName (); }} อย่าลืมเพิ่มสิ่งนี้ลงในไฟล์การกำหนดค่า ApplicationContext.xml
<ถั่ว/>
รหัสทดสอบ
การทดสอบแพ็คเกจ; นำเข้า org.springframework.context.applicationContext; นำเข้า org.springframework.context.support.classpathxmlapplicationContext; นำเข้า com.test.netty.server.timeserver; การทดสอบคลาสสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {applicationcontext ac = ใหม่ classpathxmlapplicationContext ("applicationcontext.xml"); Timeserver Timeserver = ac.getBean (Timeserver.class); ลอง {Timeserver.start (8888); } catch (interruptedException e) {// toDo บล็อก catch block ที่สร้างขึ้นอัตโนมัติ E.PrintStackTrace (); - สิ้นสุดสวิตช์ทดสอบ
การทดสอบแพ็คเกจ; นำเข้า java.io.ioException; นำเข้า Java.io.OutputStream; นำเข้า java.net.socket; นำเข้า java.util.scanner; นำเข้า com.test.netty.message.header; นำเข้า com.test.netty.message.message; clienttest คลาสสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ลอง {// เชื่อมต่อกับซ็อกเก็ตซ็อกเก็ตเซิร์ฟเวอร์ = ซ็อกเก็ตใหม่ ("127.0.0.1", 8888); ลอง {// dataOrtputStream ที่ส่งข้อมูลไปยังเซิร์ฟเวอร์ outputStream out = socket.getOutputStream (); // ตกแต่งสตรีมอินพุตมาตรฐานที่ใช้ในการป้อนข้อมูลจากเครื่องสแกนสแกนเนอร์คอนโซล = เครื่องสแกนใหม่ (System.in); ในขณะที่ (จริง) {string send = scanner.nextline (); System.out.println ("ไคลเอนต์:" + ส่ง); ไบต์ [] โดย = send.getBytes ("UTF-8"); ส่วนหัวส่วนหัว = ส่วนหัวใหม่ ((ไบต์) 1, (ไบต์) 1, (ไบต์) 1, (ไบต์) 1, (ไบต์) 1, (ไบต์) 1, "713F17CA614361FB257DC6741332CAF2" ข้อความข้อความ = ข้อความใหม่ (ส่วนหัว, ส่ง); out.write (message.tobyte ()); out.flush (); // ส่งข้อมูลที่ได้จากคอนโซลไปยังเซิร์ฟเวอร์ // out.writeUtf ("ไคลเอนต์:" + ส่ง); // อ่านข้อมูลจากเซิร์ฟเวอร์}} ในที่สุด {socket.close (); }} catch (ioexception e) {e.printstacktrace (); -ผลการทดสอบตกลง
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น