tanulas_kliens_szerver_programozas_tomoren_2 - Fehér Krisztián honlapja

Fehér Krisztián weboldala
Fehér Krisztián weboldala
Tartalomhoz ugrás

tanulas_kliens_szerver_programozas_tomoren_2

Fehér Krisztián: Kliens-szerver programozás tömören 2. rész

Tartalomjegyzék
1 Előszó
1.1 A leírás célja és szerkezete
1.2 Követelmények
1.3 Letölthető programkódok
2 A kiindulópont
2.1 Hálózati konfigurálás, infrastruktúra
2.2 Mit tudjon a szerver?
2.3 A tervezett GUI vázlata
2.4 A naplózás megvalósítása
3 Szerverprogram egy szálon
3.1 A fő vezérlési szerkezet
3.2 Socket megnyitása
3.3 Kérések fogadása és feldolgozása
3.4 Szerver leállítása
3.5 A példaprogram teljes forráskódja
3.6 Az egyszálú megoldás hátrányai
4 Szerverprogram több szálon
4.1 A tervezett GUI vázlata
4.2 A beérkező kérések számlálása
4.3 A vezérlés változásai
4.4 Dinamikus konfigurálási lehetőségek
4.5 Feldolgozás újabb szálakon
4.6 Teszteljünk!
4.7 A példaprogram teljes forráskódja
4.7.1 Példa naplóbejegyzésre
5 További gondolatok, ötletek
5.1 Szerver funkcionalitások
5.2 Átküldhető adatok mennyisége
5.3 Szoftvertesztelés

1 Előszó

1.1 A leírás célja és szerkezete

A ’Kliens-szerver programozás tömören’ c. leírásom minden olyan fontos alapismeretet tartalmaz, melyek segítségével megismerhető a Windows rendszereken megvalósítható, viszonylag alacsony szintű socket kommunikáció.
Az írások hátterét nálam mindig intenzív kutatási-tanulási munkáim adják és a témához kapcsolódóan elkezdtem egy térinformatikai projektem számára szerver programot kifejleszteni. A dolog olyan remekül sikerült, hogy logikusnak tűnt ebből is egy leírást készíteni, hiszen nagyon menő dolog, ha az ember saját szervert képes megírni, amit többféle célra is felhasználhat.
Raktam egy kis csavart a dologba és a folytatásnak, ennek a leírásnak a segítségével egy konfigurálható, szolgáltatásokat nyújtani képes kiszolgáló programot írhat otthon bárki.
Ennek a megtervezését és kivitelezését lehet végigkövetni és megvalósítani otthoni körülmények között ennek a második résznek a segítségével.
A példaprogramok nincsenek felkészítve biztonsági rések kihasználása ellen, így például puffer túlcsordulás, vagy URL-manipulációs trükkök, stb. ellen. Ezek kivédése nem nehéz, de további időráfordítást igényelhet. A programok otthoni körülmények közötti kipróbálását ezek azonban semmilyen formában nem akadályozzák.

1.2 Követelmények

Hasonlóan az első leíráshoz, ez sem kezdőknek készült. Alapvető hálózati ismeretek, konfigurációs alapkészségek mindenképpen szükségesek. Az első rész ismeretanyagának kipróbálása, elsajátítása mindenképpen kell ahhoz, hogy értelmezhető legyen ennek a leírásnak minden része, lévén azok itt nem kerülnek ismételten bemutatásra.
A leírás ismeretanyagának feldolgozásához és a kipróbáláshoz a C nyelv ismerete szükséges, a leírás példprogramjai C nyelven íródtak.
Ajánlom ‘A Windows keményvonalas programozása’, ill. a ‘Többszálú win32 programozási alapismeretek’ c. leírásaimat is, egy-egy vonatkozó megoldás mélyebb megértéséhez, mert ezeket itt háttérismeretként már feltételezem.

1.3 Letölthető programkódok

A leírásban található példaprogramok teljes forráskódja letölthető a következő weboldalról, így nem kell fáradtságos gépeléssel bajlódni:




2 A kiindulópont

Először tisztázzuk, hogy mit szeretnénk létrehozni, milyen feltételek mellett!

2.1 Hálózati konfigurálás, infrastruktúra

A leírás első részében már kitértem a különböző beállítási lépésekre, így ezeket itt most nem ismétlem meg. Egy dolgot viszont kiemelek: mit tegyünk akkor, ha két gépet fizikailag akarunk Ethernet kábellel összekapcsolni, de a beállítások után mégsem tudjuk pingelni a másik gépet.

Javasolt megoldások:
• Vírusírtó szoftver esetén elképzelhető, hogy a valós idejű védelem olyan szigorú beállításokkal fut, hogy megakadályozzák a gépeket abban, hogy normálisan lássák egymást, vagy ping kéréseket tudjanak egymásnak küldeni. A védelmi szoftver beállításainak nagyon körültekintő (akárcsak időleges) módosítása segíthet. Előfordulhat viszont az is, hogy egyszerűen a pingelés van letiltva, de maga a kommunikáció működik, ezért nem szabad azonnal feladni sem a dolgot.
• A kipróbálás idejére létrehozhatunk “lecsupaszított” rendszereket, amelyeken csak a csupasz operációs rendszer fut. Klónozhatjuk például a rendszereket a gépeken és minden felesleges dolgot leszedhetünk, de akár egy friss Windows telepítés is szóba jöhet. Egy dolgot ne tegyünk: ezeket a gépeket internetre ne csatlakoztassuk és ne használjuk normál hétköznapi feladatokra, mert túlságosan védtelenek lehetnek.
• Powershell használata esetén győződjünk meg arról, hogy rendszergazdai jogosultságokkal próbáljuk meg módosítani a hálózati kapcsolatok tulajdonságait!

2.2 Mit tudjon a szerver?

A cél egy olyan, naplózási szolgáltatással is rendelkező szerverprogram létrehozása, amely képes valódi kiszolgálóként viselkedni, így például egy böngészőprogrammal is tesztelni lehet.

Három szolgáltatást fog nyújtani:
- GETSYSTINFO: A szerver nevének és a szerverben található RAM mennyiségének visszaadása, a szerver rendszeridejével mint időbélyeggel megadva.
- GETSUM: Két paraméteres URL-hívás esetén a megadott két szám összegét adja vissza.
- CHECKSERVER: a szerver elérhetősége nyugtázva egy ‘OK’ üzenettel.

A szervernek két változatát fogom bemutatni. Az alapverzió egy plusz szálon fog futni, a továbbfejlesztett verzió (ez áll a leírás fókuszában is) pedig minden kérést (angolul: request) külön szálon dolgoz fel.
Biztosan sok kérdés felmerül most az olvasóban. Ezek HTTP kérések lesznek? Kell valamilyen más szerver is a dologhoz, hogy működjön? Milyen protokollt fog követni a kommunikáció? Lehet majd HTTPS-t, SSL-t stb. használni?
Nos, a HTTP protokoll valamiféle alapvető változatát is adaptálhatnánk, de ezt két okból nem erőltetem. Egyrészt egy erősen leegyszerűsített szerverkommunikáció megvalósítására törekszem, nem pedig egy konkrét webszerverére, másrészt egy komplett HTTP támogatás leprogramozása jóval több kódot igényel, ami esetünkben nem is fontos. Mindazonáltal minden eszköz megvan az itt bemutatott technikák között ahhoz, hogy ezt bárki megvalósítsa, ha szüksége van rá.
Ez a szerver nem lesz semmiféle biztonsági funkcióval, tanúsítványokkal kompatibilis, ez nem is cél, de így is működni fog minden.
Semmilyen más szerver nem kell, ez a mi kis szerverprogramunk maga lesz az “egyszemélyes hadsereg”, azaz egy kulcsrakész, önálló szervert fogunk kapni a végére!
Böngészőprogram használata a szerver teszteléséhez javasolt, de opcionális, hiszen az első leírás alapján írhatunk olyan kliens programot, ami el képes látni a szerverkommunikáció lebonyolítását és a válaszok (angolul: response) megjelenítését is.
2.3 A tervezett GUI vázlata

A GUI nagyjából így néz majd ki. Nem lesz dizájn nagydíjas a dolog, ellenben egyszerű és gyors lesz.
A kiszolgáló leállítása a programablak bezárásával is elérhető.



2.4 A naplózás megvalósítása

Mivel egy szerver életében mindig zajlanak az események, erősen javasolt (már csak az esetleges hibák azonosítása végett is) valamiféle naplózás megvalósítása.
A naplófájlok többnyire puritán szövegfájlok, melyek sorokból állnak. Minden bejegyzés elején időbélyeg van, ami szervereknél nemcsak dátum és óra-perc-másodperc, hanem ezredmásodperces felbontást is jelent. Ez alapvető fontosságú, hiszen egyetlen másodperc alatt több dolog is történhet, ráadásul így megvizsgálhatjuk azt is, hogy mi, mennyi ideig tart.
A példaprogramunkban a szerver naplófájljának létrehozását az init() inicializációs függvénybe raktam bele.


//************************
//PROGRAM INICIALIZÁCIÓJA
//************************
void init(void)
{
fopen_s(&flog, "LOG.txt", "wt");
fclose(flog);
}


Tehát a napló a szerver minden indulásakor törlődik. Nem biztos, hogy mindig ez a megfelelő megoldás, adott esetben a naplófájl nevét dinamikussá tehetjük, például úgy, hogy tartalmazza az adott napot. Ennek megvalósítását azonban az olvasóra bízom.
Minden új naplóbejegyzést a log() függvény végez, ami egy sztringet vár paraméterként. Ezt a sztringet írja ki, időbélyeggel ellátva. Az időbélyeget a SYSTEMTIME és a GetLocalTime() függvény segítségével határozzuk meg.

A függvény törzse:


//***********************
//Naplózás megvalósítása
//***********************
void log(const char* puffer)
{
FILE* fp = NULL;
SYSTEMTIME datumido;
GetLocalTime(&datumido);

if (vege == 1) return;
char logbuffer[2048];
int meret = sizeof(&puffer);

fopen_s(&fp, "LOG.txt", "at");
if (fp == NULL) return;

fprintf_s(fp, "%i.%i.%i %i:%i:%i.%i %s\r\n", datumido.wYear, datumido.wMonth, datumido.wDay, datumido.wHour, datumido.wMinute, datumido.wSecond, datumido.wMilliseconds, puffer);

fclose(fp);
}


A legjobb, ha minél több helyre teszünk naplózási lehetőséget a programjainkban, de mivel jóból is megárt a sok, ezért megfontolandó naplózási szinteket (angolul: log level) definiálni, melyek a naplózás részletességét befolyásolják. Gyakori naplózási szintek a valóságban: info, warning, error, full. Mi maradjunk most a maximális részletességnél!
A programkódban szereplő gyakori log() hívások egyik érdekes előnye, hogy megjegyzésként is felfoghatjuk őket. Éppen ezért viszonylag kevés explicit komment részt fog tartalmazni a forráskód.
A leírás végén egy hosszabb naplóbejegyzés is található, szemléltetésképpen.



3 Szerverprogram egy szálon

Ebben a fejezetben meghatározzuk azt az irányt, aminek a mentén a programunk alapverzióját fel fogjuk építeni.

3.1 A fő vezérlési szerkezet

Tanulási célra megfelelő lehet először egy olyan, egy szálon futó szerver megalkotása, ami egy while ciklussal folyamatosan figyeli, feldolgozza a beérkező kéréseket, majd bontja a kapcsolatot, aztán újra elérhetővé teszi a szervert és így tovább. Ezt egy szálfüggvényben fogjuk elvégezni, aminek a neve varakozas() lesz.


//***********************
//Fő vezérlési szerkezet
//***********************
void varakozas(void* pParams)
{
ShowWindow(Button1, SW_HIDE);
while (1)
{
 if (vege == 1) break;
 open_server();

 if (vege == 1) break;
 start_server_listening();

 if (vege == 1) break;
 close_server();
}
}


Ó jaj, mi ez a sok vege == 1 feltétel? Mivel ez a vezérlési szerkezet a program fő szálától különálló szálon fut, ezért előfordulhat olyan szituáció például a program bezárásakor, hogy a program már éppen bezárul, de közben egy naplózási művelet még “becsusszan” a másik szálon. Ilyenkor egy olyan tudathasadásos állapot jön létre, ami a szálprogramozás egyik alapvető problémája. Mindenesetre nem kell félni tőle, egy egyszerű segédváltozó felhasználásával minden kritikus helyen ellenőrizzük, hogy szabad-e az adott kódot még végrehajtani. Erre szolgál ez a változó. Ez a legprimitívrebb megoldás, lehetne máshogy is, de ez is megteszi.
A kérések tartalmának konkrét feldolgozására a következő fejezetben fogok kitérni, a többszálú változat bemutatásakor.
Egyébként hol indul el ez a szál, benne a while ciklussal? Ez a programablak üzenetkezelő részébe került, a ‘Start server’ feliratú gomb lenyomásához van rendelve.


case WM_COMMAND:
 switch (LOWORD(wParam))
 {
 case OBJ_ID100:
  //**********************************************
  //Fő vezérlési szerkezet indítása, külön szálon
  //**********************************************
  THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  break;
 }


Itt rögtön egy dolgot tisztáznunk kell, nehogy félreértés legyen belőle: ezt a várakozás műveletet csak egyetlen plusz szálon futtatjuk. A kérésenkénti plusz szálon futó végrehajtások a program későbbi változatában nem itt lesznek, hanem a start_server-listening() függvényből fognak meghívódni, melyek így a varakozas() függvény szálának vannak alárendelve, ha a végrehajtási hierarchiát nézzük.
Egyáltalán, miért kell cifrázni ezt a hívást külön szálon? Azért, mert ha nem így tennénk, a programunk felhasználói felülete nem tudna többé bevitelt kezelni, olyan lenne, mintha lefagyott volna. Ezt egy plusz szál futtatásával lehet kiküszöbölni. A GUI-s alkalmazások “alap szálát” mindig a felhasználói interakciók kezelésére kell szabadon tartani. Ez a szálprogramozás egyik alapvető módszere grafikus felhasználói felületek esetén.

3.2 Socket megnyitása

Az open_server() függvény a szervert a 127.0.0.1 IP címhez rendeli, a 80-as porton.


//******************************
//Szerver inicializálása
//******************************
void open_server(void)
{
serverisopen = false;
clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;

if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
 WSACleanup();
 log("Winsock init error...");
 return;
}

log("Winsock init OK.");
log(wsaData.szDescription);

gethostname(localinfo, sizeof(localinfo));
log(localinfo);
log("PORT: 80");

serversocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serversocket == INVALID_SOCKET)
{
 WSACleanup();
 log("Couldn't create server socket...");
}
log("Server socket created...");

serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(80);
if (bind(serversocket, (SOCKADDR*)&serveraddr, sizeof(serveraddr)) == SOCKET_ERROR)
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log("Couldn't bind server socket...");
 return;
}
else log("Binding server socket OK...");
}


3.3 Kérések fogadása és feldolgozása

A beérkező kliens kérések fogadását és feldolgozását a start_server_listening() függvény végzi.
A függvény semmi rendkívülit nem csinál:
• először elekezdi figyelni a bejövő kérést
• majd fogadja azt
• összegyűjti a kérés tartalmát
• kielemzi (parse-olás)
• az azonosított kérés típusától függően függvényhívásokkal létrehoz egy választ (ez egy sztring lesz)
• elküldi a választ a kérés indítójának
• majd lezárja socketet.

A listen() függvénnyel kapcsolatban egy fontos tudnivalóra hívom fel ezen a helyen a figyelmet.
A függvény maga ugye ez:

int WSAAPI listen(
[in] SOCKET s,
[in] int    backlog
);

A backlog paraméter segítségével azt adjuk meg, hogy hány függőben levő bejövő kérést kezeljen a szerverünk. Ezt otthoni körülmények között nyugodtan beállíthatjuk 1-re, de tudnunk kell, hogy ez a szám elvileg akár 65535 is lehet. Ebben az első példaprogramban ezt az értéket 1-en hagyom, a másodikban pedig 100-ra fogom beállítani. Ezzel növelni tudjuk a szerverünk teljesítményét, befogadóképességét.
Ahhoz, hogy értelmesen fel tudjuk dolgozni a szerverhez beérkező kéréseket, valamiféle parserre lesz szükségünk, ami képes kimazsolázni a beérkező adatokból a konkrét kéréseket a start_server_listening() függvényben.
Ehhez alapvetően négy függvényt fogunk felhasználni:

void get_request(char* urlrequest, char* type, char* retstr, char sepchar);

int get_keyword_location(char* srcstr, char* keyword);

void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar);

unsigned int get_string_length(char* srcstr);

A feldolgozás a start_server_listening() függvényen belül fog végrehajtódni, miután beérkezett az utolsó bájt is a kérésből, melyet a recvbuf[] tömbben tárolunk.


char recvbuf[4096];


A parse-oláshoz egy segédtömböt fogunk felhasználni, amibe átmásoljuk előtte a recvbuf tömb tartalmát:


char requrl[4096];


A HTTP kérés fogja tartalmazni azokat a szövegrészeket, amelyek alapján meg tudjuk majd különböztetni a beérkező kéréseket.
Egy beérkező HTTP kérés tipikusan így néz ki:

GET /index.htm HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: hu-HU,hu;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

Ennek bal oldali határolója a mi programunk szemszögéből a "GET /", a jobb oldali pedig a “.” sztring lesz.


strcpy_s(keyword, _countof(keyword), "GET /");


Ezután meghívjuk a get_request() függvényt, ami a get_keyword_location() és get_keyword_value() függvényeket hívja meg.


//***********************
//KÉRÉSEK PARSE-OLÁSA
//***********************
void get_request(char* urlrequest, char* type, char* retstr, char sepchar)
{
int i;

i = get_keyword_location(urlrequest, type);
get_keyword_value(urlrequest, type, i, retstr, sepchar);
i = i;
}


Előbbi a get_request() függvénynek is átadott sztring helyét keresi meg a kérésben, ami jelen estünkben "GET /" (a type paraméter).
A találat helyét (ez lesz az ‘i’ numerikus változó) a get_keyword_value() függvénynek átadva a reqvalue paraméterbe kerül bele a keresés eredménye.

Például a “GET /index.html” kérés esetén használjuk a következő függvényhívást:


get_request(requrl, keyword, reqvalue, '.');


A reqvalue az a szöveg lesz, ami a “GET /” és a következő ‘.’ jel között van. Böngészővel tesztelve ajánlott a ‘.htm’, vagy ‘.html’ végződést használni, még akkor is, ha ezt nem dolgozza fel a programunk. Így ugyanis a böngészőben könnyebben meg tudjuk tekinteni a választ.
A keresés eredménye az “index” sztring lesz a folyamat végén, ebben a példában. Ez lesz maga a kérés és ez alapján dolgozzuk fel a beérkező kéréseket.

A get_keyword_location() és get_keyword_value() függvények törzse:


//********************************
//KULCSSZÓ HELYÉNEK MEGÁLLAPÍTÁSA
//********************************
int get_keyword_location(char* srcstr, char* keyword)
{
int i = 0, j = 0, retval = -1, MAX_i = get_string_length(srcstr) - get_string_length(keyword);

while (i < MAX_i)
{
 for (j = 0; j < get_string_length(keyword) - 1; ++j)
 {
  if (srcstr[i + j] == keyword[j]) retval = 0;
  else {
   retval = -1; break;
  }
 }
 if (retval != -1) {//megvan a keyword helye
  retval = i;
  break;
 }
 ++i;
}
return retval + get_string_length(keyword);
}

//*********************
//KULCSÉRTÉK KINYERÉSE
//*********************
void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar)
{
int i = startpoz, j = 0;

for (; srcstr[i] != sepchar; ++i)
{
 retstr[j++] = srcstr[i];
}
retstr[j] = 0;
}


Ez a fajta feldolgozás egyszerű URL-ek esetében használható így.


   //*************************
   // GETSYSINFO
   //*************************
   if (strcmp(reqvalue, "getsysinfo") == 0)
   {
    set_systeminfo(htmlresp);     
   }

   //*************************
   // CHECKSERVER
   //*************************
   else if (strcmp(reqvalue, "checkserver") == 0)
   {
    strcpy_s(htmlresp, "|OK|");
   }


Semmi sem akadályoz meg bennünket abban, hogy különböző paramétereket adjunk az URL-ünkben meg. Ehhez csak azt kell megszabnunk, hogy mi jelezze egy paraméter értékét a kérésben/URL-ben.
Példaprogramunkban a ‘V1=’ és ‘V2=’ sztringek fogják jelezni az extra paraméterek helyét a kérésekben, ahol a ‘?’ jel lesz a paramétereket elválasztó jel.

Példa URL:

127.0.0.1/getsum.V1=5?V2=9.html

A paramétereket az alábbi kóddal tudjuk kinyerni és feldolgozni:


   //*************************
   // GETSUM
   //*************************
   else if (strcmp(reqvalue, "getsum") == 0)
   {
    strcpy_s(keyword, _countof(keyword), "V1=");
    get_request(requrl, keyword, val_str1, '?');
    value1 = atoi(val_str1);
    strcpy_s(keyword, _countof(keyword), "V2=");
    get_request(requrl, keyword, val_str2, '.');
    value2 = atoi(val_str2);
    value1 += value2;
    _itoa_s(value1, keyword, sizeof(keyword), 10);
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>SUM: ");
    strcat_s(htmlresp, keyword);
    strcat_s(htmlresp, "</h3></html>\0");
   }


Itt a value1 és value2 változókba kerülnek a paraméterek, némi adatkonverzió után.
Minden olyan esetben pedig, amikor egy olyan kérés érkezik, amit a szerver nem “ismer”, egy rövid HTML kódot adunk vissza, ami az “Unknown command” üzenetet jeleníti meg egy böngészőben.


   //*************************
   // MINDEN MÁS ESET
   //*************************
   else
   {
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>Unknown command!</h3></html>\0");
   }


Van még egy dolog, ami böngészők esetén képbe jön. Firefox esetén például a böngésző minden URL kérés esetén valójában két darab HTTP GET kérést küld a szerver felé.
A második az a normál GET, amit várunk. Az első azonban mindig egy ‘favicon’ GET kérés, amire nekünk semmi szükségünk, de mégis hasznos lekezelnünk, nehogy lyukra fusson a kérések feldolgozása.


   //*************************
   // FAVICON SZŰRÉS
   //*************************
   else if (strcmp(reqvalue, "favicon") == 0)
   {
    strcpy_s(htmlresp, "Browser connection accepted...");
   }


A start_server_listening() függvény teljes kódja az eddigiek tükrében:


//****************************************
//Bejövő kérések fogadása és feldolgozása
//****************************************
void start_server_listening(void)
{
int i;
int iResult;
int iSendResult;
int recvbuflen = 4096;
char recvbuf[4096];
for (i = 0; i < recvbuflen; ++i) recvbuf[i] = '\0';
if (serversocket == INVALID_SOCKET) return;

SetWindowTextA(Form1, "Bejövő kérések figyelése...");

if (listen(serversocket, 1) == SOCKET_ERROR) //max 1 kapcsolat
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log("Server socket FAILED to listen...");
 return;
}
else
{
 log("Server socket is listening...");
 serverisopen = true;
}

clientsocket = accept(serversocket, (SOCKADDR*)&clientaddr, (LPINT)&nLength);

if (clientsocket == INVALID_SOCKET)
 log("Server socket failed to accept connection...");
else
{
 log("CONNECTION OK...");
 SetWindowTextA(Form1, "Kliens kapcsolódott!");
 log("Collecting request...");

 do {
  log("Reading request...");
  iResult = recv(clientsocket, recvbuf, recvbuflen - 2, 0);

  if (iResult > 0) {    
   int meret = sizeof(recvbuf);
   log("******************");
   log("Client request:");
   log("******************");
   log(recvbuf);
   log("******************");
   shutdown(clientsocket, SD_RECEIVE);
   strcpy_s(requrl, _countof(requrl), (const char*)recvbuf);
   strcpy_s(keyword, _countof(keyword), "GET /");
   get_request(requrl, keyword, reqvalue, '.');
   log("******************");
   log("Server response:");
   log("******************");

   //*************************
   // GETSYSINFO
   //*************************
   if (strcmp(reqvalue, "getsysinfo") == 0)
   {
    set_systeminfo(htmlresp);     
   }

   //*************************
   // GETSUM
   //*************************
   else if (strcmp(reqvalue, "getsum") == 0)
   {
    strcpy_s(keyword, _countof(keyword), "V1=");
    get_request(requrl, keyword, val_str1, '?');
    value1 = atoi(val_str1);
    strcpy_s(keyword, _countof(keyword), "V2=");
    get_request(requrl, keyword, val_str2, '.');
    value2 = atoi(val_str2);
    value1 += value2;
    _itoa_s(value1, keyword, sizeof(keyword), 10);
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>SUM: ");
    strcat_s(htmlresp, keyword);
    strcat_s(htmlresp, "</h3></html>\0");
   }

   //*************************
   // CHECKSERVER
   //*************************
   else if (strcmp(reqvalue, "checkserver") == 0)
   {
    strcpy_s(htmlresp, "|OK|");
   }

   //*************************
   // FAVICON SZŰRÉS
   //*************************
   else if (strcmp(reqvalue, "favicon") == 0)
   {
    strcpy_s(htmlresp, "Browser connection accepted...");
   }

   //*************************
   // MINDEN MÁS ESET
   //*************************
   else
   {
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>Unknown command!</h3></html>\0");
   }
   log(htmlresp);
   log("******************");

   iResult = send(clientsocket, htmlresp, (int)strlen(htmlresp) + 1, 0);
   if (iResult == SOCKET_ERROR)
    log("Sending response FAILED...");
   else
   {
    log("Response sent OK...");
    shutdown(clientsocket, SD_SEND);
   }
   closesocket(clientsocket);
   closesocket(serversocket);
   WSACleanup();
   break;
  }

  else if (iResult == 0)
   log("Connection closing...");

  else {
   log("Reading request failed with error");
   closesocket(clientsocket);
   WSACleanup();
   return;
  }

 } while (iResult > 0);
}
}


3.4 Szerver leállítása

A while ciklus close_server() függvényhívása a szó szoros értelemben “zárja a boltot”, azaz a szerver teljesen leáll.


//**************************
//A SZERVER LEÁLL
//**************************
void close_server(void)
{
log("Close server...");
log("WSA cleanup...");
shutdown(clientsocket, SD_SEND);
if (serverisopen == true) closesocket(serversocket);
if (clientsocket != INVALID_SOCKET) closesocket(clientsocket);
WSACleanup();
serverisopen = false;
clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;
log("Sockets closed...");
}


3.5 A példaprogram teljes forráskódja


#include <Winsock2.h>
#include <ws2tcpip.h>
#include <WinInet.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ws2_32.lib")
#include <Windows.h>
#include <process.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <commctrl.h>

//*********************************
//ÁLTALÁNOS WIN32API VÁLTOZÓK
//*********************************
HANDLE THANDLER;
#define MAX_RESP_LENGTH 8*1024*1024
#define HIBA_00 TEXT("Error:Program initialisation process.")
HINSTANCE hInstGlob;
int SajatiCmdShow;
char szClassName[] = "WindowsApp";
HWND Form1; //Ablak kezelője
HWND Button1;
#define OBJ_ID100 100

//*********************************
//SZERVERVÉLTOZÓK
//*********************************
FILE* flog;
int vege = 0;
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
BOOL serverisopen;
SOCKET clientsocket, serversocket;
sockaddr_in serveraddr, clientaddr;
char localinfo[128], htmlresp[MAX_RESP_LENGTH];
int i, nLength = sizeof(SOCKADDR);
char requrl[4096], reqtype[1024], reqvalue[1024], keyword[1024];
char val_str1[64], val_str2[64];
int value1 = 0, value2 = 0;

LRESULT CALLBACK WndProc0(HWND, UINT, WPARAM, LPARAM);
void ShowMessage(LPCTSTR uzenet, LPCTSTR cim, HWND kuldo);


//*********************************
//SZERVERFÜGGVÉNYEK
//*********************************
void varakozas(void* pParams);
void init(void);
void set_systeminfo(char* resp_string);
void log(const char* puffer);
void open_server(void);
void start_server_listening(void);
void close_server(void);

//*********************************
//KÉRÉSEK ELEMZÉSE
//*********************************
void get_request(char* urlrequest, char* type, char* retstr, char sepchar);
int get_keyword_location(char* srcstr, char* keyword);
void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar);
unsigned int get_string_length(char* srcstr);

//*********************************
//A windows program belépési pontja
//*********************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("StdWinClassName");
HWND hwnd;
MSG msg;
WNDCLASS wndclass0;
SajatiCmdShow = iCmdShow;
hInstGlob = hInstance;
LoadLibraryA("COMCTL32.DLL");

//*********************************
//Ablak osztálypéldány előkészítése
//*********************************
wndclass0.style = CS_HREDRAW | CS_VREDRAW;
wndclass0.lpfnWndProc = WndProc0;
wndclass0.cbClsExtra = 0;
wndclass0.cbWndExtra = 0;
wndclass0.hInstance = hInstance;
wndclass0.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass0.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass0.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//LTGRAY_BRUSH
wndclass0.lpszMenuName = NULL;
wndclass0.lpszClassName = TEXT("WIN0");

//*********************************
//Ablak osztálypéldány regisztrációja
//*********************************
if (!RegisterClass(&wndclass0))
{
 MessageBox(NULL, HIBA_00, TEXT("Program Start"), MB_ICONERROR); return 0;
}

//*********************************
//Ablak létrehozása
//*********************************
Form1 = CreateWindow(TEXT("WIN0"),
 TEXT("Mini szerver"),
 (WS_OVERLAPPED | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX),
 50,
 50,
 400,
 300,
 NULL,
 NULL,
 hInstance,
 NULL);

//*********************************
//Ablak megjelenítése
//*********************************
ShowWindow(Form1, SajatiCmdShow);
UpdateWindow(Form1);

//*********************************
//Ablak üzenetkezelésének aktiválása
//*********************************
while (GetMessage(&msg, NULL, 0, 0))
{
 TranslateMessage(&msg);
 DispatchMessage(&msg);
}
return msg.wParam;
}

//*********************************
//Az ablak callback függvénye: eseménykezelés
//*********************************
LRESULT CALLBACK WndProc0(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;

switch (message)
{
 //*********************************
 //Ablak létrehozásakor közvetlenül
 //*********************************
case WM_CREATE:
 /*Init*/;
 init();
 Button1 = CreateWindow(TEXT("button"), TEXT("Szerver bekapcsolása")
  , WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE, 50, 50, 170, 30
  , hwnd, (HMENU)(OBJ_ID100), ((LPCREATESTRUCT)lParam)->hInstance, NULL);  
 //open_server();
 return 0;
 //*********************************
 //vezérlőelemek működtetése
 //*********************************
case WM_COMMAND:
 switch (LOWORD(wParam))
 {
 case OBJ_ID100:
  //**********************************************
  //Fő vezérlési szerkezet indítása, külön szálon
  //**********************************************
  THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  break;
 }

 return 0;
 //*********************************
//Ablak átméretezése
//*********************************
case WM_SIZE:
 return 0;

 //*********************************
 //Ablak kliens területének újrarajzolása
 //*********************************
case WM_PAINT:
 hdc = BeginPaint(hwnd, &ps);
 EndPaint(hwnd, &ps);
 return 0;
 //*********************************
 //Ablak bezárása
 //*********************************
case WM_CLOSE:
 vege = 1;
 close_server();
 DestroyWindow(hwnd);
 return 0;
 //*********************************
 //Ablak megsemmisítése
 //*********************************
case WM_DESTROY:
 PostQuitMessage(0);
 return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

//*********************************
//Üzenetablak OK gombbal
//*********************************
void ShowMessage(LPCTSTR uzenet, LPCTSTR cim, HWND kuldo)
{
MessageBox(kuldo, uzenet, cim, MB_OK);
}

//***********************
//Naplózás megvalósítása
//***********************
void log(const char* puffer)
{
FILE* fp = NULL;
SYSTEMTIME datumido;
GetLocalTime(&datumido);

if (vege == 1) return;
char logbuffer[2048];
int meret = sizeof(&puffer);

fopen_s(&fp, "LOG.txt", "at");
if (fp == NULL) return;

fprintf_s(fp, "%i.%i.%i %i:%i:%i.%i %s\r\n", datumido.wYear, datumido.wMonth, datumido.wDay, datumido.wHour, datumido.wMinute, datumido.wSecond, datumido.wMilliseconds, puffer);

fclose(fp);
}

//******************************
//Szerver inicializálása
//******************************
void open_server(void)
{
serverisopen = false;
clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;

if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
 WSACleanup();
 log("Winsock init error...");
 return;
}

log("Winsock init OK.");
log(wsaData.szDescription);

gethostname(localinfo, sizeof(localinfo));
log(localinfo);
log("PORT: 80");

serversocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serversocket == INVALID_SOCKET)
{
 WSACleanup();
 log("Couldn't create server socket...");
}
log("Server socket created...");

serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(80);
if (bind(serversocket, (SOCKADDR*)&serveraddr, sizeof(serveraddr)) == SOCKET_ERROR)
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log("Couldn't bind server socket...");
 return;
}
else log("Binding server socket OK...");
}

//****************************************
//Bejövő kérések fogadása és feldolgozása
//****************************************
void start_server_listening(void)
{
int i;
int iResult;
int iSendResult;
int recvbuflen = 4096;
char recvbuf[4096];
for (i = 0; i < recvbuflen; ++i) recvbuf[i] = '\0';
if (serversocket == INVALID_SOCKET) return;

SetWindowTextA(Form1, "Bejövő kérések figyelése...");

if (listen(serversocket, 1) == SOCKET_ERROR) //max 1 kapcsolat
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log("Server socket FAILED to listen...");
 return;
}
else
{
 log("Server socket is listening...");
 serverisopen = true;
}

clientsocket = accept(serversocket, (SOCKADDR*)&clientaddr, (LPINT)&nLength);

if (clientsocket == INVALID_SOCKET)
 log("Server socket failed to accept connection...");
else
{
 log("CONNECTION OK...");
 SetWindowTextA(Form1, "Kliens kapcsolódott!");
 log("Collecting request...");

 do {
  log("Reading request...");
  iResult = recv(clientsocket, recvbuf, recvbuflen - 2, 0);

  if (iResult > 0) {    
   int meret = sizeof(recvbuf);
   log("******************");
   log("Client request:");
   log("******************");
   log(recvbuf);
   log("******************");
   shutdown(clientsocket, SD_RECEIVE);
   strcpy_s(requrl, _countof(requrl), (const char*)recvbuf);
   strcpy_s(keyword, _countof(keyword), "GET /");
   get_request(requrl, keyword, reqvalue, '.');
   log("******************");
   log("Server response:");
   log("******************");

   //*************************
   // GETSYSINFO
   //*************************
   if (strcmp(reqvalue, "getsysinfo") == 0)
   {
    set_systeminfo(htmlresp);     
   }

   //*************************
   // GETSUM
   //*************************
   else if (strcmp(reqvalue, "getsum") == 0)
   {
    strcpy_s(keyword, _countof(keyword), "V1=");
    get_request(requrl, keyword, val_str1, '?');
    value1 = atoi(val_str1);
    strcpy_s(keyword, _countof(keyword), "V2=");
    get_request(requrl, keyword, val_str2, '.');
    value2 = atoi(val_str2);
    value1 += value2;
    _itoa_s(value1, keyword, sizeof(keyword), 10);
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>SUM: ");
    strcat_s(htmlresp, keyword);
    strcat_s(htmlresp, "</h3></html>\0");
   }

   //*************************
   // CHECKSERVER
   //*************************
   else if (strcmp(reqvalue, "checkserver") == 0)
   {
    strcpy_s(htmlresp, "|OK|");
   }

   //*************************
   // FAVICON SZŰRÉS
   //*************************
   else if (strcmp(reqvalue, "favicon") == 0)
   {
    strcpy_s(htmlresp, "Browser connection accepted...");
   }

   //*************************
   // MINDEN MÁS ESET
   //*************************
   else
   {
    strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>Unknown command!</h3></html>\0");
   }
   log(htmlresp);
   log("******************");

   iResult = send(clientsocket, htmlresp, (int)strlen(htmlresp) + 1, 0);
   if (iResult == SOCKET_ERROR)
    log("Sending response FAILED...");
   else
   {
    log("Response sent OK...");
    shutdown(clientsocket, SD_SEND);
   }
   closesocket(clientsocket);
   closesocket(serversocket);
   WSACleanup();
   break;
  }

  else if (iResult == 0)
   log("Connection closing...");

  else {
   log("Reading request failed with error");
   closesocket(clientsocket);
   WSACleanup();
   return;
  }

 } while (iResult > 0);
}
}

//**************************
//A SZERVER LEÁLL
//**************************
void close_server(void)
{
log("Close server...");
log("WSA cleanup...");
shutdown(clientsocket, SD_SEND);
if (serverisopen == true) closesocket(serversocket);
if (clientsocket != INVALID_SOCKET) closesocket(clientsocket);
WSACleanup();
serverisopen = false;
clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;
log("Sockets closed...");
}

//***********************
//Fő vezérlési szerkezet
//***********************
void varakozas(void* pParams)
{
ShowWindow(Button1, SW_HIDE);
while (1)
{
 if (vege == 1) break;
 open_server();
 if (vege == 1) break;
 start_server_listening();
 if (vege == 1) break;
 close_server();
}
}

//***********************
//KÉRÉSEK PARSE-OLÁSA
//***********************
void get_request(char* urlrequest, char* type, char* retstr, char sepchar)
{
int i;

i = get_keyword_location(urlrequest, type);
get_keyword_value(urlrequest, type, i, retstr, sepchar);
i = i;
}

//********************************
//KULCSSZÓ HELYÉNEK MEGÁLLAPÍTÁSA
//********************************
int get_keyword_location(char* srcstr, char* keyword)
{
int i = 0, j = 0, retval = -1, MAX_i = get_string_length(srcstr) - get_string_length(keyword);

while (i < MAX_i)
{
 for (j = 0; j < get_string_length(keyword) - 1; ++j)
 {
  if (srcstr[i + j] == keyword[j]) retval = 0;
  else {
   retval = -1; break;
  }
 }
 if (retval != -1) {//megvan a keyword helye
  retval = i;
  break;
 }
 ++i;
}
return retval + get_string_length(keyword);
}

//*********************
//KULCSÉRTÉK KINYERÉSE
//*********************
void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar)
{
int i = startpoz, j = 0;

for (; srcstr[i] != sepchar; ++i)
{
 retstr[j++] = srcstr[i];
}
retstr[j] = 0;
}

//********************************
//SZTRING HOSSZÁNAK MEGHATÁROZÁSA
//********************************
unsigned int get_string_length(char* srcstr)
{
unsigned int i = 0;
while (srcstr[i] != '\0') ++i;
return i;
}

//************************
//PROGRAM INICIALIZÁCIÓJA
//************************
void init(void)
{
fopen_s(&flog, "LOG.txt", "wt");
fclose(flog);
}

//*************************************
//GETSYSINFO ÜZENET DINAMIKUS TARTALMA
//*************************************
void set_systeminfo(char* resp_string)
{
int i;
char tempstr[256];
char cname[64];
LONGLONG TotalMemoryInKilobytes = 0;

DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
SYSTEMTIME datumido;

GetLocalTime(&datumido);

GetComputerNameA(cname, &len);

strcpy_s(resp_string, MAX_RESP_LENGTH, "System time: ");
_itoa_s(datumido.wYear, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, ".");
_itoa_s(datumido.wMonth, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, ".");
_itoa_s(datumido.wDay, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, " ");
_itoa_s(datumido.wHour, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, ":");
_itoa_s(datumido.wMinute, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, ":");
_itoa_s(datumido.wSecond, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);

strcat_s(resp_string, MAX_RESP_LENGTH, "|Computer name: ");
strcat_s(resp_string, MAX_RESP_LENGTH, (const char *)cname);
strcat_s(resp_string, MAX_RESP_LENGTH, "|Installed RAM: ");

GetPhysicallyInstalledSystemMemory((PULONGLONG)&TotalMemoryInKilobytes);
i = TotalMemoryInKilobytes;
_itoa_s(i / 1024 / 1024, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, "|");
}


3.6 Az egyszálú megoldás hátrányai

Egy egyetlen plusz végrehajtási szálat tartalmazó megoldásnak az a velejárója, hogy amíg egy beérkező kérés feldolgozása tart, addig a szerver semmi mást nem fog csinálni. Ezenkívül minden újabb kérés fogadásához végig kell játszani a teljes feldolgozási ciklust: szerver megnyitása, feldolgozás, leállítás.
Ez így nyilvánvalóan nem túl hatékony, noha egy-egy beérkező kérés kezeléséhez elegendő lehet.
Mivel azonban a mi célünk az, hogy nagyobb számú kérést is ki tudjunk szolgálni, ezért minden egyes beérkező kérés feldolgozását automatikusan egy új végrehajtási szálon fogunk elvégezni.
Ezt fogjuk megvalósítani a következő fejezetben.


4 Szerverprogram több szálon

A szerver működését most bontjuk és új végrehajtási szálakba kell átraknunk.
Ezt a fejezetet eredetileg bonyolultabbra terveztem, a CreateThread függvény és társai megírásával, de az az igazság, hogy a _beginthread függvény is tökéletes megoldást adott, ezért ennél a megoldásnál maradtam.
A kódban több helyütt hagytam változókat stb., amelyeket közvetlenül nem használok fel a példákban, de az olvasók tobvábbi kísérletezését segíthetik elő. Ilyen például a szálak indításakor létrejövő szál azonosító, amit a THANDLER változóban találhatunk. Az ilyen kis plusz betoldások reményeim szerint további kísérletezésekre sarkallják az olvasót.
A naplózás itt is ugyanígy adott.

4.1 A tervezett GUI vázlata

A GUI lehetőséget fog adni az IP cím, valamint a szolgáltatások nevének a beállítására. Ennél bonyolultabb konfigurálási lehetőséget is el lehet képzelni persze (port megadása, lehetséges URL paraméterek stb.).



Az ablak kódszerkezetét egyébként a saját fejlesztőrendszeremmel, a Felületépítővel készítettem el, de ez semmi különöset nem tartalmaz, csak a szokásos win32 API alapján elkészített vezérlők megadását, a Windows üzenetkezelőjébe ágyazva.
A felhasználói felület legfontosabb feladata a szerver IP címének, valamint a szolgáltatások neveinek konfigurálhatósága és a szerver elindítása, valamint leállítása.
A szerver elindítását a ‘Start server’ feliratú gombra történő kattintással végezhetjük el. Ezután a gomb felirata ‘Stop server’-re változik, rányomva pedig lezárul a socket és nem fogadunk több kérést. A szerver ezzel a gombbal ismét újra is indítható, nem kell bezárni hozzá a teljes programot.
Az állapotok figyelésére a server_started változót használjuk. Ennek ‘0’ értéke a szerver leállított állapotát jelzi, az ‘1’ érték pedig azt, hogy éppen aktív, fut.
A szerver indítása és leállítása az ablak üzenetkezelésében egyazon eseményhez van rendelve:


case WM_COMMAND:
 switch (LOWORD(wParam))
 {
 case OBJ_ID104:
  //******************
  //SZERVER INDÍTÁSA
  //******************
  if (server_started == 0)
  {
   vege = 0;
   open_server();
   SetWindowTextA(Button1, "STOP SERVER");
   server_started = 1;
   THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  }
  //******************
  //SZERVER LEÁLLÍTÁSA
  //******************
  else if (server_started == 1)
  {
   SetWindowTextA(Form1, "INTERAKTÍV SZERVER - Stopped...");
   SetWindowTextA(Button1, "START SERVER");
   server_started = 0;
   vege = 1;
   close_server();
  }

  break;
 }

 return 0;


Az ablak bezárásának szintén egyenértékű hatást kell kiváltania a szerver leállításával.


case WM_CLOSE:
 vege = 1;
 close_server();
 DestroyWindow(hwnd);
 return 0;


4.2 A beérkező kérések számlálása

A beérkező kéréseket a request_count változó segítségével számolja is a program, amit a bal alsó sarokban található szövegcímke meg is jelenít (“Incoming requests”).
A favicon-os kérések le lesznek vonva a számlálásból.

4.3 A vezérlés változásai

Emlékszünk még az egyszálas megoldásnál bemutatott while ciklusunkra?

while (1)
{
   open_server();
   start_server_listening();
   close_server();
}

Először is az open_server() függvényt kivesszük a ciklusból és berakjuk a már megismert varakozas() szálfüggvényünk végrehajtása elé. A szerver inicializálását ugyanis elég egyszer elvégezni, valahányszor elindítjuk azt.
A varakozas() függvényünkben így elegendő lesz csak a kérések figyelését és feldolgozását elvégezni.


//***********************
//Fő vezérlési szerkezet
//***********************
void varakozas(void* pParams)
{
   while (1)
   {
       if (vege == 1) break;
       start_server_listening();
   }
}


A varakozas() függvény elindítása egyebekben ugyanúgy működik, mint a kiindulási példaprogramban: gombnyomásra, külön szálon elindítva.


  //******************
  //SZERVER INDÍTÁSA
  //******************
  if (server_started == 0)
  {
   vege = 0;
   open_server();
   SetWindowTextA(Button1, “STOP SERVER”);
   server_started = 1;
   THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  }


4.4 Dinamikus konfigurálási lehetőségek

További változás, hogy az open_server() függvényben és később, a szolgáltatások feldolgozásakor, a thread_muvelet() függvényben (lásd következő alfejezetet) az eddig “beégetett” konfigurációs értékeket most dinamikusan olvassuk ki az ablak megfelelő beviteli mezőiből.

Például így (open_server() függvény):

char myip_address[20];

GetWindowTextA(Edit1, myip_address, 20);
inet_pton(AF_INET, myip_address, &serveraddr.sin_addr.s_addr);

Az első szolgáltatás kezeléséhez pedig így használjuk fel a megfelelő beviteli mező tartalmát (thread_muvelet() ):

char reqname1[20];

GetWindowTextA(Edit2,reqname1,20);

  if (strcmp(reqvalue, reqname1) == 0) //getsysinfo
  {
   set_systeminfo(htmlresp);
  }

A másik két szolgáltatás kódja is hasonló lesz:

GetWindowTextA(Edit3, reqname2, 20);
GetWindowTextA(Edit4, reqname3, 20);


//*************************
  // GETSUM
  //*************************
  else if (strcmp(reqvalue, reqname2) == 0) //getsum
  {
   strcpy_s(keyword, _countof(keyword), “V1=”);
   get_request(requrl, keyword, val_str1, ‘?’);
   value1 = atoi(val_str1);
   strcpy_s(keyword, _countof(keyword), “V2=”);
   get_request(requrl, keyword, val_str2, ‘.’);
   value2 = atoi(val_str2);
   value1 += value2;
   _itoa_s(value1, keyword, sizeof(keyword), 10);
   strcpy_s(htmlresp, “<!doctype HTML>\n<html><h3>SUM: “);
   strcat_s(htmlresp, keyword);
   strcat_s(htmlresp, “</h3></html>\0”);
  }

  //*************************
  // CHECKSERVER
  //*************************
  else if (strcmp(reqvalue, reqname3) == 0) //checkserver
  {
   strcpy_s(htmlresp, “|OK|”);
  }


A kérésekre adott szerveroldali válaszok előkészítése után történik meg a válaszok elküldése:


  iResult = send(kliens_socket, htmlresp, (int)strlen(htmlresp) + 1, 0);
  log(htmlresp);


Az egész konfigurálhatóság egyébként annyira bájosan működik a GetWindowTextA() függvényhívások miatt, hogy a szerver leállítása nélkül, lényegében valós időben is módosíthatjuk a szolgáltatások neveit! Nagyon vagány dolog, nemde ?
4.5 Feldolgozás újabb szálakon

Többszálú megoldás esetén továbbá meg kell keresnünk azokat a pontokat, ahol az eddig kompakt folyamatot “elvághatjuk” és egy másik szálba áthelyezhetjük.
A thread_muvelet() szálfüggvény végzi el a bejövő adatok összgyűjtését és értelmezését. Ez az a művelet, amit több szálon is képes lesz a szerverünk elvégezni.
Ezalatt a szerver fő végrehajtási szála pedig már javában fogadhatja az újabb kéréseket.
Ezt a függvényt az előző példaprogram start_server_listening() függvényéből hasítjuk ki. A start_server_listening() függvényben a kérés elfogadásáig minden úgy megy, mint eddig.

Ez az a pont:

clientsocket = accept(serversocket, (SOCKADDR*)&clientaddr, (LPINT)&nLength);

Ha ez rendben van, akkor indítunk el egy újabb végrehajtási szálat:

_beginthread(thread_muvelet, 0, NULL);

Minden további művelet a start_server_listening() függvényből átkerül a szálfüggvénybe.

A szálművelet forráskódja:


//****************************************
//ÜZENETFELDOLGOZÁS KÜLÖN SZÁLON
//****************************************
void thread_muvelet(void* pParams)
{
int iResult;
int iSendResult;
char recvbuf[RECBUFLEN];
int i = 0;
int value1 = 0, value2 = 0;
SOCKET kliens_socket = (SOCKET)pParams;
char htmlresp[10*1024], val_str1[64], val_str2[64];
char requrl[1024], reqvalue[1024], keyword[1024];
char reqname1[20], reqname2[20], reqname3[20];

GetWindowTextA(Edit2,reqname1,20);
GetWindowTextA(Edit3, reqname2, 20);
GetWindowTextA(Edit4, reqname3, 20);
for (i = 0; i < RECBUFLEN; ++i) recvbuf[i] = ‘\0’;

log(“\n*****Collecting request******”);
do {

 iResult = recv(kliens_socket, recvbuf, RECBUFLEN – 2, 0);
 if (iResult > 0) {
  log(“Reading request…”);
  int meret = sizeof(recvbuf);
  log(“******************”);
  log(“Client request:”);
  log(“******************”);
  log(recvbuf);
  log(“******************”);
  shutdown(kliens_socket, SD_RECEIVE);
  strcpy_s(requrl, _countof(requrl), (const char*)recvbuf);
  strcpy_s(keyword, _countof(keyword), “GET /”);
  get_request(requrl, keyword, reqvalue, ‘.’);
  log(“******************”);
  log(“Server response:”);
  log(“******************”);

  //*************************
  // GETSYSINFO
  //*************************
  if (strcmp(reqvalue, reqname1) == 0) //getsysinfo
  {
   set_systeminfo(htmlresp);
  }

  //*************************
  // GETSUM
  //*************************
  else if (strcmp(reqvalue, reqname2) == 0) //getsum
  {
   strcpy_s(keyword, _countof(keyword), “V1=”);
   get_request(requrl, keyword, val_str1, ‘?’);
   value1 = atoi(val_str1);
   strcpy_s(keyword, _countof(keyword), “V2=”);
   get_request(requrl, keyword, val_str2, ‘.’);
   value2 = atoi(val_str2);
   value1 += value2;
   _itoa_s(value1, keyword, sizeof(keyword), 10);
   strcpy_s(htmlresp, “<!doctype HTML>\n<html><h3>SUM: “);
   strcat_s(htmlresp, keyword);
   strcat_s(htmlresp, “</h3></html>\0”);
  }

  //*************************
  // CHECKSERVER
  //*************************
  else if (strcmp(reqvalue, reqname3) == 0) //checkserver
  {
   strcpy_s(htmlresp, “|OK|”);
  }

  //*************************
  // FAVICON SZŰRÉS
  //*************************
  else if (strcmp(reqvalue, “favicon”) == 0)
  {
   --request_count;
   strcpy_s(htmlresp, “Browser connection accepted…”);
  }

  //*************************
  // MINDEN MÁS ESET
  //*************************
  else
  {
   strcpy_s(htmlresp, “<!doctype HTML>\n<html><h3>Unknown command!</h3></html>\0”);
  }

  iResult = send(kliens_socket, htmlresp, (int)strlen(htmlresp) + 1, 0);
  log(htmlresp);
  log(“******************”);

  if (iResult == SOCKET_ERROR)
   log(“Sending response FAILED…”);
  else
  {
   log(“Response sent OK…”);
   shutdown(kliens_socket, SD_SEND);
  }
  closesocket(kliens_socket);
  //closesocket(serversocket);
  //WSACleanup();
  break;
 }
 else if (iResult == 0)
  log(“Connection closing…”);
 else {
  log(“Reading request failed with error”);
  closesocket(kliens_socket);
  WSACleanup();
  return;
 }

} while (iResult > 0);

log(“*****End of request******”);
}


4.6 Teszteljünk!

Ezzel elérkeztünk a technikai megoldások tárgyalásának a végére. Nincsen semmi varázslat, titkos összetevő, ami szükséges lenne még, egyszerűen fordítsuk le a programot, indítsuk el és próbáljuk ki egy böngészővel!

A böngészőbe gépeljük be például:

127.0.0.1/checkserver.html

majd nézzük meg, mit ad vissza a szerverünk!
Nézzük meg a keletkezett naplófájlt és elemezzük gyakorlásképpen a napló bejegyzéseket!

4.7 A példaprogram teljes forráskódja


#include <Winsock2.h>
#include <ws2tcpip.h>
#include <WinInet.h>
#pragma comment(lib, “wininet.lib”)
#pragma comment(lib, “user32.lib”)
#pragma comment(lib, “ws2_32.lib”)
#include <Windows.h>
#include <process.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <commctrl.h>

//*********************************
//ÁLTALÁNOS WIN32API VÁLTOZÓK
//*********************************
#define HIBA_00 TEXT(“Error:Program initialisation process.”)
#define WINSTYLE_DIALOG (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU)
#define IDC_STATIC -1
#define OBJ_ID100 100
#define OBJ_ID101 101
#define OBJ_ID102 102
#define OBJ_ID103 103
#define OBJ_ID104 104
HWND Form1;
HWND Label1;
HWND Edit1;
HWND Edit2;
HWND Label2;
HWND Label3;
HWND Edit3;
HWND Label4;
HWND Edit4;
HWND Button1;
HWND Label5;

HINSTANCE hInstGlob;
int SajatiCmdShow;
char szClassName[] = “WindowsApp”;

//*********************************
//SZERVERVÁLTOZÓK
//*********************************
int vege = 0, server_started = 0;
HANDLE THANDLER;
#define RECBUFLEN 4096
int request_count = 0;
#define MAX_RESP_LENGTH 10*1024
#define MIN_RESP_LENGTH 1024
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
BOOL serverisopen;
SOCKET clientsocket, serversocket;
sockaddr_in serveraddr, clientaddr;
char localinfo[128];
int i, nLength = sizeof(SOCKADDR);
FILE* flog;

void varakozas(void* pParams);
void thread_muvelet(void* pParams);

LRESULT CALLBACK WndProc0(HWND, UINT, WPARAM, LPARAM);
void ShowMessage(LPCTSTR uzenet, LPCTSTR cim, HWND kuldo);

//*********************************
//SZERVERFÜGGVÉNYEK
//*********************************
void init(void);
void set_systeminfo(char* resp_string);
void log(const char* puffer);
void open_server(void);
void start_server_listening(void);
void close_server(void);

//*********************************
//KÉRÉSEK ELEMZÉSE
//*********************************
void get_request(char* urlrequest, char* type, char* retstr, char sepchar);
int get_keyword_location(char* srcstr, char* keyword);
void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar);
unsigned int get_string_length(char* srcstr);

//*********************************
//A windows program belépési pontja
//*********************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT(“StdWinClassName”);
HWND hwnd;
MSG msg;
WNDCLASS wndclass0;
SajatiCmdShow = iCmdShow;
hInstGlob = hInstance;
LoadLibraryA(“COMCTL32.DLL”);

//*********************************
//Ablak osztálypéldány előkészítése
//*********************************
wndclass0.style = CS_HREDRAW | CS_VREDRAW;
wndclass0.lpfnWndProc = WndProc0;
wndclass0.cbClsExtra = 0;
wndclass0.cbWndExtra = 0;
wndclass0.hInstance = hInstance;
wndclass0.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass0.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass0.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);//LTGRAY_BRUSH
wndclass0.lpszMenuName = NULL;
wndclass0.lpszClassName = TEXT(“WIN0”);

//*********************************
//Ablak osztálypéldány regisztrációja
//*********************************
if (!RegisterClass(&wndclass0))
{
 MessageBox(NULL, HIBA_00, TEXT(“Program Start”), MB_ICONERROR); return 0;
}

//*********************************
//Ablak létrehozása
//*********************************
Form1 = CreateWindow(TEXT(“WIN0”),
 TEXT(“INTERAKTÍV SZERVER – Stopped…”),
 WINSTYLE_DIALOG,
 500,
 500,
 300,
 420,
 NULL,
 NULL,
 hInstance,
 NULL);

//*********************************
//Ablak megjelenítése
//*********************************
ShowWindow(Form1, SajatiCmdShow);
UpdateWindow(Form1);

//*********************************
//Ablak üzenetkezelésének aktiválása
//*********************************
while (GetMessage(&msg, NULL, 0, 0))
{
 TranslateMessage(&msg);
 DispatchMessage(&msg);
}
return msg.wParam;
}

//*********************************
//Az ablak callback függvénye: eseménykezelés
//*********************************
LRESULT CALLBACK WndProc0(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;

switch (message)
{
 //*********************************
 //Ablak létrehozásakor közvetlenül
 //*********************************
case WM_CREATE:
 /*Init*/;
 init();

 Label1 = CreateWindow(TEXT(“static”), TEXT(“IP address:”)
  , WS_CHILD | WS_VISIBLE, 10, 15, 120, 30
  , hwnd, (HMENU)(IDC_STATIC), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 Edit1 = CreateWindow(TEXT(“edit”), TEXT(“127.0.0.1”)
  , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 10, 50, 120, 30
  , hwnd, (HMENU)(OBJ_ID100), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 SendMessage(Edit1, EM_SETLIMITTEXT, 15, 0);
 Edit2 = CreateWindow(TEXT(“edit”), TEXT(“getsysinfo”)
  , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 10, 135, 120, 30
  , hwnd, (HMENU)(OBJ_ID101), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 SendMessage(Edit2, EM_SETLIMITTEXT, 20, 0);
 Label2 = CreateWindow(TEXT(“static”), TEXT(“Request #1:”)
  , WS_CHILD | WS_VISIBLE, 10, 100, 120, 30
  , hwnd, (HMENU)(IDC_STATIC), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 Label3 = CreateWindow(TEXT(“static”), TEXT(“Request #2:”)
  , WS_CHILD | WS_VISIBLE, 10, 180, 120, 30
  , hwnd, (HMENU)(IDC_STATIC), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 Edit3 = CreateWindow(TEXT(“edit”), TEXT(“getsum”)
  , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 10, 215, 120, 30
  , hwnd, (HMENU)(OBJ_ID102), ((LPCREATESTRUCT)lParam)->hInstance, NULL); //getsum.V1=5?V2=9.txt
 SendMessage(Edit3, EM_SETLIMITTEXT, 20, 0);
 Label4 = CreateWindow(TEXT(“static”), TEXT(“Request #3:”)
  , WS_CHILD | WS_VISIBLE, 10, 260, 120, 30
  , hwnd, (HMENU)(IDC_STATIC), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 Edit4 = CreateWindow(TEXT(“edit”), TEXT(“checkserver”)
  , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 10, 295, 120, 30
  , hwnd, (HMENU)(OBJ_ID103), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 SendMessage(Edit4, EM_SETLIMITTEXT, 20, 0);
 Button1 = CreateWindow(TEXT(“button”), TEXT(“START SERVER”)
  , WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE, 150, 15, 120, 310
  , hwnd, (HMENU)(OBJ_ID104), ((LPCREATESTRUCT)lParam)->hInstance, NULL);
 Label5 = CreateWindow(TEXT(“static”), TEXT(“Incoming requests: 0”)
  , WS_CHILD | WS_VISIBLE, 10, 340, 260, 30
  , hwnd, (HMENU)(IDC_STATIC), ((LPCREATESTRUCT)lParam)->hInstance, NULL);

 //open_server();
 return 0;
 //*********************************
 //vezérlőelemek működtetése
 //*********************************
case WM_COMMAND:
 switch (LOWORD(wParam))
 {
 case OBJ_ID104:
  //******************
  //SZERVER INDÍTÁSA
  //******************
  if (server_started == 0)
  {
   vege = 0;
   open_server();
   SetWindowTextA(Button1, “STOP SERVER”);
   server_started = 1;
   THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  }
  //******************
  //SZERVER LEÁLLÍTÁSA
  //******************
  else if (server_started == 1)
  {
   SetWindowTextA(Form1, “INTERAKTÍV SZERVER – Stopped…”);
   SetWindowTextA(Button1, “START SERVER”);
   server_started = 0;
   vege = 1;
   close_server();
  }

  break;
 }

 return 0;
 //*********************************
 //Ablak átméretezése
 //*********************************
case WM_SIZE:
 return 0;

 //*********************************
 //Ablak kliens területének újrarajzolása
 //*********************************
case WM_PAINT:
 hdc = BeginPaint(hwnd, &ps);
 EndPaint(hwnd, &ps);
 return 0;
 //*********************************
 //Ablak bezárása
 //*********************************
case WM_CLOSE:
 vege = 1;
 close_server();
 DestroyWindow(hwnd);
 return 0;
 //*********************************
 //Ablak megsemmisítése
 //*********************************
case WM_DESTROY:
 PostQuitMessage(0);
 return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

//*********************************
//Üzenetablak OK gombbal
//*********************************
void ShowMessage(LPCTSTR uzenet, LPCTSTR cim, HWND kuldo)
{
MessageBox(kuldo, uzenet, cim, MB_OK);
}

//***********************
//Naplózás megvalósítása
//***********************
void log(const char* puffer)
{
FILE* fp = NULL;
SYSTEMTIME datumido;
GetLocalTime(&datumido);

if (vege == 1) return;
char logbuffer[2048];
int meret = sizeof(&puffer);
//strcpy_s(logbuffer, puffer);
fopen_s(&fp, “LOG.txt”, “at”);
if (fp == NULL) return;
//fwrite(puffer,sizeof(puffer),1,flog);
fprintf_s(fp, “%i.%i.%i %i:%i:%i.%i %s\r\n”, datumido.wYear, datumido.wMonth, datumido.wDay, datumido.wHour, datumido.wMinute, datumido.wSecond, datumido.wMilliseconds, puffer);
fclose(fp);
}

//******************************
//Szerver inicializálása
//******************************
void open_server(void)
{
char myip_address[20];
serverisopen = false;
serversocket = INVALID_SOCKET;

if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
 WSACleanup();
 log(“Winsock init error…”);
 return;
}

log(“Winsock init OK.”);
log(wsaData.szDescription);

gethostname(localinfo, sizeof(localinfo));
log(localinfo);
log(“PORT: 80”);

serversocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serversocket == INVALID_SOCKET)
{
 WSACleanup();
 log(“Couldn’t create server socket…”);
}
log(“Server socket created…”);

serveraddr.sin_family = AF_INET;
GetWindowTextA(Edit1, myip_address, 20);
inet_pton(AF_INET, myip_address, &serveraddr.sin_addr.s_addr);//127.0.0.1
serveraddr.sin_port = htons(80);
if (bind(serversocket, (SOCKADDR*)&serveraddr, sizeof(serveraddr)) == SOCKET_ERROR)
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log(“Couldn’t bind server socket…”);
 return;
}
else log(“Binding server socket OK…”);
}

//****************************************
//Bejövő kérések fogadása és feldolgozása
//****************************************
void start_server_listening(void)
{
int i;
int iResult;
int iSendResult;
int recvbuflen = 4096;
char recvbuf[4096];

clientsocket = INVALID_SOCKET;

for (i = 0; i < recvbuflen; ++i) recvbuf[i] = ‘\0’;
if (serversocket == INVALID_SOCKET) return;

SetWindowTextA(Form1, “INTERAKTÍV SZERVER – Online…”);
if (listen(serversocket, 1) == SOCKET_ERROR) //max 1 kapcsolat
{
 WSACleanup();
 serversocket = INVALID_SOCKET;
 log(“Server socket FAILED to listen…”);
 return;
}
else
{
 log(“Server socket is listening…”);
 serverisopen = true;
}

clientsocket = accept(serversocket, (SOCKADDR*)&clientaddr, (LPINT)&nLength);
//clientsocket = accept(serversocket, NULL, NULL);
if (clientsocket == INVALID_SOCKET)
 log(“Server socket failed to accept connection…”);
else
{
 log(“CONNECTION OK…”);
 ++request_count;

 _itoa_s(request_count, tmpbuf2, sizeof(tmpbuf2), 10);
 strcpy_s(tmpbuf, _countof(tmpbuf), “Incoming requests : “);
 strcat_s(tmpbuf, _countof(tmpbuf), tmpbuf2);
 SetWindowTextA(Label5, tmpbuf);
 //if(server_thread_count < 9) SERVER_THREAD[server_thread_count++] = (HANDLE)_beginthread(thread_muvelet, 0, NULL);
 _beginthread(thread_muvelet, 0, (void*)clientsocket);
 clientsocket == INVALID_SOCKET;
}
}

//****************************************
//ÜZENETFELDOLGOZÁS KÜLÖN SZÁLON
//****************************************
void thread_muvelet(void* pParams)
{
int iResult;
int iSendResult;
char recvbuf[RECBUFLEN];
int i = 0;
int value1 = 0, value2 = 0;
SOCKET kliens_socket = (SOCKET)pParams;
char htmlresp[10*1024], val_str1[64], val_str2[64];
char requrl[1024], reqvalue[1024], keyword[1024];
char reqname1[20], reqname2[20], reqname3[20];
char Tmpbuf[1024], tmpbuf2[1024];

GetWindowTextA(Edit2,reqname1,20);
GetWindowTextA(Edit3, reqname2, 20);
GetWindowTextA(Edit4, reqname3, 20);
for (i = 0; i < RECBUFLEN; ++i) recvbuf[i] = ‘\0’;

log(“\n*****Collecting request******”);
do {

 iResult = recv(kliens_socket, recvbuf, RECBUFLEN – 2, 0);
 if (iResult > 0) {
  log(“Reading request…”);
  int meret = sizeof(recvbuf);
  log(“******************”);
  log(“Client request:”);
  log(“******************”);
  log(recvbuf);
  log(“******************”);
  shutdown(kliens_socket, SD_RECEIVE);
  strcpy_s(requrl, _countof(requrl), (const char*)recvbuf);
  strcpy_s(keyword, _countof(keyword), “GET /”);
  get_request(requrl, keyword, reqvalue, ‘.’);
  log(“******************”);
  log(“Server response:”);
  log(“******************”);

  //*************************
  // GETSYSINFO
  //*************************
  if (strcmp(reqvalue, reqname1) == 0) //getsysinfo
  {
   set_systeminfo(htmlresp);
  }

  //*************************
  // GETSUM
  //*************************
  else if (strcmp(reqvalue, reqname2) == 0) //getsum
  {
   strcpy_s(keyword, _countof(keyword), “V1=”);
   get_request(requrl, keyword, val_str1, ‘?’);
   value1 = atoi(val_str1);
   strcpy_s(keyword, _countof(keyword), “V2=”);
   get_request(requrl, keyword, val_str2, ‘.’);
   value2 = atoi(val_str2);
   value1 += value2;
   _itoa_s(value1, keyword, sizeof(keyword), 10);
   strcpy_s(htmlresp, “<!doctype HTML>\n<html><h3>SUM: “);
   strcat_s(htmlresp, keyword);
   strcat_s(htmlresp, “</h3></html>\0”);
  }

  //*************************
  // CHECKSERVER
  //*************************
  else if (strcmp(reqvalue, reqname3) == 0) //checkserver
  {
   strcpy_s(htmlresp, “|OK|”);
  }

  //*************************
  // FAVICON SZŰRÉS
  //*************************
  else if (strcmp(reqvalue, “favicon”) == 0)
  {
   --request_count;
   strcpy_s(htmlresp, “Browser connection accepted…”);
  }

  //*************************
  // MINDEN MÁS ESET
  //*************************
  else
  {
   strcpy_s(htmlresp, “<!doctype HTML>\n<html><h3>Unknown command!</h3></html>\0”);
  }

  //*************************
  // KÉRÉSEK SZÁMLÁLÁSA
  //*************************
  _itoa_s(request_count, tmpbuf2, sizeof(tmpbuf2), 10);
  strcpy_s(tmpbuf, _countof(tmpbuf), "Incoming requests : ");
  strcat_s(tmpbuf, _countof(tmpbuf), tmpbuf2);
  SetWindowTextA(Label5, tmpbuf);

  //*************************
  // VÁLASZ ELKÜLDÉSE
  //*************************
  iResult = send(kliens_socket, htmlresp, (int)strlen(htmlresp) + 1, 0);
  log(htmlresp);
  log(“******************”);

  if (iResult == SOCKET_ERROR)
   log(“Sending response FAILED…”);
  else
  {
   log(“Response sent OK…”);
   shutdown(kliens_socket, SD_SEND);
  }
  closesocket(kliens_socket);
  //closesocket(serversocket);
  //WSACleanup();
  break;
 }
 else if (iResult == 0)
  log(“Connection closing…”);
 else {
  log(“Reading request failed with error”);
  closesocket(kliens_socket);
  WSACleanup();
  return;
 }

} while (iResult > 0);

log(“*****End of request******”);
}

//**************************
//A SZERVER LEÁLL
//**************************
void close_server(void)
{
log(“\nEnding…”);

log(“WSA cleanup…”);
shutdown(clientsocket, SD_SEND);
if (serverisopen == true) closesocket(serversocket);
if (clientsocket != INVALID_SOCKET) closesocket(clientsocket);
shutdown(serversocket, SD_SEND);
shutdown(serversocket, SD_RECEIVE);
WSACleanup();
serverisopen = false;
/*clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;*/
log(“Sockets closed…”);
}

//***********************
//Fő vezérlési szerkezet
//***********************
void varakozas(void* pParams)
{
while (1)
{
 if (vege == 1) break;
 start_server_listening();
}
}

//***********************
//KÉRÉSEK PARSE-OLÁSA
//***********************
void get_request(char* urlrequest, char* type, char* retstr, char sepchar)
{
int i;

i = get_keyword_location(urlrequest, type);
get_keyword_value(urlrequest, type, i, retstr, sepchar);
i = i;
}

//********************************
//KULCSSZÓ HELYÉNEK MEGÁLLAPÍTÁSA
//********************************
int get_keyword_location(char* srcstr, char* keyword)
{
int i = 0, j = 0, retval = -1, MAX_i = get_string_length(srcstr) – get_string_length(keyword);

while (i < MAX_i)
{
 for (j = 0; j < get_string_length(keyword) – 1; ++j)
 {
  if (srcstr[i + j] == keyword[j]) retval = 0;
  else {
   retval = -1; break;
  }
 }
 if (retval != -1) {//megvan a keyword helye
  retval = i;
  break;
 }
 ++i;
}
return retval + get_string_length(keyword);
}

//*********************
//KULCSÉRTÉK KINYERÉSE
//*********************
void get_keyword_value(char* srcstr, char* keyword, int startpoz, char* retstr, char sepchar)
{
int i = startpoz, j = 0;

for (; srcstr[i] != sepchar; ++i)
{
 retstr[j++] = srcstr[i];
}
retstr[j] = 0;
}

//********************************
//SZTRING HOSSZÁNAK MEGHATÁROZÁSA
//********************************
unsigned int get_string_length(char* srcstr)
{
unsigned int i = 0;
while (srcstr[i] != ‘\0’) ++i;
return i;
}

//************************
//PROGRAM INICIALIZÁCIÓJA
//************************
void init(void)
{
fopen_s(&flog, “LOG.txt”, “wt”);
fclose(flog);
}

//*************************************
//GETSYSINFO ÜZENET DINAMIKUS TARTALMA
//*************************************
void set_systeminfo(char* resp_string)
{
int i;
char tempstr[2048];
char cname[64];
LONGLONG TotalMemoryInKilobytes = 0;

DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
SYSTEMTIME datumido;

GetLocalTime(&datumido);

GetComputerNameA(cname, &len);

strcpy_s(resp_string, MAX_RESP_LENGTH, “System time: “);
_itoa_s(datumido.wYear, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “.”);
_itoa_s(datumido.wMonth, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “.”);
_itoa_s(datumido.wDay, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “ “);
_itoa_s(datumido.wHour, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “:”);
_itoa_s(datumido.wMinute, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “:”);
_itoa_s(datumido.wSecond, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);

strcat_s(resp_string, MAX_RESP_LENGTH, “|Computer name: “);
strcat_s(resp_string, MAX_RESP_LENGTH, (const char*)cname);
strcat_s(resp_string, MAX_RESP_LENGTH, “|Installed RAM: “);

GetPhysicallyInstalledSystemMemory((PULONGLONG)&TotalMemoryInKilobytes);
i = TotalMemoryInKilobytes;
_itoa_s(i / 1024 / 1024, tempstr, sizeof(tempstr), 10);
strcat_s(resp_string, MAX_RESP_LENGTH, tempstr);
strcat_s(resp_string, MAX_RESP_LENGTH, “|”);
}


4.7.1 Példa naplóbejegyzésre

Az alábbiakban a szerver elindítását, majd egy kérés feldolgozásának a menetét tekinthetjük át egy naplófájl kivonatból.

2023.11.4 22:6:12.167 Winsock init OK.
2023.11.4 22:6:12.167 WinSock 2.0
2023.11.4 22:6:12.182 DESKTOP-XXXXXXX
2023.11.4 22:6:12.182 PORT: 80
2023.11.4 22:6:12.182 Server socket created…
2023.11.4 22:6:12.182 Binding server socket OK…
2023.11.4 22:6:12.182 Server socket is listening…
2023.11.4 22:6:26.23 CONNECTION OK…
2023.11.4 22:6:26.28 Server socket is listening…
2023.11.4 22:6:26.30 Reading request…
2023.11.4 22:6:26.33 ******************
2023.11.4 22:6:26.33 Client request:
2023.11.4 22:6:26.33 ******************
2023.11.4 22:6:26.33 GET /checkserver.htm HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: hu-HU,hu;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

2023.11.4 22:6:26.33 ******************
2023.11.4 22:6:26.33 ******************
2023.11.4 22:6:26.33 Server response:
2023.11.4 22:6:26.38 ******************
2023.11.4 22:6:26.38 |OK|
2023.11.4 22:6:26.38 ******************
2023.11.4 22:6:26.38 Response sent OK…
2023.11.4 22:6:26.38 *****End of request******
2023.11.4 22:6:26.149 CONNECTION OK…
2023.11.4 22:6:26.149 Server socket is listening…
2023.11.4 22:6:26.154 GET /favicon.ico HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: image/avif,image/webp,*/*
Accept-Language: hu-HU,hu;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Referer: http://127.0.0.1/checkserver.htm
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin

2023.11.4 22:6:26.154 ******************
2023.11.4 22:6:26.154 ******************
2023.11.4 22:6:26.154 Server response:
2023.11.4 22:6:26.154 ******************
2023.11.4 22:6:26.154 Browser connection accepted…
2023.11.4 22:6:26.154 ******************
2023.11.4 22:6:26.154 Response sent OK…
2023.11.4 22:6:26.154 *****End of request******



5 További gondolatok, ötletek

5.1 Szerver funkcionalitások

Egy szervert rengeteg képességgel fel lehet ruházni, a kérések egyszerű kiszolgálásán túl.

Néhány gondolat:
- beérkező kérések szűrése IP cím alapján
- kérések monitorozása, DOS támadás kivédése céljából
- hálózati forgalom tartalmi elemzése
- statisztikák készítése forgalom alapján
- nézzük meg, hogy a htmlresp tömböt használva mekkora maximális méretű választ tudunk átküldeni a kliensnek!

5.2 Átküldhető adatok mennyisége

A hálózati kommunikáció alapvetően kétféle kérés típust különböztet meg: GET és POST típusút. Előbbi mérete korlátozottabb, általában kb. 1 kilobájtnyi adat elküldését teszi lehetővé, jellemzően URL-ben.
Utóbbi esetben jóval több adatot küldhetünk, de minden plusz adatot külön meg kell adnunk az URL-től mintegy függetlenül. Hátránya, hogy picit bonyolultabb így megadni az adatokat.
A GET és a POST, illetve az ezekre érkező válaszok típusai csupán értelmezési kérdések, a socket programozás szintjén ez remekül látszik. Akár 8-10 megabájtnyi adatot is küldhetünk, nem probléma. Más kérdés viszont, hogy egy böngésző hogyan kezeli ezeket az eseteket. Egy 20MB méretű választ (dinamikusan generált HTML dokumentum) például a Firefox böngésző az én tesztjeim során már igen nehézkesen kezelt, mintha nem is egyszerre töltötte volna be, hanem fokozatosan, attól függően, hogy mennyire görgettem le az oldal tartalmában. 4-8 MB esetén már jóval kevesebb ideig gondolkozott a böngésző, míg 1-2 MB esetén lényegében azonnal “átjött” minden adat. Gyanítom, hogy ez csak a böngésző adatkezelése miatt van így, egy célprogram fürgébben elboldogulna nagyméretű válaszokkal. Szóval lehet kísérletezgetni…

5.3 Szoftvertesztelés

Érdekes felhasználási terület lehet ún. szimulátorok készítése olyan esetben, amikor nem áll rendelkezésre egy bizonyos szerver, de elégséges lehet a viselkedését (egyszerű statikus, vagy dinamikus válaszok generálása) csupán utánozni. Ilyesmit teszt automatizálással összefüggő feladatok során szoktak használni.
Mindenkiben felmerülhetnek természetesen további ötletek is. Egy dolog azonban biztos: rengeteg megoldás elkészítésére nyílik lehetőség a socket alapú kommunikáció közvetlen programozásának a segítségével.
Kellemes és hasznos kísérletezést kívánok!
KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
Vissza a tartalomhoz