设计一个高效的网络系统架构对于多人游戏和实时应用至关重要。Unity 提供了多种网络解决方案,包括 Unity 的内置网络系统(UNet,已弃用)、Mirror、Photon 和 Unity Multiplayer(MLAPI)。以下是一个通用的网络系统架构设计指南,适用于 Unity 引擎。
1. 网络系统选择
1.1 Unity Multiplayer(MLAPI)
Unity Multiplayer 是 Unity 官方推荐的网络解决方案,提供高性能和易用的 API,适用于各种类型的多人游戏。
1.2 Mirror
Mirror 是一个开源的高性能网络库,基于 UNet 开发,提供了丰富的功能和良好的社区支持。
1.3 Photon
Photon 是一个商业化的网络解决方案,提供强大的云服务和易用的 API,适用于实时多人游戏和应用。
2. 架构设计
2.1 客户端-服务器模型
客户端-服务器模型是最常见的网络架构,适用于大多数多人游戏和实时应用。
- 服务器:负责处理游戏逻辑、同步状态和管理客户端连接。
- 客户端:负责渲染、输入处理和本地逻辑。
2.2 P2P(点对点)模型
P2P 模型适用于小规模的多人游戏和应用,客户端之间直接通信,减少了服务器的负担。
- 主机客户端:一个客户端充当主机,负责处理游戏逻辑和同步状态。
- 客户端:其他客户端连接到主机,进行通信和同步。
3. 关键组件
3.1 网络管理器
网络管理器是网络系统的核心组件,负责管理连接、消息传递和状态同步。
- 连接管理:处理客户端的连接和断开。
- 消息传递:处理客户端和服务器之间的消息传递。
- 状态同步:同步游戏状态和数据。
3.2 网络对象
网络对象是需要在客户端和服务器之间同步的游戏对象。
- NetworkIdentity:标识网络对象的唯一身份。
- NetworkTransform:同步对象的变换(位置、旋转、缩放)。
- NetworkBehaviour:自定义的网络行为脚本,处理对象的网络逻辑。
3.3 网络消息
网络消息用于在客户端和服务器之间传递数据和指令。
- RPC(远程过程调用):客户端和服务器之间的函数调用。
- Command:客户端向服务器发送指令。
- ClientRpc:服务器向客户端发送指令。
4. 实现步骤
4.1 初始化网络
- 选择网络解决方案:根据项目需求选择合适的网络库(如 MLAPI、Mirror 或 Photon)。
- 配置网络管理器:设置网络管理器的参数,如最大连接数、端口号和网络模式(客户端/服务器)。
4.2 创建网络对象
- 定义网络对象:创建需要同步的游戏对象,并添加
NetworkIdentity
组件。 - 添加网络行为:编写自定义的网络行为脚本,处理对象的网络逻辑。
4.3 处理连接和消息
- 管理连接:在网络管理器中处理客户端的连接和断开事件。
- 传递消息:使用 RPC、Command 和 ClientRpc 在客户端和服务器之间传递数据和指令。
4.4 同步状态
- 同步变换:使用
NetworkTransform
同步对象的变换。 - 同步自定义数据:在网络行为脚本中同步自定义的数据和状态。
5. 性能优化
5.1 减少网络流量
- 压缩数据:使用数据压缩技术减少消息大小。
- 合并消息:合并小消息,减少消息的发送频率。
- 使用可靠传输:仅在必要时使用可靠传输,减少网络开销。
5.2 优化同步
- 插值和外推:使用插值和外推技术平滑对象的运动。
- 频率控制:根据对象的重要性和变化频率调整同步频率。
6. 安全性
6.1 验证和授权
- 身份验证:在客户端连接时进行身份验证,确保合法用户访问。
- 权限控制:在服务器端进行权限控制,防止非法操作和数据篡改。
6.2 数据加密
- 传输加密:使用 SSL/TLS 加密网络通信,防止数据在传输过程中被窃取或篡改。
- 敏感数据加密:对敏感数据进行加密存储和传输,确保数据安全。
6.3 防御常见攻击
- 防止DDoS攻击:使用防火墙和流量限制技术,防止分布式拒绝服务攻击。
- 防止作弊:在服务器端进行关键逻辑验证,防止客户端作弊行为。
- 防止重放攻击:使用时间戳和随机数防止消息重放攻击。
7. 测试和调试
7.1 单元测试
- 网络组件测试:编写单元测试,验证网络组件的功能和行为。
- 模拟网络环境:使用模拟网络环境测试网络系统的性能和稳定性。
7.2 集成测试
- 多客户端测试:在本地或云环境中进行多客户端测试,验证系统的并发性能和同步效果。
- 压力测试:进行压力测试,评估系统在高负载下的性能和稳定性。
7.3 调试工具
- 网络日志:记录网络事件和消息,帮助调试和分析问题。
- 网络监控:使用网络监控工具,实时监控网络流量和连接状态。
8. 部署和维护
8.1 服务器部署
- 云服务器:使用云服务提供商(如 AWS、Azure 或 Google Cloud)部署服务器,确保高可用性和可扩展性。
- 负载均衡:使用负载均衡技术,分配客户端请求,提升系统性能和可靠性。
8.2 版本管理
- 版本控制:使用版本控制系统(如 Git)管理代码和配置,确保一致性和可追溯性。
- 自动化部署:使用 CI/CD 工具(如 Jenkins、GitHub Actions)实现自动化构建和部署,提升开发效率和质量。
8.3 监控和维护
- 系统监控:使用监控工具(如 Prometheus、Grafana)监控服务器性能和健康状态。
- 日志管理:收集和分析日志,及时发现和解决问题。
- 定期维护:定期进行系统维护和更新,确保系统的安全性和稳定性。
9. 案例分析
9.1 小型多人游戏
- 网络解决方案:使用 Mirror 实现 P2P 模型,主机客户端负责游戏逻辑和同步。
- 关键组件:网络管理器、网络对象、网络消息。
- 优化措施:减少网络流量、优化同步、确保安全性。
9.2 大型多人在线游戏(MMO)
- 网络解决方案:使用 Unity Multiplayer(MLAPI)实现客户端-服务器模型,服务器负责游戏逻辑和同步。
- 关键组件:网络管理器、网络对象、网络消息、数据库。
- 优化措施:减少网络流量、优化同步、确保安全性、负载均衡、自动化部署。
10. 总结
设计一个高效的网络系统架构需要综合考虑多方面的因素,包括网络模型、关键组件、性能优化、安全性、测试和部署。通过选择合适的网络解决方案,合理设计和实现网络系统,开发者可以创建高质量的多人游戏和实时应用。希望这份指南对你有所帮助,祝你在 Unity 网络开发中取得成功!
unity引擎底层的网络系统代码逻辑实现
在 Unity 中实现底层的网络系统代码逻辑,可以使用低级别的网络 API,如 System.Net.Sockets
。这种方法适用于需要高度自定义和控制的网络通信场景。以下是一个简单的示例,展示如何使用 System.Net.Sockets
实现基本的客户端-服务器通信。
1. 服务器端实现
首先,实现一个简单的 TCP 服务器,监听客户端连接并处理消息。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpServer
{
private TcpListener server;
private bool isRunning;
public SimpleTcpServer(string ipAddress, int port)
{
server = new TcpListener(IPAddress.Parse(ipAddress), port);
server.Start();
isRunning = true;
Thread serverThread = new Thread(new ThreadStart(Run));
serverThread.Start();
}
private void Run()
{
while (isRunning)
{
try
{
TcpClient client = server.AcceptTcpClient();
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
clientThread.Start(client);
}
catch (Exception ex)
{
Console.WriteLine("Server exception: " + ex.Message);
}
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
// Echo the message back to the client
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
stream.Write(response, 0, response.Length);
}
client.Close();
}
public void Stop()
{
isRunning = false;
server.Stop();
}
}
2. 客户端实现
接下来,实现一个简单的 TCP 客户端,连接到服务器并发送消息。
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpClient
{
private TcpClient client;
private NetworkStream stream;
public SimpleTcpClient(string ipAddress, int port)
{
client = new TcpClient(ipAddress, port);
stream = client.GetStream();
Thread clientThread = new Thread(new ThreadStart(ReceiveMessages));
clientThread.Start();
}
public void SendMessage(string message)
{
byte[] data = Encoding.ASCII.GetBytes(message);
stream.Write(data, 0, data.Length);
}
private void ReceiveMessages()
{
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
}
}
public void Close()
{
stream.Close();
client.Close();
}
}
3. 在 Unity 中使用
将上述服务器和客户端代码集成到 Unity 项目中。
3.1 服务器端
创建一个新的脚本 ServerManager
,并将其添加到一个空的 GameObject 上。
using UnityEngine;
public class ServerManager : MonoBehaviour
{
private SimpleTcpServer server;
void Start()
{
server = new SimpleTcpServer("127.0.0.1", 8080);
}
void OnApplicationQuit()
{
server.Stop();
}
}
3.2 客户端
创建一个新的脚本 ClientManager
,并将其添加到另一个空的 GameObject 上。
using UnityEngine;
public class ClientManager : MonoBehaviour
{
private SimpleTcpClient client;
void Start()
{
client = new SimpleTcpClient("127.0.0.1", 8080);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
client.SendMessage("Hello, Server!");
}
}
void OnApplicationQuit()
{
client.Close();
}
}
4. 运行和测试(续)
- 创建一个新的场景,并将
ServerManager
和ClientManager
脚本分别添加到两个不同的 GameObject 上。 - 运行 Unity 项目,确保服务器和客户端都在同一个场景中运行。
- 按下空格键,客户端将发送消息到服务器,服务器将接收到消息并回显。
5. 进一步扩展
5.1 处理更多的客户端
当前的服务器实现只能处理一个客户端。为了处理多个客户端,可以在 SimpleTcpServer
中维护一个客户端列表,并在 HandleClient
方法中处理每个客户端的消息。
using System.Collections.Generic;
public class SimpleTcpServer
{
private TcpListener server;
private bool isRunning;
private List<TcpClient> clients = new List<TcpClient>();
public SimpleTcpServer(string ipAddress, int port)
{
server = new TcpListener(IPAddress.Parse(ipAddress), port);
server.Start();
isRunning = true;
Thread serverThread = new Thread(new ThreadStart(Run));
serverThread.Start();
}
private void Run()
{
while (isRunning)
{
try
{
TcpClient client = server.AcceptTcpClient();
clients.Add(client);
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
clientThread.Start(client);
}
catch (Exception ex)
{
Console.WriteLine("Server exception: " + ex.Message);
}
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
// Echo the message back to all clients
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
foreach (var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
clients.Remove(client);
client.Close();
}
public void Stop()
{
isRunning = false;
server.Stop();
}
}
5.2 处理断开连接
在客户端和服务器中处理断开连接的情况,确保资源正确释放。
public class SimpleTcpClient
{
private TcpClient client;
private NetworkStream stream;
private bool isRunning;
public SimpleTcpClient(string ipAddress, int port)
{
client = new TcpClient(ipAddress, port);
stream = client.GetStream();
isRunning = true;
Thread clientThread = new Thread(new ThreadStart(ReceiveMessages));
clientThread.Start();
}
public void SendMessage(string message)
{
if (isRunning)
{
byte[] data = Encoding.ASCII.GetBytes(message);
stream.Write(data, 0, data.Length);
}
}
private void ReceiveMessages()
{
byte[] buffer = new byte[1024];
int bytesRead;
while (isRunning && (bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
}
}
public void Close()
{
isRunning = false;
stream.Close();
client.Close();
}
}
5.3 处理更多的消息类型
可以扩展客户端和服务器的消息处理逻辑,支持更多的消息类型。例如,可以定义一个简单的协议,使用不同的消息前缀来区分消息类型。
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("CHAT:"))
{
// Handle chat message
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage);
}
else if (message.StartsWith("MOVE:"))
{
// Handle move message
string move消息 = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
// Handle unknown message
Console.WriteLine("Unknown message type: " + message);
}
// Echo the message back to all clients
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
foreach (var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
clients.Remove(client);
client.Close();
}
在客户端中,可以根据需要发送不同类型的消息:
public void SendMessage(string messageType, string message)
{
if (isRunning)
{
string fullMessage = messageType + ":" + message;
byte[] data = Encoding.ASCII.GetBytes(fullMessage);
stream.Write(data, 0, data.Length);
}
}
在 ClientManager
中,可以根据用户输入发送不同类型的消息:
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
client.SendMessage("CHAT", "Hello, Server!");
}
if (Input.GetKeyDown(KeyCode.M))
{
client.SendMessage("MOVE", "Player moved to position (x, y)");
}
}
6. 处理并发和线程安全
在处理多个客户端时,需要注意并发和线程安全问题。可以使用锁(lock)来确保对共享资源的访问是线程安全的。
private readonly object clientListLock = new object();
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
lock (clientListLock)
{
clients.Add(client);
}
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("CHAT:"))
{
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage);
}
else if (message.StartsWith("MOVE:"))
{
string moveMessage = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
Console.WriteLine("Unknown message type: " + message);
}
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
lock (clientListLock)
{
foreach (var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
}
lock (clientListLock)
{
clients.Remove(client);
}
client.Close();
}
7. 处理心跳和超时
为了确保客户端和服务器之间的连接是活跃的,可以实现心跳机制。客户端定期发送心跳消息,服务器检查心跳消息的接收情况,如果超时未收到心跳消息,则认为客户端已断开。
在客户端中实现心跳消息:
public void StartHeartbeat()
{
Thread heartbeatThread = new Thread(new ThreadStart(SendHeartbeat));
heartbeatThread.Start();
}
private void SendHeartbeat()
{
while (isRunning)
{
SendMessage("HEARTBEAT", "ping");
Thread.Sleep(5000); // 每5秒发送一次心跳消息
}
}
在服务器中处理心跳消息和超时:
private Dictionary<TcpClient, DateTime> clientLastHeartbeat = new Dictionary<TcpClient, DateTime>();
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
lock (clientListLock)
{
clients.Add(client);
clientLastHeartbeat[client] = DateTime.Now;
}
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("HEARTBEAT:"))
{
lock (clientListLock)
{
clientLastHeartbeat[client] = DateTime.Now;
}
}
else if (message.StartsWith("CHAT:"))
{
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage```csharp
}
else if (message.StartsWith("MOVE:"))
{
string moveMessage = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
Console.WriteLine("Unknown message type: " + message);
}
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
lock (clientListLock)
{
foreach (var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
}
lock (clientListLock)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
}
client.Close();
}
private void CheckHeartbeat()
{
while (isRunning)
{
lock (clientListLock)
{
DateTime now = DateTime.Now;
List<TcpClient> clientsToRemove = new List<TcpClient>();
foreach (var kvp in clientLastHeartbeat)
{
if ((now - kvp.Value).TotalSeconds > 10) // 超过10秒未收到心跳消息
{
clientsToRemove.Add(kvp.Key);
}
}
foreach (var client in clientsToRemove)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
client.Close();
Console.WriteLine("Client disconnected due to timeout.");
}
}
Thread.Sleep(5000); // 每5秒检查一次心跳
}
}
在服务器的构造函数中启动心跳检查线程:
public SimpleTcpServer(string ipAddress, int port)
{
server = new TcpListener(IPAddress.Parse(ipAddress), port);
server.Start();
isRunning = true;
Thread serverThread = new Thread(new ThreadStart(Run));
serverThread.Start();
Thread heartbeatThread = new Thread(new ThreadStart(CheckHeartbeat));
heartbeatThread.Start();
}
8. 处理异常和错误
在实际应用中,网络通信可能会遇到各种异常和错误。需要在代码中添加适当的异常处理,以确保系统的稳定性。
在客户端和服务器的网络操作中添加异常处理:
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
lock (clientListLock)
{
clients.Add(client);
clientLastHeartbeat[client] = DateTime.Now;
}
try
{
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("HEARTBEAT:"))
{
lock (clientListLock)
{
clientLastHeartbeat[client] = DateTime.Now;
}
}
else if (message.StartsWith("CHAT:"))
{
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage);
}
else if (message.StartsWith("MOVE:"))
{
string moveMessage = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
Console.WriteLine("Unknown message type: " + message);
}
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
lock (clientListLock)
{
foreach (var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Client handling exception: " + ex.Message);
}
finally
{
lock (clientListLock)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
}
client.Close();
}
}
在客户端中添加异常处理:
private void ReceiveMessages()
{
byte[] buffer = new byte[1024];
int bytesRead;
try
{
while (isRunning && (bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
}
}
catch (Exception ex)
{
Console.WriteLine("Client receive exception: " + ex.Message);
}
}
9. 总结(续)
通过上述步骤,我们实现了一个基本的客户端-服务器通信系统,支持多客户端连接、消息处理、心跳检测和异常处理。以下是我们实现的主要功能:
- 基本通信:客户端和服务器可以通过 TCP 进行基本的消息传递。
- 多客户端支持:服务器可以处理多个客户端的连接和消息。
- 消息类型处理:通过简单的协议区分不同类型的消息(如聊天消息和移动消息)。
- 心跳检测:客户端定期发送心跳消息,服务器检测心跳消息以判断客户端是否仍然在线。
- 异常处理:在网络操作中添加异常处理,确保系统的稳定性。
10. 进一步扩展
虽然我们已经实现了一个基本的通信系统,但在实际应用中,可能还需要更多的功能和优化。以下是一些可以进一步扩展的方向:
10.1 加密和安全
在实际应用中,网络通信需要考虑安全性。可以使用 SSL/TLS 加密通信,确保数据在传输过程中不被窃听或篡改。
10.2 数据序列化
为了传输复杂的数据结构,可以使用数据序列化技术(如 JSON、XML 或 Protobuf)。这可以使得消息传递更加灵活和强大。
10.3 高效的消息处理
对于高并发的应用,可以使用更高效的消息处理机制,如异步 I/O 或消息队列。这样可以提高系统的性能和响应速度。
10.4 负载均衡
在大规模应用中,可以使用负载均衡技术,将客户端请求分发到多个服务器上,确保系统的高可用性和扩展性。
10.5 日志和监控
为了更好地维护和调试系统,可以添加日志和监控功能,记录系统的运行状态和异常情况。
11. 示例代码
以下是完整的示例代码,包括客户端和服务器的实现:
服务器代码
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpServer
{
private TcpListener server;
private bool isRunning;
private List<TcpClient> clients = new List<TcpClient>();
private Dictionary<TcpClient, DateTime> clientLastHeartbeat = new Dictionary<TcpClient, DateTime>();
private readonly object clientListLock = new object();
public SimpleTcpServer(string ipAddress, int port)
{
server = new TcpListener(IPAddress.Parse(ipAddress), port);
server.Start();
isRunning = true;
Thread serverThread = new Thread(new ThreadStart(Run));
serverThread.Start();
Thread heartbeatThread = new Thread(new ThreadStart(CheckHeartbeat));
heartbeatThread.Start();
}
private void Run()
{
while (isRunning)
{
try
{
TcpClient client = server.AcceptTcpClient();
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
clientThread.Start(client);
}
catch (Exception ex)
{
Console.WriteLine("Server exception: " + ex.Message);
}
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
lock (clientListLock)
{
clients.Add(client);
clientLastHeartbeat[client] = DateTime.Now;
}
try
{
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("HEARTBEAT:"))
{
lock (clientListLock)
{
clientLastHeartbeat[client] = DateTime.Now;
}
}
else if (message.StartsWith("CHAT:"))
{
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage);
}
else if (message.StartsWith("MOVE:"))
{
string moveMessage = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
Console.WriteLine("Unknown message type: " + message);
}
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
lock (clientListLock)
{
foreach```csharp
(var c in clients)
{
c.GetStream().Write(response, 0, response.Length);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Client handling exception: " + ex.Message);
}
finally
{
lock (clientListLock)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
}
client.Close();
}
}
private void CheckHeartbeat()
{
while (isRunning)
{
lock (clientListLock)
{
DateTime now = DateTime.Now;
List<TcpClient> clientsToRemove = new List<TcpClient>();
foreach (var kvp in clientLastHeartbeat)
{
if ((now - kvp.Value).TotalSeconds > 10) // 超过10秒未收到心跳消息
{
clientsToRemove.Add(kvp.Key);
}
}
foreach (var client in clientsToRemove)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
client.Close();
Console.WriteLine("Client disconnected due to timeout.");
}
}
Thread.Sleep(5000); // 每5秒检查一次心跳
}
}
}
客户端代码
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpClient
{
private TcpClient client;
private NetworkStream stream;
private bool isRunning;
public SimpleTcpClient(string ipAddress, int port)
{
client = new TcpClient(ipAddress, port);
stream = client.GetStream();
isRunning = true;
Thread receiveThread = new Thread(new ThreadStart(ReceiveMessages));
receiveThread.Start();
StartHeartbeat();
}
public void SendMessage(string messageType, string message)
{
if (isRunning)
{
string fullMessage = messageType + ":" + message;
byte[] data = Encoding.ASCII.GetBytes(fullMessage);
stream.Write(data, 0, data.Length);
}
}
private void ReceiveMessages()
{
byte[] buffer = new byte[1024];
int bytesRead;
try
{
while (isRunning && (bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
}
}
catch (Exception ex)
{
Console.WriteLine("Client receive exception: " + ex.Message);
}
}
public void StartHeartbeat()
{
Thread heartbeatThread = new Thread(new ThreadStart(SendHeartbeat));
heartbeatThread.Start();
}
private void SendHeartbeat()
{
while (isRunning)
{
SendMessage("HEARTBEAT", "ping");
Thread.Sleep(5000); // 每5秒发送一次心跳消息
}
}
public void Close()
{
isRunning = false;
stream.Close();
client.Close();
}
}
使用示例
在主程序中,可以创建服务器和客户端实例,并进行简单的交互:
class Program
{
static void Main(string[] args)
{
// 启动服务器
SimpleTcpServer server = new SimpleTcpServer("127.0.0.1", 8888);
// 启动客户端
SimpleTcpClient client = new SimpleTcpClient("127.0.0.1", 8888);
// 发送聊天消息
client.SendMessage("CHAT", "Hello, Server!");
// 发送移动消息
client.SendMessage("MOVE", "Player moved to position (x, y)");
// 等待一段时间以便观察消息传递
Thread.Sleep(20000);
// 关闭客户端
client.Close();
}
}
12. 结语(续)
通过上述代码,我们实现了一个基本的客户端-服务器通信系统,支持多客户端连接、消息处理、心跳检测和异常处理。这个系统可以作为更复杂应用的基础,进一步扩展和优化以满足实际需求。希望这个示例对你有所帮助!
13. 进一步优化和改进
虽然我们已经实现了一个基本的通信系统,但在实际应用中,可能还需要更多的功能和优化。以下是一些可以进一步优化和改进的方向:
13.1 异步编程
使用异步编程可以提高系统的性能和响应速度。C# 提供了 async
和 await
关键字,可以方便地实现异步操作。
13.2 使用高级协议
可以使用更高级的协议(如 WebSocket)来实现实时通信。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,适用于需要低延迟和高频率消息传递的应用。
13.3 负载均衡和高可用性
在大规模应用中,可以使用负载均衡技术,将客户端请求分发到多个服务器上,确保系统的高可用性和扩展性。可以使用反向代理服务器(如 Nginx)或云服务提供的负载均衡器。
13.4 日志和监控
为了更好地维护和调试系统,可以添加日志和监控功能,记录系统的运行状态和异常情况。可以使用日志库(如 NLog 或 log4net)和监控工具(如 Prometheus 和 Grafana)。
13.5 单元测试和集成测试
为了确保系统的稳定性和可靠性,可以编写单元测试和集成测试。可以使用测试框架(如 NUnit 或 xUnit)来编写和运行测试。
14. 完整示例代码
以下是完整的示例代码,包括服务器和客户端的实现,以及主程序的使用示例。
服务器代码
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpServer
{
private TcpListener server;
private bool isRunning;
private List<TcpClient> clients = new List<TcpClient>();
private Dictionary<TcpClient, DateTime> clientLastHeartbeat = new Dictionary<TcpClient, DateTime>();
private readonly object clientListLock = new object();
public SimpleTcpServer(string ipAddress, int port)
{
server = new TcpListener(IPAddress.Parse(ipAddress), port);
server.Start();
isRunning = true;
Thread serverThread = new Thread(new ThreadStart(Run));
serverThread.Start();
Thread heartbeatThread = new Thread(new ThreadStart(CheckHeartbeat));
heartbeatThread.Start();
}
private void Run()
{
while (isRunning)
{
try
{
TcpClient client = server.AcceptTcpClient();
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
clientThread.Start(client);
}
catch (Exception ex)
{
Console.WriteLine("Server exception: " + ex.Message);
}
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
lock (clientListLock)
{
clients.Add(client);
clientLastHeartbeat[client] = DateTime.Now;
}
try
{
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
if (message.StartsWith("HEARTBEAT:"))
{
lock (clientListLock)
{
clientLastHeartbeat[client] = DateTime.Now;
}
}
else if (message.StartsWith("CHAT:"))
{
string chatMessage = message.Substring(5);
Console.WriteLine("Chat message: " + chatMessage);
}
else if (message.StartsWith("MOVE:"))
{
string moveMessage = message.Substring(5);
Console.WriteLine("Move message: " + moveMessage);
}
else
{
Console.WriteLine("Unknown message type: " + message);
}
byte[] response = Encoding.ASCII.GetBytes("Echo: " + message);
lock (clientListLock)
{
foreach (var c in clients```csharp
{
c.GetStream().Write(response, 0, response.Length);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Client handling exception: " + ex.Message);
}
finally
{
lock (clientListLock)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
}
client.Close();
}
}
private void CheckHeartbeat()
{
while (isRunning)
{
lock (clientListLock)
{
DateTime now = DateTime.Now;
List<TcpClient> clientsToRemove = new List<TcpClient>();
foreach (var kvp in clientLastHeartbeat)
{
if ((now - kvp.Value).TotalSeconds > 10) // 超过10秒未收到心跳消息
{
clientsToRemove.Add(kvp.Key);
}
}
foreach (var client in clientsToRemove)
{
clients.Remove(client);
clientLastHeartbeat.Remove(client);
client.Close();
Console.WriteLine("Client disconnected due to timeout.");
}
}
Thread.Sleep(5000); // 每5秒检查一次心跳
}
}
}
客户端代码
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SimpleTcpClient
{
private TcpClient client;
private NetworkStream stream;
private bool isRunning;
public SimpleTcpClient(string ipAddress, int port)
{
client = new TcpClient(ipAddress, port);
stream = client.GetStream();
isRunning = true;
Thread receiveThread = new Thread(new ThreadStart(ReceiveMessages));
receiveThread.Start();
StartHeartbeat();
}
public void SendMessage(string messageType, string message)
{
if (isRunning)
{
string fullMessage = messageType + ":" + message;
byte[] data = Encoding.ASCII.GetBytes(fullMessage);
stream.Write(data, 0, data.Length);
}
}
private void ReceiveMessages()
{
byte[] buffer = new byte[1024];
int bytesRead;
try
{
while (isRunning && (bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
}
}
catch (Exception ex)
{
Console.WriteLine("Client receive exception: " + ex.Message);
}
}
public void StartHeartbeat()
{
Thread heartbeatThread = new Thread(new ThreadStart(SendHeartbeat));
heartbeatThread.Start();
}
private void SendHeartbeat()
{
while (isRunning)
{
SendMessage("HEARTBEAT", "ping");
Thread.Sleep(5000); // 每5秒发送一次心跳消息
}
}
public void Close()
{
isRunning = false;
stream.Close();
client.Close();
}
}
使用示例
在主程序中,可以创建服务器和客户端实例,并进行简单的交互:
class Program
{
static void Main(string[] args)
{
// 启动服务器
SimpleTcpServer server = new SimpleTcpServer("127.0.0.1", 8888);
// 启动客户端
SimpleTcpClient client = new SimpleTcpClient("127.0.0.1", 8888);
// 发送聊天消息
client.SendMessage("CHAT", "Hello, Server!");
// 发送移动消息
client.SendMessage("MOVE", "Player moved to position (x, y)");
// 等待一段时间以便观察消息传递
Thread.Sleep(20000);
// 关闭客户端
client.Close();
}
}
15. 结语
通过上述代码,我们实现了一个基本的客户端-服务器通信系统,支持多客户端连接、消息处理、心跳检测和异常处理。这个系统可以作为更复杂应用的基础,进一步扩展和优化以满足实际需求。希望这个示例对你有所帮助!
在实际应用中,可能还需要根据具体需求进行更多的优化和改进,例如使用异步编程、加密通信、负载均衡、日志和监控等。希望你能根据自己的需求,进一步完善和优化这个系统。祝你编程愉快!