Télécharger un fichier (win32, sockets)

Soyez le premier à donner votre avis sur cette source.

Vue 17 709 fois - Téléchargée 1 820 fois

Description

Voici un petit programme qui se connecte à un serveur http et qui télécharge un fichier (a partir d'une URL).
Une progressbar donne une idée de la progression du téléchargement. La vitesse de téléchargement est affichée, ainsi que le temp restant (théorique).

Lorsque l'on entre une URL et que l'on apuis sur Entrer, le programme lance un thread qui se charge du téléchargement. Le modele d'utilisation de winsock choisi est WSAEventSelect(...).
Le fichier téléchargé est copié dans 'mes documents'.

Source / Exemple :


#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include "resource.h"
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "comctl32.lib")

#define BUF_SIZE 8192 // 8KO
#define M(szTXT) SetWindowText(g_hSTA, szTXT)

HWND g_hSTA, g_hBAR, g_hSpeed, g_hDlg;
BOOL g_bDownloading = false;

BOOL CALLBACK AppDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI DownloadProc(void * szURL);
DWORD GetServerAddress(char * szURL);
int ParseHeaderHttp(char * buffer, int buffersize, int * sizeoffile);
char * GetFileNameFromURL(char * szURL);
int GetDocumentsDirectory(char * szbuf, int bufsize);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	WSADATA wsa;
	if(WSAStartup(0x0202, &wsa)) return 0;
	DialogBoxParam(hInstance, "MainDialog", 0, AppDlgProc, 0);
	WSACleanup();
	return 0;
}

BOOL CALLBACK AppDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HANDLE hThread = 0;
	static HWND hURL;
	static char szURL[256];
	switch(uMsg) 
	{
	case WM_INITDIALOG:
		InitCommonControls();
		SetClassLong(hDlg, GCL_HICON,(long) "icone");
		g_hDlg = hDlg;
		hURL = GetDlgItem(hDlg, IDC_URL);
		g_hBAR = GetDlgItem(hDlg, IDC_BAR);
		g_hSpeed = GetDlgItem(hDlg, IDC_SPEED);
		g_hSTA = GetDlgItem(hDlg, IDC_STA);
		SetWindowText(g_hDlg, "Téléchargement");
	return 1;
	case WM_COMMAND:
		switch(LOWORD(wParam)) 
		{
		case IDOK:
			if(!g_bDownloading)
			{
				g_bDownloading = true;
				GetWindowText(hURL, szURL, 256);
				SendMessage(g_hBAR, PBM_SETPOS, 0, 0);
				if(hThread) CloseHandle(hThread);
				hThread = CreateThread(0, 0, DownloadProc, szURL, 0, 0);
			}
			return 0;
		case IDCANCEL:
			if(g_bDownloading)
			{
				if(IDNO == MessageBox(hDlg, "Fichier actuellement en cours de téléchargement.\r\nEtes vous sur de vouloir quitter ?", "Téléchargement en cours", MB_YESNO|MB_ICONWARNING))
					return 0;
			}
			CloseHandle(hThread);
			EndDialog(hDlg, 0);
		}
	}
return 0;
}

DWORD WINAPI DownloadProc(void * szURL)
{
	SOCKET s;
	sockaddr_in sin;
	WSAEVENT hEvent[1];
	WSANETWORKEVENTS NetworkEvent;
	HANDLE hFile;
	BYTE buffer[BUF_SIZE], *pBuffer;
	DWORD dwSize = 0, dwRecu = 0, dwEcrit = 0, dwEcritTotal = 0;
	DWORD dwLastEcritTotal = 0, dwDebit = 0, dwLastTickCount = 0;
	DWORD dwTickCountDebut = 0, dwTempRestant = 0, dwDebitMoyen = 0;
	char szTEMP[256], szLOCAL[256],  *szFileName;
	bool bHttpHeader = false;

	// I: initialisation de la connexion
	s = socket(AF_INET, SOCK_STREAM, 0);
	if(s == -1) {
		M("Erreur création socket."); 
		goto err_socket;}

	sin.sin_family = AF_INET;
	sin.sin_port   = htons(80);
	sin.sin_addr.S_un.S_addr = GetServerAddress((char*)szURL);

	hEvent[0] = WSACreateEvent();
	WSAEventSelect(s, hEvent[0], FD_READ | FD_CONNECT | FD_CLOSE);

	connect(s, (sockaddr*)&sin, sizeof(sin));
	M("Connexion au serveur...");

	// II: initialisation du fichier local 
	if(!GetDocumentsDirectory(szTEMP, 256)) strcpy(szTEMP, "c:\\");
	strcpy(szLOCAL, szTEMP);
	szFileName = GetFileNameFromURL((char*) szURL);
	strcat(szLOCAL, szFileName);
	hFile = CreateFile(szLOCAL, FILE_ALL_ACCESS, FILE_SHARE_WRITE, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
	if(hFile == INVALID_HANDLE_VALUE)
	{
		M("Erreur fichier.");
		goto err_socket;
	}

	// III: connexion, déconnexion, reception du fichier
	while(1)
	{	
		memset(&NetworkEvent, 0, sizeof(NetworkEvent));

		WSAWaitForMultipleEvents(1, hEvent, 0, WSA_INFINITE, 0);
		WSAEnumNetworkEvents(s, hEvent[0], &NetworkEvent);

		if(NetworkEvent.lNetworkEvents & FD_CONNECT)
		{
			// erreur a la connexion
			if(NetworkEvent.iErrorCode[FD_CONNECT_BIT]) {
				M("Erreur lors de la connexion.");
				CloseHandle(hFile); DeleteFile(szLOCAL);
				goto err_socket;}

			// si connexion ok, envoi de la requette
			strcpy(szTEMP, "GET ");
			strcat(szTEMP,(const char*) szURL);
			strcat(szTEMP, " HTTP/1.0\r\n\r\n");
			send(s, szTEMP, strlen(szTEMP), 0);
			M("Envoi de la requette http au serveur...");
		}

		if(NetworkEvent.lNetworkEvents & FD_READ)
		{
			dwRecu = 0;
			pBuffer = buffer;
			dwRecu = recv(s,(char*) buffer, BUF_SIZE, 0);

			//traitement du header
			if(!bHttpHeader)
			{
				int iHeaderLen;
				dwLastTickCount = dwTickCountDebut = GetTickCount(); // debut du download
				iHeaderLen = ParseHeaderHttp((char*)buffer, BUF_SIZE,(int*) &dwSize);
				if(!iHeaderLen)
				{
					M("Fichier introuvable.");
					CloseHandle(hFile); DeleteFile(szLOCAL);
					goto err_socket;
				}
				pBuffer += iHeaderLen; 
				dwRecu  -= iHeaderLen;
				bHttpHeader = true;
				M("Header HTTP recu.");
			}

			// ecriture dans le fichier
			WriteFile(hFile, pBuffer, dwRecu, &dwEcrit, 0);
			dwEcritTotal += dwEcrit;
			
			// affichage progressbar, infos, vitesse & temp restant
			SendMessage(g_hBAR, PBM_SETPOS, (int)(100 * ((double)dwEcritTotal/(double)dwSize)), 0);

			sprintf(szTEMP, "%s: %d/%d", szFileName, dwEcritTotal, dwSize);
			M(szTEMP);

			if(dwLastTickCount+1000 < GetTickCount())
			{
				// vitesse download
				dwDebit = (dwEcritTotal - dwLastEcritTotal) / 1024;
				dwLastTickCount = GetTickCount();
				dwLastEcritTotal = dwEcritTotal;
				sprintf(szTEMP, "%d ko/s", dwDebit);
				SetWindowText(g_hSpeed, szTEMP);

				// temp restant
				dwDebitMoyen = dwEcritTotal / (GetTickCount()-dwTickCountDebut+1);
				dwTempRestant = (dwSize-dwEcritTotal) / (dwDebitMoyen * 1000 + 1);
				sprintf(szTEMP, "Téléchargement [%d %s]", 
					     (dwTempRestant >= 60) ? dwTempRestant/60 : dwTempRestant,
					     (dwTempRestant >= 60) ? "min" : "sec");
				SetWindowText(g_hDlg, szTEMP);
			}

			// téléchargement fini
			if(dwSize == dwEcritTotal)
			{
				M("Téléchargement terminé.");
				goto err_file;
			}
		}

		if(NetworkEvent.lNetworkEvents & FD_CLOSE)
		{
			// verifie s'il ne reste plus rien a lire sur le socket
			do {
				memset(&NetworkEvent, 0, sizeof(NetworkEvent));
				WSAEnumNetworkEvents(s, 0, &NetworkEvent);
				if(NetworkEvent.lNetworkEvents & FD_READ)
				{
					dwRecu = 0;
					dwRecu = recv(s,(char*) buffer, BUF_SIZE, 0);
					WriteFile(hFile, buffer, dwRecu, &dwEcrit, 0);
					dwEcritTotal += dwEcrit;
					if(dwSize == dwEcritTotal) break;
				}
			}while(NetworkEvent.lNetworkEvents & FD_READ);
	
			if(dwSize == dwEcritTotal) M("Téléchargement terminé.");
			else M("Déconnecté du serveur.");
			goto err_file;
		}
	}

err_file:
	CloseHandle(hFile);
err_socket:
	closesocket(s);
	WSACloseEvent(hEvent[0]);
	SendMessage(g_hBAR, PBM_SETPOS, 0, 0);
	SetWindowText(g_hSpeed, 0);
	g_bDownloading = false;
	return 0;
}

// retourne un DWORD directement copiable dans sin_addr.S_un.S_addr (ip du serveur)
// szURL doit etre dans un buffer
DWORD GetServerAddress(char * szURL)
{
	hostent * serv;
	char * z;              // z mettra le zero final
	char * d = szURL;      // d pointera sur le debut du nom du serveur
	if(*d=='h' && *(d+1)=='t' && *(d+2)=='t' && *(d+3)=='p') d += 7; // 7 = http://
	z = d;
	while(*z != '/' && *z) z++; *z = 0; 
	serv = gethostbyname(d);

  • z = '/';
if(!serv) return 0; return (DWORD)*((DWORD*)serv->h_addr_list[0]); } // parse la 1ere réponse du serveur http apres une requette download de fichier // extrait la taille du fichier, retourne la taille du header ou 0 si erreur int ParseHeaderHttp(char * buffer, int buffersize, int * sizeoffile) { char * c, * f; bool reponse_ok = false; c = buffer; f = c + buffersize; // c ne dois jamais depasser f while(c < f) { // on verifie que le serveur réponds bien 200 if(*c=='2' && *(c+1)=='0' && *(c+2)=='0' && *(c+3)==32) reponse_ok = true; // taille du fichier dans ligne de forme: "Content-Length: 123456" if(*c =='\n') { if(!strncmp(c+1,"Content-Length:", 15)) { c += 17;
  • sizeoffile = atoi(c);}}
// ligne vide = fin header http = debut fichier if(*c=='\r' && *(c+1)=='\n' && *(c+2)=='\r' && *(c+3)=='\n' && reponse_ok == true && *sizeoffile) { c += 4; return (int)(c - buffer);} c++; } return 0; } // retourne un pointeur sur le nom du fichier dans une URL char * GetFileNameFromURL(char * szURL) { char * c = szURL; while(*c) c++; while(c > szURL && *c != '/') c--; return (char*)++c; } // trouve le chemin de Mes Documents, rajoute le '\' final int GetDocumentsDirectory(char *szbuf, int bufsize) { HKEY hKey; int size, l; char *c; if(RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 0, KEY_QUERY_VALUE, &hKey)) return 0; size = bufsize; l = RegQueryValueEx(hKey, "Personal", 0, 0, (BYTE*) szbuf, (DWORD*) &size); RegCloseKey(hKey); if(l) return 0; c = szbuf + size - 1; if(*(c-1) != '\\') {*c++ = '\\'; *c = 0;} return (c - szbuf); }

Codes Sources

A voir également

Ajouter un commentaire Commentaires
Messages postés
69
Date d'inscription
samedi 4 novembre 2000
Statut
Membre
Dernière intervention
15 septembre 2009

Ok j'ai rien dit... 10/10 !
Messages postés
69
Date d'inscription
samedi 4 novembre 2000
Statut
Membre
Dernière intervention
15 septembre 2009

Salut,ça m'a bien l'air d'une tres bonne source pourtant ça ne compile pas (devcpp)
j'ai bien viré les pragma et linké manuellement libcomctl32.a et libwsock32.a
pourtant j'obtient des [Linker error] undefined reference to `WSACreateEvent@0' etc...
Une idée ?
Messages postés
16
Date d'inscription
jeudi 16 novembre 2006
Statut
Membre
Dernière intervention
29 janvier 2016

Oui, en fait, j'ai mieux géré les offsets, et ca a marché. Pour tester, le mieux est de faire sur un fichier txt afin de voir si les phrases sont dans le bon ordre.
Mais maintenant ca marche parfaitement, merci pour ton aide.
Messages postés
1905
Date d'inscription
mercredi 22 janvier 2003
Statut
Membre
Dernière intervention
17 septembre 2012
2
Salut,
C'est sur qu'avant d'ecrire dans le fichier, il faut prendre en compte les offsets de la reponse et pas ceux que tu as demandés, car ils peuvent etre different.
Par contre pour le fichier corrompu, je pense qu'il faut logguer toutes les requetes et les reponses et que tu fais, avec les offsets et les content-length des réponses, et verifier 'a la main'.
Messages postés
16
Date d'inscription
jeudi 16 novembre 2006
Statut
Membre
Dernière intervention
29 janvier 2016

Salut,

Bon, le téléchargement "par partie" ne fonctionne pas du fait de "fichier corrompu".
Je vais te dire ce que je fais, et peut-être que quelqu'un pourra me dire pourquoi ca ne marche pas :
lors du premier lancement, j'arrête la fonction après récupération de la taille du fichier, donc après lecture dans le header. Puis je défini un pas d'avancement (1/15 de la taille du fichier)
pour les 14 autres lancements (pour tester le téléchargement par partie, en fait, je récupère le fichier découpé en 14 partie) :
pareil sauf que au lieu de faire un get "simple", je lui ajoute les propriétés "Range : bytes=tailleDebut-tailleFin"
tailleDebut est la valeur qu'avait tailleFin lors de la dernière exécution.
tailleFin est égale à sa valeur précédente+pas, sauf si cette valeur est inférieure à la taille réellement téléchargée (qui est sûre diffère à 99%).

Est-ce que quelqu'un aurait une idée? Je pense que c'est du au fait que je prenne en considération les header à chaque fois que je télécharge, mais je ne suis pas sur.
Afficher les 42 commentaires

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.