I’m trying to send clients big texture files (200 Kb, definitely not for one packet) on when they connect to server. How could I possibly achieve this? I’ve tried using NetworkWriter, but couldn’t get it working with limited amount of documentation
Here you go!
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.IO;
using Ionic.Zip;
using UnityEngine.UI;
public class FileTranfer : NetworkBehaviour
{
//Server only stuff
public int packageSize = 1024;
//Client only stuff
private int recievedSize = 0;
private int fileSize = 0;
private string filePath = "";
private byte[] clientData;
private string chunkCallback;
private string finishedCallback;
//Set up messages
public class MyMsgType
{
public static short FileInformation = MsgType.Highest + 1;
public static short FileChunk = MsgType.Highest + 2;
};
public class FileInformation : MessageBase
{
public string fileName;
public int fileSize;
}
public class FileChunk : MessageBase
{
public byte[] fileData;
}
//Functions
public void ClientRequestMap(string name, string myChunkCallback, string myFinishedCallback)
{
finishedCallback = myFinishedCallback;
chunkCallback = myChunkCallback;
NetworkClient netClient = NetworkLobbyManager.singleton.client;
int clientId = netClient.connection.connectionId;
netClient.RegisterHandler (MyMsgType.FileInformation, OnFileInformation);
netClient.RegisterHandler (MyMsgType.FileChunk, OnFileChunk);
CmdRequestMap (name, clientId);
}
[Server]
private void SendMap(string fileName, int clientId)
{
string shortName = fileName;
fileName = Application.dataPath + "/Maps/" + fileName;
string zipName = fileName + ".zip";
using (ZipFile zip = new ZipFile())
{
zip.AddDirectory(fileName);
zip.Save(zipName);
}
byte[] data = File.ReadAllBytes (zipName);
File.Delete (zipName);
Debug.Log ("Server: Sending File: " + fileName + " with size: " + data.Length);
FileInformation msg = new FileInformation();
msg.fileName = shortName;
msg.fileSize = data.Length;
NetworkServer.SendToClient(clientId, MyMsgType.FileInformation, msg);
StartCoroutine (SendFilesRoutine(data, clientId));
}
[Server]
IEnumerator SendFilesRoutine(byte[] data, int clientId)
{
int bufferSize = packageSize;
int dataLeft = data.Length;
while (0 < dataLeft)
{
//determine the remaining amount of bytes, still need to be sent.
int remaining = dataLeft - bufferSize;
if (remaining < 0)
bufferSize = dataLeft;
byte[] buffer = new byte[bufferSize];
System.Array.Copy(data, data.Length - dataLeft, buffer, 0, bufferSize);
//send the chunk
FileChunk msg = new FileChunk();
msg.fileData = buffer;
NetworkServer.SendToClient(clientId, MyMsgType.FileChunk, msg);
dataLeft -= bufferSize;
yield return null;
}
}
[Command]
public void CmdRequestMap(string fileName, int clientId)
{
SendMap(fileName, clientId);
}
public void OnFileInformation(NetworkMessage netMsg)
{
FileInformation msg = netMsg.ReadMessage<FileInformation>();
fileSize = msg.fileSize;
filePath = Application.dataPath + "/Maps/" + msg.fileName;
clientData = new byte[msg.fileSize];
}
public void OnFileChunk(NetworkMessage netMsg)
{
FileChunk msg = netMsg.ReadMessage<FileChunk>();
byte[] data = msg.fileData;
System.Array.Copy (data, 0, clientData, recievedSize, data.Length);
recievedSize += data.Length;
gameObject.SendMessage (chunkCallback, recievedSize / fileSize);
if (fileSize == recievedSize)
{
string zipFile = filePath + ".zip";
File.WriteAllBytes (zipFile, clientData);
using (ZipFile zip = ZipFile.Read(zipFile))
{
zip.ExtractAll(filePath);
}
File.Delete (zipFile);
gameObject.SendMessage (finishedCallback);
}
}
}
In this case, its the client that requests a file from the server, but can be changed to just client sending a file with ease.
Client requests a file using the “ClientRequestMap” function, and the strings are callback functions for updating progress bar or something and doing whatever when the file transfer is complete. This script also compresses whatever file thats being sent, but that can be removed as well, it requires an external dll.
Use:
//Request file
fileTranfer.ClientRequestMap(StaticSettings.project, "OnMapChunk", "OnMapFinished");
//For every chunk recieved, update progressbar
public void OnMapChunk(float progress)
{
progressImg.fillAmount = progress;
}
//When file is done, do stuff
public void OnMapFinished()
{
toggle.interactable = true;
string mapPath = Application.dataPath + "/Maps/" + StaticSettings.project;
ReadMap(mapPath);
Debug.Log("Client:File complete!");
}
Seems way simplier than I have thought! I’ll try it this evening after I’ll get home and let you know how it went! Thanks!
Edit: Okay, actually not that easy with my situation. Thing is, I want to send couple of byte arrays and I don’t know how to put it all together in clean loop without messing it all up. Is there any way to have a coroutine that waits for a certain message before going on to another cycle of the loop?
If you use reliable channel the packages should always arrive in the same order as you sent them, so you should not have to wait for confirmation, thats handled by the lower layers already.
Yeah, you’re right. Managed to make it work in one Coroutine using additional loop, thanks!