异步Socket编程
1.所谓异步操作方式,就是我们希望让某个工作开始以后,能在这个工作尚未完成的时候继续处理其他工资。比如:客户端服务器端随时可以接收与发送信息,不需要等待。
2.Unity异步通讯聊天工具开发
1)服务器端开发
界面开发
使用Unity创建一个2D文件
将当前场景另存为Server,新建UI的Panel界面,其中包含四个Input Field控件,四个Text控件,一个Dropdown控件,三个Button控件,一个空物体控件,调整控件名称及属性位置,如下图所示
新建服务器端控制脚本ServerScripts,并将代码挂在空物体_ScriptsControl,为Btn_Exit按钮增加On Click(单击)事件,与脚本中的ExitSys()方法连接,为Btn_Start按钮增加On Click(单击)事件,与脚本中的EnableServerReady()方法连接,为Btn_Send按钮增加On Click(单击)事件,与脚本中的SendMsg()方法连接,为Dd_selectIPandPort下拉列表增加On Value Changed事件,与脚本中的GetCurrentSelectIpInfo()方法连接。
代码如下:
/***
* Title:Socket服务器端开发
*/
using System.Collections;
using System.Collections.Generic; //泛型空间引用
using UnityEngine;
using UnityEngine.UI; //UI控件命名空间
using System;
using System.Text;
using System.Threading; //多线程命名空间
using System.Net; //网络命名空间
using System.Net.Sockets; //Socket命名空间
public class ServerScripts : MonoBehaviour
{
public InputField InpIPAddress; //IP地址
public InputField InpPort; //端口号
public InputField InpDisplayInfo; //显示信息
public InputField InpSendMsg; //发送信息
public Dropdown Drd_IPList; //客户端的IP列表(相当于QQ聊天中的“好友列表”)
private Socket _SockServer; //服务端套接字
private bool _IsListenContection = true; //是否正在监听。
private StringBuilder _SbDisplayInfo = new StringBuilder();//追加信息
private string _CurrentClientIPValues = String.Empty; //当前选择的IP地址信息
//保存"客户端通讯的套接字"(相当于“QQ列表”)
private Dictionary<string, Socket> _DicSocket = new Dictionary<string, Socket>();
//保存Dropdown 中的数据,目的为了删除节点信息。
private Dictionary<string, Dropdown.OptionData> _DicDropdown = new Dictionary<string, Dropdown.OptionData>();
void Start()
{
//控件的初始化
InpIPAddress.text = "127.0.0.1";
InpPort.text = "2000";
InpSendMsg.text = string.Empty;
//下拉列表处理
Drd_IPList.options.Clear(); //清空列表信息
//添加一个空节点
Dropdown.OptionData op = new Dropdown.OptionData();
op.text = "";
Drd_IPList.options.Add(op);
}
/// <summary>
/// 退出系统
/// </summary>
public void ExitSys()
{
//退出会话Socket
//退出监听Socket
if (_SockServer != null)
{
try
{
_SockServer.Shutdown(SocketShutdown.Both); //关闭连接
}
catch
{ }
_SockServer.Close(); //清理资源
}
Application.Quit();
}
/// <summary>
/// 获取“当前好友”。
/// </summary>
public void GetCurrentSelectIpInfo()
{
//获取当前玩家选择的客户端列表信息(“好友”)
_CurrentClientIPValues = Drd_IPList.options[Drd_IPList.value].text;
}
/// <summary>
/// 启动服务器端
/// </summary>
public void EnableServerReady()
{
//需要绑定的地址与端口号
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(InpIPAddress.text), Convert.ToInt32(InpPort.text));
//定义监听Socket
_SockServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定
_SockServer.Bind(endPoint);
//开始Sockect监听
_SockServer.Listen(10);
//显示内容(用控件显示)
DisplayMsg("服务器端启动...");
//开启后台线程(监听客户端连接)
Thread thClinetCon = new Thread(ListenClientCon);
thClinetCon.IsBackground = true; //后台线程
thClinetCon.Name = "ThrListenClientCon";
thClinetCon.Start();
}
/// <summary>
/// (后台线程) 监听客户端连接
/// </summary>
private void ListenClientCon()
{
Socket sockMsgServer = null; //会话Socket
try
{
while (_IsListenContection)
{
//等待接收客户端连接,此处会“阻断”当前线程的运行
sockMsgServer = _SockServer.Accept(); //监听客户端连接,返回会话Socket
//获取远程(客户端)节点信息(IP,Port)
string strClientIpAndPort = sockMsgServer.RemoteEndPoint.ToString();
//把“远程节点”信息,添加到DropDown 控件中
Dropdown.OptionData op = new Dropdown.OptionData();
op.text = strClientIpAndPort;
Drd_IPList.options.Add(op);
//把会话Socket 添加到字典集合中(为了后面发送信息使用)
_DicSocket.Add(strClientIpAndPort, sockMsgServer);
//控件显示,有客户端连接
DisplayMsg("有客户端连接");
//开启后台线程,接收客户端会话信息。
Thread thClientMsg = new Thread(ReceiveMsg);
thClientMsg.IsBackground = true;
thClientMsg.Name = "thListenClientMsg";
thClientMsg.Start(sockMsgServer);
}//While_end
}
catch (Exception)
{
_IsListenContection = false;
//关闭会话Socket
if (sockMsgServer != null)
{
sockMsgServer.Shutdown(SocketShutdown.Both);
sockMsgServer.Close();
}
//关闭监听Socket
if (_SockServer != null)
{
_SockServer.Shutdown(SocketShutdown.Both);
_SockServer.Close();
}
}
}//ListenClientCon_end
/// <summary>
/// (后台线程) 接收客户端会话
/// </summary>
/// <param name="sockMsg"></param>
private void ReceiveMsg(object sockMsg)
{
Socket socketMsg = sockMsg as Socket;
try
{
while (true)
{
//准备接收“数据缓存”
byte[] msgArray = new byte[1024 * 1024]; //1M空间
//接收客户端发来的套接字消息数据
int trueClinetMsgLength = socketMsg.Receive(msgArray);
//byte数组转字符串
string strMsg = System.Text.Encoding.UTF8.GetString(msgArray, 0, trueClinetMsgLength);
//显示接收的客户端消息内容
DisplayMsg("客户端信息: " + strMsg);
}
}
catch (Exception)
{
}
finally
{
DisplayMsg("有客户端断开连接了:" + socketMsg.RemoteEndPoint.ToString());
//字典类移除断开连接的客户端信息
_DicSocket.Remove(socketMsg.RemoteEndPoint.ToString());
//客户端列表中移除
if (_DicDropdown.ContainsKey(socketMsg.RemoteEndPoint.ToString()))
{
Drd_IPList.options.Remove(_DicDropdown[socketMsg.RemoteEndPoint.ToString()]);
}
//关闭Socket
socketMsg.Shutdown(SocketShutdown.Both);
socketMsg.Close();
}
}
/// <summary>
/// 发送客户端会话
/// </summary>
public void SendMsg()
{
//参数检查
_CurrentClientIPValues = _CurrentClientIPValues.Trim();
if (string.IsNullOrEmpty(_CurrentClientIPValues))
{
DisplayMsg("请选择要聊天的用户名称");
return;
}
//判断是否存在指定的客户端通信的Socket
if (_DicSocket.ContainsKey(_CurrentClientIPValues))
{
//得到发送的信息
string strSendMsg = InpSendMsg.text;
strSendMsg = strSendMsg.Trim();
if (!string.IsNullOrEmpty(strSendMsg))
{
//信息转码
byte[] byMsgArray = System.Text.Encoding.UTF8.GetBytes(strSendMsg);
//发送数据
_DicSocket[_CurrentClientIPValues].Send(byMsgArray);
//记录发送数据
DisplayMsg("已发送:" + strSendMsg);
//控件重置
InpSendMsg.text = string.Empty;
}
else
{
DisplayMsg("发送的信息不能为空!");
}
}
else
{
DisplayMsg("请选择合法的聊天用户!,请重新选择");
}
}
/// <summary>
/// 主显示控件,显示消息
/// </summary>
/// <param name="str">需要显示的消息</param>
private void DisplayMsg(string str)
{
str = str.Trim();
if (!string.IsNullOrEmpty(str))
{
_SbDisplayInfo.Append(System.DateTime.Now.ToString());
_SbDisplayInfo.Append(" ");
_SbDisplayInfo.Append(str);
_SbDisplayInfo.Append("\r\n");
InpDisplayInfo.text = _SbDisplayInfo.ToString();
}
}
}
2)客户端开发
界面开发
新建一个场景Client,新建UI的Panel界面,其中包含四个Input Field控件,四个Text控件,三个Button控件,一个空物体控件,调整控件名称及属性位置,如下图所示
新建服务器端控制脚本ClientScripts,并将代码挂在空物体_ScriptsControl,为Btn_Exit按钮增加On Click(单击)事件,与脚本中的ExitSystem()方法连接,为Btn_Start按钮增加On Click(单击)事件,与脚本中的EnableClientCon()方法连接,为Btn_Send按钮增加On Click(单击)事件,与脚本中的SendMsg()方法连接
新建客户端控制脚本ClientScripts,代码如下:
/***
* Title:Socket客户端开发
*/
using System.Collections;
using System.Collections.Generic; //泛型空间引用
using UnityEngine;
using UnityEngine.UI; //UI控件命名空间
using System;
using System.Text;
using System.Threading; //多线程命名空间
using System.Net; //网络命名空间
using System.Net.Sockets; //Socket命名空间
public class ClientScripts : MonoBehaviour
{
public InputField InpIPAddress; //IP地址
public InputField InpPort; //端口号
public InputField InpDisplayInfo; //显示信息
public InputField InpSendMsg; //发送信息
private Socket _SockClient; //客户端Socket
private IPEndPoint endPoint;
private bool _isSendDataConnection = true; //发送数据连接
private StringBuilder _SbDisplayInfo = new StringBuilder();//控件追加信息
void Start()
{
InpIPAddress.text = "127.0.0.1";
InpPort.text = "2000";
}
/// <summary>
/// 客户端退出系统
/// </summary>
public void ExitSystem()
{
//关闭客户端Socket
if (_SockClient != null)
{
try
{
//关闭连接
_SockClient.Shutdown(SocketShutdown.Both);
}
catch
{ }
//清理资源
_SockClient.Close();
}
//退出
Application.Quit();
}
/// <summary>
/// 启动客户端连接
/// </summary>
public void EnableClientCon()
{
//通讯IP与端口号
endPoint = new IPEndPoint(IPAddress.Parse(InpIPAddress.text), Convert.ToInt32(InpPort.text));
//建立客户端Socket
_SockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
//建立连接
_SockClient.Connect(endPoint);
//启动线程,监听服务端返回的信息
Thread thrListenMsgFromServer = new Thread(ListenMsgInfoFromServer);
thrListenMsgFromServer.IsBackground = true;
thrListenMsgFromServer.Name = "thrListenMsgFromServer";
thrListenMsgFromServer.Start();
}
catch (Exception)
{
}
//控件显示连接服务端成功。
DisplayMsg("连接服务器成功!");
}
/// <summary>
/// (后台线程) 监听服务端发来的信息
/// </summary>
private void ListenMsgInfoFromServer()
{
try
{
while (true)
{
//开辟消息内存区域
byte[] byMsgArray = new byte[1024 * 1024]; //1M空间
//客户端接收服务器返回的数据
int intTrueMsgLengt = _SockClient.Receive(byMsgArray);
//转字符串
string strMsg = System.Text.Encoding.UTF8.GetString(byMsgArray, 0, intTrueMsgLengt);
//显示收到的信息
DisplayMsg("服务器返回信息:" + strMsg);
}
}
catch
{
}
finally
{
DisplayMsg("服务器断开连接了!");
//关闭Socket
_SockClient.Disconnect(true);
_SockClient.Close();
}
}
/// <summary>
/// 客户端发送数据到服务器端
/// </summary>
public void SendMsg()
{
string strSendMsg = InpSendMsg.text;
if (!string.IsNullOrEmpty(strSendMsg))
{
//字节转换
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(strSendMsg);
//发送
_SockClient.Send(byteArray);
//显示发送的内容
DisplayMsg("我:" + strSendMsg);
//控件清空
InpSendMsg.text = string.Empty;
}
else
{
DisplayMsg("提示:发送的数据不能为空!,请输入发送信息");
}
}
/// <summary>
/// 主显示控件,显示消息
/// </summary>
/// <param name="str">需要显示的消息</param>
private void DisplayMsg(string str)
{
str = str.Trim();
if (!string.IsNullOrEmpty(str))
{
_SbDisplayInfo.Append(System.DateTime.Now.ToString());
_SbDisplayInfo.Append(" ");
_SbDisplayInfo.Append(str);
_SbDisplayInfo.Append("\r\n");
InpDisplayInfo.text = _SbDisplayInfo.ToString();
}
}
}
将两个场景用Unity中File-->Buid Setting分别打包,即可实现异步通讯交互
感言:
学习C#,至今为止就告一段落了,在学校的时候也学过C#,一直都在查缺补漏.目前的阶段是面临毕业的实习生,在一家使用Unity做XR的技术部Unity组中学习进步。接下来就会开始Unity的学习笔记的更新,一起努力进步,互相借鉴吧!