#c_sharp
Есть основное приложение WinForms в котором ведётся парсинг интернет ресурса с записью необходимых данных в двумерный массив string[5000, 50]. Где 1й столбец строки это ссылка, а остальные столбцы данные с ресурса. Как только набирается строка c данными , данные этой строки сгруппировано выводятся в окне приложения. Как мне эти данные передавать в массив другого приложения WinForms по интернету и так же выводить ? Главный вопрос, как передавать эти данные c записью в массив ? Самостоятельно искал, читал немного про сокеты, но пока каша в голове и не знаю про подводные камни. Хотелось бы как то тупо, при заполнении строки массива в главном приложении, сразу эту строку массива передавать в массив удалённого приложения.
Ответы
Ответ 1
Я мог бы вам предложить один из вариантов организации данного функционала: Логика : 1) Серверная часть Ваш Parser после анализа полученных данных и формирования массива значений (рекомендую использовать коллекции в место статичного массива) сериализуется в json(или XML формат) строку и добавляет их в базу данных MySQL (локальную или удалённую) (с одновременной проверкой наличия там таких данных) - SQL запрос для организации структуры базы данных для ваше случая должна быть примерно таковой CREATE DATABASE IF NOT exists parsersystem; use parsersystem; CREATE TABLE IF NOT exists Procedures ( number int AUTO_INCREMENT, id int, jsonData TEXT, ts_create timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', ts_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (number) ); где number это автоинкриментное поле (уникальный номер записи в БД) id - уникальный специфичный номер записи (заданный пользователем) jsonData - сериализация любых данных в том числе и массива ваших значений ts_create - дата и время создание записи ts_update - дата и время обновления записи MySQL сервер настраивается таким образом чтобы к нему имели доступ клиенты через интернет либо локальную сеть (в зависимости от ваших нужд) 2) Клиенты Клиентское приложение будь то Desktop, Android или другое, не имеет связи непосредственно с вашим серверным Parser приложением, а обращается к открытой MySQL базе (закрытой по желанию паролем), на предмет наличия новых записей, это становится возможным при использовании поля ts_create - которое отражает время создание записи Логика: клиент периодически делает запрос к базе данных примерно такой SELECT * FROM Procedures WHERE ts_create > TIMESTAMP (время последней добавленной клиентом записи) ORDER BY ts_create DESC таким образом клиент получит только новые записи из таблицы на стороне клиента происходит десереализация jsondata и формирование своей локальной копии массива (идентичного тому что на стороне сервера) клиенту следует хранить дату и время последней добавленной записи для последующих запросов Преимущество такого подхода : независимость клиентской и серверной части приложения, (данные хранятся отдельно в БД) нет необходимости в организации специфического протокола общения (сервер -клиент) при использовании подхода передачи при помощи сокетов Предположим что мы имеем некий класс описывающий модель наших данных public class Procedure(){ public String Name { get; set; } public ListData { get; set; } } А также мы на стороне сервера имеем набор данных (коллекцию) public List procedures; Для записи в базу данных такой конструкции стоит применять примерно такой метод: public AddProceduresToDataBase(List procedureList) { string Connect = "Port=3306; Database=parsersystem;Data Source=127.0.0.1;Password=mysql_password; MySqlConnection myConnection = null; try { myConnection = new MySqlConnection(Connect); myConnection.Open(); } catch (Exception e) { myConnection.Close(); Console.Out.WriteLine("Ошибка доступа к базе данных"); } foreach (var proc in procedureList) { var json = new JavaScriptSerializer().Serialize(proc); // Экранируем спец символы json = json.Replace(Convert.ToChar(0171) + "", "") .Replace(Convert.ToChar(0187) + "", "") .Replace("\\\"", "") .Replace("\\","\\\\"); MySqlCommand myCommand_add = new MySqlCommand(@"INSERT INTO Procedures(id,jsondata,ts_create,ts_update) VALUE (" + сгенерить нужно ID + "+ json + "',null,null);", myConnection); myCommand_add.ExecuteNonQuery(); } myConnection.Close(); //Обязательно закрываем соединение! } Для чтения коллекции из базы данных стоит использовать примерно такой метод public List GetProcedures() { string Connect = "Port=3306; Database=parsersystem;Data Source=127.0.0.1;Password=mysql_password; MySqlConnection myConnection = null; try { myConnection = new MySqlConnection(Connect); myConnection.Open(); } catch (Exception e) { myConnection.Close(); Console.Out.WriteLine("Ошибка доступа к базе данных"); } string CommandText = "SELECT * FROM Procedures "; MySqlCommand myCommand = new MySqlCommand(CommandText, myConnection); MySqlDataReader MyDataReader; MyDataReader = myCommand.ExecuteReader(); List procedures = new List (); String jsonData; while (MyDataReader.Read()) { try { jsonData = MyDataReader.GetString(2); // Читаем jsondata из 2го столбца таблицы Procedure newProcedure = new JavaScriptSerializer().Deserialize (jsonData); procedures.Add(newProcedure); } catch { Console.Out.WriteLine("Ошибка разбора JSON данных о процедуре"); } } MyDataReader.Close(); myConnection.Close(); return procedures; } ПС. естественно методы нужно дорабатывать, внедрить проверку на наличие уже такого рода данных в БД, расширить функционал чтения для получения новых записей , но это я уже оставляю вам. ПС. так же тут в принципе показаны примеры сериализации и десиарилизации данных в JSON Ответ 2
Хотя автор вопроса уже сделал свой выбор не дождавшись других вариантов. Я все-таки попробую составить конкуренцию :) Дамы и господа, вашему вниманию предлагается модный и молодежный Firebase от известнейшей корпорации добра! Шутка :) Все хипстеры, попивая смузи в Жан-Жаке, мечтают таки написать свой собственный кошерный (с шахматами и девочками) чатик для Айфона. Ну, и конечно, ничего кроме Firebase ими просто не берется в расчет. Потому, если вы даже почти шепотом посмеете сказать или даже намекнуть про какой-нибудь мускул или подобное, то в ответ вы услышите лишь презрительное фи, хлопок закрывающегося макбука и досвидос. Опять была шутка :) Работает это так Зарегистрируйтесь, зайдите в консоль управления, добавьте новый проект Слева в меню перейдите к разделу БД Зайдите в правила Измените на открытый доступ так Да, я знаю, что это страшно и небезопасно. Но, мы же только учимся... 1) Создание библиотеки для работы с Firebase Создайте проект библиотеки, и в нем класс Пакета такой public class Package { public DateTime Date { get; set; } public string Content { get; set; } //ctor public Package() { Date = DateTime.Now; Content = ""; } public override string ToString() { return $"{Date.ToLongTimeString()}: {Content}"; } public override bool Equals(object obj) { var package = obj as Package; return package != null && Date == package.Date && Content == package.Content; } public override int GetHashCode() { var hashCode = -165655869; hashCode = hashCode * -1521134295 + Date.GetHashCode(); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Content); return hashCode; } } Для работы с сабжем создадим класс FirebaseClient public class FirebaseClient { private enum HttpVerb { GET, POST, PATCH, DELETE } private const string _CONTENT_TYPE = "application/json"; private const string _CONTENT_TYPE_WITH_CHARSET = "application/json: charset=utf-8"; private const string _EMPTY_ANSWER_FIREBASE = "null"; private readonly string _path; private WebRequest _webRequest; //ctor public FirebaseClient(string path) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); _path = path; } /// /// Добавление новых данных /// /// текст в формате Json ///public string Insert(string json) { if (String.IsNullOrEmpty(json)) throw new ArgumentNullException(nameof(json)); SetWebRequest(HttpVerb.POST); WriteJsonToStream(json); return GetResultAfterPostOrPatch(); } /// /// Обновление данных /// /// текст в формате Json ///public string Update(string json) { if (String.IsNullOrEmpty(json)) throw new ArgumentNullException(nameof(json)); SetWebRequest(HttpVerb.PATCH); WriteJsonToStream(json); return GetResultAfterPostOrPatch(); } private void SetWebRequest(HttpVerb httpVerb) { _webRequest = WebRequest.CreateHttp(_path); switch (httpVerb) { case HttpVerb.GET: _webRequest.Method = HttpVerb.GET.ToString(); _webRequest.ContentType = _CONTENT_TYPE_WITH_CHARSET; break; case HttpVerb.POST: _webRequest.Method = HttpVerb.POST.ToString(); _webRequest.ContentType = _CONTENT_TYPE; break; case HttpVerb.PATCH: _webRequest.Method = HttpVerb.PATCH.ToString(); _webRequest.ContentType = _CONTENT_TYPE; break; case HttpVerb.DELETE: _webRequest.Method = HttpVerb.DELETE.ToString(); _webRequest.ContentType = _CONTENT_TYPE; break; default: throw new ArgumentException(nameof(httpVerb)); } } private void WriteJsonToStream(string json) { byte[] buffer = Encoding.UTF8.GetBytes(json); _webRequest.ContentLength = buffer.LongLength; _webRequest.GetRequestStream().Write(buffer, 0, buffer.Length); } private string GetResultAfterPostOrPatch() { string result = String.Empty; var response = _webRequest.GetResponse(); result = (new StreamReader(response.GetResponseStream())).ReadToEnd(); _webRequest = null; return result; } /// /// Получение данных /// ///public string Get() { string result = String.Empty; SetWebRequest(HttpVerb.GET); using (HttpWebResponse response = (HttpWebResponse)_webRequest.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) { result = GetStringResponse(response); } } //если в базе нет данных в ответ приходит текст:"null" if (result == _EMPTY_ANSWER_FIREBASE) { result = String.Empty; } _webRequest = null; return result; } private string GetStringResponse(HttpWebResponse response) { using (Stream responseStream = response.GetResponseStream()) { if (responseStream == null) return String.Empty; using (StreamReader reader = new StreamReader(responseStream, Encoding.UTF8)) { return reader.ReadToEnd(); } } } /// /// Удаление данных /// public void Remove() { SetWebRequest(HttpVerb.DELETE); var response = _webRequest.GetResponse(); _webRequest = null; } } Установите нугет пакет Newtonsoft.Json в нашу библиотеку, напишем класс для отправки сообщений public class SenderService { private readonly FirebaseClient _service; //ctor public SenderService(FirebaseClient service) { _service = service ?? throw new ArgumentNullException(nameof(service)); } public string SendPackage(Package package) { if (package == null) throw new ArgumentNullException(nameof(package)); string json = Newtonsoft.Json.JsonConvert.SerializeObject(package); return _service.Insert(json); } public string UpdatePackage(Package package) { if (package == null) throw new ArgumentNullException(nameof(package)); string json = Newtonsoft.Json.JsonConvert.SerializeObject(package); return _service.Update(json); } } А это класс для получения сообщений public class ReceiverService { private readonly FirebaseClient _service; //ctor public ReceiverService(FirebaseClient service) { _service = service ?? throw new ArgumentNullException(nameof(service)); } public IEnumerableReceivePackage() { List result = new List (); string answer = _service.Get(); if (String.IsNullOrEmpty(answer)) return result; //парсинг ответа JObject jObject = JObject.Parse(answer); var children = jObject.Children().ToArray(); foreach (var token in children) { var package = token.Children() .First() .ToObject (); result.Add(package); } return result; } } 2) Приложение для отправки сообщений Я написал WPF, приведу только ViewModel public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; /// /// Внимание! Этот адрес через пару дней я удалю. /// Создайте свой собственный с правилами: /// { /// "rules": { /// ".read": true, /// ".write": true /// } /// } /// И не забудьте в конце адреса дописать .json /// private readonly string _firebase = "https://testtodo-8d83d.firebaseio.com/.json"; private int _sendIndex = 1; //ctor public MainViewModel() { } private string _InputText = "Сообщение"; public string InputText { get { return _InputText; } set { _InputText = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InputText))); } } private string _OutputText; public string OutputText { get { return _OutputText; } set { _OutputText = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OutputText))); } } private bool _IsIndexed = true; public bool IsIndexed { get { return _IsIndexed; } set { _IsIndexed = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsIndexed))); } } public void SendOnePackage() { if (String.IsNullOrEmpty(InputText)) { OutputText = "Невозможно отправить пустое сообщение"; return; } if (IsIndexed) { var chars = InputText.ToCharArray() .Where(c => !char.IsDigit(c)) .ToArray(); InputText = new string(chars) + _sendIndex.ToString(); _sendIndex++; } Package package = new Package { Content = InputText }; SenderService service = new SenderService(new FirebaseClient(_firebase)); var answer = service.SendPackage(package); answer = answer.Substring(9, answer.Length - 11); OutputText = $"Получен и сохранен с именем: {answer}"; } } 3) Приложение получатель Тоже только ViewModel public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; ////// Внимание! Этот адрес через пару дней я удалю. /// Создайте свой собственный с правилами: /// { /// "rules": { /// ".read": true, /// ".write": true /// } /// } /// И не забудьте в конце адреса дописать .json /// private readonly string _firebase = "https://testtodo-8d83d.firebaseio.com/.json"; private readonly IMainWindow _mainWindow; private volatile bool _shouldHearing; private SynchronizationContext _context = SynchronizationContext.Current; private HashSet_hashSet; private int _countElements; //ctor public MainViewModel(IMainWindow mainWindow) { _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); } private ObservableCollection _Messages; public ObservableCollection Messages { get { return _Messages; } set { _Messages = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Messages))); } } /// /// Начать слушать /// public void DoHear() { _shouldHearing = true; _hashSet = new HashSet(); _countElements = 0; Task.Run(() => Hearing()); } private void Hearing() { while (_shouldHearing) { var service = new ReceiverService(new FirebaseClient(_firebase)); IEnumerable packages = service.ReceivePackage(); if (packages.Count() == 0) continue; _context.Post(delegate { AddPackages(packages); }, null); } } private void AddPackages(IEnumerable packages) { foreach (var package in packages) { _hashSet.Add(package); } if (_countElements < _hashSet.Count) { Messages = new ObservableCollection (_hashSet); _countElements = _hashSet.Count; } } /// /// Остановка /// public void StopHearing() { _shouldHearing = false; if (Messages?.Count > 0) { _mainWindow.ChangeStateControls(StateControls.MessagesExists); } else { _mainWindow.ChangeStateControls(StateControls.Startup); } } ////// Удаление ранее отправленных /// public void RemovePackages() { if (Messages.Count == 0) return; var client = new FirebaseClient(_firebase); client.Remove(); Messages.Clear(); _mainWindow.ChangeStateControls(StateControls.Startup); } } Весь пример лежит здесь.
Комментариев нет:
Отправить комментарий