С Новым годом! Форум программистов, компьютерный форум, киберфорум
C++: Сети
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.82/11: Рейтинг темы: голосов - 11, средняя оценка - 4.82
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
1

fstream некорректно работает в потоке созданном CreateThread

02.02.2020, 22:08. Показов 1899. Ответов 10

Author24 — интернет-сервис помощи студентам
Здравствуйте! Я пишу программку, которая получает TCP пакеты (их содержимое) от сервера и сохраняет их в файл. Делается это в потоке. Поток создаю функцией CreateThread, с файлами работаю посредством fstream. В файл успешно записываются сообщения от сервера, но только в том случае, если сообщения примерно одинаковой длины! Если длина сообщений разнится, то в файле некоторые сообщения дублируются, какие-то сообщения теряются, вообщем плохо получается. Я не могу понять почему так! Я пробовал по разному, сначала думал, что fstream как-то плохо себя в потоке ведет, и переписал с системными функциями CreateFile и WriteFile, но результат тот же. Ещё я пробовал записывать сообщения не сразу, а складывать какое-то их кол-во в буфер, а потом записывать этот буфер, в котором содержится 10-20 сообщений, в файл, но не помогло.

Среда разработки Qt, но для работы с сетью и потоками я использую WinAPI функции, а Qt только для QUI.

Вот код функции потока
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
DWORD WINAPI threadProc(const LPVOID lpParam)
{
     // инициализация сокета, подключение к серверу, открытие файлов и другое
     int rlen = 1;
     while (rlen > 0) // пока функция recv получает какое-то кол-во байт
     {
 
         rlen = recv(sock, rbuf, MAX_RECV_BUF_SIZE, 0);
 
         // принимаем TCP пакеты, в каждом пакете может содержаться несколько сообщений разделенных '\n'
         // я достаю каждое сообщение из очередного принятого пакета и помещаю в вектор messages методом push_back
         // ...
         
         // вот собственно, код записи в файл
         for (std::vector<std::string>::iterator it = messages.begin(); it != messages.end(); ++it)
         {
              outfile << *it << "\n\n";
         }
 
          // ...
          // очистим вектор разобранных сообщений
          messages.clear();
      }
      
      // освобождение всех дескрипторов, закрытие файлов, сокетов и выход из потока
      return 0;
}
 
int main()
{
     // ...
     HANDLE hThread = CreateThread(NULL, 0, &threadProc, NULL, NULL, NULL);
     // ...
}
Повторюсь, что все работает хорошо, когда сервер посылает сообщения вида

message from server 1
message from server 2
message from server ...

но, если я пробую генерировать на сервере сообщения разной длины, то они записываются в файл некорректно, с коллизиями, путаются между собой

Если нужно, могу привести полный код функции threadProc
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
02.02.2020, 22:08
Ответы с готовыми решениями:

Скачивание файлов стало некорректно работать в отдельном потоке
В общем, был у меня код, который скачивал файлы по списку, переделал его для отдельного потока,...

не работает fstream.h
Всем доброго дня !!! Решил поработать с файлами. В учебнике четко сказанно , что fstream.h не...

В VS2015 не работает fstream
Подключаю файл fstream, но при создании объекта fstream fin(&quot;in.bin&quot;, ios::binary); Компилятор...

работает fstream в VC++2010
скажите пожалуйста библиотечный файл &lt;fstream.h&gt; поддерживает VC++2010? а то почему то даже...

10
Любитель чаепитий
3744 / 1800 / 566
Регистрация: 24.08.2014
Сообщений: 6,016
Записей в блоге: 1
03.02.2020, 06:26 2
покажите, как разбирается пакет.
и переменную rbuf и MAX_RECV_BUF_SIZE.
в общем, давайте полную функцию. быстрее будет.
1
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
03.02.2020, 13:23  [ТС] 3
Спасибо за ответ!

Вот полный код поточной функции
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
DWORD WINAPI threadProc(const LPVOID lpParam)
{
    flagRunningTelThread = true; // отметим, что поток начал свою работу
 
    WSAData wsa;
    SOCKADDR_IN telServ;
 
    if (WSAStartup(0x0202, &wsa) != 0)
    {
        return 0;
    }
 
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock <= 0)
    {
        WSACleanup();
        return 0;
    }
 
    telServ.sin_family = AF_INET;
    telServ.sin_port = htons(Review::settings_file.inputPort); // порт сервера телеметрии
    telServ.sin_addr.s_addr = inet_addr(Review::settings_file.inputIP.c_str()); // IP адрес сервера
 
    // узнаем сегодняшнюю дату
    QDate curDate = QDate::currentDate();
    curDate.toString("yyyy-MM-dd");
    std::string strCurDate(curDate.toString("yyyy-MM-dd").toLocal8Bit().constData());
    std::string curDateFileName = "\\data\\" + strCurDate + ".txt";
    std::string strPrivDate = strCurDate;
 
    // узнаем текущую директорию приложения
    std::string currentDir = getCurrentDir();
 
    if(fileExists(currentDir + curDateFileName)) // если файл с сегодняшней датой уже существует
    {
        outfile.open(currentDir + curDateFileName, std::ios_base::app); // откроем его для записи в конец
    }
    else
    {
        // иначе, создадим новый файл с сегодняшней датой и откроем его для записи
        outfile.open(currentDir + curDateFileName, std::ios_base::out);
    }
 
    if(!outfile.is_open()) // произошла ошибка
        return 0;
 
    const int MAX_RECV_BUF_SIZE = 1024;
 
    if (::connect(sock, (SOCKADDR *)&telServ, sizeof(telServ)) != SOCKET_ERROR)
    {
        int rlen = 1; // кол-во принятых байт функцией recv
        char rbuf[MAX_RECV_BUF_SIZE]; // принимаем данные порциями по 1024 байт
        int messageCount = 1; // счетчик полученных сообщений
 
        std::string sep = "\n";
        std::size_t sep_size = sep.size();
        std::string temp;
        std::string privMessagePart; // предыдущая часть сообщения
 
        while (rlen > 0) // пока функция recv получает какое-то кол-во байт
        {
            // проверим, не пора ли нам завершить свою работу
            if(flagRunningTelThread == false)
            {
                break;
            }
 
            rlen = recv(sock, rbuf, MAX_RECV_BUF_SIZE, 0);
            // получена новая порция данных
 
            // узнаем дату получения
            QDate curDate = QDate::currentDate();
            std::string strCurDate(curDate.toString("yyyy-MM-dd").toLocal8Bit().constData());
 
            // узнаем время получения с точностью до миллисекунд
            QTime curTime = QTime::currentTime();
            std::string strCurTime(curTime.toString("hh:mm:ss.zzz").toLocal8Bit().constData());
 
            if(strCurDate != strPrivDate) // если уже начался новый день
            {
                // закроем старый файл
                outfile.close();
 
                // создадим и откроем новый файл с датой нового (только что наступившего дня)
                std::string curDateFileName = "\\data\\" + strCurDate + ".txt";
                outfile.open(currentDir + curDateFileName, std::ios_base::app); // открываем для записи в конец файла
                if(!outfile.is_open())
                    return 0;
 
                // запомним текущий день
                strPrivDate = strCurDate;
 
                // теперь будем записывать в новый файл
            }
 
            // теперь необходимо извлечь из этих данных сообщение
 
            // преобразуем принятые данные в std::string, чтобы с ними было удобней работать
            std::string recvBuff(rbuf);
 
            std::string borderMessage; // полное сообщение, которое было разделено по двум пакетам данных
 
            std::vector<std::string> messages; // все полученные сообщения в очередном пакете данных
 
            int q = 0;
 
            // вот код разделения сообщений
            while (1)
            {
                temp = recvBuff.substr(0, recvBuff.find(sep));
 
                if (q == 0)
                {
                    borderMessage = privMessagePart + temp;
                    messages.push_back(borderMessage);
                }
                else
                {
                    if (temp.size() == recvBuff.size())
                    {
                        privMessagePart = temp;
                        break; // переходим к следующему пакету данных
                    }
                    else
                    {
                        messages.push_back(temp);
                    }
                }
 
                if (temp.size() != recvBuff.size())
                {
                    recvBuff = recvBuff.substr(temp.size() + sep_size);
                }
                ++q;
            }
 
            for (std::vector<std::string>::iterator it = messages.begin(); it != messages.end(); ++it)
            {
                // сформируем строку json сообщения
                std::string jsonStr = std::to_string(Telemetry::threadsCount) + ": {\"id\":" + std::to_string(messageCount) +
                ",\"timestamp\":\"" + strCurDate + " " + strCurTime +
                "\",\"message\":\"" + *it + "\"}";
 
                ++messageCount;
 
                outfile << jsonStr << "\n\n";
            }
            
            // перешлём полученные сообщения всем WebSocket клиентам
            server_instance.sendToAllClients(jsonStr); // эта функция по факту не выполняется, потому что я тестировал программку без подключеных клиентов
 
            // очистим вектор разобранных сообщений
            messages.clear();
           
 
        } // end while (rlen > 0)
    }
 
    closesocket(sock);
    WSACleanup();
    if(outfile.is_open()) // если файл все еще открыт
        outfile.close(); // закроем его
    return 0;
}
Код подключения к серверу с последующим принятием сообщений это строки 49-157

Вообще, все работает, если не разделять сообщения, а просто записывать содержимое принятого буфера в файл

Если сообщения такого вида
1-й пакет [message 1\n]
2-й пакет [message 2\n]

Я просто пишу
C++
1
2
3
4
5
rlen = recv(sock, rbuf, MAX_RECV_BUF_SIZE, 0);
std::string recvBuff(rbuf);
// сформировал json строку
// jsonStr = ... + recvBuff ...
outfile << jsonStr << "\n\n";
то все нормально

Но я хочу сделать дополнительную проверку, ведь размер буфера, который отправляет сервер может быть больше чем MAX_RECV_BUF_SIZE, конечно, можно сделать MAX_RECV_BUF_SIZE большим, но это выглядит как-то не очень, поэтому я решил проверять кол-во сообщений в одном пакете, например, сообщения могут выглядеть так:

1-й пакет [message 1\nmessa]
2-й пакет [ge 2\nmessage 3\n]
3-й пакет [some data from ser]
4-й пакет [ver\nlolollolloollol\n]

И я вроде как успешно разделяю их, но вот в файл сообщения записываются плохо

Под пакетом я подразумеваю не сам TCP пакет, а тот кусочек данных, который приняла функция recv

Или может быть я вообще не рационально это делаю и есть более хороший способ

Добавлено через 24 минуты
Сам смысл этой этой части программы в том, что я постоянно принимаю TCP пакеты от сервера, достаю из этих пакетов сообщения, которые разделяются символом '\n' и сохраняю их в файл с временной меткой полученного сообщения. Файл имеет имя сегодняшней даты, например "03 02 20.txt", если программа обнаруживает, то начался новый день, то она создаст новый файл "04 02 20.txt" и начнет записывать сообщения в него.

Ещё отмечу что размер буфера, который отправляет сервер мне не известен, поэтому я и провожу такую проверку с разделением сообщений

Вот так я создаю поток, попробовал назначить ему высокий приоритет, но не помогло
C++
1
2
3
4
5
6
hTelemetryThread = CreateThread(NULL, 0, &threadProc, NULL, THREAD_PRIORITY_NORMAL, NULL);
SetThreadPriority(hTelemetryThread, THREAD_PRIORITY_HIGHEST);
if(hTelemetryThread == NULL)
{
    // обработка ошибки
}
Я просто сильно удивлен! Как я уже писал, тот код функции threadProc работает, если сообщения одинаковый по длине, ну +- 1 байт, но если сообщения имеют разную длину, то запись в файл работает плохо

Перед записью я пробовал использовать flush(), но проблема не решилась
0
фрилансер
5846 / 5376 / 1103
Регистрация: 11.10.2019
Сообщений: 14,368
03.02.2020, 13:32 4
peoplep8, как отлаживать подобное:

1) все операции вывода в файл заменить на вызов функции, в которой:
- выводимая строка выводится в лог (если символы в строке непечатные, то лучше выводить классический в hex виде)
- производится запись в файл

2) в этот же лог можно выводить и любые другие события потока, чтобы проследить их порядок

3) запускаешь, смотришь лог. Уже на этом этапе можно много удивительного обнаружить
1
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
03.02.2020, 14:03  [ТС] 5
Спасибо, я бы так и делал, но когда я собираю проект как Debug (чтобы иметь возможность выводить лог в консоль Qt) и запускаю программу прямо из Qt, то она не имеет прав ни на что, я не знаю почему так, ведь я работаю в Qt от имени администратора! Она даже файлы не создает! Кроме того, в режиме отладки программа собирается минут 5) Может попробую как-нибудь консоль создать, хотя у меня QUI приложение. А пока для отладки использовал QMessageBox и просто выводил ошибки в файл

Добавлено через 17 минут
Вот еще кусочек кода, как сервер отправляет сообщения
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
char allSymbols[] = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
 
// ...
 
for (int i = 0; i < 1000; ++i)
{
     int delayTime = getRandomNumber(250, 1000); // интервал задержки (мсек) между отправками сообщений
     int messLength = getRandomNumber(25, 50); // длина очередного сообщения
     int messLength = 25; // если длина сообщения константа, то все нормально работает, сообщения записываются правильно
 
     // генерируем сообщение
     // если использовать такой способ генерации, то плохо записывается в файл
     int buffInd = 0;
     for (; buffInd < messLength; ++buffInd)
         sendBuff[buffInd] = allSymbols[getRandomNumber(0, strlen(allSymbols)-1)];
     sendBuff[buffInd] = '\n';
     sendBuff[buffInd + 1] = '\0';
 
     // если вместо генерации случайных сообщений, отправлять такую строку, то все нормально работает
     //wsprintfA(sendBuff, "%s %d\n", "message from server", i+1);
 
     // отправляем клиенту
     if ((slen = send(connSock, sendBuff, strlen(sendBuff), 0)) != strlen(sendBuff))
     {
         nstd::cout << "send() returned " << slen << ", but we need to send " << strlen(sendBuff) << " bytes! something went wrong" << std::endl;
         break;
     }
     Sleep(delayTime);
}
0
фрилансер
5846 / 5376 / 1103
Регистрация: 11.10.2019
Сообщений: 14,368
03.02.2020, 14:10 6
peoplep8, какую IDE используешь? Надо разобраться, с такой ерундой невозможно разрабатывать и отлаживать. Может, переустановка поможет

Добавлено через 2 минуты
Цитата Сообщение от peoplep8 Посмотреть сообщение
Кроме того, в режиме отладки программа собирается минут 5)
нужно ещё уточнить, что промежуточные релизные и дебажные файлы падают в разные папки, а то будет каждый раз перекомпиляция при переключении режима

также настрой количество потоков для работы компилятора, поставь значение, равное количеству ядер минус 1 . В студии и в QtCreator это настраивается
1
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
03.02.2020, 14:15  [ТС] 7
Я использую QtCreator 5.13.2 в Windows, собирает все это дело MinGW x64. Вообще, я новичек в Qt и использую его только для QUI. Попробую переустановить, но с моими ресурсами это займет достаточно времени)

Добавлено через 1 минуту
Хорошо, попробую
0
фрилансер
5846 / 5376 / 1103
Регистрация: 11.10.2019
Сообщений: 14,368
03.02.2020, 14:17 8
peoplep8, какая IDE используется?

QtCreator?

Добавлено через 1 минуту
peoplep8, пристегни проект в ZIP . Только папки release и debug удали, а также экзешники

Добавлено через 34 секунды
сам не обещаю поразбираться, если только время будет. Но, может, кто более свободный поможет
1
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
03.02.2020, 16:27  [ТС] 9
Да, спасибо, сегодня вечером прикреплю архив
0
Любитель чаепитий
3744 / 1800 / 566
Регистрация: 24.08.2014
Сообщений: 6,016
Записей в блоге: 1
03.02.2020, 17:27 10
Лучший ответ Сообщение было отмечено peoplep8 как решение

Решение

Цитата Сообщение от peoplep8 Посмотреть сообщение
rlen = recv(sock, rbuf, MAX_RECV_BUF_SIZE, 0);
попробуйте перед этой строкой вставить ::memset(rbuf, 0, sizeof(rbuf));.
1
9 / 6 / 3
Регистрация: 01.07.2018
Сообщений: 49
03.02.2020, 17:43  [ТС] 11
Ураааа!!!!! Все работает! Я забыл про то что буфер нужно обнулять XD Спасибо! А я еще думаю, ну что может быть там не так... Мой мозг просто забыл про обнуление :/
0
03.02.2020, 17:43
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
03.02.2020, 17:43
Помогаю со студенческими работами здесь

Как работает write в fstream?
есть файл с текстом: fstream f(&quot;f.txt&quot;,ios::in); надо его разделить его на части по сколько-то...

fstream не работает и чтение и запись
Добрый день! Не пойму, почему введение функции getline становится причиной того, что запись в файл...

fstream::tellp() не работает как надо.
#include &lt;iostream&gt; #include &lt;stdlib.h&gt; #include &lt;fstream&gt; using namespace std; int...

Почему не работает код при переходе на fstream?
Доброго времени суток! Ломаю, по незнанию, голову над следующим вопросом. Ниже такой код (из книги...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru