На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
    > Передача данных в локальной сети
      Всем привет. Делаю небольшое приложение для передачи файла по локальной сети использую WinSock2. Суть проблемы в том, что текстовые файлы(.txt) передаются без каких-либо проблем.
      При попытке передать любое изображение сталкиваюсь с ошибкой, что передаются не все данные, а только их часть.
      Инициализация и закрытие сокетов:
      ExpandedWrap disabled
        WSADATA _WSA;
        WSAStartup(MAKEWORD(2,2),&_WSA);
        SOCKET hSock = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
        if (hSock == INVALID_SOCKET)
        {
            cout<<"Error: "<< GetLastError()<<endl;
            system("pause");
            return 0;
        }
        ...
        closesocket(hSock);
        WSACleanup();
        system("pause");
        return 0;


      Передача файла:
      ExpandedWrap disabled
        void SendDateTo(const char *path, SOCKET hSock)  //path - путь до файла
        {
            char buffer[MAX_SIZE];
            int ReceivedBytes, buff_size=MAX_SIZE;
            char * t = getNamefromPath(path); //Выделяю из пути только имя файла, чтобы передать его адресату, для создания файла с тем же именем и расширением.
            HANDLE hFile = CreateFileA(path,GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); //открытие файла
            if (hFile==INVALID_HANDLE_VALUE)
                throw "Can`t open file";
            LARGE_INTEGER FileSize;
            GetFileSizeEx(hFile,&FileSize); //Получение размера файла в байтах
            strcat(t, "%");  //Знак '%' означает окончание сообщения, передаваемого по сети
                                 //Сделано для того, что не всегда recv у адресата принимает сразу все данные, которые были отправлены
                                 //С помощью этого знака я сообщаю что сообщение окончено
            buff_size = strlen(t);
            ReceivedBytes = send(hSock,t,buff_size,NULL);   //Отправляю адресату имя файла
            if (ReceivedBytes !=buff_size)   //Проверка на успешную отправку.
                throw "Exception";
            ReceivedBytes = recvdata(hSock,buffer,MAX_SIZE);  //recvdata() функция-оболочка для recv, которая получает сообщение целиком и помещаешь его в buffer
            if (strcmp(GOOD,buffer)!=0)
                throw "Can't get GOOD signal";  //Сигнал о том, что адресат получил сообщение и готов принимать следущее
            
            while(FileSize.QuadPart>0)  //Передавать файл, пока его размер > 0
            {
                DWORD dwRead;
                ReadFile(hFile,buffer,MAX_SIZE-1,&dwRead,NULL); //Читаю блок данных размеров MAX_SIZE - 1
                FileSize.QuadPart -= dwRead; //Уменьшаю размер файла на число прочитанных байт
                buffer[dwRead] = '%'; //Помещаю в конец строки терминатор сообщения
                ReceivedBytes = send(hSock,buffer,++dwRead,NULL); //Отправляю блок данных с терминатором конца сообщения в конце блока
                if (dwRead != ReceivedBytes) //Проверка на успешную отправку
                    throw "Can't send message";
                ReceivedBytes=recvdata(hSock,buffer,MAX_SIZE); //Примем сообщения об успешной доставке
                if (strcmp(GOOD,buffer)!=0)  //Если сообщение не было успешно доставлено - исключение
                   throw "Can't get GOOD signal";
            }
            strcpy(buffer,END_OF_SESSION);
            strcat(buffer, "%");
            buff_size = strlen(buffer);
            ReceivedBytes = send(hSock,buffer,buff_size,NULL);  //Отправляю адресату сообщение об окончании сеанса передачи данных.
            CloseHandle(hFile);
        }


      Прием файла:
      ExpandedWrap disabled
        void GetDataFrom(const char * DirPath, SOCKET hSock) //DirPath - директория сохранения файла
        {
            char buffer[MAX_SIZE];
            int Recervefbytes, boffer_size=MAX_SIZE;  //Буфер для передачи данных
            Recervefbytes=recvdata(hSock, buffer, boffer_size); //Ждем от отправителя имя файла
            char * tmp = new char[strlen(DirPath) + Recervefbytes];
            strcpy(tmp, DirPath);
            strcat(tmp, buffer);
            //Конкатенируем строки директории и имени файла и создаем файл
                //То, что файл уже существует пока не обрабатывается, идет простая перезапись
            HANDLE hFile = CreateFileA(tmp,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
            if (hFile==INVALID_HANDLE_VALUE)
                throw "Can`t create file";
            strcpy(buffer, GOOD);
            boffer_size = strlen(buffer) + 1;
            strcat(buffer,"%");
            Recervefbytes = send(hSock,buffer,boffer_size, NULL); //Отправляем сигнал о том, что файл создан и мы готовы принимать данные.
            if(Recervefbytes != boffer_size)  //Проверка на успешно отправленное сообщение
                throw "Can't send data";
            while (true)
            {
                        DWORD dwRead;
                Recervefbytes=recvdata(hSock, buffer,MAX_SIZE - 1);  //Принимаем блок данных
                if (strcmp(buffer, END_OF_SESSION)==0)  //Если сообщение = окончанию сессии - выходим из цикла
                    break;
                WriteFile(hFile,buffer,Recervefbytes,&dwRead,NULL);  //Записываем полученный блок данных в файл
                strcpy(buffer, GOOD);
                strcat(buffer,"%");
                Recervefbytes = send(hSock, buffer, strlen(buffer), NULL); //Отправляем сообщение о готовности принять следующий блок данных
                if(Recervefbytes != strlen(buffer))
                    throw "Can't send data";
            }
            delete tmp;
            CloseHandle(hFile);
        }


      Функция recvdata()
      ExpandedWrap disabled
        DWORD recvdata(SOCKET hs,char *buffer, int buff_size)
        {
            if(hs == INVALID_SOCKET)  //Проверка аргументов
                return -1;
            if(buff_size <=0)
                    return -2;
            buffer[0] = '\0';  //Обрезание буфера
            char *tmp = new char[buff_size]; //Создаем временную переменную для приема данных
            int true_size = 0, ReceivedBytes;
            do
            {
                ReceivedBytes=recv(hs,tmp,buff_size,NULL); //Принимаем часть данных
                tmp[ReceivedBytes] = '\0'; //Помещаем терминатор в конец
                if (ReceivedBytes <= 0)  //В случае ошибки приёма - выходим
                    return -3;
                true_size += ReceivedBytes;  //true_size хранит фактическое количество принятых байт
                strcat(buffer,tmp); //конкатенируем часть данных с буфером
            }while(tmp[ReceivedBytes-1] !='%'); //Если в конце сообщения находится терминатор сообщения - выход из цикла
                                                    //Иначе прием еще порции данных
            buffer[true_size - 1] = '\0'; //Терминатор в конец полученного блока данных
            delete tmp;
            return true_size;
        }


      Проблема в том, что если я передаю, например, изображение то в нем могут содержаться спец.символы вроде NULL
      Так как передача осуществляется блока типа char *, то строка из файла вида
      ExpandedWrap disabled
        IHDR[NULL][NULL][STX]|[BS][STX]JEKKSE32[NULL]4cИL...


      Обрезается до первого символа NULL, т.е. "IHDR".
      Считывается из файла информация при помощи ReadFile() полностью, все MAX_SIZE символов, т.е. за первым NULL лежат правильные символы, просто char * дальше NULL ничего не видит
      При записи в файл у адресата наблюдается то, что после первого символа NULL в строке ничего нет.
      Либо строка обрезается до первого NULL в самой функции recv, либо я что-то делаю не так. Прошу помочь мне в этом разобраться

      Пытался найти функцию, которая передает не массив char *, а массив void *, но такой функции в Windows нет, а в UNIX существует.
        Принимай не чары, а байты.
          Цитата Gonarh @
          Принимай не чары, а байты.

          Именно это я и хочу, но recv() принимает только массив char *. Может вы подскажите аналогичную функцию, работающую с массивом байт
            Цитата linuxoid @
            Именно это я и хочу, но recv() принимает только массив char *. Может вы подскажите аналогичную функцию, работающую с массивом байт


            char * это и есть байтовый массив. У тебя проблема в том, что ты используешь strcat(buffer,tmp), а надо что то типа memcpy(buffer, tmp, ReceivedBytes). А лучше используй std::vector<char>
              Вот это уже вызывает подозрения:
              ExpandedWrap disabled
                       ReceivedBytes = send(hSock,buffer,++dwRead,NULL); //Отправляю блок данных с терминатором конца сообщения в конце блока
                        if (dwRead != ReceivedBytes) //Проверка на успешную отправку

              Пример send
              Ошибка будет , если:
              ExpandedWrap disabled
                    iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
                    if (iResult == SOCKET_ERROR) {

              Если
              ExpandedWrap disabled
                 if (dwRead != ReceivedBytes)

              Это не ошибка.
              Так может быть: dwRead >= ReceivedBytes
              В этом случае надо проверять, сколько передано и остаток "досылать".
              Цитата

              Return value

              If no error occurs, send returns the total number of bytes sent, which can be less than the number requested
              to be sent
              in the len parameter.
              Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

              Сообщение отредактировано: ЫукпШ -
                Какой размер передаваемых данных?
                От этого много зависит.
                Если файлы небольшие, то есть смысл отключить алгоритм Нагла.
                Если большие, то есть смысл увеличить размер буфера передачи сокета.
                  Цитата Oleg2004 @
                  Если большие, то есть смысл увеличить размер буфера передачи сокета.

                  Удивительно, но в последней версии Fedora, буфер передачи
                  можно увеличить. Но при этом ограничение размера массива переданных
                  байт всё равно остаётся, не растёт и существенно меньше размера буфера.
                  Возможно, это баг, но ждать когда его исправят просто глупо.
                  Надёжнее контролировать число переданных байт.
                    Цитата ЫукпШ @
                    Но при этом ограничение размера массива переданных
                    байт всё равно остаётся, не растёт и существенно меньше размера буфера.
                    Возможно, это баг, но ждать когда его исправят просто глупо.

                    Да, странная ситуация.
                    Тогда контроль - по старинке - :) надежнее.
                      Цитата ЫукпШ @
                      Вот это уже вызывает подозрения:
                      ExpandedWrap disabled
                               ReceivedBytes = send(hSock,buffer,++dwRead,NULL); //Отправляю блок данных с терминатором конца сообщения в конце блока
                                if (dwRead != ReceivedBytes) //Проверка на успешную отправку

                      Пример send
                      Ошибка будет , если:
                      ExpandedWrap disabled
                            iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
                            if (iResult == SOCKET_ERROR) {

                      Если
                      ExpandedWrap disabled
                         if (dwRead != ReceivedBytes)

                      Это не ошибка.
                      Так может быть: dwRead >= ReceivedBytes
                      В этом случае надо проверять, сколько передано и остаток "досылать".
                      Цитата

                      Return value

                      If no error occurs, send returns the total number of bytes sent, which can be less than the number requested
                      to be sent
                      in the len parameter.
                      Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

                      char * это и есть байтовый массив. У тебя проблема в том, что ты используешь strcat(buffer,tmp), а надо что то типа memcpy(buffer, tmp, ReceivedBytes). А лучше используй std::vector<char>

                      У меня размер буфера составляет 256 байт, кажется, что 256 байт за один раз функция send сможет послать, поэтому не проверяю на то ушли все данные или нет.

                      Добавлено
                      Цитата Олег М @
                      Цитата linuxoid @
                      Именно это я и хочу, но recv() принимает только массив char *. Может вы подскажите аналогичную функцию, работающую с массивом байт


                      char * это и есть байтовый массив. У тебя проблема в том, что ты используешь strcat(buffer,tmp), а надо что то типа memcpy(buffer, tmp, ReceivedBytes). А лучше используй std::vector<char>

                      Идея хорошая, не обратил внимание, что использую strcat. Лучше использовать, как вы и предложили, memcpy(). Попробую это и о результате сообщу, но что-то мне подсказывает, что это решение проблемы
                      Сообщение отредактировано: linuxoid -
                        Цитата linuxoid @
                        У меня размер буфера составляет 256 байт, кажется, что 256 байт за один раз функция send сможет послать, поэтому не проверяю на то ушли все данные или нет.

                        Что то мне это напоминает эту ситуацию:
                        Цитата
                        Маленькие пакеты (называемые тиниграммами, от английского tiny – крошечный, маленький) – обычно не проблема для локальных сетей, так как большинство локальных сетей не перегружаются, однако они могут привести к перегрузке глобальной сети. В RFC 896 Джон Нейгл (John Nagle,1984), предложил свое решение этой проблемы, которое называется алгоритмом Нейгла (Nagle algorithm).
                        Из алгоритма следует, что в TCP-соединении может присутствовать только один исходящий "маленький" сегмент, который еще не был подтвержден.
                        Модуль TCP относит к "маленьким" все пакеты, чей размер меньше MSS. Следующие подобные сегменты могут быть посланы только после того, как было получено подтверждение. Поэтому, вместо того, чтобы отправить их сразу, малые порции данных накапливаются в буфере передачи и отправляются одним TCP-сегментом, лишь когда прибывает подтверждение на первый пакет. Алгоритм получается самонастраивающимся: чем быстрее придет подтверждение, тем быстрее будут отправлены данные. В результате в медленных глобальных сетях, где необходимо уменьшить количество небольших пакетов, отправляется меньше сегментов.
                        ..............
                        Вывод может быть сделан следующий: если по каким-то причинам в сеть посылаются небольшие пакеты, то отключение алгоритма приведет к заполнению сети этими пакетами; если же алгоритм включен, количество пакетов резко уменьшается, но время суммарной доставки данных увеличивается. По умолчанию в модуле TCP алгоритм Nagle включен.

                        Посмотрите свой MSS и поймете что файлик в 256 байтов это уже меньше чем MSS
                        Сообщение отредактировано: Oleg2004 -
                          Проблема явно не в плоскости описанного рфц, т.к. он относится к т.н. "контролю перегрузок", этот механизм лежит гораздо ниже уровня работы программы ТС, и никак на него не влияет. У меня клиент-серверное приложение обменивается пакетами до 200 байт, всё работает нормально, ничего не теряется.
                            Цитата Gonarh @
                            т.к. он относится к т.н. "контролю перегрузок", этот механизм лежит гораздо ниже уровня работы программы ТС

                            Мы наверно про разное. :)
                            Да только RFC 3168 - CWR (Congestion Window Reduced) — Поле «Окно перегрузки уменьшено»
                            и
                            ECE (ECN-Echo) — Поле «Эхо ECN»
                            к алгоритму Нэйгла не относится.
                            Впрочем, у ТС могут быть и свои заморочки, о которых он просто умалчивает. Так с ним уже было в одной недавней теме. :(
                            А что до вашего приложения, то там наверно TCP_NODELAY установлен. Или реализация стека TCP/IP не Rеnо и не Vegas и уже подправлено :)
                            Потому что приложения типа rlogin уже сейчас практически не используются.
                            Ну не суть...ждем ТС :)
                              Попробовал я копировать в свой буфер путем memcpy() и результат остался прежним. Посмотрел что считывается с сокета на стороне получателя и там приходят данные только до NULL - символа. Приходит обрезанное сообщение
                                Цитата linuxoid @
                                приходят данные только до NULL - символа


                                1) Забыть про все функции, начинающияе с str*! Заменить их на на соответствующие реализации с mem*
                                2) Забыть про терминатор %! А если он - часть передаваемых данных?

                                Лучше предусмотреть контроль длины передаваемых данных иным способом.
                                Например. Сперва передается длина, которая "расшифровывается" следующим образом:
                                a) Переданный первый байт меньше или равен 0x0F - указывает длину последующих передаваемых данных явно своим значением
                                б) Переданный первый байт равен 0x1F - указывает что второй байт так же указывает длину, и длина равна 0x1F+значение 2-го байта
                                в) Переданный первый байт равен 0x2F - указывает что второй и третий байт так же указывает длину, и длина равна 0x1F+uint16(значение 2-го и 3-го) байта
                                ... и т.д.

                                Тогда контроль длины переданного и полученного не будет зависеть от "внутренностей" передаваемого. По желанию - можно еще передавать контрольную сумму сразу после длины. Ну это уже все зависит от фантазии.

                                А так, не мудрено, что не работает.
                                  Цитата JoeUser @
                                  Цитата linuxoid @
                                  приходят данные только до NULL - символа


                                  1) Забыть про все функции, начинающияе с str*! Заменить их на на соответствующие реализации с mem*
                                  2) Забыть про терминатор %! А если он - часть передаваемых данных?

                                  Лучше предусмотреть контроль длины передаваемых данных иным способом.
                                  Например. Сперва передается длина, которая "расшифровывается" следующим образом:
                                  a) Переданный первый байт меньше или равен 0x0F - указывает длину последующих передаваемых данных явно своим значением
                                  б) Переданный первый байт равен 0x1F - указывает что второй байт так же указывает длину, и длина равна 0x1F+значение 2-го байта
                                  в) Переданный первый байт равен 0x2F - указывает что второй и третий байт так же указывает длину, и длина равна 0x1F+uint16(значение 2-го и 3-го) байта
                                  ... и т.д.

                                  Тогда контроль длины переданного и полученного не будет зависеть от "внутренностей" передаваемого. По желанию - можно еще передавать контрольную сумму сразу после длины. Ну это уже все зависит от фантазии.

                                  А так, не мудрено, что не работает.

                                  "%" использовал на скорую руку, далее я бы сделал все примерно так, как вы описываете. Но проблема возникает на этом этапе.
                                  На Хосте А у меня формируется сообщение, к примеру, = "ver33\0vdv4\0,hjmk,\0fsdf\0\svdf\0sf23453dfg\0\0\0fghfgh\0";
                                  Отправляю его на Хост B при помощи функции send() и на хост B приходит сообщение = "ver33\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\\0\0\0\0\0"

                                  Грешил тоже, что использую str* (использовал их потому что функция send передает char * и только его) заменил на mem*, но суть остается все той же.

                                  Добавлено
                                  Ну или проще говоря, как мне передать любое изображение при помощи сокетов без подключения сторонних библиотек. Хотелось бы организовать такую передачу самому
                                  0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                  0 пользователей:


                                  Рейтинг@Mail.ru
                                  [ Script execution time: 0,0581 ]   [ 17 queries used ]   [ Generated: 28.03.24, 22:28 GMT ]