前言:
UDP客戶(hù)端作為圖片發(fā)送方奖唯,UDP服務(wù)端作為圖片接收方惨缆。
實(shí)現(xiàn)功能:
在發(fā)送方鼠標(biāo)左鍵點(diǎn)擊game視圖,將本地圖片發(fā)送到接收方,接收方將收到的圖片保存到本地坯墨,并將接收到的圖片在UI上展示幾秒鐘寂汇。
發(fā)送方腳本掛載方式:
接收方腳本掛載方式:
客戶(hù)端代碼:
NewUDPClient:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class NewUDPClient : MonoBehaviour {
public static NewUDPClient instance;
//服務(wù)端的IP
string UDPClientIP;
//目標(biāo)socket
Socket socket;
//服務(wù)端
EndPoint serverEnd;
//服務(wù)端端口
IPEndPoint ipEnd;
//接收的字符串
string recvStr;
//接收的數(shù)據(jù),必須為字節(jié)
byte[] recvData;
//發(fā)送的數(shù)據(jù)捣染,必須為字節(jié)
byte[] sendData;
//接收的數(shù)據(jù)長(zhǎng)度
int recvLen = 0;
//連接線(xiàn)程
Thread connectThread;
bool isClientActive = false;
//連接服務(wù)器時(shí)發(fā)送的vector3類(lèi)型
Vector3 startVec = Vector3.zero;
bool isStartHeart = false;
int port;
//判斷是否讓客戶(hù)端重新發(fā)送數(shù)據(jù)包
bool isReSend = false;
string reSendStrIndex;
public delegate void ReSendIndexDeledate(string str);
public event ReSendIndexDeledate ReSendIndexEvent;
private void Awake()
{
instance = this;
}
void Start()
{
UDPClientIP = "127.0.0.1";//服務(wù)端的IP.自己更改
port = 9000;
UDPClientIP = UDPClientIP.Trim();
isClientActive = true;
InitSocket(); //在這里初始化
}
private void Update()
{
if (isStartHeart)
{
HeartSend();
}
//檢測(cè)心跳與心跳反饋的間隔時(shí)間健无,
timerInterval += Time.deltaTime;
if (timerInterval > 6f)
{
print("連接異常");
timerInterval = 0f;
}
if (isReSend)
{
print(111);
if (ReSendIndexEvent!=null)
{
ReSendIndexEvent(reSendStrIndex);
reSendStrIndex = null;
isReSend = false;
}
}
}
//初始化
void InitSocket()
{
//定義連接的服務(wù)器ip和端口,可以是本機(jī)ip液斜,局域網(wǎng)累贤,互聯(lián)網(wǎng)
ipEnd = new IPEndPoint(IPAddress.Parse(UDPClientIP), port);
//定義套接字類(lèi)型,在主線(xiàn)程中定義
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//socket.Bind(ipEnd);
//定義服務(wù)端
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
serverEnd = (EndPoint)sender;
print("local:等待連接");
isStartHeart = true;
//開(kāi)始心跳監(jiān)聽(tīng)
//客戶(hù)端發(fā)送心跳消息后,計(jì)時(shí)器開(kāi)始計(jì)時(shí)少漆,判斷3秒內(nèi)是否能收到服務(wù)端的反饋
HeartSend();
//開(kāi)啟一個(gè)線(xiàn)程連接臼膏,否則主線(xiàn)程卡死
connectThread = new Thread(new ThreadStart(SocketReceive));
connectThread.Start();
}
//發(fā)送字符串
public void SocketSend(string sendStr)
{
//清空發(fā)送緩存
sendData = new byte[1500];
//數(shù)據(jù)類(lèi)型轉(zhuǎn)換
sendData = Encoding.UTF8.GetBytes(sendStr);
//發(fā)送給指定服務(wù)端
socket.SendTo(sendData, sendData.Length, SocketFlags.None, ipEnd);
}
//發(fā)送消息頻率
float timerRate = 5;
//接收服務(wù)端心跳反饋的時(shí)間間隔
float timerInterval = 0f;
byte[] heartSendData = new byte[1024];
/// <summary>
/// 心跳
/// </summary>
void HeartSend()
{
timerRate += Time.deltaTime;
if (timerRate > 5f)
{
try
{
SocketSend("alive");
}
catch
{
}
timerRate = 0f;
}
}
//客戶(hù)端接收服務(wù)器消息
void SocketReceive()
{
//進(jìn)入接收循環(huán)
while (isClientActive)
{
//對(duì)data清零
recvData = new byte[20000];
try
{
//獲取服務(wù)端端數(shù)據(jù)
recvLen = socket.ReceiveFrom(recvData, ref serverEnd);
if (isClientActive == false)
{
break;
}
}
catch
{
}
//輸出接收到的數(shù)據(jù)
if (recvLen > 0)
{
//接收到的信息
recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
}
print("server:" + recvStr);
//心跳回饋
if (recvStr == "keeping")
{
// 當(dāng)服務(wù)端收到客戶(hù)端發(fā)送的alive消息時(shí)
print("連接正常");
timerInterval = 0;
}
else if(recvStr!=null)
{
reSendStrIndex = recvStr;
isReSend = true;
}
}
}
//連接關(guān)閉
void SocketQuit()
{
//最后關(guān)閉socket
if (socket != null)
socket.Close();
}
void OnApplicationQuit()
{
isStartHeart = false;
isClientActive = false;
SocketQuit();
Thread.Sleep(25);
}
}
SendImageScript:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Threading;
public class SendImageScript : MonoBehaviour {
byte[] imagebytes;
string newImageString;
string path;
private void Start()
{
NewUDPClient.instance.ReSendIndexEvent += GetReSendIndexFromUDPClient;
path = Application.streamingAssetsPath + "/" + "222.jpg";
FileStream files = new FileStream(path, FileMode.Open);
imagebytes = new byte[files.Length]; files.Read(imagebytes, 0, imagebytes.Length); files.Close();
files.Close();
picStr = Convert.ToBase64String(imagebytes);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
StartCoroutine(SendPicture());
}
}
string picStr;
IEnumerator SendPicture() {
UDPSplit(picStr);
//將圖片發(fā)送給投影機(jī)
yield return new WaitForSeconds(0.1f);
for (int i = 0; i < num - 1000; i++)
{
if (UDPStringDic.TryGetValue(i, out newImageString))
{
NewUDPClient.instance.SocketSend(newImageString);
}
}
yield return new WaitForSeconds(0.1f);
//發(fā)送完成后發(fā)一條信息給服務(wù)端
NewUDPClient.instance.SocketSend("done");
}
string[] reSendNum;
int newindex;
void GetReSendIndexFromUDPClient(string str)
{
reSendNum = str.Split('_');
for (int i = 0; i < reSendNum.Length; i++)
{
if (int.TryParse(reSendNum[i], out newindex))
{
if (UDPStringDic.TryGetValue(newindex, out newImageString))
{
NewUDPClient.instance.SocketSend(newImageString);
}
}
}
//發(fā)送完成后發(fā)一條信息給服務(wù)端
NewUDPClient.instance.SocketSend("done");
print("重新發(fā)送完畢");
}
Dictionary<int, string> UDPStringDic = new Dictionary<int, string>();
int index = 0;
int maxIndex = 1000;
string newstr;
int num;
void UDPSplit(string str)
{
index = 0;
maxIndex = 1000;
int stringTag = 1000;
UDPStringDic.Clear();
num = (str.Length / 1000) + 1 + 1000; //將數(shù)字變成四位數(shù)的,三個(gè)字節(jié)
// print(num-1000);
for (int i = 0; i < num - 1000; i++)
{
if (maxIndex > str.Length - index)
{
maxIndex = str.Length - index;
}
newstr = "1551683020" + "_" + num + "_" + stringTag + "_" + str.Substring(index, maxIndex); //包名示损,包長(zhǎng)渗磅,包的順序號(hào),包的內(nèi)容
UDPStringDic.Add(stringTag - 1000, newstr);
stringTag++;
index += 1000;
}
}
public static long GetTimeStamp(bool bflag = true)
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
long ret;
if (bflag)
ret = Convert.ToInt64(ts.TotalSeconds);
else
ret = Convert.ToInt64(ts.TotalMilliseconds);
return ret;
}
private void OnDisable()
{
NewUDPClient.instance.ReSendIndexEvent -= GetReSendIndexFromUDPClient;
}
}
服務(wù)端代碼
NewUDPServer:
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System;
using System.IO;
using UnityEngine.UI;
public class NewUDPServer : MonoBehaviour {
public static NewUDPServer instance;
Socket socket; //目標(biāo)socket
EndPoint clientEnd; //客戶(hù)端
IPEndPoint ipEnd; //偵聽(tīng)端口
[HideInInspector]
public string recvStr; //接收的字符串
string sendStr; //發(fā)送的字符串
byte[] recvData; //接收的數(shù)據(jù)检访,必須為字節(jié)
byte[] sendData = new byte[1024]; //發(fā)送的數(shù)據(jù)始鱼,必須為字節(jié)
int recvLen; //接收的數(shù)據(jù)長(zhǎng)度
Thread connectThread; //連接線(xiàn)程
[HideInInspector]
public bool isStartSend = false;
int port;
bool isSendImage;
public delegate void UDPServerDeledate(Texture2D byths);
public event UDPServerDeledate UDPserverEvent;
//接收到的圖片字節(jié)數(shù)組的圖片字節(jié)長(zhǎng)度
int imageLength;
string imageStr;
/// <summary>
/// 初始化
/// </summary>
void InitSocket()
{
//定義偵聽(tīng)端口,偵聽(tīng)任何IP
ipEnd = new IPEndPoint(IPAddress.Any, port);
//定義套接字類(lèi)型,在主線(xiàn)程中定義
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
//服務(wù)端需要綁定ip
socket.Bind(ipEnd);
//定義客戶(hù)端
IPEndPoint sender = new IPEndPoint(IPAddress.Broadcast, 0);
clientEnd = (EndPoint)sender;
print("local:等待連接數(shù)據(jù)");
//開(kāi)啟一個(gè)線(xiàn)程連接
connectThread = new Thread(new ThreadStart(SocketReceive));
connectThread.Start();
}
/// <summary>
/// 服務(wù)器向客戶(hù)端發(fā)送消息
/// </summary>
/// <param name="sendStr"></param>
public void SocketSend(string sendStr)
{
//清空發(fā)送緩存
sendData = new byte[20000];
//數(shù)據(jù)類(lèi)型轉(zhuǎn)換
sendData = Encoding.UTF8.GetBytes(sendStr);
//發(fā)送給指定客戶(hù)端
socket.SendTo(sendData, sendData.Length, SocketFlags.None, clientEnd);
}
bool isServerActive = false;
/// <summary>
/// 服務(wù)器接收來(lái)自客戶(hù)端的消息
/// </summary>
void SocketReceive()
{
//進(jìn)入接收循環(huán)
while (isServerActive)
{
//對(duì)data清零
recvData = new byte[1500];
try
{
//獲取服務(wù)端端數(shù)據(jù)
recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
if (isServerActive == false)
{
break;
}
}
catch
{
}
if (recvLen > 0)
{
recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
//輸出接收到的數(shù)據(jù)
// Debug.Log("Clent:__" + recvStr + "++" + recvLen);
//客戶(hù)端心跳回饋
if (recvStr == "alive")
{
HeartCheck();
}
else if (recvStr == "done")
{
//當(dāng)接收到的信息為done時(shí),判斷接收到的圖片包數(shù)量是否夠脆贵,不夠就發(fā)送未收到的包的標(biāo)識(shí)號(hào)医清,讓客戶(hù)端再發(fā)送一下
CheckPackage();
}
else if (recvLen > 18) //圖片包頭為29個(gè)字節(jié)
{
// print("這是圖片");
//合并發(fā)來(lái)的圖片
ConmbineString(recvStr);
}
}
}
}
//未發(fā)送包的標(biāo)識(shí)號(hào)
string reSendPackageindex;
/// <summary>
/// 當(dāng)接收到客戶(hù)端發(fā)送的done消息后,判斷接收到的圖片包是否完整
/// </summary>
void CheckPackage()
{
reSendPackageindex = null;
if (doneIndex.Count <= 0)
{
print("接收成功");
for (int i = 0; i < newImageDic.Count; i++)
{
if (newImageDic.TryGetValue(i, out dicStr))
{
newConbineStr = newConbineStr + dicStr;
}
}
isSendImage = true;
newImageCount = 0;
newStrIndex = 0;
isFirst = true;
newImageDic.Clear();
doneIndex.Clear();
}
else
{
print("接收失敗卖氨,重新請(qǐng)求");
//判斷哪些包沒(méi)有收到
for (int i = 0; i < doneIndex.Count; i++)
{
reSendPackageindex = doneIndex[i] + "_" + reSendPackageindex;
}
SocketSend(reSendPackageindex);
print("請(qǐng)求發(fā)送未成功包");
}
}
string newConbineStr;
string newImageName;
int newImageCount = 0;
int newStrIndex = 0;
string newImageMessage;
//判斷是否是第一次接受消息
bool isFirst = true;
string oldImageName;
Dictionary<int, string> newImageDic = new Dictionary<int, string>();
List<int> doneIndex = new List<int>();
string dicStr;
//將分包發(fā)來(lái)的消息合成一個(gè)包
void ConmbineString(string perStr)
{
//0.圖片名字(21字節(jié))--1.包的長(zhǎng)度(1000為起始點(diǎn)会烙,4字節(jié))--2.包的下標(biāo)(1000為起始點(diǎn)4個(gè)字節(jié))--3.包的內(nèi)容
//分割字符串 "_"
string[] strs = perStr.Split('_');
//名字
newImageName = strs[0];
newImageCount = int.Parse(strs[1]) - 1000;
newStrIndex = int.Parse(strs[2]) - 1000;
newImageMessage = strs[3];
if (isFirst)
{
oldImageName = newImageName;
isFirst = false;
newConbineStr = null;
//將將要收到的包的標(biāo)識(shí)號(hào)存進(jìn)集合里邊,每接收到對(duì)應(yīng)的數(shù)據(jù)就移除該標(biāo)識(shí)號(hào)
for (int i = 0; i < newImageCount; i++)
{
doneIndex.Add(i);
}
}
if (newImageName == oldImageName)
{
// print(newImageCount);
if (!newImageDic.ContainsKey(newStrIndex))
{
//每接收到對(duì)應(yīng)的數(shù)據(jù)就移除該標(biāo)識(shí)號(hào)
try
{
doneIndex.Remove(newStrIndex);
}
catch
{
print("數(shù)據(jù)傳輸失敗");
}
newImageDic.Add(newStrIndex, newImageMessage);
}
}
}
float timerInterval = 0;
bool isStartCheck = false;
void HeartCheck()
{
isStartCheck = true;
timerInterval = 0f;
SocketSend("keeping");
print("連接正常");
}
void Update()
{
timerInterval += Time.deltaTime;
if (isStartCheck)
{
if (timerInterval > 6f)
{
print("網(wǎng)絡(luò)連接異常");
timerInterval = 0f;
}
}
if (isSendImage)
{
ParseBYTeArr(newConbineStr);
newConbineStr = null;
isSendImage = false;
}
}
/// <summary>
/// 發(fā)來(lái)的字節(jié)包括:圖片的字節(jié)長(zhǎng)度(前四個(gè)字節(jié))和圖片字節(jié)
/// 得到發(fā)來(lái)的字節(jié)中圖片字節(jié)長(zhǎng)度和圖片字節(jié)數(shù)組
/// </summary>
void ParseBYTeArr(string receStr)
{
byte[] bytes = Convert.FromBase64String(receStr);
string timestamp = GetTimeStamp().ToString();
string filename = Application.streamingAssetsPath + "/Imags/"+timestamp + ".jpg";
File.WriteAllBytes(filename, bytes);
Texture2D tex2D = new Texture2D(100, 100);
tex2D.LoadImage(bytes);
if (UDPserverEvent != null)
{
UDPserverEvent(tex2D);
}
}
//連接關(guān)閉
void SocketQuit()
{
//最后關(guān)閉socket
if (socket != null)
{
try
{
socket.Close();
}
catch
{
}
}
Debug.LogWarning("local:斷開(kāi)連接");
}
private void Awake()
{
instance = this;
}
void Start()
{
port = 9000;
isServerActive = true;
InitSocket(); //在這里初始化server
}
void OnDisable()
{
isServerActive = false;
SocketQuit();
Thread.Sleep(100);
}
public static long GetTimeStamp(bool bflag = true)
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
long ret;
if (bflag)
ret = Convert.ToInt64(ts.TotalSeconds);
else
ret = Convert.ToInt64(ts.TotalMilliseconds);
return ret;
}
}
LoadImageFromClient:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoadImageFromClient : MonoBehaviour {
public RawImage newImage;
public Transform showPanel;
void Start()
{
NewUDPServer.instance.UDPserverEvent += ReceiveByteFromUDPServer;
}
/// <summary>
/// 發(fā)來(lái)的字節(jié)包括:圖片的字節(jié)長(zhǎng)度(前四個(gè)字節(jié))和圖片字節(jié)
/// 得到發(fā)來(lái)的字節(jié)中圖片字節(jié)長(zhǎng)度和圖片字節(jié)數(shù)組
/// </summary>
void ReceiveByteFromUDPServer(Texture2D newTexture)
{
newImage.gameObject.SetActive(true);
newImage.texture = newTexture;
Invoke("SetDefultImage", 5f);
}
void SetDefultImage() {
newImage.texture = null;
}
void OnDisable()
{
NewUDPServer.instance.UDPserverEvent -= ReceiveByteFromUDPServer;
}
}