Архитектура многопоточного постера

Язык C#
  1. 6 года назад

    Есть у меня программа, которая отправляет личные сообщения на форуме пользователям по моему списку, со списка моих аккаунтов. За раз можно отправлять сообщение нескольким пользователям (до пяти). Причём если одному из этих пяти по какой-то причине сообщение нельзя отправить (запрет на ЛС, переполнение ящика, etc.) то не отправляет никому. С одного аккаунта нельзя отправлять сообщение чаще чем раз в 60 секунд. При сбое в работе или остановке - нужно запомнить последнюю позицию и начать в следующий раз с места остановки. Вот все ограничения. Программа работает, но неэффективно. Решил переписать по уму, с блекджеком и потоками. Но при этом - конструкции типа async/await использовать нельзя (для совместимости). И задумался я над архитектурой приложения. Как организовать хранение данных и запись логов (блокировки, критические секции), как правильно использовать куки. Ничего не знаю. Нужно разобраться.

    То, что есть работает так:
    В качестве исходных данных есть два текстовых файла. Один - names.txt - список пользователей, для которых у меня есть сообщения (по одному на строку). Во втором - accs.txt -логины и пароли моих аккаунтов, разделённых двоеточием тоже по одному на строку.

     static void Main(string[] args)
            {
     
                string mainfile = @"names.txt";
                string tempfile = @"temp.txt"; //Временный файл
                string accsfile = @"accs.txt";
     
    //1. Считываем все строки из файла с аккаунтами accs.txt в массив. 
    //Массив доступен из других частей программы. Т.к. если акк нерабочий - его нужно выкинуть
     
                accsLines = File.ReadAllLines(accsfile, Encoding.GetEncoding(1251));
                int counter = 0;
                int totalLines = accsLines.Length;  //Нужно, чтобы знать, сколько всего осталось
     
     
     
                while (true)
                {
                    int ammosize = 5;  //Столько можно отправить сообщений за раз 
     
                    //Считать получателей из файла в массив
                    string[] names = File.ReadAllLines(mainfile, Encoding.GetEncoding(1251));
     
                    Console.WriteLine("Осталось: " + names.Length.ToString());
     
                    if (names.Length == 0)
                    {
                        Console.WriteLine("Всё отправлено!");
                        File.Delete(mainfile);
                        Console.ReadKey();
                        break;
                    }
                    if (ammosize > names.Length) //Если в массиве имён осталось меньше, чем 5
                    {
                        ammosize = names.Length; 
                    }
     
                    string[] users = new string[ammosize];
                    //Первые пять (или сколько там осталось) перенести в users
     
                    for (int i = 0; i < ammosize; i++)
                    {
                        users[i] = names[i];
     
                    }
     
                    string accsLine = accsLines[counter]; //выбираем очередной аккаунт, с которого будем работать
     
                    char[] delimiterChars = { ':' };
                    string[] accsPart = accsLine.Split(delimiterChars); //Разбиваем на логин/пароль
                    string username = accsPart[0];
                    string password = accsPart[1];
     
     
                    Console.WriteLine(" Оправляем с аккаунта: " + username + ":" + password);
     
    //Вызвать  postLoad c параметрами
    //postLoad - это моя главная функция. Принимает данные для авторизации и массив из списка 
    //получателей. Если кому-то из users не удаётся отправить сообщение - функция выкидывает  из 
    //списка получателей того, кому не удалось отправить письмо и рекурсивно вызываем себя с массивом
    //users без ошибочного получателя
     
                    if (postLoad(username, password, users) == null)
                    {
                        counter--;
                        postLoad(username, password, users);
                    }
                    else
                    {
                       //Тут я пытаюсь обеспечить работу при обрыве соединения или вылете.
                        //Массив от пятого элемента до конца записать во временный файл
     
                        var str = new StringBuilder();
                        for (int i = ammosize; i < names.Length; i++)
                        {
                            //  str.AppendFormat("{0}", names[i], Environment.NewLine);
                            str.AppendLine(names[i]);
                        }
                        File.AppendAllText(tempfile, str.ToString(), Encoding.GetEncoding(1251));
     
                        File.Delete(mainfile);
                        //Переименовать временный в основной
                        File.Move(tempfile, mainfile);           
     
     
                    }
        //Взять следующий аккаунт
                    counter++;
     
                    if (counter == totalLines)     //Если дошли до конца списка аккаунтов 
                    {
                        counter = 0;  //Начинаем сначала
     
                    }
     
                }
  2. PostLoad(). Тут всё ещё страшнее:

        public static string postLoad(string accName, string accPass, string[] users)
            {
                CookieContainer container = new CookieContainer();
                //авторизация
                string data = "vb_login_username=" + accName + "&cookieuser=1&vb_login_password=" + accPass + "&securitytoken=guest&do=login";
                string url = "http://forum.forum.ru/login.php?do=login";
                string result = Post(url, data, false, ref container);
     
    //Идём на главную, методом getSession парсим значение, которое зачем-то нужно
                string index = "http://forum.forum.ru/index.php?s=" + getSession(result);
                result = Get(index, false, ref container);
                Log(result);
     
     
    //Тут ужасная проверка на работоспобосность аккаунта
                if (result.IndexOf("Вы были заблокированы") > -1)
                {
                    Console.WriteLine("Аккаунт " + accName + " заблокирован");
     
                    string bad = accName + ":" + accPass;
                    accsLines = accsLines.Where(item => item != bad).ToArray();
     
                    var str = new StringBuilder();
                    for (int i = 0; i < accsLines.Length; i++)
                    {
                        str.AppendLine(accsLines[i]);
                    }
                    File.Delete("accs.txt");
                    File.AppendAllText("accs.txt", str.ToString(), Encoding.GetEncoding(1251));
                    Console.WriteLine(" Удаляем из списка");
                    accsLines = File.ReadAllLines("accs.txt", Encoding.GetEncoding(1251));
                    return null;
                }
                else
                {
                    //Акк целый. Идём на страницу с приватными сообщениями
     
                    string messagePage = "http://forum.forum.ru/private.php?do=newpm";
                    result = Get(messagePage, false, ref container);
                    securitytoken = getSecurityToken(result); //Вот эта штука в коде каждой страницы. Приходится //постоянно её тягать
     
                    //список пользователей в строку с ;
     
                    string ammo = string.Empty;
     
                    foreach (string user in users)
                    {
                        ammo += user + ";";
                    }
                    Console.WriteLine("Используем набор имён:" + ammo);
                    //Попытка отправки. В случае успеха пишем лог и подчищаем
     
     
                    //string recipients = "vasya;petya;"; Образец данных для отравки
                    string recipients = ammo;
                    string bccrecipients = ""; //скрытые получатели
                    string title = "Заголовок";
                    string message = "Сообщение";
                    string preview = "Предварительный просмотр";      
     
                    data = "recipients=" + recipients +
                           "&bccrecipients" + bccrecipients +
                           "&message_backup=" + message +
                           "&message=" + message +
                           "&title=" + title +
                           "&wysiwyg=0" +
                           "&iconid=0" +
                           "&s=" +
                           "&securitytoken=" + securitytoken +
                           "&do=insertpm" +
                           "&pmid=" +
                           "&forward=0" +
                           "&receipt=1" +
                           "&savecopy=1" +
                           "&parseurl=1" +
                           "&preview = " + preview;
     
                    messagePage = "http://forum.forum.ru/private.php?do=insertpm";
                    result = Post(messagePage, data, false, ref container);
     
     
    //Ниже проверяем нет ли в полученной странице сообщения, что пользователю нельзя отправлять сообщения
                    string errorUser = GetErrorUserName(result, users); 
                    if (errorUser == null)
                    {
    //Если всё хорошо подчищаем за собой, чтобы список личных сообщений не переполнялся.
                 //Тянем страницу со списком отправленных сообщений. Ищем id отправленного
                        string html = Get(@"http://forum.forum.ru/private.php?folderid=-1", false, ref container);
                        HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
                        doc.LoadHtml(html);
                        HtmlAgilityPack.HtmlNode pmidNode = doc.DocumentNode.SelectSingleNode("//li[@class='blockrow pmbit']");
     
                        if (pmidNode != null)
                        {
     
                            string pmid = pmidNode.Attributes["id"].Value;
                            pmid = pmid.Replace("pm_", "");
             
     
     
                            data = "s=" +
                                   "&securitytoken=" + getSecurityToken(html) +
                                   "&do=managepm" +
                                   "&folderid=-1" +
                                   "&allbox=all" +
                                   "&pm[" + pmid + "]=-1_yesterday" +
                                   "&dowhat=delete";
    //И удаляем
                            result = Post(@"http://forum.forum.ru/private.php?do=managepm&folderid=-1", data, false, ref container);
                            Log(result);
     
     
                        }
             
     
                        Console.WriteLine("Успешно отправлено!");
                    }
     
                    else
                    { //Если всё-таки есть пользователь. Из-за которого отправить не удалось.
                        //Попытка отправки. В случае ошибки удалеям из строки с ; левое имя и рекурсивно вызвываем функцию.
                        Console.WriteLine("Не удалось отправить сообщение из-за " + errorUser);
                        string r = errorUser + ";"; //Имя с ;
                        ammo = ammo.Replace(r, ""); //Удаляем
                        char[] delimiterChars = { ';' };
                        users = ammo.Split(delimiterChars);
                        Console.WriteLine("Повторяем попытку");
                        postLoad(accName, accPass, users);
                    }
     
     
     
                    return "ok";
     
     
                }
            }

    В общем нужно рассмотреть хотя бы основную часть и решить, как именно её сделать многопоточной (Ручное манипулирование потоками, TPL или ещё как). Плюс указать на явные ошибки. Буду учиться.

  3. Alexandr_Erohin

    Mar 26 Администратор

    Вам нужно заспамить какой-то конкретный форум или написать общий спам-бот? ;)

  4. Вообще у меня это регулярная проблема. Это может быть не форум, а что-то ещё. Поэтому хочется иметь что-то максимально общее. Чтобы при необходимости я бы тупо создавал нужную последовательно из POST и GET запросов, определял используемые данные и методику совместного доступа к ним. Было бы здорово любой много поточный регер или постер создавать за полчаса. Можно сказать - моя мечта. Поэтому очень хотелось бы найти объяснение как правильно делать такие штуки

  5. Всё ещё нужна помощь

или зарегистрируйтесь чтобы ответить