Ранее я показывал ролик того, что хочу сделать. Как оказалось уже существует 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