通信原理
unity 支持自定义的Socket:在使用的时候需要关闭防火墙
动态查找IP:因为服务端的Ip不是固定的,所以服务器用UDP广播一段特殊的消息,客服端接受并匹配上那么链接成功。
发送消息:
1 客服端: 封装消息体。 封装的原因:防止沾包
封包=标头+消息内容
2.服务端:解包
服务端
/**
* 2019.9.20
* 定义Socket属性
* */
using System;
using System.Text;
using System.Net.Sockets;
using UnityEngine;
namespace Item_A
{
class Conn
{
public const int buff_size = 1024;
public Socket socket;
public bool IsUse = false;
public Byte[] readBuff = new byte[buff_size];
public int byteCount = 0;
//解析 字节流
StringBuilder receiveStr;
int receiveCount;
private string type;
//help
public static bool PlayAudioState=false;
public void Init(Socket socket)
{
this.socket = socket;
IsUse = true;
byteCount = 0;
}
/// <summary>
/// 缓冲字节数
/// </summary>
public int Buffemain()
{
return buff_size - byteCount;
}
/// <summary>
/// 得到服务器端口号和IP
/// </summary>
public string GetAdress()
{
if (!IsUse)
return "无法获取地址";
return socket.RemoteEndPoint.ToString();
}
/// <summary>
/// 关闭服务器
/// </summary>
public void Colse()
{
if (!IsUse)
return;
Debug.Log("客服端与服务端断开");
IsUse = false;
socket.Close();
}
private int num = 4;
private int index = 5;
/// <summary>
/// 解析数据 ,count 新读取到的数据长度
/// </summary>
public StringBuilder ReadMessage(int count)
{
byteCount = count;
//用while表示缓存区,可能有多条数据
while (true)
{
//缓存区小于4个字节,表示连表头都无法解析
if (byteCount <= num) return null;
//读取四个字节数据,代表这条数据的内容长度(不包括表头的4个数据)
receiveCount = BitConverter.ToInt32(readBuff, 0);
//int receiveCount = BitConverter(readBuff, 0);
//缓存区中的数据,不够解析一条完整的数据
if (byteCount - num < receiveCount) return null;
//2、解析数据
//从除去表头4个字节开始解析内容,解析的数据长度为(表头数据表示的长度)
receiveStr = new StringBuilder(Encoding.UTF8.GetString(readBuff, index, receiveCount));
type = Encoding.UTF8.GetString(readBuff, 4, 1);
switch (type)
{
case "0":
Debug.LogError("数字" + " 消息内容:" + receiveStr);
break;
case "1":
Debug.LogError("汉字" + " 消息内容:" + receiveStr);
PlayAudioState = true;
break;
case "2":
Debug.LogError("心跳" + " 消息内容:" + receiveStr);
break;
case "3":
Debug.LogError("不播放音效" + " 消息内容:" + receiveStr);
break;
}
//把剩余的数据Copy到缓存区头部位置
Array.Copy(readBuff, index + receiveCount, readBuff, 0, byteCount - index - receiveCount);
byteCount = byteCount - index - receiveCount;
return receiveStr;
}
}
}
}
/*
*2019.9.20
* 定义服务器
* */
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
using System.Text.RegularExpressions;
namespace Item_A
{
class Server
{
public Socket socketfd;
public int maxConnect = 50;
public Conn[] conns;
public static string str;
public static float num;
private string message;
/// <summary>
/// 连接池
/// </summary>
/// <returns></returns>
public int NewIndex()
{
if (conns == null)
{
return -1;
}
for (int i = 0; i < conns.Length; i++)
{
if (conns == null)
{
conns[i] = new Conn();
return i;
}
else if (conns[i].IsUse == false)
{
return i;
}
}
return -1;
}
public void SeverStart(string Host, int port)
{
conns = new Conn[maxConnect];
for (int i = 0; i < conns.Length; i++)
{
conns[i] = new Conn();
}
//Socket
socketfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAdress = IPAddress.Parse(Host); //本机Ip地址
IPEndPoint ipEp = new IPEndPoint(ipAdress, port); //端口号
//绑定Ip
socketfd.Bind(ipEp);
//开始监听
socketfd.Listen(maxConnect);
//接收客户端的消息
socketfd.BeginAccept(AcceptCb, null);
Debug.Log("开始监听");
}
//回调(循环的接受)
private void AcceptCb(IAsyncResult ar)
{
try
{
Socket socketA = socketfd.EndAccept(ar);
int index = NewIndex();
if (index < 0)
{
Debug.Log("连接已经满了");
}
else
{
Conn conn = conns[index];
conn.Init(socketA);
string adr = conn.GetAdress();
Debug.Log("客服端连接:" + adr + "索引:" + index + "个数:" + conn.byteCount);
conn.socket.BeginReceive(conn.readBuff, conn.byteCount, conn.Buffemain(), SocketFlags.None, ReceiveCb, conn);
socketfd.BeginAccept(AcceptCb, null);//再次调用
}
}
catch (Exception e)
{
Debug.Log("回调失败" + e);
}
}
Conn conn;
//回调
private void ReceiveCb(IAsyncResult ar)
{
conn = (Conn)ar.AsyncState;
try
{
int count = conn.socket.EndReceive(ar);
//客服端与服务端断开连接
if (count <= 0)
{
Debug.Log(conn.GetAdress() + "与服务器断开连接");
conn.Colse();
return;
}
message = conn.ReadMessage(count).ToString(); // Encoding.UTF8.GetString(conn.readBuff, 0, count);
if (message == "心跳")
{
SendCb(conn.socket, message);
}
else
{
str = message;
}
num = conn.GetconnNum;
conn.socket.BeginReceive(conn.readBuff, conn.byteCount, conn.Buffemain(), SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Debug.Log("收到" + conn.GetAdress() + "断开连接" + e.Message);
conn.Colse();
}
}
public void SendCb(Socket socket, string value)
{
if (string.IsNullOrEmpty(value))
{
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(value);
socket.Send(bytes);
}
public void SendCb( string value)
{
if (string.IsNullOrEmpty(value))
{
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(value);
conn.socket.Send(bytes);
}
}
/**
* 2019.9.20
* 开启服务器
* */
using Item_A;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
public class StartSever : MonoBehaviour
{
UdpClient UdpSend;
const string specialText = "123!!@@##$$%%-XinyuanInteractiveDisplaySystem";
Thread serverThread;
public static string str = "";
public Text text;
Server server;
void Start()
{
server = new Server();
BroadcastMessage();
//server.Start("192.168.1.169", 1533);
server.SeverStart(IPAddress.Any.ToString(), 1533);
}
private void Update()
{
if (text != null)
{
text.text = str;
}
}
public void BroadcastMessage()
{
serverThread = new Thread(() =>
{
UdpSend = new UdpClient();
while (true)
{
Thread.Sleep(1000);
byte[] buf = Encoding.Unicode.GetBytes(specialText);
UdpSend.Send(buf, buf.Length, new IPEndPoint(IPAddress.Broadcast, 1533));
print("广播中....");
}
});
serverThread.IsBackground = true;
serverThread.Start();
}
public void Complate()
{
//if (server != null)
//{
// server.SendCb("完成");
//}
}
void OnApplicationQuit()
{
serverThread.Abort();
server.SendCb("结束");
}
}
/**
*开启客户端
*/
using UnityEngine;
using System.Text;
using UnityEngine.UI;
using System.Net;
using System.Net.Sockets;
using System;
using System.Threading;
using System.IO;
public class ConnnectA : MonoBehaviour
{
public string serverstr;
public GameObject WaitePlane;
Socket socket;
const int buff_size = 1024;
byte[] readbuff = new byte[buff_size];
public static ConnnectA ConnnectAInstance;
public Text index;
//private
private Thread clientThread;
private UdpClient UdpListen;
private int port = 1533;
private bool clientIsRun = true;
const string specialText = "123!!@@##$$%%-XinyuanInteractiveDisplaySystem";
public static string connectStr = "";
//心跳属性
public bool isConnect = false;
public static float heartbeatTime = 0f;
public Thread heartbeatThread;
private void Start()
{
ConnnectAInstance = this;
heartbeatTime = 0f;
Connection();
}
//实时更新数据
private void Update()
{
if (!clientIsRun && isConnect)
{
if (heartbeatTime > 10f)
{
heartbeatTime = 0f;
Reconnect();
}
else
{
heartbeatTime += Time.deltaTime;
}
}
if (connectStr == "完成连接")
{
SetWaitePlaneState(false);
}
if (connectStr == "连接断开")
{
SetWaitePlaneState(true);
}
}
private void ReceiveCb(IAsyncResult ar)
{
int count = socket.EndReceive(ar);
try
{
string str = Encoding.UTF8.GetString(readbuff, 0, count);
if (str == "心跳")
{
heartbeatTime = 0f;
}
if (str == "结束")
{
connectStr = "连接断开";
}
if (serverstr.Length > 300)
{
serverstr = "";
}
serverstr += str + "\n";
//继续接受数据
socket.BeginReceive(readbuff, 0, buff_size, SocketFlags.None, ReceiveCb, null);
}
catch (Exception e)
{
connectStr += "断开连接";
print("客服端与服务端断开" + e.Message);
socket.Close();
}
}
public void Send(string value,int type)
{
if (socket == null)
{
print("未连接:" + value);
return;
}
try
{
socket.Send(SendMsg(value,type));
print("发送消息:" + value);
}
catch (Exception e)
{
print("发送失败" + e.Message);
}
}
//接受消息
public void Connection()
{
SetWaitePlaneState(true);
clientThread = new Thread(() =>
{
while (clientIsRun)
{
connectStr = "接受UDP";
UdpListen = new UdpClient(new IPEndPoint(IPAddress.Any, port));
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port);
byte[] bufRev = UdpListen.Receive(ref endpoint);
string msg = Encoding.Unicode.GetString(bufRev, 0, bufRev.Length);
if (msg.Contains(specialText))
{
connectStr = "接受UDP完成";
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(endpoint.Address.ToString(), port);
//心跳
isConnect = true;
heartbeatTime = 0f;
heartbeatThread = new Thread(() =>
{
while (isConnect)
{
Thread.Sleep(5000);
Send("心跳",2);
}
});
heartbeatThread.Start();
socket.BeginReceive(readbuff, 0, buff_size, SocketFlags.None, ReceiveCb, null);
connectStr = "完成连接";
Debug.Log("连接成功");
CloseClientBroadcast();
return;
}
}
});
//CloseClientBroadcast();
clientThread.Start();
}
//关闭广播
public void CloseClientBroadcast()
{
clientIsRun = false;
if (UdpListen != null) UdpListen.Close();
if (clientThread != null && clientThread.IsAlive) clientThread.Abort();
}
//标头
private byte[] HeadBytes;
private int length;
private byte[] headerBytes;
//消息内容
private byte[] bodyBytes;
private byte[] typeBytes;
private byte[] tempBytes;
private byte[] _bytes;
/// <summary>
/// 构造发送数据
/// </summary>
/// <param name="msg"></param>
/// <param name="type">0是代表数字类型 1是代表string类型 2是代表"心跳" 3代表不播放音效</param>
/// <returns></returns>
public byte[] SendMsg(string msg, int type)
{
HeadBytes = Encoding.UTF8.GetBytes(msg);
length = HeadBytes.Length;
//构造表头数据,固定4个字节的长度,表示内容的长度
headerBytes = BitConverter.GetBytes(length);
//构造内容
bodyBytes = Encoding.UTF8.GetBytes(msg);
typeBytes = Encoding.UTF8.GetBytes(type.ToString());
using (MemoryStream memoryStream = new MemoryStream()) //创建内存流
{
BinaryWriter binaryWriter = new BinaryWriter(memoryStream); //以二进制写入器往这个流里写内容
binaryWriter.Write(headerBytes);
binaryWriter.Write(typeBytes);
binaryWriter.Write(bodyBytes);
_bytes = memoryStream.ToArray(); //将流内容写入自定义字节数组
binaryWriter.Close(); //关闭写入器释放资源
}
return _bytes;
}
public static byte[] ToArray(byte[] firstBytes, int firstIndex, int firstLength, byte[] secondBytes, int secondIndex, int secondLength)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryWriter bw = new BinaryWriter(ms);
bw.Write(firstBytes, firstIndex, firstLength);
bw.Write(secondBytes, secondIndex, secondLength);
bw.Close();
bw.Dispose();
return ms.ToArray();
}
}
/// <summary>
/// 初始重连
/// </summary>
public void Reconnect()
{
clientIsRun = true;
isConnect = false;
heartbeatTime = 0f;
print("重连服务器");
// SetWaitePlaneState(false);
if (socket != null)
{
socket.Close();
}
if (UdpListen != null) UdpListen.Close();
if (clientThread != null && clientThread.IsAlive) clientThread.Abort();
if (heartbeatThread != null && heartbeatThread.IsAlive) heartbeatThread.Abort();
Connection();
}
//string GetAddressIP()
//{
// / 获取服务端
// string AddressIP = string.Empty;
// foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
// {
// if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
// {
// AddressIP = _IPAddress.ToString();
// }
// }
// return AddressIP;
//}
/// <summary>
/// 等待面板状态
/// </summary>
/// <param name="state"></param>
public void SetWaitePlaneState(bool state)
{
if(WaitePlane.activeSelf!= state)
WaitePlane.SetActive(state);
}
void OnApplicationQuit()
{
CloseClientBroadcast();
}
}
效果图