Показать полную графическую версию : [решено] Реализация обновления программы - ClientSocket и ClientServer
Привет. Заранее приношу извинение если буду использовать неверную терминологию, но до недавнего времени у меня небыло необходимости в использовании этих компонентов и самой технологии. Задача в общем-то такова, есть клиет-программа, на сервере есть обновления к этой программе, нужно по нажатию на кнопку\пункт меню "Обновить", чтобы программа скачала только те модули, которые новее тех, которые присутствуют рядом с программой. Аналог такого обновления, любой антивирус или 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
Спасибо.
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;
}
ganselo, спасибо, но если можно подробнее, куда и как этот код нужно подставить? Я не понимаю. :dont-know
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;
}
ganselo, То что есть комментарии это хорошо, но как правильно эту функцию объявить? Как функцию вызвать? Откуда?
куда и как этот код нужно подставить? »
Вот обработчик кнопки Обновить, как мне отсюда правильно вызывать функцю DownloadThread(LPVOID lpParam);
...
// Обновление----------------------------------------------------------
void __fastcall TForm1::UpdateClick(TObject *Sender)
{
// Как вызвать эту функцию - DownloadThread?
}
//---------------------------------------------------------------------------
...
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);
}
//---------------------------------------------------------------------------
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);
}
//---------------------------------------------------------------------------
Подключите к проекту библиотеку wininet.lib (Project->Add to project->wininet.lib).
ganselo, О, теперь всё скомпилировалось. :) Но по нажатию на кнопку Обновить, ничего не скачивается.
Думаю проблема в этом:
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).
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{ //не считали, пытаемся повторить скачивание
...
А как теперь реализовать эту часть?
Цитата 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.
Ну например:
1) проверить версию, которая лежит рядом с прогой.
2) Попытаться загрузить версию > из 1). Если ошибка, то новой версии нет иначе качаем новую. »С этим я разберусь уже, можно обновлять если размер скачиваемого обновления больше того модуля, что лежит рядом, поскольку обновлённый модуль всегда будет немного увеличиваться.
можно парсить из data.Url. »Это да, смогу тоже, ищем последний слеш "/" + 1. И это будет начало нашего имени файла.
А вот с кодом, который узнаёт размер файла, опять не могу совладать, куда и как правильно его пристроить? :not-me:
куда и как правильно его пристроить? »
Ну например после создания сессии вызывать данный код.
Код я взял с инета и увы под рукой нет компилятора(... не могу проверить.
Ну например после создания сессии вызывать данный код. »Я так тоже подумал, но при компиляции, нагнуло 26 ошибок. И что там исправлять на что, тоже без понятия.
Вот вариант без WinInet. Используем TIdHTTP.
Изменяюсь... Не всё за архивировал.
ganselo, Да, но там только исполнимый файл, и не хватает ещё одного файла .cpp, как раз главного. :)
ganselo, Спасибо, вопрос решён окончательно! :up: Из проекта нужно удалить LIBMYSQL.LIB и всё будет в порядке, я когда первый раз открыл проект, мне нагнуло ошибок, что мол, у вас нет компонента TIdHTTP, а вкладки Indy Clients у меня действительно не было, результат убирания неиспользуемых компонентов, пытался доустановить - результат нулевой, сносил билдер, уже нервничать начал, запустил в конце концов C++ Builder 2009 там всё есть, чуть подправил, всё компилится, но заставить работать так и не удалось, по всей видимости это связано с типами данных\переменных, а в этой среде я только-только осваиваюсь. В общем переустановил ещё раз Builder 6.0 все вкладки появились, скомпилилось и работает. Спасибо, ganselo!!!
P.S. Не удаляй пока что архив. Я его ещё раз перезакачаю.
Из проекта нужно удалить LIBMYSQL.LIB »
У меня в билдере используется данная либа.
В общем переустановил ещё раз Builder 6.0 все вкладки появились, скомпилилось и работает. »
Ну я писал всё это в Builder 6.0.
© OSzone.net 2001-2012
vBulletin v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.