Войти

Показать полную графическую версию : [решено] Реализация обновления программы - ClientSocket и ClientServer


Страниц : [1] 2

Drongo
14-04-2010, 20:43
Привет. Заранее приношу извинение если буду использовать неверную терминологию, но до недавнего времени у меня небыло необходимости в использовании этих компонентов и самой технологии. Задача в общем-то такова, есть клиет-программа, на сервере есть обновления к этой программе, нужно по нажатию на кнопку\пункт меню "Обновить", чтобы программа скачала только те модули, которые новее тех, которые присутствуют рядом с программой. Аналог такого обновления, любой антивирус или AVZ. Поискал в сети, нашёл статью - Передача файлов в C++Builder через TClientSocket и TServerSocket (http://devoid.com.ua/c-builder/cppbuilder-network-programming/peredacha-failov-v-c-builder-cherez-socket.html). По примеру сделал тестовую программу, в рамках одного компьютера она работает, но там, в примере, есть клиентская часть и серверная.

Вот представьте, что наше обновление - этот файл - QuickKiller_2.20.7z нужно скачать и находится он здесь - http://tools.oszone.net/Drongo/QuickKiller_2.20.7z. Но скачивать нужно как обновление (как менеджер закачек), а не через диалоговое окно "Сохранить файл".

Архив простенького проекта прикрепил, собирал по примеру выше, при нажатии на кнопку "Скачать" устанавливает соединение и на этом всё. Собственно, я не знаю в какую сторону смотреть и те ли компоненты, которые я выбрал, нужны для этих целей? Среда разработки
Borland C++ Builder 6.0 Enterprise Suite
Спасибо.

ganselo
15-04-2010, 00:19
ClientSocket работает на низком уровне (т.е напрямую по TCP), а файлик лежит в WEB. Думаю нужно использовать библиотеку WinInet.
Вот кусок кода осуществляющий скачку с Web используя WinInet

DWORD WINAPI DownloadThread(LPVOID lpParam)
{
DOWNLOAD_DATA data = *(DOWNLOAD_DATA *)lpParam;
HINTERNET hInetSession;
HINTERNET hInetFile;
HANDLE hFile;
OVERLAPPED ovlp;
DWORD dwOffset = 0;
DWORD dwRead;
TCHAR ReadBuf[4*1024];

lstrcpy((LPTSTR)&data, NULL);
hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)",
PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);
if(hInetSession == NULL)
{
WritePrivateProfileString(data.FileName, "URL", data.Url, IniFile);
data.sock->SendFormatText(0, "INET_OPEN_ERROR\r\n%li", GetLastError());
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL);
if(hInetFile == NULL)
{
WritePrivateProfileString(data.FileName, "URL", data.Url, IniFile);
data.sock->SendFormatText(0, "INET_OPEN_URL_ERROR\r\n%li", GetLastError());
return -1;
}

hFile = CreateFile(data.FullFileName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if(BAD_HANDLE(hFile))
{
data.sock->SendFormatText(0, "SAVE_FILE_OPEN_ERROR\r\n%li", GetLastError());
InternetCloseHandle(hInetSession);
InternetCloseHandle(hInetFile);
return -1;
}

if(data.dwOffset == 1)
InternetSetFilePointer(hInetFile, GetFileSize(hFile, NULL), NULL, FILE_BEGIN, 0);

data.sock->SendFormatText(0, "DOWNLOAD_BEGIN\r\n%s", data.FileName);
do
{
InternetReadFile(hInetFile, ReadBuf, sizeof(ReadBuf), &dwRead);
if(dwRead >= 0)
{
ovlp.hEvent = NULL;
ovlp.OffsetHigh = NULL;
ovlp.Offset = GetFileSize(hFile, NULL);

WriteFile(hFile, ReadBuf, dwRead, NULL, &ovlp);
dwOffset += dwRead;
}
else
{
InternetCloseHandle(hInetSession);
InternetCloseHandle(hInetFile);

hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)",
PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);
if(hInetSession == NULL)
{
WritePrivateProfileString(data.FileName, "URL", data.Url, IniFile);
data.sock->SendFormatText(0, "INET_OPEN_ERROR\r\n%li", GetLastError());
CloseHandle(hFile);
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL);
if(hInetFile == NULL)
{
WritePrivateProfileString(data.FileName, "URL", data.Url, IniFile);
data.sock->SendFormatText(0, "INET_OPEN_URL_ERROR\r\n%li", GetLastError());
CloseHandle(hFile);
InternetCloseHandle(hInetSession);
return -1;
}

InternetSetFilePointer(hInetFile, dwOffset, NULL, FILE_BEGIN, NULL);
}
}
while(dwRead);

CloseHandle(hFile);
InternetCloseHandle(hInetSession);
InternetCloseHandle(hInetFile);
data.sock->SendFormatText(0, "DOWNLOAD_END\r\n%s", data.FileName);
return 1;
}

Drongo
15-04-2010, 12:32
ganselo, спасибо, но если можно подробнее, куда и как этот код нужно подставить? Я не понимаю. :dont-know

ganselo
15-04-2010, 15:24
Drongo, я этот код выдернул из своего примитивного менеджера закачек.
Вот код с комментами:


struct DOWNLOAD_DATA
{
char Url[1024]; //ссылка на файл
BOOL bFlag; // если TRUE, то осуществляется докачка
};
//закачку я осуществлял в отдельном потоке.
DWORD WINAPI DownloadThread(LPVOID lpParam)
{
DOWNLOAD_DATA data = *(DOWNLOAD_DATA *)lpParam;
HINTERNET hInetSession;
HINTERNET hInetFile;
HANDLE hFile;
OVERLAPPED ovlp;
DWORD dwOffset = 0;
DWORD dwRead;
TCHAR ReadBuf[4*1024];

hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)",
PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0); //Возвращает хэндл сессии
if(hInetSession == NULL) //тут всё понятно
{
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL); //по сути открываем файл
if(hInetFile == NULL) //снова проверяем
{
return -1;
}

hFile = CreateFile(data.FullFileName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL); //открываем файл для сохранения
if(hFile == INVALID_HANDLE_VALUE) //проверяем
{
InternetCloseHandle(hInetSession); //закрываем сессию
InternetCloseHandle(hInetFile); //закрываем файл
return -1;
}

if(data.bFlag) //осуществляется докачка
InternetSetFilePointer(hInetFile, GetFileSize(hFile, NULL), NULL, FILE_BEGIN, 0); //сдвигаем чтение на GetFileSize бит

//начинаем скачку
do
{
InternetReadFile(hInetFile, ReadBuf, sizeof(ReadBuf), &dwRead); //читаем в буффер
if(dwRead >= 0) //если считали
{
ovlp.hEvent = NULL;
ovlp.OffsetHigh = NULL;
ovlp.Offset = GetFileSize(hFile, NULL);

WriteFile(hFile, ReadBuf, dwRead, NULL, &ovlp); //пишем в наш файл
dwOffset += dwRead;
}
else //не считали, пытаемся повторить скачивание
{
InternetCloseHandle(hInetSession); //закрываем сессию
InternetCloseHandle(hInetFile); //закрываем удалённый файл

hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)",
PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);//пытаемся повторить скачку
if(hInetSession == NULL)
{
CloseHandle(hFile);
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL);
if(hInetFile == NULL)
{
CloseHandle(hFile);
InternetCloseHandle(hInetSession);
return -1;
}

InternetSetFilePointer(hInetFile, dwOffset, NULL, FILE_BEGIN, NULL);
}
}
while(dwRead);

//закрываем всё лишнее
CloseHandle(hFile);
InternetCloseHandle(hInetSession);
InternetCloseHandle(hInetFile);
return 1;
}

Drongo
15-04-2010, 16:15
ganselo, То что есть комментарии это хорошо, но как правильно эту функцию объявить? Как функцию вызвать? Откуда?
куда и как этот код нужно подставить? »
Вот обработчик кнопки Обновить, как мне отсюда правильно вызывать функцю DownloadThread(LPVOID lpParam);
...
// Обновление----------------------------------------------------------
void __fastcall TForm1::UpdateClick(TObject *Sender)
{
// Как вызвать эту функцию - DownloadThread?
}
//---------------------------------------------------------------------------
...

ganselo
15-04-2010, 19:15
void __fastcall TForm1::UpdateClick(TObject *Sender)
{
HANDLE hThread;
DOWNLOAD_DATA data;

strcpy(data.Url, "ссылка на файл");
data.bFlag = false;
hThread = CreateThread(NULL, 0, DownloadThread, &data, 0, NULL);
if(hThread == INVALID_HANDLE_VALUE)
{
ShowMessage("Error. Can't create thread!");
return;
}
CloseHandle(hThread);
}
//---------------------------------------------------------------------------

Drongo
15-04-2010, 21:38
ganselo, Вот такая ошибка компоновщика получается.

[Компоновщик Ошибка] Unresolved external 'InternetReadFile' referenced from C:\PROGRAM FILES\BORLAND\CBUILDER6\PROJECTS\CLIENTSOCKET\CLIENTSOCKET1.OBJ
[Компоновщик Ошибка] Unresolved external 'InternetOpenA' referenced from C:\PROGRAM FILES\BORLAND\CBUILDER6\PROJECTS\CLIENTSOCKET\CLIENTSOCKET1.OBJ
[Компоновщик Ошибка] Unresolved external 'InternetOpenUrlA' referenced from C:\PROGRAM FILES\BORLAND\CBUILDER6\PROJECTS\CLIENTSOCKET\CLIENTSOCKET1.OBJ
[Компоновщик Ошибка] Unresolved external 'InternetSetFilePointer' referenced from C:\PROGRAM FILES\BORLAND\CBUILDER6\PROJECTS\CLIENTSOCKET\CLIENTSOCKET1.OBJ
[Компоновщик Ошибка] Unresolved external 'InternetCloseHandle' referenced from C:\PROGRAM FILES\BORLAND\CBUILDER6\PROJECTS\CLIENTSOCKET\CLIENTSOCKET1.OBJВот такой код использую. Заголовочный файл WinInet.h включён. Что я делаю не так?
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "ClientSocket1.h"
#include <WinInet.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

struct DOWNLOAD_DATA
{
char Url[1024]; //ссылка на файл
BOOL bFlag; // если TRUE, то осуществляется докачка
};

//закачку я осуществлял в отдельном потоке.
DWORD WINAPI DownloadThread(LPVOID lpParam)
{
DOWNLOAD_DATA data = *(DOWNLOAD_DATA *)lpParam;
HINTERNET hInetSession;
HINTERNET hInetFile;
HANDLE hFile;
OVERLAPPED ovlp;
DWORD dwOffset = 0;
DWORD dwRead;
TCHAR ReadBuf[4*1024];

hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)", PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0); //Возвращает хэндл сессии
if(hInetSession == NULL){ //тут всё понятно
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL); //по сути открываем файл
if(hInetFile == NULL){ //снова проверяем
return -1;
}

hFile = CreateFile(data.Url, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //открываем файл для сохранения
if(hFile == INVALID_HANDLE_VALUE){ //проверяем
InternetCloseHandle(hInetSession); //закрываем сессию
InternetCloseHandle(hInetFile); //закрываем файл
return -1;
}

if(data.bFlag) //осуществляется докачка
InternetSetFilePointer(hInetFile, GetFileSize(hFile, NULL), NULL, FILE_BEGIN, 0); //сдвигаем чтение на GetFileSize бит

//начинаем скачку
do{
InternetReadFile(hInetFile, ReadBuf, sizeof(ReadBuf), &dwRead); //читаем в буффер
if(dwRead >= 0){ //если считали
ovlp.hEvent = NULL;
ovlp.OffsetHigh = NULL;
ovlp.Offset = GetFileSize(hFile, NULL);
WriteFile(hFile, ReadBuf, dwRead, NULL, &ovlp); //пишем в наш файл
dwOffset += dwRead;
}
else{ //не считали, пытаемся повторить скачивание
InternetCloseHandle(hInetSession); //закрываем сессию
InternetCloseHandle(hInetFile); //закрываем удалённый файл
hInetSession = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914)", PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);//пытаемся повторить скачку
if(hInetSession == NULL){
CloseHandle(hFile);
return -1;
}

hInetFile = InternetOpenUrl(hInetSession, data.Url, NULL, 0, 0, NULL);
if(hInetFile == NULL){
CloseHandle(hFile);
InternetCloseHandle(hInetSession);
return -1;
}

InternetSetFilePointer(hInetFile, dwOffset, NULL, FILE_BEGIN, NULL);
}
}
while(dwRead);

//закрываем всё лишнее
CloseHandle(hFile);
InternetCloseHandle(hInetSession);
InternetCloseHandle(hInetFile);
return 1;
}
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------------------------
void __fastcall TForm1::UpdateClick(TObject *Sender)
{
HANDLE hThread;
DOWNLOAD_DATA data;

strcpy(data.Url, "http://tools.oszone.net/Drongo/QuickKiller_2.20.7z");
data.bFlag = false;
hThread = CreateThread(NULL, 0, DownloadThread, &data, 0, NULL);
if(hThread == INVALID_HANDLE_VALUE)
{
ShowMessage("Error. Can't create thread!");
return;
}
CloseHandle(hThread);
}
//---------------------------------------------------------------------------

ganselo
15-04-2010, 23:54
Подключите к проекту библиотеку wininet.lib (Project->Add to project->wininet.lib).

Drongo
16-04-2010, 10:40
ganselo, О, теперь всё скомпилировалось. :) Но по нажатию на кнопку Обновить, ничего не скачивается.

ganselo
16-04-2010, 12:52
Думаю проблема в этом:

hFile = CreateFile(data.Url, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //открываем файл для сохранения

Первый параметр CreateFile - это название файла (в данном случае, если файл не существует, то CreateFile создаёт его). Попробуйте вместо data.Url написать например "C:\\QuickKiller_2.20.7z". (Название файла лучше парсить из data.Url).

Drongo
16-04-2010, 15:09
ganselo, Ух, класс, здорово. Получилось. :up: Задача макси решена.

А как теперь реализовать эту часть?чтобы программа скачала только те модули, которые новее тех, которые присутствуют рядом с программой »

И как правильно сделать ход загрузки? По логике я должен узнать размер файла в байтах и задать это значение ProgressBar. Только, как я понял, CreateFile создаёт файл на диске и поэтому если так делать как я делаю, будет нулевой размер, а как правильн узнать размер скачиваемого файла, дату создания и имя?
...
if(data.bFlag) //осуществляется докачка
InternetSetFilePointer(hInetFile, GetFileSize(hFile, NULL), NULL, FILE_BEGIN, 0); //сдвигаем чтение на GetFileSize бит

// Ход загрузки
Form1->ProgressBar1->Position = 0;
Form1->ProgressBar1->Max = GetFileSize(hFile, NULL);

//начинаем скачку
do{
Form1->ProgressBar1->StepIt(); // Добавил ход загрузки

InternetReadFile(hInetFile, ReadBuf, sizeof(ReadBuf), &dwRead); //читаем в буффер
if(dwRead >= 0){ //если считали
ovlp.hEvent = NULL;
ovlp.OffsetHigh = NULL;
ovlp.Offset = GetFileSize(hFile, NULL);
WriteFile(hFile, ReadBuf, dwRead, NULL, &ovlp); //пишем в наш файл
dwOffset += dwRead;
}
else{ //не считали, пытаемся повторить скачивание
...

ganselo
16-04-2010, 17:19
А как теперь реализовать эту часть?
Цитата Drongo:
чтобы программа скачала только те модули, которые новее тех, которые присутствуют рядом с программой » »

Ну например:
1) проверить версию, которая лежит рядом с прогой.
2) Попытаться загрузить версию > из 1). Если ошибка, то новой версии нет иначе качаем новую.

а как правильн узнать размер скачиваемого файла »

// открываем запрос
LPCWSTR rgszAcceptTypes[2] = {pstLRF_Params->szDocumentType,NULL};
hRequest = ::HttpOpenRequest(hSessiont, L"GET", szUrlPath, L"HTTP/1.1", NULL,rgszAcceptTypes,
INTERNET_FLAG_KEEP_CONNECTION,1);
if (hRequest == NULL) throw L"HttpOpenRequest Error";

// посылаем запрос
fResult= ::HttpSendRequest(hRequest, NULL,0, NULL,0);
if (!fResult) throw L"HttpSendRequest Error";

// получаем информацию о размере данных
fResult = HttpQueryInfo(hRequest,HTTP_QUERY_CONTENT_LENGTH|HTTP_QUERY_FLAG_NUMBER,&dwFileLength,&dwDwordLength,NULL);
if (!fResult && (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND))
dwFileLength = (DWORD)pstLRF_Params->uFileLength;


дату создания и имя? »
Не уверен, что можно узнать дату создания файла, а имя (это вы про названия файла?) можно парсить из data.Url.

Drongo
16-04-2010, 19:25
Ну например:
1) проверить версию, которая лежит рядом с прогой.
2) Попытаться загрузить версию > из 1). Если ошибка, то новой версии нет иначе качаем новую. »С этим я разберусь уже, можно обновлять если размер скачиваемого обновления больше того модуля, что лежит рядом, поскольку обновлённый модуль всегда будет немного увеличиваться.
можно парсить из data.Url. »Это да, смогу тоже, ищем последний слеш "/" + 1. И это будет начало нашего имени файла.

А вот с кодом, который узнаёт размер файла, опять не могу совладать, куда и как правильно его пристроить? :not-me:

ganselo
16-04-2010, 19:33
куда и как правильно его пристроить? »
Ну например после создания сессии вызывать данный код.
Код я взял с инета и увы под рукой нет компилятора(... не могу проверить.

Drongo
16-04-2010, 19:49
Ну например после создания сессии вызывать данный код. »Я так тоже подумал, но при компиляции, нагнуло 26 ошибок. И что там исправлять на что, тоже без понятия.

ganselo
17-04-2010, 00:21
Вот вариант без WinInet. Используем TIdHTTP.


Изменяюсь... Не всё за архивировал.

Drongo
17-04-2010, 13:39
ganselo, Да, но там только исполнимый файл, и не хватает ещё одного файла .cpp, как раз главного. :)

ganselo
17-04-2010, 13:55
Drongo, перезалил.

Drongo
17-04-2010, 18:48
ganselo, Спасибо, вопрос решён окончательно! :up: Из проекта нужно удалить LIBMYSQL.LIB и всё будет в порядке, я когда первый раз открыл проект, мне нагнуло ошибок, что мол, у вас нет компонента TIdHTTP, а вкладки Indy Clients у меня действительно не было, результат убирания неиспользуемых компонентов, пытался доустановить - результат нулевой, сносил билдер, уже нервничать начал, запустил в конце концов C++ Builder 2009 там всё есть, чуть подправил, всё компилится, но заставить работать так и не удалось, по всей видимости это связано с типами данных\переменных, а в этой среде я только-только осваиваюсь. В общем переустановил ещё раз Builder 6.0 все вкладки появились, скомпилилось и работает. Спасибо, ganselo!!!

P.S. Не удаляй пока что архив. Я его ещё раз перезакачаю.

ganselo
17-04-2010, 19:32
Из проекта нужно удалить LIBMYSQL.LIB »
У меня в билдере используется данная либа.

В общем переустановил ещё раз Builder 6.0 все вкладки появились, скомпилилось и работает. »
Ну я писал всё это в Builder 6.0.




© OSzone.net 2001-2012