wielowątkowy server http

0

Hej mam problem. Pisze server http, ma on wyświetlać zdjęcia, któe znajdują się w jednym z moich folderów. Problem jest taki, iż niby się one otwierają są one widoczne w źródle strony lecz przeglądarka ich nie wyświetla. Nie wiem co jest nie tak proszę o pomoc.

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <winsock2.h>
#include <Windows.h>
#include <fstream>
//-----------------------------------------------------------------------
#ifdef UNICODE
std::wstring path;
#else
std::string path;
#endif

using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define MAX_THREAD 5
#define true 1
char index[2000 * 600];
int page_size = 0;

//------------------------------------------------------------------------
//funkcja tworząca główną stronę
void mainPage(char index)
{
path = TEXT("d:\img\
");
WIN32_FIND_DATAA fileData;
HANDLE fileHandle;
fileHandle = FindFirstFileA("d:\img\*", &fileData);

char img_name[255];
WIN32_FIND_DATA data;
HANDLE find = INVALID_HANDLE_VALUE;
TCHAR szDir[MAX_PATH];
strcpy(index, "<html><head></head><body><span style="text-align: center;">");

find = FindFirstFile(path.c_str(), &data);
cout << "<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>"<<endl;
//cout << "Find: " << find << endl;
//cout << "FindNextFileA(find,  &fileData) " << FindNextFileA(find,  &fileData) << endl;


while (FindNextFileA(fileHandle, &fileData) != 0)
{
	//cout << "data.dwFileAttributes: " << data.dwFileAttributes << endl;
	//cout << "FILE_ATTRIBUTE_DIRECTORY: "<<(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) << endl;
    if (!(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
    {
		cout << " w ifie " << endl;
        sprintf(img_name, "<img src='img/%s'>", fileData.cFileName);
	    strncat(index, img_name, strlen(img_name));
    }
	
}

strcat(index, "</span></body></html>");
page_size = strlen(index);
for(int i =0; i< strlen(index); i++)
{
cout << index[i];
}
cout << endl;

}
//------------------------------------------------------------------------
DWORD generateThread(LPVOID* arg)
{
char buffor[1024], sciezka[255], img2[10];
int i = 6, buffor2 = 1024, retval;
FILE *img = NULL;
fstream plik;

fpos_t rozmiar;
SOCKET connfd = (SOCKET) * arg;

retval = recv(connfd, buffor, sizeof(buffor), 0);
if (retval == 0) 
    fprintf(stdout, "Blad polaczenia\n");

for (i = 6; i < sizeof(buffor); i++)
{
	if (buffor[i]==' ')
	{
		buffor[i] = 0;
		break;
	}
}

strcpy(sciezka, buffor + 5);
//cout << "scizka : "<<sciezka << endl;
if ((sciezka[0] == 'i') && (sciezka[1] == 'm') && (sciezka[2] == 'g')) //sprawdzanie, czy tag = img
{
	if (sciezka[strlen(sciezka) - 4]=='.')
	{
		strncpy(img2, sciezka + strlen(sciezka) - 3, 3);
		img2[3] = 0;
	}
	else if (sciezka[strlen(sciezka) - 5]=='.')
	{
		strncpy(img2, sciezka + strlen(sciezka) - 4, 4);
		img2[4] = 0;
	}
	string strPath = sciezka;
	strPath = "d:/"+strPath;
	cout << "sciezka 2 : " << strPath << endl;
	img = fopen(strPath.c_str(), "rb");
	FILE *iii = NULL;
	fstream plik(strPath.c_str(), ios::in||ios::out||ios::binary);
	if(plik.is_open())
	{
		cout << "otwarty plik" <<endl;
	}
	//iii = fopen("ll.txt", "rb");
	//cout << "iii : " << img << endl;
	cout << "img : " << img << endl;
	if(img == NULL)
	{
		cout << "Blad czytania pliku" << endl;
	}
	else
	{
		cout << "UF<><><>" << endl;
		fseek(img, 0, SEEK_END);
		fgetpos(img, &rozmiar);
		cout << "rozmiar : " <<  rozmiar << endl;
		printf("\tWyslano plik: %s\n", sciezka);

		strcpy(buffor, "HTTP/1.1 200 OK\n");
		strcat(buffor, "Date: Sat, 24 Mar 2012 17:13:05 GMT\n");
		strcat(buffor, "Content-Length: ");

		sprintf(buffor + strlen(buffor), "%d\n", rozmiar);
		strcat(buffor, "Connection: close\n");
		strcat(buffor, "Content-Type: image/");
		sprintf(buffor + strlen(buffor), "%s\n\n", img2);
		
		retval = send(connfd, buffor, strlen(buffor), 0);
		fseek(img, 0, SEEK_SET);

		while(!feof(img))
		{
			rozmiar = fread(buffor, 10, buffor2, img);	 
			retval = send(connfd, buffor, rozmiar, 0);
		}
	}
}
else
{
	strcpy(buffor, "HTTP/1.1 200 OK\n");
	strcat(buffor, "Date: Sat, 24 Mar 2012 17:13:05 GMT\n");
	strcat(buffor, "Content-Length: ");

	sprintf(buffor + strlen(buffor), "%d\n", page_size);
	strcat(buffor, "Connection: close\n");
	strcat(buffor, "Content-Type: text/html; charset=iso-8859-2\n\n");

	retval = send(connfd, buffor, strlen(buffor), 0);
	retval = send(connfd, index, page_size, 0);
}

}
//------------------------------------------------------------------------
int main(int argc, char** argv)
{
SOCKET gniazda[MAX_THREAD]; //sockety dla każdego z wątków
LPDWORD watki[MAX_THREAD], alive; //wątki, zmienna sprawdzająca, czy wątek nadal funkcjonuje
HANDLE uchwyty[MAX_THREAD]; //uchwyty do wątków
int i = 0, j = 0, size = 0, which = 0, retval = 0, client_addr_len = 0, server_addr_len = 0;
WSADATA wsaData;
SOCKET listenfd, connfd;
struct sockaddr_in server_addr, client_addr;

//wyczyszczenie tablicy wątków, socketów, uchwytów
memset(&gniazda, NULL, sizeof(gniazda));
memset(&uchwyty, 0, sizeof(uchwyty));
memset(&watki, 0, sizeof(watki));

if (argc != 2) 
{
    fprintf(stderr, "Invocation: %s <PORT>\n", argv[0]);
    system("pause");
    exit(EXIT_FAILURE);
}

retval = WSAStartup(MAKEWORD(2,2), &wsaData);
if (retval != 0) 
{
    fprintf(stderr, "WSAStartup failed: %d\n", retval);
    exit(EXIT_FAILURE);
}

listenfd = socket(PF_INET, SOCK_STREAM, 0);
if (listenfd == INVALID_SOCKET) {
    fprintf(stderr, "socket() failed: %d\n", WSAGetLastError());
    WSACleanup();
    exit(EXIT_FAILURE);
}

memset(&server_addr, 0,     sizeof(server_addr));
server_addr.sin_family      =   AF_INET;
server_addr.sin_addr.s_addr =   htonl(INADDR_ANY);
server_addr.sin_port        =   htons(atoi(argv[1]));
server_addr_len             =   sizeof(server_addr);

if (bind(listenfd, (struct sockaddr*) &server_addr, server_addr_len) == SOCKET_ERROR) 
{
    fprintf(stderr, "bind() failed: %d\n", WSAGetLastError());
    closesocket(listenfd);
    WSACleanup();
    exit(EXIT_FAILURE);
}

if (listen(listenfd, SOMAXCONN) == SOCKET_ERROR) 
{
    fprintf(stderr, "listen() failed: %d\n", WSAGetLastError());
    closesocket(listenfd);
    WSACleanup();
    exit(EXIT_FAILURE);
}

fprintf(stdout, "Oczekiwanie na polaczenie...\n");
client_addr_len = sizeof(client_addr); 

mainPage(index);

while (true)
{
	connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_addr_len);

	if (connfd == INVALID_SOCKET) 
	{
		fprintf(stderr, "accept() failed: %d\n", WSAGetLastError());
		closesocket(listenfd);
		WSACleanup();
		exit(EXIT_FAILURE);
	}

	j = 0;
	while (j < MAX_THREAD)
	{
		switch((int)uchwyty[j])
		{
			case 0:
				which = j;
				gniazda[which] = connfd;
				j = MAX_THREAD;
				break;
			default:
				alive = (LPDWORD)WaitForSingleObject(uchwyty[j], 0);
				if (alive == 0)
				{
					which = j;
					gniazda[which] = connfd;
					j = MAX_THREAD;			
				}
		}
		j++;
	}
	fprintf(stdout, "Polaczenie z: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    uchwyty[which] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&generateThread, &gniazda[which], 0,  (LPDWORD)&watki[which]);
}

fprintf(stdout, "Closing listening socket and terminating server...\n");
if (closesocket(listenfd) == SOCKET_ERROR) 
    fprintf(stderr, "closesocket() failed: %d\n", WSAGetLastError());

if (WSACleanup() == SOCKET_ERROR) 
    fprintf(stderr, "WSACleanup failed: %d\n", WSAGetLastError());

exit(EXIT_SUCCESS);

}
//------------------------------------------------------------------------

0
  1. Kod w znaczniki.
  2. Brak ' # ' przed dyrektywami preprocesora.
  3. Mi wyskakuje tylko okienko "naciśnij dowolny klawisz"
0

uruchomienie:
nazwa_projektu.exe nr:portu - > start servera
potem w przeglądarce wpisujemy <ip_servera>:<nr_portu>

'#' mi usuneło jak przeklejałem kod z środowiska

1

Załączam poprawki w formie załącznika, bo troszkę linijek tam przybyło.
W linii poleceń możesz podać ip/hosta/interfejs(nazwa/guid) na którym serwer ma działać, domyślnie jest 00:0.
Domyślny port (zmienna g_ServerPort) zmieniłem na 88 bo 80 mam już zajęty.

Główne zmiany:

  1. Zawsze podawaj jaki chcesz protokół w funkcji socket, bo podając zero, dostaniesz albo wybrakowanego raw socketa (poniżej visty) albo pierwsze lepsze cośtam z listy dostępnych protokołów. Tak samo jest w linuxie. Jakiś leń wkleił kod z zerem, a teraz wszyscy to powielają, bo akurat u nich to czasowo działa, do czasu aż coś do/prze/instalują.

  2. Nazwy plikow - staraj się je kodować w UTF-8, bo polska strona kodowa padnie na plikach dalszych krajów, szczególnie tych na wschodzie. Znacznik kodowania poza treścią html (np. brak meta charset) też nie jest wskazany.

  3. Wysyłanie danych socketem - funkcja send nie zawsze wyśle wszystko.

  4. Dodałem odsyłanie kilku podstawowych kodów błędu i "wyświetlanie w konsoli" tekstu ostatniego błędu, zamiast jego kodu. Niech program mówi literami co go boli!

  5. Dodałem minimalne sprawdzenie zapytania od klienta - czy nie wychodzi ponad katalog domowy, czy metoda jest GET, czy numer protokołu http jest 1.0 lub 1.1, czy jest podany Host. Wszystkie zapytania o /img/ są przekierowywane do funkcji sendfile(). Wszystkie do / lecą do sendIndex(), a reszta odsyła na stronę główną za pomocą "301 moved permanently".

  6. Krok zwalniania zasobów przesunąłem do destruktorów w pomocniczych klasach. Zwykłe "return" sprząta po nas. A sprzątać trzeba często, jeżeli program ma działać tygodniami, bez zamulania całego systemu.

  7. Zmieniłem tryb socketu serwera na asynchroniczny - serwer co dwie sekundy sprawdza czy konsolowy kod klawisza escape wpadł na stdin i czy jakiś klient zakończył sesję, by zamknąć uchwyt wątku. Nie jest to najlepszy sposób, bo wątek klienta sam powinien mutualnie usuwać się z tablicy serwera. Jest tam sekcja krytyczna, która aktualnie daje wyłączność jednemu klientowi do potencjalnie nieprzystosowanych do wielowątkowej aplikacji funkcji bibliotecznych, ale równie dobrze dodatkowa taka krytyczna sekcja nada się do uzyskania wyłączności do tablicy klientów.

  8. W main'ie miałeś kilka zerujących memset'ów - zastąpiłem je jednym memsetem przypisującym {0} do lokalnej strukturki, do której przeniosłem parę zmiennych z maina.

  9. Indeks plików jest teraz generowany i wysyłany w locie - szkoda pamięci na jednokrotny odczyt wszystkich plików na starcie programu. Ślepe dopisywanie plików na koniec zapisanego miejsca w tablicy nie jest zbyt rozsądne. Można stracić wiele dni, zanim wyjdzie na j

aw, że program się wysypuje, bo w katalogu jest za dużo o jeden plik. Pobranie listy plików jest ślimacze tylko za pierwszym razem, nie wiem, czy to bylo powodem tej wielkiej zmiennej index.

No nic, wystarczy na razie tych zmian, sam zerknij jak to działa i polepsz program.
0

dziękuję za tak obszerną pomoc. Dopiero zaczynam pisanie takich rzeczy i szczerze trochę się w tym pogubiłem, ale teraz mam nadzieje będę już wiedział

0

W drugiej wersji serwera (w załączniku) dodałem:
a) wspomaganie dla "keep-alive"
b) brakujący znacznik strony kodowej (utf-8) pliku z kodem serwera. W pierwszej wersji zapewne miałeś problemy na polskich znakach - ustaw sobie kodowanie edytora na utf8.
c) wątek klienta sam usuwa się z listy klientów (funkcja clientRemove)
d) usunąłem kilka małych błędów i gdzieniegdzie dokleiłem "static" do zmiennych
e) wysyłając plik do klienta, jego data ostatniej modyfikacji jest wysyłana - GetFileTime w funkcji sendfile
f) dodałem brakujące "GMT" do daty
g) lista plików z folderu /img jest zapisywana najpierw w tymczasowym pliku, by dało się obliczyć Content-Length (na potrzeby keep-alive).

Program budowany pod starego visuala 2005, niektórych funkcji może brakować w MinGw - _get_errno, SHCreateDirectory.

Analizuj od funkcji main() - po kolei co się dzieje. Jeżeli czegoś nie rozumiesz, to wstaw kilka "printf" żeby wypisało zawartości zmiennych, lub co aktualnie się wykonuje.

0

tak własnie zrobię dziękuję za pomoc. Możesz mi powiedzieć o co chodzi z tym spamem?

0

To jest zabezpieczenie przeciw klientom (testerzy, hakerzy, programy testujące stabilność), którzy zapętlają send() żeby pozbawić serwer pamięci, lub chociaż w nadziei, że serwer wczytuje dane do jakiegoś małego bloku pamięci i nie sprawdza, czy jeszcze jest miejsce na dane.

4096 bajtów na zapytanie klienta powinno wystarczyć - sam url ma ograniczoną długość do troszkę ponad 2KB - przynajmniej na tyle wskazuje stała INTERNET_MAX_PATH_LENGTH=2048 z wininet.h, kilka podstawowych parametrów takich jak Host, Connection, Accept* - ileż może zająć miejsca? W tym serwerze na początek mamy 8192 bajtów zarezerwowanych do odbioru zapytania (druga pętla while w funkcji clientThread). Bufor ten w razie potrzeby rośnie, ale nie więcej jak do 1MB. Dopiero wtedy klient dostaje kopa za spamowanie - wysyłanie niepotrzebnych informacji w nadmiarowej ilości.

1 użytkowników online, w tym zalogowanych: 0, gości: 1