网页游戏原生socket

0

网页游戏原生Socket通信是指在网页游戏中,使用Socket编程技术进行客户端与服务器之间的实时数据传输。Socket是一种网络通信协议,它允许两台计算机之间进行双向通信,常用于网络编程,如服务器与客户端的通信。

在网页游戏中,由于浏览器的限制,直接使用Socket通信通常不可行,因为浏览器的安全策略通常会阻止这种级别的网络访问。但是,可以通过以下几种方式实现类似的效果:

1. **HTML5 Websocket**:Websocket是HTML5引入的一种新的网页通信技术,它在客户端和服务器之间建立一个持久性的连接,使得数据可以双向传输。虽然不如Socket灵活,但已经能满足大部分实时游戏的需求。

2. **Server-Sent Events (SSE)**:SSE是另一种在浏览器和服务器间建立长连接的方式,服务器可以推送数据到客户端,适合实时更新的情况。

3. **Flash Socket 或 Adobe Socket.IO**:对于旧版本的浏览器,可以使用Flash技术(现在已经过时,但仍有部分用户使用)或者第三方库如Socket.IO来实现类似的功能。

4. **WebSocket API(Hybrid App)**:对于混合应用(如使用HTML5+原生代码),可以在原生部分使用Socket进行通信,然后通过API与网页部分交互。

总的来说,虽然网页游戏不能直接使用原生Socket,但有多种方法可以实现类似的功能,保证游戏的实时性。

// ServerCallback because we don't want a warning if OnTriggerEnter is

// position, because both the server and the client simulate it.

// set velocity for server and client. this way we don't have to sync the

网页游戏原生socket

我们再看 [ServerCallback] ,它与 [Server] 一样,只能在服务端调用,只是没有 Warning输出而已中华三国志dp版单机游戏,如下

炮弹需要有一个生命周期控制,超过 5秒 自动销毁,执行 NetworkServer.Destroy(gameObject) 来销毁对象,

// this is called on the tank that fired for all observers

GameObject projectile = Instantiate(projectilePrefab, projectileMount.position, transform.rotation);

animator.SetBool("Moving", agent.velocity != Vector3.zero);

agent.velocity = forward * Mathf.Max(vertical, 0) * agent.speed;

Vector3 forward = transform.TransformDirection(Vector3.forward);

transform.Rotate(0, horizontal * rotationSpeed * Time.deltaTime, 0);

我们看到反编译出来的 Tank 的 CmdFire 函数的代码已经完全变了另外一个逻辑了,它发送了一个 “CmdFire” 消息给服务端,

进入 工程路径 / Library / ScriptAssemblies 这个目录, Mirror 的案例代码是编译在 Mirror.Examples.dll 中,

这里用到了两个注解 [Command] 、 [ClientRpc] ,我们上面讲到它是 NetworkBehaviour 组件的函数注解。

GameObject projectile = Instantiate(projectilePrefab, projectileMount.position, transform.rotation);

animator.SetBool("Moving", agent.velocity != Vector3.zero);

agent.velocity = forward * Mathf.Max(vertical, 0) * agent.speed;

Vector3 forward = transform.TransformDirection(Vector3.forward);

transform.Rotate(0, horizontal * rotationSpeed * Time.deltaTime, 0);

// isLocalPlayer是父类NetworkBehaviour的属性,用于判断当前NetworkBehaviour对象是否为本地对象;

网络对象的行为脚本需要继承 NetworkBehaviour ,所以 Tank 类需要继承 NetworkBehaviour,

炮弹的 Transform 信息不使用 NetworkTransform 进行同步,而是通过 Rigibody 刚体组件的力来使炮弹飞行,所以只需要同步一下力即可,在 Projectile 脚本中实现炮弹的逻辑。

选中 NetworkManager 物体,给 NetworkManager 组件赋值 PlayerPrefab 为坦克预设,

使用 [ClientRpc] 注解对函数进行标记,表示这个函数是由服务端调用,由客户端来执行。具体原理我下文会通过反编译 dll 来解释。

使用 [Command] 注解对函数进行标记,表示这个函数是由客户端调用,由服务端来执行。具体原理我下文会通过反编译 dll 来解释。

在 NetworkBehaviour 中,我们可以使用 [Server] 、 [ServerCallback] 、 [Client] 、 [ClientCallback] 这些注解对函数进行标注。

意思就是说, NetworkBehaviour 脚本处理具有 NetworkIdentity 组件的游戏对象, NetworkBehaviour 的子类中可以处理高级 API 功能,例如 Commands 、 ClientRpc's 、 SyncEvents 、SyncVars 。

意思就是说, NetworkTransform 组件会通过网络自动同步 position 、 rotation 和 scale。

关于导航系统的使用,可以参见我之前写的文章: 《Unity游戏开发——新发教你做游戏(五):导航系统Navigation》

Mirror 帮我们封装了各种不同等级的传输协议(各种 Transport 组件),常用的是 KcpTransport 和 TelepathyTransport 。

我以多人坦克对战为例,双击 Assets / Mirror / Examples / Tanks / Scenes/ Scene 进入场景,

使用 Mirror ,客户端、服务端是在同一个工程中的,这就是为什么它叫 Mirror 。 也就是说它没有一个独立的服务端,而是由一台客户端作为 Host ,它既是客户端又是服务端,其他客户端连接这台 Host 客户端 。画成图是这样子:

注:在 Unity 5.1 ~ Unity2018 中你可以使用 UNet (全称 Unity Networking ),到 Unity 2019 之后 UNet 就被废弃了, Mirror 就是来替代 UNet 的。你在网上搜到的 Unity Netwoking的教程就是 UNet ,它已经过时了,不要再使用 UNet 了!

有些同学做了一个单机版的小 Demo ,想改成局域网多人联机版,要处理好多复杂的同步问题,比如物理碰撞、状态同步等等,这个对于 Unity 萌新来说,不大友好。

上面的简单聊天室功能,我们是做了一个独立的服务端负责消息的转发,聊天本身的逻辑非常简单,我们把大部分工作花在了维护 Socket 上,要解决多线程问题,要解决连接断开,要解决消息的序列化和反序列化等等。

我们不想全屏显示客户端,在 Player Settings 中,找到 Resolution and Presentation ,设置 Fullscreen Mode 为 Windowed ,设置窗口默认宽高为 640 x 360 ,

byte[] data = System.Text.Encoding.UTF8.GetBytes(jsonStr);

string jsonStr = JSONConvert.SerializeObject(jsonObj);

connectBtnText.text = clientSocket.connected ? "断开" : "连接";

stateTxt.text = clientSocket.connected ? "已连接" : "未连接";

byte[] data = System.Text.Encoding.UTF8.GetBytes(jsonStr);

这里用了一个迷你版的 json 库: JSONConvert ,源码可以参见我之前写的这篇文章 :《用C#实现一个迷你json库,无需引入dll(可直接放到Unity中使用)》

var msgStr = System.Text.Encoding.UTF8.GetString(msg);

Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

socket.BeginReceive(recvBuff, 0, recvBuff.Length, SocketFlags.None, recvCb, this);

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

由于监听客户端( socket.accept )和接收消息( socket.recv )都是 阻塞 的,为了不阻塞主线程,我们使用 子线程 来处理。

然而我眼花看错了,看成了 网格 ,我还专门写了一篇文章: 《【游戏开发进阶】Unity网格探险之旅(Mesh | 动态合集 | 骨骼动画 | 蒙皮 )》

在网络游戏中,心跳检测和断线重连一般都由客户端发起。客户端每隔一段时间发一个心跳消息,同时去监听服务器回复的消息,如果客户端连续发送了N的消息都没有收到回复,就做相应的处理,比如说在用户无感的情况下默默地重新建立连接,又或者是给用户弹出一个提示告知断网,都是很常见的处理方式。

系统提供的Keep Alive功能从机制上来说是比较完善的,不过,在实际应用中三国类型的单机游戏手机版,一般并不建议使用系统提供的TCP Keep Alive来做心跳检测。原因主要有几个,首先keep alive只有TCP协议才支持,UDP和Raw(自定义协议)模式都是无法支持的;更重要的原因是,整个过程对应用层来说全部是不可控的,对业务来说不够灵活。实际上,了解Keep Alive的原理,自己写一个也是很简单的事情,所以一般建议自己在应用中实现心跳检测的功能。

如果Socket使用的是TCP协议,现在的主流操作系统都支持TCP的Keep Alive模式。Keep Alive就是系统帮你封装好的心跳检测,每隔一段时间系统帮你发个空包(注意:空包不代表没有任何数据传输,只是没有任何业务层数据而已,在TCP通信中,即使是空包也是有数据传输的),对面回了说明这条Socket连接还活着,对面没回说明连接已完蛋。

很多事情的本质其实是一样的,在Socket连接中,碰到断网也是类似的处理方式。最常规的方法就是用心跳包来检测。心跳包的基本原理是,每隔一段时间,一方就向另一方打个招呼,发送一段很小的数据,如果对方有回应,那就OK,如果对方没有回应,那就是断网了。由于这种隔一会儿就来一下判断是不是还活着的方式很像是心跳,所以通常称之为“心跳检测”。

另一种情况相对复杂一点,那就是被动断线,一般情况就是断网。碰到断网,Socket是无法马上得知的。那么如何来判断到底有没有断网呢?

当对方主动关闭连接或者发生其他异常时,另一端的recv接口的返回值会小于等于0,这个返回值是即时生效没有延迟的,这时候就知道,对方关闭了连接,在这里可以做相应的处理。比如说客户端主动关闭,服务器收到消息后,可以记录一下用户的离线时间;服务器主动关闭,客户端收到消息可以重新连接或者给用户弹出一些提示“你被踢下线了”之类的。大部分情况下,浏览器的网页刷新、浏览器或app关闭时,都会立刻收到这个消息。当然事无绝对,这一般跟你的应用程序载体(即操作系统或浏览器)有关,也有部分载体没有这个功能,如果没这个功能就得看下面的处理方式了。

既然是网络通信,就一定会遇到网络断线的问题。短连接游戏的处理相对比较简单,收不到正确数据重发即可,本文主要讨论一下Socket长连接中的相应处理。

之所以在游戏领域大家通常将HTTP和Socket放在一起说,其实主要指的是短连接和长连接这两种模式。短连接一般使用HTTP协议,只要完成一次数据交换就关闭连接,服务器并不保存连接状态和数据,也没有办法主动向客户端发送消息。而长连接一般使用TCP协议,直接调用Socket接口进行数据通信,在双向流式传输的Socket通信中,双方的地位是对等的,既可以由客户端向服务端发起消息,也可以由服务端向客户端发送消息。

那么问题来了,HTTP难道不是点对点的吗?那么HTTP和Socket有什么区别呢?答案是这两者本来就没有什么冲突关系,它们并不是一个维度上的说法,就像一个人喜欢吃火锅和这个人是程序员这两者之间没有什么联系。HTTP是网络协议层的东西,而Socket是一种通信方式。

Socket大致上可以理解为一种点对点的通信方式,操作系统为应用程序提供了一系列Socket相关的接口,而应用程序调用相关接口可以更方便快捷地操作数据,与另一个Socket端口进行通信。虽然在网络游戏中Socket通常使用的是TCP协议,实际上Socket也支持UDP,甚至支持自己写协议(Raw模式)。只要是通过操作系统提供的Socket接口进行点对点的数据通信,就叫做Socket。(所以其实Socket翻译成插口插槽还更形象一些)

专题: 三国杀游戏单机   三国义单机游戏   龙三国单机游戏