从零开始搭建游戏服务器 第一节 创建一个简单的服务器架构

目录

    • 引言
    • 技术选型
    • 正文
      • 创建基础架构
        • IDEA创建项目
        • 添加Netty监听端口
        • 编写客户端进行测试
    • 总结

引言

由于现在java web太卷了,所以各位同行可以考虑换一个赛道,做游戏还是很开心的。

本篇教程给新人用于学习游戏服务器的基本知识,给新人们一些学习方向,有什么错误的地方欢迎各位同行进行讨论。

技术选型

本篇教程预计使用Java+Redis+Mongo

正文

本着先完成再完美的原则,从最简单的echo服务器开始。

在这里插入图片描述

Echo服务器就是,客户端发什么数据,服务端就原样返回回去。

创建基础架构

IDEA创建项目

在这里插入图片描述

我这边用Gradle进行依赖管理,使用的版本为 gradle8.0.2, openjdk19.

修改build.gradle导入几个基础开发包。

dependencies {
    //网络
    implementation group: 'io.netty', name: 'netty-all', version: '4.1.90.Final'
    //spring
    implementation group: 'org.springframework', name: 'spring-context', version: '6.0.6'
    //log
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36'
    implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.11'
    implementation group: 'ch.qos.logback', name: 'logback-access', version: '1.2.11'
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.11'
    //lombok
    compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
}

创建Bean配置类

@Configuration
@ComponentScan(basePackages = {"com.wfgame"})
public class GameBeanConfiguration {
}

创建主类

@Component
@Slf4j
public class GameMain {
    public static void main(String[] args) {
        // 初始化Spring
        AnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext(GameBeanConfiguration.class);
        springContext.start();
        log.info("server start!");
    }
}

运行一下,正常输出server start!

我们会发现,程序执行后马上停止了,对于游戏服务器来说,我们需要保持运行状态,等待玩家接入进行游戏。所以我们main中增加一个循环,不停读取控制台输入,当读取到控制台输入stop时,我们再进行停服。

修改main方法如下:

    public static void main(String[] args) {
        // 初始化Spring
        AnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext(GameBeanConfiguration.class);
        springContext.start();

        log.info("server start!");

        //region 处理控制台输入,每秒检查一遍 stopFlag,为true就跳出循环,执行关闭操作
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 设置循环使服务器不立刻停止
        while (true) {
            if (stopFlag) {
                log.info("receive stop flag, server will stop!");
                break;
            }
            // 每次循环停止一秒,避免循环频率过高
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //处理控制台指令
            try {
                if (br.ready()) {
                    String cmd = br.readLine().trim();
                    if (cmd.equals("stop")) {//正常关服
                        stopFlag = true;
                        log.info("Receive stop flag, time to stop.");
                    } else {
                        log.info("Unsupported cmd:{}", cmd);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //停掉虚拟机
        System.exit(0);
    }

这样我们就获得了一个可以控制停服的服务器。当我们控制台输入stop时,程序结束运行。

添加Netty监听端口

要与客户端进行TCP连接,需要建立socket通道,然后通过socket通道进行数据交互。

传统BIO一个线程一个连接,有新的连接进来时就要创建一个线程,并持续读取数据流,当这个连接发送任何请求时,会对性能造成严重浪费。

NIO一个线程通过多路复用器可以监听多个连接,通过轮询判断连接是否有数据请求。

Netty对java原生NIO进行了封装,简化了代码,便于我们的使用。

Netty的包我们之前已经导入过了,直接拿来用即可。

首先我们创建一个Netty自定义消息处理类。

@Sharable
public class NettyMessageHandler extends SimpleChannelInboundHandler<Object> {
    /**
     * 读取数据
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.doRead(ctx, msg);
    }

    private void doRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("received msg = : " + msg);
        // 马上将原数据返回
        ctx.writeAndFlush(msg);
    }
}

然后编写Netty服务器启动代码,我们修改GameMain类的代码

@Component
@Slf4j
public class GameMain {

    // 停服标志
    private static boolean stopFlag = false;

    public static void main(String[] args) {
        // 初始化Spring
        AnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext(GameBeanConfiguration.class);
        springContext.start();

        // 启动Netty服务器
        try {
            startNetty();
            log.info("Netty server start!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("server start!");

        //region 处理控制台输入,每秒检查一遍 stopFlag,为true就跳出循环,执行关闭操作
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 设置循环使服务器不立刻停止
        while (true) {
            if (stopFlag) {
                log.info("receive stop flag, server will stop!");
                break;
            }
            // 每次循环停止一秒,避免循环频率过高
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //处理控制台指令
            try {
                if (br.ready()) {
                    String cmd = br.readLine().trim();
                    if (cmd.equals("stop")) {//正常关服
                        stopFlag = true;
                        log.info("Receive stop flag, time to stop.");
                    } else {
                        log.info("Unsupported cmd:{}", cmd);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //停掉虚拟机
        System.exit(0);
    }

    /**
     * 启动netty服务器
     */
    private static void startNetty() throws InterruptedException {
        int port = 2333;
        log.info("Netty4SocketServer start---Normal, port = " + port);

        final NioEventLoopGroup bossGroup = new NioEventLoopGroup(2);
        final NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.option(ChannelOption.SO_REUSEADDR, true);//允许重用端口
        bootstrap.option(ChannelOption.SO_BACKLOG, 512);//允许多少个新请求进入等待
        bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//是否使用内存池
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);    // 保持连接活动
        bootstrap.childOption(ChannelOption.TCP_NODELAY, false);    // 禁止Nagle算法等待更多数据合并发送,提高信息及时性
        bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//是否使用内存池

        final NettyMessageHandler handler = new NettyMessageHandler();
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline cp = ch.pipeline();
                cp.addLast(new StringDecoder());
                cp.addLast(new StringEncoder());
                cp.addLast("handler", handler);
            }
        });
        // 绑定并监听端口
        bootstrap.bind(port).sync();//线程同步阻塞等待服务器绑定到指定端口

        // 优雅停机
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }));

        log.info("Netty4SocketServer ok,bind at :" + port);
    }

我们先创建了一个startNetty()方法,用于启动Netty服务器,同时绑定了端口2333

我们要注意一下initChannel这块代码,我们注册了String编码解码器,他们是用换行符作为一个消息的结束标志,因此我们等下通过客户端发送消息过来需要在行尾添加换行符。同时将我们自定义的消息处理类也注册进pipeline中,当客户端发送消息过来,先通过StringDecoder进行解码,然后流入自定义处理类中进行下一步处理。

至此服务端Netty接入完毕,我们下面编写一个客户端进行测试。

编写客户端进行测试

我们增加了ClientMain类,用socket与服务器进行连接,读取控制台输入上行到服务器,同时接受服务器下行的消息。

public class ClientMain {

    private static Socket socket = null;
    private static BufferedReader br = null;
    private static BufferedWriter writer = null;
    private static BufferedReader receivedBufferedReader = null;
    public static void main(String[] args) {
        // 新增连接到服务器
        startSocket();
    }

    /**
     * 启动socket连接
     */
    private static void startSocket() {
        try {
            socket = new Socket("127.0.0.1", 2333);
            br = new BufferedReader(new InputStreamReader(System.in));
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            receivedBufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            new Thread(() -> {
                try {
                    while (true) {
                        Thread.sleep(1000L);
                        String s = receivedBufferedReader.readLine();
                        if (s!=null && !s.equals("")) {
                            System.out.println("receive: " + s);
                        }
                    }
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            while (true) {
                Thread.sleep(1000L);
                if (br.ready()) {
                    writer.write(br.readLine().trim() + "\n");
                    writer.flush();
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (receivedBufferedReader != null) {
                    receivedBufferedReader.close();
                }
                if (writer != null) {
                    writer.close();
                }
                if (br != null) {
                    br.close();
                }
                if (socket != null) {
                    socket.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

测试一下,我们先运行服务器,再运行客户端。

在客户端控制台下输入测试信息。

在这里插入图片描述

可以成功进行信息交互

总结

本节一共做了这么几件事:

  1. 项目的初步创建,通过build.gradle进行依赖包的管理。
  2. Netty服务器的启动,并且不断监听控制台输入,客户端上行数据的读取。
  3. 编写测试用客户端,与服务器进行数据交互。

下一节将进行注册登录的开发,内容将会比较多,感兴趣的点点关注或者留言评论。

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

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

相关文章

伪静态技术

网址和纯静态一样&#xff0c;只是&#xff0c;其内部仍旧需要查询数据库&#xff1a;如果有一个页面&#xff0c;希望这个页面利于seo,但是有不适合使用真静态&#xff0c;比如csdn论坛帖子&#xff0c;可以考虑使用伪静态&#xff0c;即: 形式上是一个静态地址&#xff0c;比…

Chapter7.2:MATLAB在频率法中的应用及频率法稳定性分析

该系列博客主要讲述Matlab软件在自动控制方面的应用&#xff0c;如无自动控制理论基础&#xff0c;请先学习自动控制系列博文&#xff0c;该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接&#xff1a;https://blog.csdn.net/qq_39032096/category_10287468…

PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络

01、实例&#xff1a;DIEN 模拟兴趣演化的序列网络深度兴趣演化网络(Deep Interest Evolution Network,DIEN)是阿里巴巴团队在2018年推出的另一力作,比DIN 多了一个Evolution,即演化的概念。在DIEN 模型结构上比DIN 复杂许多,但大家丝毫不用担心,我们将DIEN 拆解开来详细地说明…

Intel 处理器 macOS降级到Big Sur

1 创建可引导的 macOS 安装器 将移动硬盘作安装 Mac 操作系统的启动磁盘。 创建可引导安装器需要满足的条件 移动硬盘&#xff08;格式化为 Mac OS 扩展格式&#xff09;&#xff0c;至少有 14GB 可用空间已下载 macOS Big Sur的安装器 2 下载 macOS macOS Big Sur安装器会…

【GPT4】微软 GPT-4 测试报告(1)总体介绍

欢迎关注【youcans的AGI学习笔记】原创作品&#xff0c;火热更新中 微软 GPT-4 测试报告&#xff08;1&#xff09;总体介绍 微软 GPT-4 测试报告&#xff08;2&#xff09;多模态与跨学科能力 微软 GPT-4 测试报告&#xff08;3&#xff09;GPT4 的编程能力 【GPT4】微软 GPT-…

Thingsboard使用gateway网关

简介&#xff1a; 本次是想测试一下thingsboard网关的使用&#xff0c;实现通过网关mqttthingsboardemqx 实现间接设备创建和数据传输 前期准备&#xff1a; thingsboard平台 thingsboard网关 emqx平台 MQTTX工具 详细过程&#xff1a; 1&#xff1a;thingsboard平台搭建 …

字节、阿里等大厂年薪50w+的测试都什么水平?

各位做测试的朋友&#xff0c;但凡经历过几次面试&#xff0c;那么你一定曾被问到过以下问题&#xff1a; 1、在Linux环境下&#xff0c;怎么执行web自动化测试&#xff1f; 2、Shell如何&#xff0c;Docker熟悉吗&#xff1f; 3、全链路的压测实操过吗&#xff0c;如何推进与开…

FasterNet实战:使用FasterNet实现图像分类任务(一)

文章目录摘要安装包安装timm安装 grad-cam数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集摘要 论文翻译&#xff1a;https://wanghao.blog.csdn.net/article/details/129485972?spm1001.2014.3001.5502 官方源码&#xff1a; https://github.com/JierunChen/Faste…

VR实景导航,解决最后几十米的导航问题

你是否跟朋友有过这样的经历&#xff1a;“哎&#xff0c;你说的那个餐厅在哪呀&#xff1f;”&#xff0c;“这家商场好复杂&#xff0c;怎么转啊”&#xff0c;“你在医院哪一层&#xff1f;我怎么找不到你呀&#xff01;”等等。在建筑内部&#xff0c;我们的地图导航并不是…

在U盘上运行的 Windows

版本&#xff1a;5.6.1平台&#xff1a;Win x64语言&#xff1a;简体中文,繁体中文,英文更新时间&#xff1a;2023-03-04 下载地址&#xff1a;https://dl.luobotou.org/wtga5610.zip 解压后运行WTGA文件夹中的wintogo.exe启动程序。从5.5版本开始不再支持32位系统、Win7系统…

HTTP协议加强

HTTP协议加强 Date: January 19, 2023 Sum: HTTP请求、响应、请求方法、响应状态代码 HTTP协议简介 什么是通信 通信&#xff0c;就是信息的传递和交换。 通信三要素&#xff1a;通信的主体、通信的内容、通信的方式 现实生活中的通信 案例&#xff1a;张三要把自己考上传…

美颜SDK技术原理、技术应用、代码分析

随着社交媒体的普及&#xff0c;人们对于自己的外貌越来越重视。为了满足用户对于美颜需求&#xff0c;各大科技公司纷纷推出了美颜SDK技术&#xff0c;使得用户可以在拍照和视频中实现美颜效果。本文将对美颜SDK技术进行详细分析。 一、美颜SDK技术的原理 美颜SDK技术是一种基…

精简指令集结构(Reduced Instruction Set Computer,RISC)

ARM内核采用精简指令集结构&#xff08;Reduced Instruction Set Computer&#xff0c;RISC&#xff09;体系结构。RISC技术产生于20世纪70年代&#xff0c;其设计目标是创建一种能以每个时钟周期执行一条指令的速度很快的计算机。RISC的设计重点在于降低由硬件执行的指令复杂度…

Kubeadm生成的k8s证书内容说明以及延长证书过期时间

Kubeadm生成的k8s证书内容说明Kubeadm生成的k8s证书内容说明&#xff1a;证书分组Kubernetes 集群根证书由此根证书签发的证书有:kube-apiserver 代理根证书(客户端证书)etcd 集群根证书etcd server 持有的服务端证书peer 集群中节点互相通信使用的客户端证书pod 中定义 Livene…

函数的定义与使用及七段数码管绘制

函数的定义 函数是一段代码的表示 函数是一段具有特定功能的、可重用的语句组 函数是一种功能的抽象&#xff0c;一般函数表达特定功能 两个作用&#xff1a;降低编程难度 和 代码复用 求一个阶乘 fact就是 函数名 n就是参数 return就是输出部分即返回值 而函数的调用就是…

【计量】回归背后的微操作——论文自救记录(进行中)

【计量】回归背后的微操作 1. 变量的选择 与 模型的设定 https://zhuanlan.zhihu.com/p/50577508?yidian_smb 2. 变量的处理 2.1 常用的处理——中心化、标准化、归一化 目的&#xff1a;统一量纲&#xff08;Scale&#xff09;—— 可以理解成 100分制下&#xff0c;1 2分…

Spring Security 6.0系列【2】认证篇之使用数据库存储用户

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 源码地址&#xff1a;https://gitee.com/pearl-organization/study-spring-security-demo 文章目录前言1. 环境搭建1.1 创建用户表1.…

Oracle用户密码过期,修改永不过期

修改密码有效过期时间&#xff0c;可以通过以下四步设置&#xff0c;如果再第一步发现本身的密码过期时间为无限期的&#xff0c;那就请各位小伙伴绕过&#xff0c;如果发现不是无期限的&#xff0c;那么必须设置第四步&#xff0c;才会生效。 目录 第一步&#xff1a;查询密码…

实验三 数据更新及视图

实验三 数据更新及视图 1.实验目的 1.加深对数据库相关性质的理解&#xff1b; 2.各种约束性理解&#xff1b; 3.学会数据库中数据的更新的方法&#xff1b; 4.学会视图的创建与查询。 2.实验内容 对已建好的各表输入适当的数据并练习数据的插入、删除和修改&#xff0c;注意…

瑞吉外卖项目Day2———完善登录问题、员工功能

创建过滤器类(filter) package com.study.filter;import com.alibaba.fastjson.JSON; import com.study.common.R; import lombok.extern.slf4j.Slf4j; import org.springframework.util.AntPathMatcher;import javax.servlet.*; import javax.servlet.annotation.WebFilter; …