36.网络通信-套接字Socket-UDP通信-同步-客户端综合练习题
36.1 知识点
如同TCP通信一样制作客户端网络通信模块,需要具备的功能有:
- 区分消息类型
- 发送消息
- 接受消息
- 判断如果不是服务端发送的消息不处理
定义UdpNetManager类,实现继承Monobehaviour的单例。定义必要的变量。
public class UdpNetManager : BaseSingletonInMonoBehaviour<UdpNetManager>
{
private EndPoint serverIpPoint; // 服务器的IP地址和端口信息
private Socket socket; // 客户端Socket
// 客户端socket是否关闭
private bool isClose = true;
// 两个消息队列:发送消息和接收消息,在多线程中可以操作
private Queue<BaseMessage> sendQueue = new Queue<BaseMessage>();
private Queue<BaseMessage> receiveQueue = new Queue<BaseMessage>();
private byte[] cacheBytes = new byte[512]; // 用于缓存接收的字节数据
}
实现启动客户端和关闭客户端方法。销毁时需要调用关闭客户端。
/// <summary>
/// 启动客户端socket相关的方法
/// </summary>
/// <param name="ip">远端服务器的IP</param>
/// <param name="port">远端服务器的port</param>
public void StartClient(string ip, int port)
{
// 如果当前是开启状态,就不用再开了
if (!isClose)
return;
// 先记录服务器地址,一会发消息时会使用
serverIpPoint = new IPEndPoint(IPAddress.Parse(ip), port);
// 创建一个本地IP和端口,用于绑定客户端Socket
IPEndPoint clientIpPort = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(clientIpPort);
isClose = false;
print("客户端网络启动");
// 启动消息接收和发送线程
ThreadPool.QueueUserWorkItem(ReceiveMsg);
ThreadPool.QueueUserWorkItem(SendMsg);
}
catch (System.Exception e)
{
print("启动Socket出问题" + e.Message);
}
}
// 关闭socket
public void Close()
{
if (socket != null)
{
isClose = true;
QuitMessage quitMessage = new QuitMessage();
// 发送一个退出消息给服务器,让其移除记录
socket.SendTo(quitMessage.Writing(), serverIpPoint);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket = null;
}
}
// 销毁时关闭socket
private void OnDestroy()
{
Close();
}
实现收发消息方法
// 接收消息的方法
private void ReceiveMsg(object obj)
{
EndPoint tempIpPoint = new IPEndPoint(IPAddress.Any, 0);
int nowIndex;
int msgID;
int msgLength;
while (!isClose)
{
if (socket != null && socket.Available > 0)
{
try
{
socket.ReceiveFrom(cacheBytes, ref tempIpPoint);
// 为了避免处理非服务器发来的骚扰消息
if (!tempIpPoint.Equals(serverIpPoint))
continue; // 如果发现发消息给你的不是服务器,那么证明是骚扰消息,就不用处理
// 处理服务器发来的消息
nowIndex = 0;
// 解析消息ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
// 解析消息长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
// 解析消息体
BaseMessage baseMessage = null;
switch (msgID)
{
case 1001:
baseMessage = new PlayerMessage();
// 反序列化消息体
baseMessage.Reading(cacheBytes, nowIndex);
break;
}
if (baseMessage != null)
receiveQueue.Enqueue(baseMessage);
}
catch (SocketException s)
{
print("接受消息出问题" + s.SocketErrorCode + s.Message);
}
catch (Exception e)
{
print("接受消息出问题(非网络问题)" + e.Message);
}
}
}
}
// 每帧检查接收数组看看要不要接收消息
void Update()
{
if (receiveQueue.Count > 0)
{
BaseMessage baseMessage = receiveQueue.Dequeue();
switch (baseMessage)
{
case PlayerMessage playerMessage:
print(playerMessage.playerID);
print(playerMessage.playerData.name);
print(playerMessage.playerData.atk);
print(playerMessage.playerData.lev);
break;
}
}
}
// 发送消息的方法
private void SendMsg(object obj)
{
while (!isClose)
{
if (socket != null && sendQueue.Count > 0)
{
try
{
socket.SendTo(sendQueue.Dequeue().Writing(), serverIpPoint);
}
catch (SocketException s)
{
print("发送消息出错" + s.SocketErrorCode + s.Message);
}
}
}
}
// 发送消息到服务器
public void Send(BaseMessage baseMessage)
{
sendQueue.Enqueue(baseMessage);
}
客户端入口连接服务端,并定义发消息测试按钮
public InputField InputField;
public Button sendButton;
void Start()
{
UdpNetManager.Instance.StartClient("127.0.0.1", 8080);
sendButton.onClick.AddListener(() =>
{
PlayerMessage playerMessage = new PlayerMessage();
playerMessage.playerData = new PlayerData();
playerMessage.playerID = 1;
playerMessage.playerData.name = "韬老狮的客户端发的消息";
playerMessage.playerData.atk = 888;
playerMessage.playerData.lev = 666;
UdpNetManager.Instance.Send(playerMessage);
});
}
开启UDP服务端和客户端进行测试
36.2 知识点代码
UdpNetManager
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
public class UdpNetManager : BaseSingletonInMonoBehaviour<UdpNetManager>
{
private EndPoint serverIpPoint; // 服务器的IP地址和端口信息
private Socket socket; // 客户端Socket
// 客户端socket是否关闭
private bool isClose = true;
// 两个消息队列:发送消息和接收消息,在多线程中可以操作
private Queue<BaseMessage> sendQueue = new Queue<BaseMessage>();
private Queue<BaseMessage> receiveQueue = new Queue<BaseMessage>();
private byte[] cacheBytes = new byte[512]; // 用于缓存接收的字节数据
/// <summary>
/// 启动客户端socket相关的方法
/// </summary>
/// <param name="ip">远端服务器的IP</param>
/// <param name="port">远端服务器的port</param>
public void StartClient(string ip, int port)
{
// 如果当前是开启状态,就不用再开了
if (!isClose)
return;
// 先记录服务器地址,一会发消息时会使用
serverIpPoint = new IPEndPoint(IPAddress.Parse(ip), port);
// 创建一个本地IP和端口,用于绑定客户端Socket
IPEndPoint clientIpPort = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(clientIpPort);
isClose = false;
print("客户端网络启动");
// 启动消息接收和发送线程
ThreadPool.QueueUserWorkItem(ReceiveMsg);
ThreadPool.QueueUserWorkItem(SendMsg);
}
catch (System.Exception e)
{
print("启动Socket出问题" + e.Message);
}
}
// 关闭socket
public void Close()
{
if (socket != null)
{
isClose = true;
QuitMessage quitMessage = new QuitMessage();
// 发送一个退出消息给服务器,让其移除记录
socket.SendTo(quitMessage.Writing(), serverIpPoint);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket = null;
}
}
// 销毁时关闭socket
private void OnDestroy()
{
Close();
}
// 接收消息的方法
private void ReceiveMsg(object obj)
{
EndPoint tempIpPoint = new IPEndPoint(IPAddress.Any, 0);
int nowIndex;
int msgID;
int msgLength;
while (!isClose)
{
if (socket != null && socket.Available > 0)
{
try
{
socket.ReceiveFrom(cacheBytes, ref tempIpPoint);
// 为了避免处理非服务器发来的骚扰消息
if (!tempIpPoint.Equals(serverIpPoint))
continue; // 如果发现发消息给你的不是服务器,那么证明是骚扰消息,就不用处理
// 处理服务器发来的消息
nowIndex = 0;
// 解析消息ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
// 解析消息长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
// 解析消息体
BaseMessage baseMessage = null;
switch (msgID)
{
case 1001:
baseMessage = new PlayerMessage();
// 反序列化消息体
baseMessage.Reading(cacheBytes, nowIndex);
break;
}
if (baseMessage != null)
receiveQueue.Enqueue(baseMessage);
}
catch (SocketException s)
{
print("接受消息出问题" + s.SocketErrorCode + s.Message);
}
catch (Exception e)
{
print("接受消息出问题(非网络问题)" + e.Message);
}
}
}
}
//每帧检查接收数组看看要不要接收消息
void Update()
{
if (receiveQueue.Count > 0)
{
BaseMessage baseMessage = receiveQueue.Dequeue();
switch (baseMessage)
{
case PlayerMessage playerMessage:
print(playerMessage.playerID);
print(playerMessage.playerData.name);
print(playerMessage.playerData.atk);
print(playerMessage.playerData.lev);
break;
}
}
}
// 发送消息的方法
private void SendMsg(object obj)
{
while (!isClose)
{
if (socket != null && sendQueue.Count > 0)
{
try
{
socket.SendTo(sendQueue.Dequeue().Writing(), serverIpPoint);
}
catch (SocketException s)
{
print("发送消息出错" + s.SocketErrorCode + s.Message);
}
}
}
}
// 发送消息到服务器
public void Send(BaseMessage baseMessage)
{
sendQueue.Enqueue(baseMessage);
}
}
Lesson36_网络通信_套接字Socket_UDP通信_同步_客户端综合练习题
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class Lesson36_网络通信_套接字Socket_UDP通信_同步_客户端综合练习题 : MonoBehaviour
{
public InputField InputField;
public Button sendButton;
void Start()
{
UdpNetManager.Instance.StartClient("127.0.0.1", 8080);
sendButton.onClick.AddListener(() =>
{
PlayerMessage playerMessage = new PlayerMessage();
playerMessage.playerData = new PlayerData();
playerMessage.playerID = 1;
playerMessage.playerData.name = "韬老狮的客户端发的消息";
playerMessage.playerData.atk = 888;
playerMessage.playerData.lev = 666;
UdpNetManager.Instance.Send(playerMessage);
});
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com