当前位置: 首页 > news >正文

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

目录

    • 引言
    • 技术选型
    • 正文
      • 创建基础架构
        • 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'//springimplementation group: 'org.springframework', name: 'spring-context', version: '6.0.6'//logimplementation 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'//lombokcompileOnly 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) {// 初始化SpringAnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext(GameBeanConfiguration.class);springContext.start();log.info("server start!");}
}

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

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

修改main方法如下:

    public static void main(String[] args) {// 初始化SpringAnnotationConfigApplicationContext 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> {/*** 读取数据*/@Overrideprotected 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) {// 初始化SpringAnnotationConfigApplicationContext 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>() {@Overrideprotected 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. 编写测试用客户端,与服务器进行数据交互。

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

相关文章:

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

目录引言技术选型正文创建基础架构IDEA创建项目添加Netty监听端口编写客户端进行测试总结引言 由于现在java web太卷了&#xff0c;所以各位同行可以考虑换一个赛道&#xff0c;做游戏还是很开心的。 本篇教程给新人用于学习游戏服务器的基本知识&#xff0c;给新人们一些学习…...

C++中那些你不知道的未定义行为

引子 开篇我们先看一个非常有趣的引子&#xff1a; // test.cpp int f(long *a, int *b) {*b 5;*a 1;return *b; }int main() {int x 10;int *p &x;auto q (long *)&x;auto ret f(q, p);std::cout << x << std::endl;std::cout << ret <&…...

java基础面试题(四)

Mysql索引的基本原理 索引是用来快速寻找特定的记录&#xff1b;把无序的数据变成有序的查询把创建索引的列数据进行排序对排序结果生成倒排表在倒排表的内容上拼接上地址链在查询时&#xff0c;先拿到倒排表内容&#xff0c;再取出地址链&#xff0c;最后拿到数据聚簇索引和非…...

@PropertySource使用场景

文章目录一、简单介绍二、注解说明1. 注解源码① PropertySource注解② PropertySources注解2. 注解使用场景3. 使用案例&#xff08;1&#xff09;新增test.properties文件&#xff08;2&#xff09;新增PropertySourceConfig类&#xff08;3&#xff09;新增PropertySourceTe…...

【C语言进阶:刨根究底字符串函数】strtok strerror函数

本节重点内容&#xff1a; 深入理解strtok函数的使用深入理解strerror函数的使用⚡strtok Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part ofstr1sep参数是个字符串&#xff0c;定义了用作分隔符的字符集合。第一个参数指…...

西安石油大学C语言期末重点知识点总结

大一学生一周十万字爆肝版C语言总结笔记 是我自己在学习完C语言的一次总结&#xff0c;尽管会有许多的瑕疵和不足&#xff0c;但也是自己对C语言的一次思考和探索&#xff0c;也让我开始有了写作博客的习惯和学习思考总结&#xff0c;争取等我将来变得更强的时候再去给它优化出…...

读《Multi-level Wavelet-CNN for Image Restoration》

Multi-level Wavelet-CNN for Image Restoration&#xff1a;MWCNN摘要一. 介绍二.相关工作三.方法摘要 存在的问题&#xff1a; 在低级视觉任务中&#xff0c;对于感受野尺寸与效率之间的平衡是一个关键的问题&#xff1b;普通卷积网络通常以牺牲计算成本去扩大感受野&#…...

【Linux】安装DHCP服务器

1、先检测网络是否通 get dhcp.txt rpm -qa //查看软件包 rpm -qa |grep dhcp //确定是否安装 yum install dhcp //进行安装 安装完成后 查询 rpm -ql dhcp 进行配置 cd /etc/dhcp 查看是否有遗留dhcpd.conf.rpmsave 删除该文件 cp /usr/share/doc/dhcp-4.1.1/dhcpd.conf.sampl…...

功能测试转型测试开发年薪27W,又一名功能测试摆脱点点点,进了大厂

咱们直接开门见山&#xff0c;没错我的粉丝向我投来了喜报&#xff0c;从功能测试转型测试开发&#xff0c;进入大厂&#xff0c;摆脱最初级的点点点功能测试&#xff0c;拿到高薪&#xff0c;遗憾的是&#xff0c;这名粉丝因为个人原因没有经过指导就去面试了&#xff0c;否则…...

数据结构之哈希表

常见的三种哈希结构 数组set&#xff08;集合&#xff09;map(映射) set&#xff08;集合&#xff09; 集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率std::set红黑树有序否否O(log n)O(log n)std::multiset红黑树有序是否O(log n)O(log n)std::unordere…...

linux信号理解

linux信号&#xff1a;用户、系统或进程发送给目标进程的信息&#xff0c;以通知目标进程中某个状态的改变或是异常。 信号产生原因&#xff1a;软中断或者硬中断。可细分为如下几种原因&#xff1a; ①系统终端Terminal中输入特殊的字符来产生一个信号&#xff0c;比如按下&am…...

HC小区管理系统window系统安装教程

实操视频 HC小区管理系统局域网window物理机部署教程_哔哩哔哩_bilibili 一、下载安装包 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1XAjxtpeBjHIQUZs4M7TsRg 提取码&#xff1a;hchc 或者 123盘 hc-window.zip官方版下载丨最新版下载丨绿色版下…...

自动化测试工具软测界的不二之选,还不快速来了解

目录 引言&#xff1a; 前言&#xff1a; 一.龙测AI-TestOps云平台使用教程 1.如何登录龙测AI-TestOps云平台 登录方法① 登录方法② 2.龙测AI-TestOps云平台界面布局 3.龙测AI-TestOps云平台菜单功能 ①创建项目 ②应用管理 ③设备管理 ④订单 二.总结 引言&#…...

centos系统/dev/mapper/centos-root目录被占满的解决方式

最近在做虚拟机部署docker微服务时&#xff0c;发现磁盘内存占满&#xff0c;无法进行操作。open /var/lib/dpkg/info/libc6:amd64.templates: no space left on device接下来就写下我在备份虚拟机上如何解决根目录被占满的问题&#xff1a;1、查看虚拟机磁盘使用情况df -h可以…...

【C++】STL容器、算法的简单认识

几种模板首先认识一下函数模板、类模板、栈模板。函数模板函数模板就是一个模型&#xff0c;而模板函数是函数模板经过类型实例化的函数。如下template<class T>是一个简单的函数模板&#xff1a;template<class T> T Max(T a, T b) {return a > b ? a : b; } …...

把python开发的web服务,打包成docker镜像的方法

要将Python开发的服务打成Docker镜像&#xff0c;可以按照以下步骤操作&#xff1a;1. 创建一个Dockerfile文件&#xff0c;该文件描述了如何构建Docker镜像。例如&#xff0c;以下是一个简单的Dockerfile文件&#xff0c;用于构建一个基于Python的Web应用程序&#xff1a; FRO…...

【Linux】多线程

进程和线程进程&#xff1a;一个正在运行的程序。状态&#xff1a;就绪&#xff0c;运行&#xff0c;阻塞&#xff1b;线程是进程中的一个执行路径&#xff0c;一个进程中至少有一个主线程&#xff08;main函数&#xff09;&#xff1b;有多条执行路径为多线程。创建一个线程用…...

Qt 设置窗口背景图片的几种方法实例

1.在paintEvent事件中绘制图片 void Widget::paintEvent(QPaintEvent * ev) {QPainter painter(this);painter.drawPixmap(rect(),QPixmap(":/bg.jpg"),QRect()); } drawPixmap在Widget的整个矩形区域绘制背景图片&#xff0c;第三个参数为要绘制的图片区域&#x…...

springcloud微服务架构搭建过程

项目地址&#xff1a;源代码 仅作为学习用例使用&#xff0c;是我开发过程中的总结、实际的一部分使用方式 开发环境&#xff1a; jdk11 springboot2.7.6 springcloud2021.0.5 alibabacloud 2021.0.4.0 redis6.0 mysql8.0 一、项目搭建 wdz-api&#xff1a;存放远程服务调用相关…...

LeetCode:215. 数组中的第K个最大元素

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340;算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;215. 数组中的第K个最大元素 题目描述&#xff1a;给定整数数组nums和整…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...