Java游戏服务器开发流水账(7)网络通信简介

在 Java 游戏服务器开发中,网络通讯是核心组成部分,它主要负责客户端与服务器之间的数据交换。

一、网络通讯基础

1. 网络模型
  • C/S 架构:游戏服务器采用客户端 / 服务器模式,客户端向服务器发送请求,服务器处理请求并返回响应。
  • B/S 架构:部分网页游戏采用浏览器 / 服务器模式,但实时性要求高的游戏通常不采用这种架构。
2. 通讯协议
  • TCP:面向连接的可靠协议,保证数据按序到达,适合需要可靠传输的场景,如 MMORPG。
  • UDP:无连接的不可靠协议,不保证数据顺序和完整性,但延迟低,适合实时性要求高的游戏,如 FPS。
  • HTTP/HTTPS:常用于游戏中的非实时通信,如登录验证、数据同步等。
3. 数据格式
  • 文本协议:如 JSON、XML,易于调试但效率较低。
  • 二进制协议:如 Protobuf、MessagePack,效率高,适合高性能服务器。
  • 自定义协议:根据游戏需求设计的专用协议,通常是二进制格式。

二、Java 网络编程 API

Java 提供了多种网络编程 API,适用于不同的应用场景:

1. 传统的阻塞 IO (BIO)
  • ServerSocket/Socket:基于线程池实现多客户端连接,每个连接占用一个线程。
  • 缺点:线程开销大,不适合高并发场景。
2. 非阻塞 IO (NIO)
  • Selector/Channel:单线程管理多个连接,基于事件驱动,适合高并发场景。
  • 缺点:编程模型复杂,需要处理各种状态。
3. 异步 IO (AIO)
  • AsynchronousServerSocketChannel/AsynchronousSocketChannel:基于回调机制,真正的异步非阻塞,适合长连接、高并发场景。
  • 优点:线程利用率高,编程模型相对简单。
4. 高性能网络框架
  • Netty:基于 NIO 的高性能网络框架,简化了网络编程,广泛应用于游戏服务器开发。
  • Mina:类似 Netty 的网络框架,提供了简单易用的 API。

三、Java AIO 网络通讯实现原理

在前面提供的示例中,我们使用了 Java AIO 实现游戏服务器的网络通讯。下面详细解释其工作原理:

1. 服务器端核心组件
  • AsynchronousChannelGroup:线程池管理,负责处理 IO 操作和回调任务。
  • AsynchronousServerSocketChannel:异步服务器套接字通道,用于监听客户端连接。
  • AsynchronousSocketChannel:异步套接字通道,用于与客户端进行数据交换。
  • CompletionHandler:回调接口,处理 IO 操作完成后的逻辑。
2. 连接建立流程
  1. 创建线程池和服务器通道

    ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
    group = AsynchronousChannelGroup.withThreadPool(executor);
    serverChannel = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(port));
    
  2. 接受客户端连接

    serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel client, Void attachment) {// 处理新连接acceptConnections(); // 继续接受下一个连接}
    });
    
3. 数据读写流程
  • 读取数据

    channel.read(readBuffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesRead, Void attachment) {// 处理读取到的数据read(); // 继续读取下一次数据}
    });
    
  • 写入数据

    channel.write(writeBuffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesWritten, Void attachment) {// 继续写入剩余数据if (writeBuffer.hasRemaining()) {channel.write(writeBuffer, null, this);}}
    });
    
4. 异步回调机制

Java AIO 的核心是异步回调机制:

  • 当 IO 操作完成时(如连接建立、数据读取),会触发相应的 CompletionHandler 回调方法。
  • 回调方法在 AsynchronousChannelGroup 的线程池中执行,不会阻塞发起 IO 操作的线程。
  • 这种机制使得一个线程可以处理多个客户端连接,大大提高了服务器的并发处理能力。

四、游戏服务器网络优化策略

1. 减少网络延迟
  • 使用 UDP 协议:对于实时性要求高的游戏,如动作游戏、竞技游戏,考虑使用 UDP 协议。
  • 优化服务器位置:将服务器部署在离玩家近的地理位置,减少物理距离造成的延迟。
  • 预测与补偿算法:在客户端实现预测算法,减少玩家操作的感知延迟。
2. 提高吞吐量
  • 使用高性能网络框架:如 Netty,它提供了更好的性能和更简单的编程模型。
  • 优化线程池配置:根据服务器硬件和业务特点调整线程池大小。
  • 采用对象池技术:减少内存分配和垃圾回收开销。
3. 降低带宽消耗
  • 压缩数据:对发送的数据进行压缩,如使用 Zlib、Snappy 等压缩算法。
  • 减少不必要的数据包:只发送必要的数据,避免冗余信息。
  • 使用增量更新:只发送变化的数据,而不是整个状态。
4. 增强可靠性
  • 实现可靠 UDP:在 UDP 协议之上实现可靠性保证,如确认机制、重传机制。
  • 心跳机制:定期发送心跳包,检测连接状态。
  • 断线重连:实现客户端断线重连功能,保持游戏状态。

五、安全与性能监控

1. 网络安全
  • 防止 DDOS 攻击:使用防火墙、流量过滤等技术防御 DDOS 攻击。
  • 数据加密:对敏感数据进行加密传输,如登录信息、支付信息。
  • 协议验证:验证客户端发送的数据包格式和内容,防止恶意攻击。
2. 性能监控
  • 连接数监控:监控当前连接数,防止过多连接导致服务器崩溃。
  • 流量监控:监控网络流量,及时发现异常流量。
  • 响应时间监控:监控服务器响应时间,及时发现性能瓶颈。

六、简单的源码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;public class GameClient {private final String host;private final int port;private AsynchronousSocketChannel channel;private final ByteBuffer readBuffer = ByteBuffer.allocate(1024);private final Scanner scanner = new Scanner(System.in);public GameClient(String host, int port) {this.host = host;this.port = port;}public void start() throws IOException {// 打开客户端通道channel = AsynchronousSocketChannel.open();// 连接到服务器channel.connect(new InetSocketAddress(host, port), null, new CompletionHandler<Void, Void>() {@Overridepublic void completed(Void result, Void attachment) {System.out.println("已连接到服务器: " + host + ":" + port);// 开始读取服务器消息read();// 启动用户输入处理线程new Thread(GameClient.this::handleUserInput).start();}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("连接服务器失败: " + exc.getMessage());}});}private void read() {readBuffer.clear();channel.read(readBuffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesRead, Void attachment) {if (bytesRead == -1) {// 服务器关闭了连接System.out.println("服务器断开连接");close();return;}readBuffer.flip();byte[] data = new byte[bytesRead];readBuffer.get(data);String message = new String(data, StandardCharsets.UTF_8);// 显示服务器消息System.out.print("\r" + message);System.out.print("> ");// 继续读取read();}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("读取消息失败: " + exc.getMessage());close();}});}private void handleUserInput() {BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));try {while (channel.isOpen()) {System.out.print("> ");String message = reader.readLine();if (message == null || message.equalsIgnoreCase("/exit")) {close();break;}sendMessage(message);}} catch (IOException e) {System.err.println("读取用户输入失败: " + e.getMessage());close();}}private void sendMessage(String message) {ByteBuffer buffer = ByteBuffer.wrap((message + "\n").getBytes(StandardCharsets.UTF_8));channel.write(buffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesWritten, Void attachment) {// 继续写入剩余数据,如果有的话if (buffer.hasRemaining()) {channel.write(buffer, null, this);}}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("发送消息失败: " + exc.getMessage());close();}});}public void close() {try {if (channel.isOpen()) {channel.close();System.out.println("客户端已关闭");}} catch (IOException e) {System.err.println("关闭客户端失败: " + e.getMessage());}}public static void main(String[] args) {GameClient client = new GameClient("localhost", 8080);try {client.start();// 保持主线程运行Thread.currentThread().join();} catch (IOException | InterruptedException e) {e.printStackTrace();client.close();}}
}    
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class GameServer {private final int port;private AsynchronousChannelGroup group;private AsynchronousServerSocketChannel serverChannel;private final Map<String, PlayerSession> sessions = new HashMap<>();public GameServer(int port) {this.port = port;}public void start() throws IOException {// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);group = AsynchronousChannelGroup.withThreadPool(executor);// 打开服务器通道serverChannel = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(port));System.out.println("游戏服务器启动,监听端口: " + port);// 开始接受连接acceptConnections();}private void acceptConnections() {serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel client, Void attachment) {// 继续接受下一个连接acceptConnections();// 处理新连接handleNewConnection(client);}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("接受连接失败: " + exc.getMessage());}});}private void handleNewConnection(AsynchronousSocketChannel client) {try {String sessionId = UUID.randomUUID().toString();PlayerSession session = new PlayerSession(sessionId, client, this);// 存储会话sessions.put(sessionId, session);System.out.println("新玩家连接: " + sessionId + " 来自 " + client.getRemoteAddress());// 开始读取客户端消息session.read();// 发送欢迎消息session.send("欢迎加入游戏服务器! 您的ID: " + sessionId);} catch (IOException e) {System.err.println("处理新连接失败: " + e.getMessage());try {client.close();} catch (IOException ex) {ex.printStackTrace();}}}public void broadcast(String message, String excludeSessionId) {for (PlayerSession session : sessions.values()) {if (!session.getSessionId().equals(excludeSessionId)) {session.send(message);}}}public void removeSession(String sessionId) {sessions.remove(sessionId);System.out.println("玩家断开连接: " + sessionId);}public void stop() {try {// 关闭所有会话for (PlayerSession session : sessions.values()) {session.close();}// 关闭服务器通道和组if (serverChannel != null && serverChannel.isOpen()) {serverChannel.close();}if (group != null && !group.isShutdown()) {group.shutdownNow();}System.out.println("游戏服务器已停止");} catch (IOException e) {System.err.println("停止服务器失败: " + e.getMessage());}}public static void main(String[] args) {GameServer server = new GameServer(8080);try {server.start();// 让服务器保持运行Thread.currentThread().join();} catch (IOException | InterruptedException e) {e.printStackTrace();server.stop();}}
}    
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;public class PlayerSession {private final String sessionId;private final AsynchronousSocketChannel channel;private final GameServer server;private final ByteBuffer readBuffer = ByteBuffer.allocate(1024);private final ByteBuffer writeBuffer = ByteBuffer.allocate(1024);// 玩家状态private String username;private int x, y;private boolean isLoggedIn = false;public PlayerSession(String sessionId, AsynchronousSocketChannel channel, GameServer server) {this.sessionId = sessionId;this.channel = channel;this.server = server;}public String getSessionId() {return sessionId;}public void read() {readBuffer.clear();channel.read(readBuffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesRead, Void attachment) {if (bytesRead == -1) {// 客户端关闭了连接close();return;}readBuffer.flip();byte[] data = new byte[bytesRead];readBuffer.get(data);String message = new String(data, StandardCharsets.UTF_8).trim();// 处理消息handleMessage(message);// 继续读取read();}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("读取消息失败: " + exc.getMessage());close();}});}private void handleMessage(String message) {System.out.println("收到来自 " + sessionId + " 的消息: " + message);// 简单的命令处理if (message.startsWith("/login ")) {handleLogin(message.substring(7));} else if (message.startsWith("/move ")) {handleMove(message.substring(6));} else if (message.equals("/logout")) {handleLogout();} else if (message.equals("/players")) {sendPlayerList();} else {// 广播消息给其他玩家if (isLoggedIn) {server.broadcast("[" + username + "] " + message, sessionId);} else {send("请先登录 /login <用户名>");}}}private void handleLogin(String username) {if (isLoggedIn) {send("您已经登录为 " + this.username);return;}this.username = username;this.isLoggedIn = true;this.x = 0;this.y = 0;send("登录成功,欢迎 " + username);server.broadcast(username + " 加入了游戏", sessionId);}private void handleMove(String direction) {if (!isLoggedIn) {send("请先登录 /login <用户名>");return;}switch (direction.toLowerCase()) {case "up": y--; break;case "down": y++; break;case "left": x--; break;case "right": x++; break;default: send("无效的移动方向: " + direction);return;}send("您移动到了位置 (" + x + ", " + y + ")");server.broadcast(username + " 移动到了位置 (" + x + ", " + y + ")", sessionId);}private void handleLogout() {if (!isLoggedIn) {send("您尚未登录");return;}server.broadcast(username + " 离开了游戏", sessionId);this.isLoggedIn = false;send("您已登出");}private void sendPlayerList() {StringBuilder builder = new StringBuilder("在线玩家列表:\n");// 实际应用中应该遍历所有玩家并添加到列表builder.append(username).append(" (").append(x).append(", ").append(y).append(")\n");send(builder.toString());}public void send(String message) {writeBuffer.clear();writeBuffer.put((message + "\n").getBytes(StandardCharsets.UTF_8));writeBuffer.flip();channel.write(writeBuffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer bytesWritten, Void attachment) {// 继续写入剩余数据,如果有的话if (writeBuffer.hasRemaining()) {channel.write(writeBuffer, null, this);}}@Overridepublic void failed(Throwable exc, Void attachment) {System.err.println("发送消息失败: " + exc.getMessage());close();}});}public void close() {try {if (channel.isOpen()) {channel.close();server.removeSession(sessionId);}} catch (IOException e) {System.err.println("关闭会话失败: " + e.getMessage());}}
}    

六、总结

虽然AIO 提供了高效的异步非阻塞编程模型,适合开发高性能的游戏服务器。但是手撸Java 游戏服务器的网络通讯相对比较复杂,需要综合考虑性能、可靠性、安全性等多个方面。上面的代码是最简单的实现

在实际开发中,通常会使用成熟的网络框架如 Netty,以简化开发流程并提高系统稳定性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用ADB命令操作Android的apk/aab包

keystore文件转换jks文件 操作步骤&#xff1a; 步骤1&#xff0c;生成P12文件&#xff1a; keytool -importkeystore -srckeystore [文件名].keystore -srcstoretype JKS -deststoretype PKCS12 -destkeystore [文件名].p12 步骤2&#xff0c;生成jks文件&#xff1a; keytool…

图文展示HDFS、YARN、MapReduce三者关系

MapReduce架构概述 MapReduce将计算过程分为两个阶段&#xff1a;Map和Reduce &#xff08;1&#xff09;Map阶段并行处理输入数据 &#xff08;2&#xff09;Reduce阶段对Map结果进行汇总 HDFS、YARN、MapReduce三者关系

DL00219-基于深度学习的水稻病害检测系统含源码

&#x1f33e; 基于深度学习的水稻病害检测系统 — 智能农业的未来&#xff0c;守护农田的每一寸土地&#xff01; &#x1f69c; 完整系统获取见文末 水稻病害检测&#xff0c;一直是农业领域的一大难题。传统的人工检测不仅耗时耗力&#xff0c;还容易因经验不足导致漏检或误…

github 上的 CI/CD 的尝试

效果 步骤 新建仓库设置仓库的 page 新建一个 vite 的项目&#xff0c;改一下 vite.config.js 中的 base 工作流 在项目的根目录下新建一个 .github/workflows/ci.yml 文件&#xff0c;然后编辑一下内容 name: Build & Deploy Vue 3 Appon:push:branches: [main]permi…

鸿蒙5.0项目开发——鸿蒙天气项目的实现(介绍)

【高心星出品】 文章目录 项目简介&#xff1a;项目运行效果图&#xff1a;主要功能&#xff1a;使用的技能点&#xff1a;开发环境&#xff1a; 项目简介&#xff1a; 这是一个基于鸿蒙系统&#xff08;HarmonyOS&#xff09;开发的天气应用&#xff0c;采用 ArkTS 语言开发&…

SpringCloud之Eureka基础认识-服务注册中心

0、认识Eureka Eureka 是 Netflix 开源的服务发现组件&#xff0c;后来被集成到 Spring Cloud 生态中&#xff0c;成为 Spring Cloud Netflix 的核心模块之一。它主要用于解决分布式系统中​​服务注册与发现​​的问题。 Eureka Server 有必要的话&#xff0c;也可以做成集群…

【氮化镓】电子辐照下温度对GaN位移阈能的影响

2024年,华东师范大学的彭胜国等人基于从头算分子动力学(AIMD)方法,研究了低能电子束辐照下温度对氮化镓(GaN)位移阈能(TDE)的影响。实验结果表明,在初始动能40至80 eV的范围内,镓(Ga)和氮(N)原子作为初级击出原子(PKAs)引发的位移对温度呈现不同的敏感性:Ga 的…

Java 中的数据类型误导点!!!

在 Java 中&#xff0c;数据类型分为两大类&#xff1a;基本类型&#xff08;Primitive Types&#xff09; 和 引用类型&#xff08;Reference Types&#xff09;。它们的存储方式和行为完全不同。 1. 基本类型 Java 有 8 种基本数据类型&#xff0c;它们直接存储值&#xff…

二次封装 el-dialog 组件:打造更灵活的对话框解决方案

文章目录 引言为什么需要二次封装&#xff1f;封装思路代码实现1. 基础封装组件 (Dialog.vue)2. Vue中引入使用示例 封装后的优势进阶优化建议 总结 引言 在 Vue 项目中&#xff0c;Element UI 的 el-dialog 是一个非常实用的对话框组件。但在实际开发中&#xff0c;我们经常会…

框架篇八股(自用)

框架篇 Spring框架中的bean不是线程安全的 Scope&#xff08;&#xff09; singleton单例 prototype多例 一个类中有可修改的成员变量需要考虑线程安全 bean没有可变状态&#xff08;service类&#xff0c;DAO类&#xff09; 某种程度单例bean是线程安全的 AOP面向切面编程…

Go语言安装proto并且使用gRPC服务(2025最新WINDOWS系统)

1.protobuf简介 protobuf 即 Protocol Buffers&#xff0c;是一种轻便高效的结构化数据存储格式&#xff0c;与语言、平台无关&#xff0c;可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的&#xff0c;占用空…

rust-candle学习笔记11-实现一个简单的自注意力

参考&#xff1a;about-pytorch 定义ScaledDotProductAttention结构体&#xff1a; use candle_core::{Result, Device, Tensor}; use candle_nn::{Linear, Module, linear_no_bias, VarMap, VarBuilder, ops};struct ScaledDotProductAttention {wq: Linear,wk: Linear,wv: …

spark MySQL数据库配置

Spark 连接 MySQL 数据库的配置 要让 Spark 与 MySQL 数据库实现连接&#xff0c;需要进行以下配置步骤。下面为你提供详细的操作指南和示例代码&#xff1a; 1. 添加 MySQL JDBC 驱动依赖 你得把 MySQL 的 JDBC 驱动添加到 Spark 的类路径中。可以通过以下两种方式来完成&a…

web 自动化之 KDT 关键字驱动详解

一、什么是关键字驱动&#xff1f; 1、什么是关键字驱动&#xff1f;&#xff08;以关键字函数驱动测试&#xff09; 关键字驱动又叫动作字驱动&#xff0c;把项目业务封装成关键字函数&#xff0c;再基于关键字函数实现自动化测试 2、关键字驱动测试原理 关键字驱动测试是一…

Java使用POI+反射灵活的控制字段导出Excel

前端传入哪些字段&#xff0c;后端就导出哪些到Excel表格中&#xff0c;具体代码实现如下 controller /*** 用户导出* param dto*/PostMapping("/exportUser")public void exportCharterOrder(RequestBody UserExportDTO dto){userService.exportUser(dto);} serv…

Qt/C++面试【速通笔记八】—Qt的事件处理机制

在Qt中&#xff0c;事件处理机制是应用程序与用户或系统交互的核心。通过事件处理&#xff0c;Qt能够响应用户的输入、窗口的变化、定时器的触发等各种情况。 1. 事件循环&#xff08;Event Loop&#xff09; 在Qt应用程序中&#xff0c;事件循环是事件处理机制的基础。事件循…

TTL (Time-To-Live) 解析

文章目录 TTL (Time-To-Live) 解析&#xff1a;网络与Java中的应用一、TTL的定义二、TTL在网络中的应用1. **路由和数据包的生命周期**2. **DNS中的TTL**3. **防止环路** 三、TTL在Java中的应用1. **缓存管理**2. **Java中的ThreadLocal**3. **网络通信中的TTL** 四、TTL的注意…

HDFS的客户端操作(2)文件上传

我们向/maven下上传一个文件。 要用到的api是put (或者copyFormLocalFile&#xff09;。核心代码如下。 public void testCopyFromLocalFile() throws IOException, InterruptedException, URISyntaxException {// 1 获取文件系统Configuration configuration new Configurati…

光谱相机的光电信号转换

光谱相机的光电信号转换是将分光后的光学信息转化为可处理的数字信号的核心环节&#xff0c;具体分为以下关键步骤&#xff1a; 一、分光后光信号接收与光电转换 ‌分光元件作用‌ 光栅/棱镜/滤光片等分光元件将入射光分解为不同波长单色光&#xff0c;投射至探测器阵列表面…

网络协议分析 实验二 IP分片与IPv6

文章目录 索引及重要内容实验2 IP 高级实验实验2.1 IPv4协议分片实验实验2.2 IPV6协议实验2.3 ARP初级 索引及重要内容 实验2 IP 高级实验 实验2.1 IPv4协议分片实验 icmp的不可达报文 实验2.2 IPV6协议 实验2.3 ARP初级 arp –a 查看ARP缓存表内容 arp –s IP地址(格式&…