Ранее я показывал ролик того, что хочу сделать. Как оказалось уже существует AirShare trademark, поэтому теперь обзывается эта пакость AirFileExchange.
Более детально о том, о сем читаем далее.
Рассуждения
На самом деле Air тут для красоты, все будет (должно) работать в локальной сети на ура. К примеру, у меня дома есть один роутер, к нему подключен большой ПК через Ethernet + 2 ноутбука через WiFi. Все друг друга видят и передают/принимают файлы.
Основная цель приложения - быстрая, легкая и интуитивная передачи и прием файлов в локальной сети. Что может быть проще, чем просто набор изображений профилей пользователей, имен пользователей и компьютеров? Пусть будет так, считаю что достаточная информация чтобы понять кому шлем файлы.
Как будем искать доступных пользователей? Как будем передавать файлы? А что остается? Будем использовать UDP - для процесса обнаружения и TCP - для обмена файлами, и для облегчения жизни, все файлы будем кидать на рабочий стол в папку с названием приложения. А сообщения будут простыми XML пакетами.
Больше ничего и не требуется, вот такая вот маленькая программулина.
+ Новое видео работы данной утилиты (все как обычно, по делитански):
Open Source
Решил сделать свой первый Open Source проект, разместился я на github'е. Коды смотреть тут, а дальше будем продолжать рассуждать о логике приложения.
Обнаружение
Логику построил следующим образом:
- Broadcast рассылка по UDP с сообщением о присутствии со статусом 'ask', что значит, просто запрос - если кто-то есть, ответьте
- Прослушивание порта (3000) по протоколу UDP в ожидании прихода сообщения о присутствии
- При получении сообщения о присутствии, чтение статуса и выполнение соответствующих действий
private void DiscoveringUsers()
{
Random random = new Random();
while (this.discoveringThread.IsAlive)
{
try
{
FilterTimeoutUsers();
try
{
Udp.SendBroadcast(Helper.XmlSerialize(new RequestPresence()
{
Status = "ask",
UserAddress = new UserAddress()
{
Address = Helper.LocalIPAddress(),
Port = Udp.DefaultPort
},
UserInfo = null
}));
Log.WriteLn("Send: ask - broadcast");
}
catch
{
}
Thread.Sleep(random.Next(8000, 14000));
}
catch
{
}
}
} Статусы сообщения о присутствииДля реализации общения и обнаружения, потребуется 3 статуса:
- 'presence' - сообщение о присутствии содержит полную информацию о пользователе (имя пользователя, имя компьютера, изображение профайла)
- 'ask' - запрос на ответ о присутствии со статусом 'presence'
- 'left' - сообщение о том, что указанный пользователь в сообщении покинул сеть (выключил сервер, закрыл приложение)
При выходе, выключении сервера, требуется разослать всем 'left', я отказался от broadcast, а просто в цикле пробегаемся по уже добавленным пользователям и каждому, лично, рассылаем 'left'.
private void IdentificationMe()
{
using (UdpClient udpClient = new UdpClient())
{
udpClient.EnableBroadcast = true;
IPEndPoint udpBroadcastPoint = new IPEndPoint(IPAddress.Any, Udp.DefaultPort);
udpClient.Client.Bind(udpBroadcastPoint);
while (this.identificationThread.IsAlive)
{
try
{
TimeSpan timeToWait = TimeSpan.FromMilliseconds(50);
IAsyncResult result = udpClient.BeginReceive(null, null);
result.AsyncWaitHandle.WaitOne(timeToWait);
try
{
IPEndPoint remotePoint = null;
byte[] buffer = udpClient.EndReceive(result, ref remotePoint);
try
{
RequestPresence requestPresence = Helper.XmlDeserialize(Encoding.UTF8.GetString(buffer));
Log.WriteLn("Recv: {0} - {1}", requestPresence.Status, remotePoint.Address.ToString());
if ("presence".Equals(requestPresence.Status))
{
IPEndPointHolder ipEndPointHolder = listOfAvailablePCs.Find(
item => item.IpEndPoint.Address.Equals(IPAddress.Parse(requestPresence.UserAddress.Address)));
if (ipEndPointHolder == null)
{
ipEndPointHolder = new IPEndPointHolder();
ipEndPointHolder.IpEndPoint = new IPEndPoint(IPAddress.Parse(requestPresence.UserAddress.Address),
requestPresence.UserAddress.Port);
ipEndPointHolder.IsAvailable = true;
listOfAvailablePCs.Add(ipEndPointHolder);
if (UserPresenceReceivedRequest != null)
{
UserPresenceReceivedRequest(requestPresence, ipEndPointHolder);
}
}
else
{
ipEndPointHolder.IsAvailable = true;
}
}
if ("ask".Equals(requestPresence.Status))
{
UserInfo userInfo = null;
bool isVisible = true;
if (UserPresenceReceivedAsk != null)
{
UserPresenceReceivedAsk(out isVisible, ref userInfo);
}
if (isVisible)
{
Udp.Send(Helper.XmlSerialize(new RequestPresence()
{
Status = "presence",
UserAddress = new UserAddress()
{
Address = Helper.LocalIPAddress(),
Port = Udp.DefaultPort
},
UserInfo = userInfo
}), new IPEndPoint(IPAddress.Parse(requestPresence.UserAddress.Address),
requestPresence.UserAddress.Port));
Log.WriteLn("Send: presence - {0}", requestPresence.UserAddress.Address.ToString());
IPEndPointHolder ipEndPointHolder = listOfAvailablePCs.Find(
item => item.IpEndPoint.Address.Equals(IPAddress.Parse(requestPresence.UserAddress.Address)));
if (ipEndPointHolder == null)
{
Udp.Send(Helper.XmlSerialize(new RequestPresence()
{
Status = "ask",
UserAddress = new UserAddress()
{
Address = Helper.LocalIPAddress(),
Port = Udp.DefaultPort
},
UserInfo = null
}), new IPEndPoint(IPAddress.Parse(requestPresence.UserAddress.Address),
requestPresence.UserAddress.Port));
Log.WriteLn("Send: ask - {0}", requestPresence.UserAddress.Address.ToString());
}
else
{
ipEndPointHolder.IsAvailable = true;
}
}
}
if ("left".Equals(requestPresence.Status))
{
IPEndPointHolder ipEndPointHolder = listOfAvailablePCs.Find(
item => item.IpEndPoint.Address.Equals(remotePoint.Address));
if (ipEndPointHolder != null)
{
ipEndPointHolder.IsAvailable = false;
if (UserPresenceGotTimeout != null)
{
UserPresenceGotTimeout(ipEndPointHolder);
}
listOfAvailablePCs.Remove(ipEndPointHolder);
}
}
}
catch
{
}
}
catch
{
}
}
catch (ThreadAbortException)
{
UserInfo userInfo = null;
bool isVisible = false;
if (UserPresenceReceivedAsk != null)
{
UserPresenceReceivedAsk(out isVisible, ref userInfo);
}
foreach (IPEndPointHolder ipEndPointHolder in listOfAvailablePCs)
{
Udp.Send(Helper.XmlSerialize(new RequestPresence()
{
Status = "left",
UserAddress = new UserAddress()
{
Address = Helper.LocalIPAddress(),
Port = Udp.DefaultPort
},
UserInfo = null
}), ipEndPointHolder.IpEndPoint);
Log.WriteLn("Send: left - {0}", ipEndPointHolder.IpEndPoint.Address.ToString());
}
}
}
}
} Передача файловКак было раньше написано, будем использовать TCP. При запуске сервера, начинаем слушать порт (3001) по протоколу TCP в ожидании подключения клиента. В теории, никто из пользователей не знает IP, к которому подключиться и начинать слать файлы. Для этого мы уже описали процесс обнаружения, который и занимается предоставлением всей необходимой информации о том, кому и от кого, и что слать.
Если посмотреть данный слой, передачу файлов, последовательно, то увидим следующее:
- Отправитель файлов подключается к серверу (получателю)
- Отправитель формирует сообщение включающее: список файлов, их размеры и др.
- Получатель ожидает сообщение с информацией о файлах, после запрашивает у пользователя разрешение на прием файлов
- После получения разрешения, сервер отправляет ответ со статусами разрешения передачи 'allow' или отмены - 'denied'
- После получения разрешения, отправитель начинает последовательно открыть отправляемые файлы и слать прочитанные байты в одном потоке, без каких-либо разделителей
- Сервер получая данные из потока делит на файлы, исходя из раннее полученного списка передаваемых файлов и их размеров в байтах
private void ListeningFiles()
{
tcpListener = new TcpListener(IPAddress.Any, AirClient.DefaultPort);
tcpListener.Start();
while (this.listeningFilesThread.IsAlive)
{
try
{
TcpClient client = tcpListener.AcceptTcpClient();
new Thread(new ParameterizedThreadStart(AcceptTcpClient)).Start(client);
}
catch
{
}
}
}
private void AcceptTcpClient(object param)
{
TcpClient client = (TcpClient)param;
try
{
byte[] bytes = new byte[256];
StringBuilder stringBuilder = new StringBuilder();
NetworkStream networkStream = client.GetStream();
int i;
while ((i = networkStream.Read(bytes, 0, bytes.Length)) != 0)
{
stringBuilder.Append(Encoding.UTF8.GetString(bytes, 0, i));
if (i < bytes.Length)
break;
}
// Sending a list of files?
try
{
SendFiles sendFiles = Helper.XmlDeserialize(stringBuilder.ToString());
if (UserWantToSendFiles != null)
{
AirServer.IPEndPointHolder ipEndPointHolder = new IPEndPointHolder();
ipEndPointHolder.IpEndPoint = new IPEndPoint(IPAddress.Parse(sendFiles.UserAddress.Address), sendFiles.UserAddress.Port);
ipEndPointHolder.IsAvailable = true;
UserWantToSendFiles(sendFiles, ipEndPointHolder, client);
}
else
{
client.Close();
}
}
catch
{
}
}
catch
{
client.Close();
}
}
public void ReceiveFilesFrom(IPEndPointHolder ipEndPoint, TcpClient client, SendFiles sendFiles, bool receive, object state,
UserReceiveFilesProgress progress)
{
try
{
byte[] bytes = new byte[1024];
NetworkStream networkStream = client.GetStream();
{
byte[] buffer = Encoding.UTF8.GetBytes(Helper.XmlSerialize(new SendFilesData()
{
UserAddress = new UserAddress()
{
Address = Helper.LocalIPAddress(),
Port = AirClient.DefaultPort
},
Status = receive ? "allowed" : "denied"
}));
networkStream.Write(buffer, 0, buffer.Length);
}
if (receive)
{
int i = 0;
long n = 0;
bool cancel = false;
long totalSize = 0, receivedTotal = 0, receivedCurrent = 0;
foreach (SendFile file in sendFiles.Files)
{
totalSize += file.Size;
}
string folder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\Air File Exchange\\";
Directory.CreateDirectory(folder);
foreach (SendFile file in sendFiles.Files)
{
using (FileStream fileStream = new FileStream(folder + file.Name, FileMode.OpenOrCreate, FileAccess.Write))
{
fileStream.SetLength(0);
receivedCurrent = 0;
if (i - n > 0)
{
fileStream.Write(bytes, (int)n, i - (int)n);
receivedCurrent += i - (int)n;
receivedTotal += i - (int)n;
if (progress != null)
{
progress(file, ipEndPoint, receivedCurrent, file.Size, receivedTotal, totalSize, state, out cancel);
}
}
while (!cancel && (i = networkStream.Read(bytes, 0, bytes.Length)) != 0)
{
n = file.Size - fileStream.Length;
if (i <= n)
{
n = i;
}
fileStream.Write(bytes, 0, (int)n);
receivedCurrent += n;
receivedTotal += n;
if (progress != null)
{
progress(file, ipEndPoint, receivedCurrent, file.Size, receivedTotal, totalSize, state, out cancel);
}
if (file.Size == fileStream.Length)
{
break;
}
}
}
if (cancel)
{
throw new OperationCanceledException();
}
}
if (totalSize > receivedTotal)
{
throw new OperationCanceledException();
}
}
}
finally
{
client.Close();
}
}
public void ReceiveFilesFromAsync(IPEndPointHolder ipEndPoint, TcpClient client, SendFiles sendFiles, bool receive, object state,
UserReceiveFilesProgress progress, UserReceiveFilesComplete complete)
{
new Thread(new ThreadStart(() =>
{
try
{
ReceiveFilesFrom(ipEndPoint, client, sendFiles, receive, state, progress);
if (complete != null)
{
complete(sendFiles, ipEndPoint, state, null);
}
}
catch (Exception e)
{
if (complete != null)
{
complete(sendFiles, ipEndPoint, state, e);
}
}
})).Start();
}
No comments:
Post a Comment