Netty+SpringBoot+protobuf3 搭建socket游戲網(wǎng)絡層

網(wǎng)絡包設計 (2+4+n)

[包頭 2 Bytes] : 除了包頭2個字節(jié)外包體的大小
[包體 4 Bytes] :包的協(xié)議ID
[包體 ] : protobuf 字節(jié)數(shù)據(jù)

小編使用的是gradle項目進行搭建游戲引擎掰吕,開發(fā)工具:eclipse
gradle項目引用的依賴:

allprojects {
    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'eclipse'
    apply plugin: 'findbugs'
    compileJava.options.encoding = 'UTF-8'
    sourceCompatibility = 1.8
    targetCompatibility = 1.8
    ext.projectName = "$name"
    repositories {
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        jcenter()
    }
    dependencies {
    
        compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.5.1'
        compile group: 'io.netty', name: 'netty-all', version: '4.1.6.Final'
        compile group: 'org.springframework', name: 'spring-context', version: '5.0.7.RELEASE'
        compile group: 'org.springframework', name: 'spring-core', version: '5.0.7.RELEASE'
        compile group: 'org.springframework', name: 'spring-beans', version: '5.0.7.RELEASE'
        compile group: 'org.springframework', name: 'spring-context-support', version: '5.0.7.RELEASE'
        compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15'        
        compile group: 'com.google.guava', name: 'guava', version: '25.1-jre'
        compile group: 'com.alibaba', name: 'fastjson', version: '1.2.58'    
        compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
        compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7'      
        compile group: 'commons-lang', name: 'commons-lang', version: '2.6'     
        compile(
                fileTree(dir: '../libs', include: '*.jar'),
        )
        compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.0'
    }
    // findbugs
    tasks.withType(FindBugs) {
        ignoreFailures = true
        reports {
            xml.enabled = false
            html.enabled = true
        }
        reportLevel = "high"
    }
}
subprojects {
    sourceSets.main.java.srcDirs = ["src", "conf"]
    task initPath {
        sourceSets.main.java.srcDirs.each { it.mkdirs() }
    }
    //清除上次的編譯過的文件
    task clearPj(type: Delete) {
        delete 'build', 'target'
    }
    task copyJar(type: Copy) {
        from configurations.runtime
        into('build/libs/lib')
    }
    //把JAR復制到目標目錄
    task release(type: Copy, dependsOn: [build, copyJar]) {
        from 'conf'
        into('build/libs/conf') // 目標位置
    }
    task export(type: Copy) {
        from 'build/libs'
        into '../export/' + projectName
    }
    task zip(type: Zip) {
        from 'build/libs'
    }
    task copyzip(type: Copy) {
        from 'build/distributions'
        into '../export/zip/'
    }
}

main的啟動方法
小編使用的是springboot的加載方式進行初始化相關(guān)的配置


/**
 * socket 啟動類
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月10日
 */
@Slf4j
public class ScoketApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        context.start();
        SocketServer socket = context.getBean(SocketServer.class);
        new Thread(socket).start();
    }
}

/**
 * 服務配置
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月10日
 */
@Configuration
@PropertySource("classpath:/scoket.properties")
@ImportResource("classpath:/quartz.xml")
public class BeanConfig {
    @Autowired
    Environment env;
    @Autowired
    void init() {
        // 系統(tǒng)配置 設置
        String ip = env.getProperty("ip");
        int port = env.getProperty("port", int.class);
        System.err.println("ip = " + ip + "port = " + port);
    }

    @Bean
    SocketServer socketServer() {
        return new SocketServer(scoketInitializer(), env.getProperty("port", int.class));
    }
    @Bean
    SocketInitializer scoketInitializer() {
        return new SocketInitializer();
    }
}

scoket.properties的配置

#scoket的ip
ip=127.0.0.1
#socket的端口
port=8888

SocketInitializer 初始化器

/**
 * socket 初始器
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月11日
 */
@AllArgsConstructor
public class SocketInitializer extends ChannelInitializer < SocketChannel > {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // TODO Auto-generated method stub
        ChannelPipeline pipeline = ch.pipeline();
        System.out.println("報告");
        System.out.println("信息:有一客戶端鏈接到本服務端");
        System.out.println("IP:" + ch.localAddress().getHostName());
        System.out.println("Port:" + ch.localAddress().getPort());
        System.out.println("報告完畢");
        
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("decoder",new Decoder());//解碼器
        pipeline.addLast("encoder", new Encoder());//編碼器
//        pipeline.addLast(new ByteArrayEncoder());
        pipeline.addLast(new ServerHandler()); // 客戶端觸發(fā)操作
    }

}

解碼器 (TCP的包處理)

/**
 * 解碼器
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月10日
 */
@Slf4j
public class Decoder extends MessageToMessageDecoder<ByteBuf> {

    /**
     * [包頭 2 Bytes] : 除了包頭2個字節(jié)外包體的大小</br>
     * [包體 4 Bytes] :包的協(xié)議ID </br>
     * [包體 ] : protobuf 字節(jié)數(shù)據(jù)</br>
     * 1.先讀取2個字節(jié)并解析出剩下數(shù)據(jù)流的長度dataLength</br>
     * 2.如果剩下的數(shù)據(jù)流不滿足dataLength的長度則繼續(xù)等待</br>
     * 3.如果剩下的數(shù)據(jù)流滿足dataLength的長度則放行
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        // out.add(Unpooled.wrappedBuffer(msg));//不進行任何的解析直接放行
        // 可讀長度
        int readableBytes = msg.readableBytes();
        if (readableBytes < 4) {
            System.err.println("接受到信息! 可讀長度小于4! 不進行解碼!");
            return;
        }
        // 標記讀取位置
        msg.markReaderIndex();
        int dataLength = msg.readShort();// 讀取兩個節(jié) 查看剩下的數(shù)據(jù)流長度
        if (msg.readableBytes() < dataLength) {
            log.info("剩下可讀長度小于 dataLength! 剩下可讀長度 = {} ", msg.readableBytes());
            // 移除讀取標準位置
            msg.resetReaderIndex();
            return;
        }
        ByteBuf buf = Unpooled.buffer(dataLength + 2);
        buf.writeShort(dataLength);
        buf.writeBytes(msg);
        // 放行到hander層
        out.add(Unpooled.wrappedBuffer(buf));
    }
}

解碼器

/**
 * 編碼器
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月10日
 */
public class Encoder extends MessageToMessageEncoder<byte[]> {

    @Override
    protected void encode(ChannelHandlerContext ctx, byte[] msg, List<Object> out) throws Exception {
        // TODO Auto-generated method stub
          out.add(Unpooled.wrappedBuffer(msg));
    }
}

服務器Handler


/**
 * 服務器Handler
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月10日
 */
public class ServerHandler  extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        // TODO Auto-generated method stub
        short readShort = msg.readShort();//2個字節(jié)
        int cmd = msg.readInt();//4個字節(jié)
        int dataLeng= readShort -4;//protobuf的長度   
        byte[] result1 = new byte[dataLeng];
        msg.readBytes(result1);
        c2s_login_user parseFrom = GameProto.c2s_login_user.parseFrom(result1);
        System.err.println("收到客戶端的protobuf信息 = "+parseFrom.toString()); 
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        super.channelActive(ctx);
        System.err.println("客戶端鏈接上廊散!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // TODO Auto-generated method stub
        super.exceptionCaught(ctx, cause);
    }
}

模擬客戶端

/**
 * 模擬客戶端發(fā)送
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月11日
 */
public class MyClientTest {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyClientInitializer());
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }

    }
}

客戶端初始化器

/**
 * 客戶端初始化器
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月11日
 */
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // TODO Auto-generated method stub
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ByteArrayEncoder());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new MyClientHandler());
    }
}

客戶端處理器

/**
 * 客戶端處理器
 * 
 * @bk https://home.cnblogs.com/u/huanuan/
 * @簡書 http://www.reibang.com/u/d29cc7d7ca49
 * @Author 六月星辰
 * @Date 2020年1月11日
 */
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //與服務端鏈接成功 并發(fā)送一個登錄的請求
        //組建protobuf的字節(jié)數(shù)據(jù)
        Builder builder = c2s_login_user.newBuilder();
        builder.setAccount("123456789");
        builder.setPassword("123456789");
        byte[] byteArray = builder.build().toByteArray();
        
        //發(fā)送的長度 = 2 +4 +n
        int length = 6 + byteArray.length;
        int dataLength = 4 + byteArray.length;
        // 4個字節(jié)長度的命令號
        int cmd = 1001;// 命令號
        
        //獲取發(fā)送長度的字節(jié)ByteBuf流
        ByteBuf buf = Unpooled.buffer(length);
        buf.writeShort(dataLength);//[包頭 2 Bytes] : 除了包頭2個字節(jié)外包體的大小
        buf.writeInt(cmd);//    [包體 4 Bytes] :包的協(xié)議ID
        buf.writeBytes(byteArray);//  [包體 ] : protobuf 字節(jié)數(shù)據(jù)
        
        byte[] newdata = new byte[buf.readableBytes()];
        buf.readBytes(newdata);
        
        //發(fā)送數(shù)據(jù)
        ctx.writeAndFlush(Unpooled.copiedBuffer(newdata)); // 必須有flush
    }

    /**
     * 處理服務端發(fā)送過來的消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("讀取服務端通道信息..");
    }

}

先啟動服務端在啟動客戶端让禀,效果如下

image.png

image.png

小編項目源碼下載地址:
https://github.com/zhuhuanuan/nettySocket

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雄妥,一起剝皮案震驚了整個濱河市培己,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薄霜,老刑警劉巖某抓,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惰瓜,居然都是意外死亡否副,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門崎坊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來备禀,“玉大人,你說我怎么就攤上這事奈揍∏” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵男翰,是天一觀的道長另患。 經(jīng)常有香客問我,道長蛾绎,這世上最難降的妖魔是什么昆箕? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮租冠,結(jié)果婚禮上鹏倘,老公的妹妹穿的比我還像新娘。我一直安慰自己顽爹,他們只是感情好纤泵,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镜粤,像睡著了一般捏题。 火紅的嫁衣襯著肌膚如雪玻褪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天涉馅,我揣著相機與錄音归园,去河邊找鬼黄虱。 笑死稚矿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捻浦。 我是一名探鬼主播晤揣,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼朱灿!你這毒婦竟也來了昧识?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盗扒,失蹤者是張志新(化名)和其女友劉穎跪楞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侣灶,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡甸祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了褥影。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片池户。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凡怎,靈堂內(nèi)的尸體忽然破棺而出校焦,到底是詐尸還是另有隱情,我是刑警寧澤统倒,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布寨典,位于F島的核電站,受9級特大地震影響房匆,放射性物質(zhì)發(fā)生泄漏耸成。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一坛缕、第九天 我趴在偏房一處隱蔽的房頂上張望墓猎。 院中可真熱鬧,春花似錦赚楚、人聲如沸毙沾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽左胞。三九已至寇仓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烤宙,已是汗流浹背遍烦。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躺枕,地道東北人服猪。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像拐云,于是被迫代替她去往敵國和親罢猪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容