从零开始手写mmo游戏从框架到爆炸(一)— 开发环境

     

  一、创建项目

  1、首先创建一个maven项目,pom文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.loveprogrammer</groupId>
        <artifactId>eternity-online</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>eternity-client</artifactId>
    <packaging>jar</packaging>

    <name>eternity-client</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

    </dependencies>

    <build>
        <plugins>
            <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-clean-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>

            <!--
            编译插件
            mvn compile
            To compile your test sources, you'll do:
            mvn test-compile
            -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerVersion>${java.version}</compilerVersion>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <!-- maven 3.6.2及之后加上编译参数,可以让我们在运行期获取方法参数名称。 -->
                    <parameters>true</parameters>
                    <skip>true</skip>
                </configuration>
            </plugin>

            <!-- 打包时跳过单元测试 https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <!-- 打包源码 https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-source-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.1</version>
                <configuration>
                    <attach>true</attach>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
</project>

 2、先创建两个module,eternity-client、eternity-server,如下:

主pom文件增加:

  <modules>
    <module>eternity-client</module>
    <module>eternity-server</module>
  </modules>

二、Log4j2框架

       让我们先把日志弄好,目前原则上咱们整体不考虑使用spring框架,所以综合考虑我们就使用log4j2。

1、log4j2 的简介
        Apache Log4j2 是对Log4j 的升级版本,参考了logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:

  1. 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
  2. 性能提升,log4j2 相较于log4j 和 logback 都具有明显的性能提升,有18倍性能提升,后面会有官方测试的数据。
  3. 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
  4. 无垃圾机制,log4j2 在大部分情况下,都可以使用其设计的一套无垃圾机制【对象重用、内存缓冲】,避免频繁的日志收集导致的 jvm gc。

官网:https://logging.apache.org/log4j/2.x/

2、引入依赖

      在父pom文件中增加日志的依赖

         <!-- log4j2 日志门面 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
        <!-- log4j2 日志实面 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>

         <!-- 使用slf4j 作为日志门面 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
        <!-- 使用 log4j2 的适配器进行绑定 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

3、编写日志配置文件

 配置文件 eternity-server下 src/resources/log4j2.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">logs/server</property>
    </properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>

        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>

        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>

        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="logs/server/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <OnStartupTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
                <TimeBasedTriggeringPolicy />
            </Policies>
            <DefaultRolloverStrategy max="30" />
        </RollingFile>

    </Appenders>

    <Loggers>
        <Root level="trace">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</configuration>

4、修改eternity-server 的启动main函数

public class EternityServerMain
{
    // 为了保证使用时,不需要每次都去创建logger 对象,我们声明静态常量
    public static final Logger LOGGER = LoggerFactory.getLogger(EternityServerMain.class);

    public static void main( String[] args )
    {
        LOGGER.info( "Hello World!" );
        LOGGER.info( "Hello END!" );
    }
}

三、基本netty网络编程

        我们来完成一个简单的netty网络编程

        首先完成服务端:eternity-server的开发,增加两个类:SocketServer.java、SocketServerHandler.java

 1、SocketServerHandler

package com.loveprogrammer.netty.simple;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SocketServerHandler
 * @Description TODO
 * @Author admin
 * @Date 2024/1/29 17:29
 * @Version 1.0
 */
public class SocketServerHandler extends SimpleChannelInboundHandler<String> {
    private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        logger.info("数据内容:data=" + msg);
        String result = "我是服务器,我收到了你的信息:" + msg;
        result += "\r\n";
        ctx.writeAndFlush(result);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("出现异常",cause);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("建立连接");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("连接断开");
        super.channelInactive(ctx);
    }
}

  2、SocketServer

package com.loveprogrammer.netty.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SocketServer
 * @Description TODO
 * @Author admin
 * @Date 2024/1/29 17:08
 * @Version 1.0
 */
public class SocketServer {

    private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);
    private static final String IP = "127.0.0.1";

    private static final int PORT = 8088;

    // 分配用于处理业务的线程组数量
    private static final int BIS_GROUP_SIZE = Runtime.getRuntime().availableProcessors() * 2;

    /**
     * 每个线程组中线程的数量
     */
    private static final int WORK_GROUP_SIZE = 4;

    private static EventLoopGroup bossGroup = new NioEventLoopGroup(BIS_GROUP_SIZE);
    private static EventLoopGroup workerGroup = new NioEventLoopGroup(WORK_GROUP_SIZE);

    public void run() throws Exception {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup,workerGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                // 以(/n)为结尾分隔的 解码器
                pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                pipeline.addLast("decoder",new StringDecoder());
                pipeline.addLast("encoder",new StringEncoder());
                pipeline.addLast(new SocketServerHandler());
            }
        });
        bootstrap.bind(IP,PORT).sync();
        logger.info("Socket服务器已经启动完成");
    }

    protected static void shutdown() {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        logger.info("开始启动Socket服务器...");
        new SocketServer().run();
    }

}

       然后完成客户端:eternity-client的开发,增加两个类:SocketClient.java、SocketClientHandler.java

 1、SocketClientHandler 类

package com.loveprogrammer.netty.simple;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SocketClientHandler
 * @Description TODO
 * @Author admin
 * @Date 2024/1/29 17:41
 * @Version 1.0
 */
public class SocketClientHandler extends SimpleChannelInboundHandler<String> {

    private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class);
    @Override
    public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) {
        logger.info("异常发生", arg1);
    }

    @Override
    public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {
        super.channelRead(arg0, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext arg0, String data) {
        logger.info("数据内容:data=" + data);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端连接建立");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端连接断开");
        super.channelInactive(ctx);
    }

}

 2、SocketClient 类

package com.loveprogrammer.netty.simple;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SocketClient
 * @Description TODO
 * @Author admin
 * @Date 2024/1/29 17:43
 * @Version 1.0
 */
public class SocketClient {

    private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
    private static final String IP = "127.0.0.1";
    private static final int PORT = 8088;

    private static EventLoopGroup group = new NioEventLoopGroup();

    protected static void run() throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer() {
            protected void initChannel(Channel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                pipeline.addLast("decoder",new StringDecoder());
                pipeline.addLast("encoder",new StringEncoder());
                pipeline.addLast(new SocketClientHandler());
            }
        });

        // 连接服务器
        ChannelFuture channelFuture = bootstrap.connect(IP, PORT).sync();
        String msg = "大哥你好,我是客户端";
        msg += "\r\n";
        channelFuture.channel().writeAndFlush(msg);
        logger.info("向服务器发送消息 {}",msg);
        channelFuture.channel().closeFuture().sync();

    }

    public static void main(String[] args) throws InterruptedException {
        logger.info("开始连接Socket服务器...");
        try {
            run();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }


}

下一章: 

从零开始手写mmo游戏从框架到爆炸(二)— 核心组件抽离与工厂模式创建-CSDN博客

全部源码详见:

gitee : eternity-online: 多人在线mmo游戏 - Gitee.com

分支:step-01

参考:

java游戏服务器开发: https://blog.csdn.net/cmqwan/category_7690685.html

log4j2 的使用【超详细图文】:log4j2 的使用【超详细图文】-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374188.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100 | 链表(中下)

目录 1 19. 删除链表的倒数第 N 个节点 2 24. 两两交换链表中的节点 3 25. K 个一组翻转链表 4 138. 随机链表的复制 菜鸟做题第三周&#xff0c;语言是 C 1 19. 删除链表的倒数第 N 个节点 到底是节点还是结点。。。 解题思路&#xff1a; 设置双指针 left 和 ri…

Vue代理模式和Nginx反向代理(Vue代理部署不生效)

在使用axios时&#xff0c;经常会遇到跨域问题。为了解决跨域问题&#xff0c;可以在 vue.config.js 文件中配置代理&#xff1a; const { defineConfig } require(vue/cli-service) module.exports defineConfig({transpileDependencies: true,devServer: {port: 7070,prox…

Unity3D实现项目限制功能(使用次数限制和时间限制)

系列文章目录 unity工具 文章目录 系列文章目录前言一、时间限制1-1、代码如下&#xff1a; 二、次数限制2-1、 在Unity项目中需要对注册表进行操作&#xff0c;还需要设置一下API兼容级别设置成 .NET Framework2-2、设置如下图 Player里面2-3、代码如下&#xff1a; 三、同时…

python学习21

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

EasyX图形库学习(三、用easyX控制图形界面中的小球、图片-加载、输出)

目录 小球视频 图像输出函数 loadimage用于从文件中读取图片 putimage在当前设备上绘制指定图像。 initgraph 函数 图片输出 代码详解&#xff1a; 1. 初始化图形界面 2. 设置背景颜色并清除屏幕 3. 加载并显示图片 4. 等待用户输入并退出程序 图形界面中的小球 1…

docer compose部署simple-docker

简介 一个看似简陋但是功能足够用的docker管理工具 安装 创建目录 mkdir -p /opt/simple-docker cd /opt/simple-docker 创建并启动容器 编写docker-compose.yml文件,内容如下 version: 3 services: redis: image: redis:latest restart: always web: image: registry.cn-…

Linux线程库封装

一 MyThread.hpp #pragma once #include<pthread.h> #include<iostream> #include<unistd.h> #include<string> #include<ctime>typedef void (*callback_t)(); static int num 1; //任务和线程绑定 class Thread {static void* Routine(void …

解决hive表新增的字段查询为空null问题

Hive分区表新增字段&#xff0c;查询时数据为NULL的解决方案 由于业务拓展&#xff0c;需要往hive分区表新增新的字段&#xff0c;hive版本为2点多。 于是利用 alter table table_name add columns (col_name string )新增字段&#xff0c;然后向已存在分区中插入数据&#x…

MySQL学习记录——사 表结构的操作

文章目录 1、创建表2、查看表结构3、改变表结构4、删除表5、总结 1、创建表 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 例子 create table users ( id int, name varchar(20) c…

Linux Shell系列--realpath 返回给定路径的规范化绝对路径名

一、目的 在linux系统中有绝对路径、相对路径&#xff0c;还有符号链接&#xff0c;我们在shell脚本中获取一个文件或者路径的绝对路径名称&#xff0c;这个时候就需要realpath命令。 本篇主要介绍realpath命令的相关内容。 二、介绍 realpath命令主要功能是解析给定的路径&am…

游戏服务器租用多少钱一年?看完再买不吃亏!

2024年更新腾讯云游戏联机服务器配置价格表&#xff0c;可用于搭建幻兽帕鲁、雾锁王国等游戏服务器&#xff0c;游戏服务器配置可选4核16G12M、8核32G22M、4核32G10M、16核64G35M、4核16G14M等配置&#xff0c;可以选择轻量应用服务器和云服务器CVM内存型MA3或标准型SA2实例&am…

GC垃圾回收

文章目录 GC垃圾回收一、垃圾回收概述1、什么是垃圾&#xff1f;2、什么是垃圾回收&#xff1f;3、为什么需要垃圾回收&#xff1f;4、Java垃圾回收机制5、Java垃圾回收区域 二、对象存活判断1、引用计数算法&#xff08;Python&#xff09;1&#xff09;基本思路2&#xff09;…

C语言——联合体类型

&#x1f4dd;前言&#xff1a; 在前面两篇文章&#xff1a;C语言——结构体类型&#xff08;一&#xff09;和C语言——结构体&#xff08;二&#xff09;中&#xff0c;我们讲述了C语言中重要的数据类型之一&#xff1a;结构体类型&#xff0c;今天我们来介绍一下C语言中的另…

初识C语言·编译与链接

1 翻译环境和运行环境 C语言标准ANSI C 实现C语言代码的时候 一般需要经过两种环境&#xff0c;一是翻译环境&#xff0c;二是运行环境&#xff0c;计算机能识别的是二进制的指令&#xff0c;人写完代码后通过翻译环境&#xff0c;使代码变成计算机能读懂的可执行的机器指令&a…

Mac上软件闪退(意外退出)的解决方法

mac苹果电脑上运行软件会意外退出&#xff0c;怎么办&#xff0c;可以试试下面的方法&#xff0c;亲测可行&#xff01; 第一种方法&#xff1a; 1、打开访达&#xff0c;进入应用程序目录&#xff0c;找到闪退的软件图标&#xff0c;在软件图标上右键选择“显示简介”&#…

华为OD机试真题C卷-篇3

文章目录 查找一个有向网络的头节点和尾节点幼儿园篮球游戏 查找一个有向网络的头节点和尾节点 在一个有向图中&#xff0c;有向边用两个整数表示&#xff0c;第一个整数表示起始节点&#xff0c;第二个整数表示终止节点&#xff1b;图中只有一个头节点&#xff0c;一个或者多…

配备Apple T2 安全芯片的 Mac 机型及T2芯片mac电脑U盘装系统教程

T2 芯片为 Mac 提供了一系列功能&#xff0c;例如加密储存和安全启动功能、增强的图像信号处理功能&#xff0c;以及适用于触控 ID 数据的安全保护功能。哪些电脑配备了 T2 安全芯片呢&#xff0c;T2芯片mac电脑又如何重装系统呢&#xff1f;跟随小编一起来看看吧&#xff01; …

Dell服务器iDRAC9忘记密码, 通过RACADM工具不重启 重置密码

系列文章目录 文章目录 系列文章目录前言一、RACADM工具二、linux环境1.解压安装RACADM工具测试RACADM工具重置iDRAC密码 Windows环境 前言 一、RACADM工具 RACADM工具 官网参考信息 https://www.dell.com/support/kbdoc/zh-cn/000126703/%E5%A6%82%E4%BD%95-%E9%87%8D%E7%BD…

跟着cherno手搓游戏引擎【21】shaderLibrary(shader管理类)

前置&#xff1a; ytpch.h&#xff1a; #pragma once #include<iostream> #include<memory> #include<utility> #include<algorithm> #include<functional> #include<string> #include<vector> #include<unordered_map> #in…

天线阵列车载应用——第1章 介绍 1.1节 汽车工业中的天线阵列:应用和频率范围

1.1 汽车工业中的天线阵列:应用和频率范围 无线通信系统的发展需要新的技术来支持更高质量的通信、新的服务和应用。近年来&#xff0c;汽车无线通信市场得到了极大的扩展。现代汽车使用不同的服务:AM/FM收音机、卫星广播(SDARS)、移动电话通信、数字音频广播(DAB)、远程无钥匙…