前言:
UDP客户端作为图片发送方,UDP服务端作为图片接收方。
实现功能:
在发送方鼠标左键点击game视图,将本地图片发送到接收方,接收方将收到的图片保存到本地,并将接收到的图片在UI上展示几秒钟。
发送方脚本挂载方式:
接收方脚本挂载方式:
客户端代码:
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;
//服务端的IP
string UDPClientIP;
//目标socket
Socket socket;
//服务端
EndPoint serverEnd;
//服务端端口
IPEndPoint ipEnd;
//接收的字符串
string recvStr;
//接收的数据,必须为字节
byte[] recvData;
//发送的数据,必须为字节
byte[] sendData;
//接收的数据长度
int recvLen = 0;
//连接线程
Thread connectThread;
bool isClientActive = false;
//连接服务器时发送的vector3类型
Vector3 startVec = Vector3.zero;
bool isStartHeart = false;
int port;
//判断是否让客户端重新发送数据包
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";//服务端的IP.自己更改
port = 9000;
UDPClientIP = UDPClientIP.Trim();
isClientActive = true;
InitSocket(); //在这里初始化
}
private void Update()
{
if (isStartHeart)
{
HeartSend();
}
//检测心跳与心跳反馈的间隔时间,
timerInterval += Time.deltaTime;
if (timerInterval > 6f)
{
print("连接异常");
timerInterval = 0f;
}
if (isReSend)
{
print(111);
if (ReSendIndexEvent!=null)
{
ReSendIndexEvent(reSendStrIndex);
reSendStrIndex = null;
isReSend = false;
}
}
}
//初始化
void InitSocket()
{
//定义连接的服务器ip和端口,可以是本机ip,局域网,互联网
ipEnd = new IPEndPoint(IPAddress.Parse(UDPClientIP), port);
//定义套接字类型,在主线程中定义
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//socket.Bind(ipEnd);
//定义服务端
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
serverEnd = (EndPoint)sender;
print("local:等待连接");
isStartHeart = true;
//开始心跳监听
//客户端发送心跳消息后,计时器开始计时,判断3秒内是否能收到服务端的反馈
HeartSend();
//开启一个线程连接,否则主线程卡死
connectThread = new Thread(new ThreadStart(SocketReceive));
connectThread.Start();
}
//发送字符串
public void SocketSend(string sendStr)
{
//清空发送缓存
sendData = new byte[1500];
//数据类型转换
sendData = Encoding.UTF8.GetBytes(sendStr);
//发送给指定服务端
socket.SendTo(sendData, sendData.Length, SocketFlags.None, ipEnd);
}
//发送消息频率
float timerRate = 5;
//接收服务端心跳反馈的时间间隔
float timerInterval = 0f;
byte[] heartSendData = new byte[1024];
/// <summary>
/// 心跳
/// </summary>
void HeartSend()
{
timerRate += Time.deltaTime;
if (timerRate > 5f)
{
try
{
SocketSend("alive");
}
catch
{
}
timerRate = 0f;
}
}
//客户端接收服务器消息
void SocketReceive()
{
//进入接收循环
while (isClientActive)
{
//对data清零
recvData = new byte[20000];
try
{
//获取服务端端数据
recvLen = socket.ReceiveFrom(recvData, ref serverEnd);
if (isClientActive == false)
{
break;
}
}
catch
{
}
//输出接收到的数据
if (recvLen > 0)
{
//接收到的信息
recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
}
print("server:" + recvStr);
//心跳回馈
if (recvStr == "keeping")
{
// 当服务端收到客户端发送的alive消息时
print("连接正常");
timerInterval = 0;
}
else if(recvStr!=null)
{
reSendStrIndex = recvStr;
isReSend = true;
}
}
}
//连接关闭
void SocketQuit()
{
//最后关闭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);
//将图片发送给投影机
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);
//发送完成后发一条信息给服务端
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);
}
}
}
//发送完成后发一条信息给服务端
NewUDPClient.instance.SocketSend("done");
print("重新发送完毕");
}
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; //将数字变成四位数的,三个字节
// 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); //包名,包长,包的顺序号,包的内容
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;
}
}
服务端代码
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; //目标socket
EndPoint clientEnd; //客户端
IPEndPoint ipEnd; //侦听端口
[HideInInspector]
public string recvStr; //接收的字符串
string sendStr; //发送的字符串
byte[] recvData; //接收的数据,必须为字节
byte[] sendData = new byte[1024]; //发送的数据,必须为字节
int recvLen; //接收的数据长度
Thread connectThread; //连接线程
[HideInInspector]
public bool isStartSend = false;
int port;
bool isSendImage;
public delegate void UDPServerDeledate(Texture2D byths);
public event UDPServerDeledate UDPserverEvent;
//接收到的图片字节数组的图片字节长度
int imageLength;
string imageStr;
/// <summary>
/// 初始化
/// </summary>
void InitSocket()
{
//定义侦听端口,侦听任何IP
ipEnd = new IPEndPoint(IPAddress.Any, port);
//定义套接字类型,在主线程中定义
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
//服务端需要绑定ip
socket.Bind(ipEnd);
//定义客户端
IPEndPoint sender = new IPEndPoint(IPAddress.Broadcast, 0);
clientEnd = (EndPoint)sender;
print("local:等待连接数据");
//开启一个线程连接
connectThread = new Thread(new ThreadStart(SocketReceive));
connectThread.Start();
}
/// <summary>
/// 服务器向客户端发送消息
/// </summary>
/// <param name="sendStr"></param>
public void SocketSend(string sendStr)
{
//清空发送缓存
sendData = new byte[20000];
//数据类型转换
sendData = Encoding.UTF8.GetBytes(sendStr);
//发送给指定客户端
socket.SendTo(sendData, sendData.Length, SocketFlags.None, clientEnd);
}
bool isServerActive = false;
/// <summary>
/// 服务器接收来自客户端的消息
/// </summary>
void SocketReceive()
{
//进入接收循环
while (isServerActive)
{
//对data清零
recvData = new byte[1500];
try
{
//获取服务端端数据
recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
if (isServerActive == false)
{
break;
}
}
catch
{
}
if (recvLen > 0)
{
recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
//输出接收到的数据
// Debug.Log("Clent:__" + recvStr + "++" + recvLen);
//客户端心跳回馈
if (recvStr == "alive")
{
HeartCheck();
}
else if (recvStr == "done")
{
//当接收到的信息为done时,判断接收到的图片包数量是否够,不够就发送未收到的包的标识号,让客户端再发送一下
CheckPackage();
}
else if (recvLen > 18) //图片包头为29个字节
{
// print("这是图片");
//合并发来的图片
ConmbineString(recvStr);
}
}
}
}
//未发送包的标识号
string reSendPackageindex;
/// <summary>
/// 当接收到客户端发送的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("接收失败,重新请求");
//判断哪些包没有收到
for (int i = 0; i < doneIndex.Count; i++)
{
reSendPackageindex = doneIndex[i] + "_" + reSendPackageindex;
}
SocketSend(reSendPackageindex);
print("请求发送未成功包");
}
}
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;
//将分包发来的消息合成一个包
void ConmbineString(string perStr)
{
//0.图片名字(21字节)--1.包的长度(1000为起始点,4字节)--2.包的下标(1000为起始点4个字节)--3.包的内容
//分割字符串 "_"
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;
//将将要收到的包的标识号存进集合里边,每接收到对应的数据就移除该标识号
for (int i = 0; i < newImageCount; i++)
{
doneIndex.Add(i);
}
}
if (newImageName == oldImageName)
{
// print(newImageCount);
if (!newImageDic.ContainsKey(newStrIndex))
{
//每接收到对应的数据就移除该标识号
try
{
doneIndex.Remove(newStrIndex);
}
catch
{
print("数据传输失败");
}
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("网络连接异常");
timerInterval = 0f;
}
}
if (isSendImage)
{
ParseBYTeArr(newConbineStr);
newConbineStr = null;
isSendImage = false;
}
}
/// <summary>
/// 发来的字节包括:图片的字节长度(前四个字节)和图片字节
/// 得到发来的字节中图片字节长度和图片字节数组
/// </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);
}
}
//连接关闭
void SocketQuit()
{
//最后关闭socket
if (socket != null)
{
try
{
socket.Close();
}
catch
{
}
}
Debug.LogWarning("local:断开连接");
}
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>
/// 发来的字节包括:图片的字节长度(前四个字节)和图片字节
/// 得到发来的字节中图片字节长度和图片字节数组
/// </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;
}
}