tanulas_kliens_szerver_programozas_tomoren_1 - Fehér Krisztián honlapja

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

tanulas_kliens_szerver_programozas_tomoren_1

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

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 Socket programozás alapjai
2.1 Infrastrukturális előfeltételek
2.2 Szoftveres hálózati konfigurálás
3 Kliens programok írása
3.1 Internetes erőforrások közvetlen letöltése
3.2 Biztonsági megfontolások
3.3 Parancssoros böngésző készítése
4 Kliens-szerver kommunikáció megvalósítása
4.1 Parancssoros kliens program írása
4.2 Parancssoros szerver írása
5 GUI-s kiszolgáló (szerver) program írása
5.1 Előkészületek
5.2 Kérés érkeztetése és válasz küldése
5.3 Lezárás
6 A HTTP fejlécekről
7 Záró gondolatok

1 Előszó

Ez a leírás azon érdeklődőknek szól, akik mélyebben is meg szeretnék ismerni a szerver-kliens programok működését.

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

A leírás célja a Windows Socket, ill. Internet API-jának olyan, alapszintű ismertetése, melynek segítségével az olvas képes lesz saját maga leprogramozni egyszerű működésű szerver- és kliensprogramokat, melyek a TCP protokollt használják.
A leírás ismeretanyaga nem alkalmas arra, hogy nagyteljesítményű, vagy szuperbiztonságos webszervert készítsünk, sokkal inkább a szemléltetés és a technikai folyamatok bemutatása van előtérben.
Egyszerű példákkal kezdünk, majd fejest ugrunk a témába és először a kliens-, majd szerverprogramok megalkotásán rágjuk végig magunkat.

1.2 Követelmények

Ez a leírás nem kezdőknek készült. Alapvető hálózati alapsimeretek mindenképpen szükségesek.
A leírás ismeretanyagának feldolgozásához és a kipróbáláshoz a C nyelv ismerete szükséges, mivel a leírás példái a klasszikus Windows alkalmazásfejlesztés nyomdokában járnak, C / C++ programozási nyelven.
A WIN32 API és a Visual Studio Community Edition mint fejlesztőeszközök alapvető ismerete erősen javasolt.
Erősen ajánlott két fizikai számítógép a példák legteljesebb körű és nyugodt kiprópbálásához, bár ez nem kötelező, egy géppel is kipróbálható minden, sőt, még internethozzáférés sem kell hozzá.

1.3 Letölthető programkódok

A leírásban található kódokat használó példaalkalmazások teljes forráskódja letölthető a következő weboldalról:




2 A Socket programozás alapjai

A hálózati kommunikációt megvalósító alkalmazásoknak a klasszikus megközelítés szerint két típusa van: kliens és szerver.
A kliens alkalmazások kéréseket küldenek a szerver alkalmazások felé és fogadják a szerverek válaszait.
A szerver alkalmazás kéréseket szolgál ki oly módon, hogy azokra valamilyen választ ad.

2.1 Infrastrukturális előfeltételek

Ahhoz, hogy TCP/IP protokoll felhasználásával alkalmazások kommunikálni tudjanak egymással, azonos hálózaton kell lenniük, hogy láthassák egymást. Ennek viszont az is a feltétele, hogy a mind a kliens, mind a szerver alkalmazást futtató gép rendelkezzen saját IP címmel. Alternatívaként az alapértelmezett 127.0.0.1 IP címet is használhatjuk a gépünkön és akár egyazon gépen futhat a szerver és a kliens is, ez a legegyszerűbb megoldás. A következőkben két fizikai gép közötti kapcsolat beállításáról fogok szólni. Ha ilyen felállás mellett döntünk, akkor még többet tanulhatunk a hálózatok működéséről.
A kapcsolat fizikai vetülete legyen egy közönséges Ethernet kommunikáció, amelyben két gépet közvetlenül kötünk össze egy Cat kábellel. Régebben erre speciális, ún. crosslink kábelt használtak, de napjainkban az Ethernet vezérlők felismerik, ha LAN kábellel két gép közvetlenül össze van kötve és ennek megfelelően tudnak használni egy “sima” LAN kábelt is.
A sebesség tekintetében érdemes a 2.5Gbps -ot, vagy többet belőni etalonnak, ha nagyon sok adatot kell mozgatni, de az 1 Gbps sebesség is bőven megfelel a leírás példaprogramjainak kipróbálásához.
Az alábbiakban bemutatom, hogyan tudjuk előkészíteni a gépeket Windows rendszeren. ezek a lépések Windows 10 és 11 rendszeren használhatóak, más verzióknál a szükséges lépések eltérhetnek.

2.2 Szoftveres hálózati konfigurálás

Nyissuk meg a Windowsban a Vezérlőpultot, majd kattintsunk duplán az alábbi ikonra!



Ezt követően válasszuk ki az ‘Adapterbeállítások módosítása’ lehetőséget!



Megjelennek a gépünkön elérhető hálózati adapterek. Egy átlagos gépen egy Ethernet, esetleg egy WLAN adapter található többnyire. Az ’Ethernet’ adapteren kattintsunk a jobb egérgombbal, majd a megjelenő helyi menüben válasszuk ki a ’Tulajdonságok’ lehetőséget!



A megjelenő beállításablakban tegyünk egy pipát a ‘TCP/IP protokoll 4-es verziója’ lehetőség mellé, válasszuk is ki ezt az elemet, majd kattintsunk a ‘Tulajdonságok’ nyomógombra!



Ezután meg kell adnunk egy IP címet a gépünkhöz. A hozzá tartozó ún. alhálózati maszk automatikusan kitöltésre kerül.



Két lépés még hátra van. Először is, a hálózatunknak magánhálózatnak kell lennie. Ehhez nyissuk meg a PowerShell-t és írjuk be a következőt:

Set-NetConnectionProfile -Interfacealias Ethernet -networkcategory “Private”

A fenti PowerShell példában a hálózati adapter neve ‘Ethernet’ volt. Más gépeken ez a név eltérhet.
Utolsó lépésként engedélyeznünk kell a Windows számára az ún. hálózatfelderítést. Ezt a Hálózati és megosztási központ-Speciális megosztási beállítások beállításcsoportjánál tudjuk megtenni.



Előfordulhat, hogy az aktuálisan használt vírusírtó szoftverünk blokkolja ezt a lehetőséget. Ilyenkor a víruskereső programban manuálisan is aktiválnunk kell ezt a lehetőséget.
Ezek a legfontosabb beállítások, melyeket a teszthálózatunk valamennyi gépén el kell végeznünk!
Győződjünk meg róla, hogy a kábel csatlakoztatva van-e mindkét gépen (használhatunk switch-et is, ha még több gépet akarunk összekötni) és az alaplapi Ethernet csatlakozó mindkét ún. link, azaz kapcsolatjelző lámpácskája világít-e!
Ezután egy parancssori PING paranccsal ellenőrizzük, hogy mindegyik gép eléri-e a másikat hálózaton keresztül!
Csak ha a fentiek működnek, kezdhetjük el a tényleges programozási munkát.



3 Kliens programok írása

Ebben a fejezetben a kliens-szerver programozás talán könnyebbnek nevezhető végével foglalkozunk: a kliens programok írásával.
Itt egy kérést kell tudnia megfogalmaznia a programunknak és a szerver válaszát kell visszaolvasnunk.
Minden, amit egy ilyen programmal megtehetünk, az megegyezik azzal, amit egy böngészővel kaphatunk, amikor egy internetes erőforrást (URL – Universal Ressource Locator) megnyitunk. Azt, hogy mit tudunk megtekinteni, természetesen függ az adott kiszolgáló gép beállításaitól is. Ez azt is jelenti, hogy csak olyan válaszokat fogunk kapni, amiket a szerver egyébként megenged a konfigurációjának megfelelően, tehát egy PHP fájl esetén csak a PHP fájl futásának kimenetét látjuk majd, nem magát a fájl tartalmát, továbbá nem fogunk tudni letiltott hozzáférésű fájlokhoz sem hozzáférni.
Szintén eltérő válaszokat kaphatunk a biztonságos HTTPS-t használó weboldalak, szerverek esetén. Egységes eredményeket ne várjunk, de érdemes próbálkozni!
Az összes segédprogramunk a következő fejlécállományokat és könyvtárakat igényli majd:


#include <Windows.h>
#include <iostream>
#include <WinInet.h>
#include <stdio.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "user32.lib")


3.1 Internetes erőforrások közvetlen letöltése

Amennyiben csak általánosan hozzáférhető fájlokat, vagy csak HTML dokumentumokat akarunk letölteni, megfontolható lehet a Windows Internet API-ja is. Ez egy sor magasszintű függvényt kínál internetes erőforrások eléréséhez.
Ennek az API-nak a bemutatása nem célja ezen leírásnak, de hasznossága miatt egy rövid példa erejéig érdemes kitérni rá.
Az alábbi példaprogram (PELDA_01) az InternetOpenUrlA függvény paramétereként megadott internetes erőforrásfájlt fogja letölteni és elmenteni egy fájlba.
A parancssoros program használatát felhasználóbaráttá tesszük azáltal, hogy a weboldal URL címét és az elmentendő fájl nevét manuálisan megadhatjuk, például így:

pelda_01.exe https://www.XYZ.com/index.html weboldal.html

Természetesen a www.XYZ.com cím helyett egy létező weboldalt kell megadnunk.
Megjegyzendő, hogy a segédprogrammal bármilyen erőforrást, például képeket is le lehet tölteni, parancssorból.

Forráskód:


#include <Windows.h>
#include <iostream>
#include <WinInet.h>
#include <stdio.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "user32.lib")

char buffer[1000000];

void download_URL(char *eleresiut, char* fajlnev);

//Főprogram
int main(int argc, char *argv[])
{
 if (argc != 3) printf("Hasznalat: PROGAMNEV URL FAJLNEV\n");
 else download_URL(argv[1], argv[2]);
 return 0;
}

//Erőforrás letöltése
void download_URL(char* eleresiut, char* fajlnev)
{
 //A kapcsolat letrehozasa
 HINTERNET internet = InternetOpenA("Letolto", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, NULL);
 if (internet)
 {
   HINTERNET file_handle = InternetOpenUrlA(internet, eleresiut, NULL, 0, INTERNET_FLAG_RAW_DATA, 0);
   if (file_handle)
   {
     DWORD bytes_read = 0;
     
//A tavoli eroforras letoltese
     InternetReadFile(file_handle, buffer, 1000000, &bytes_read);

     //fájl kiírása
     FILE* fp;
     fopen_s(&fp, fajlnev, "wb");
     if (fp != NULL)
     {
       fwrite(buffer, bytes_read, 1, fp);
       fclose(fp);
     }
   }

   InternetCloseHandle(internet);
 }
}


3.2 Biztonsági megfontolások

A módszer akár weboldalak automatizált tesztelésére, például kimenetek, válaszok tartalmának ellenőrzésére, vagy teljesítménymérésre is alkalmazható.
Ilyen és ehhez hasonló programok írásakor azonban mindig nagyon körültekintően kell eljárni a letöltési kérések generálása tekintetében. Egy rosszul átgondolt programot “ráeresztve” egy élesben működő szerverre könnyen leterhelheti a kiszolgálógépet, nagyon hosszúra megnyújtva annak válaszadási képességeit, ezért elsősorban elszeparált, lokális, de semmiképpen sem éles rendszereken ajánlatos kísérletezni.

3.3 Parancssoros böngésző készítése

A következőkben egy nagyon primitív parancssoros böngészőt fogunk elkészíteni (PELDA_02), aminek a segítségével valóban konzolablakban meg tudjuk jeleníteni tetszés szerinti weboldalak tartalomtípusait.
Ennek a példának az erejéig még megmaradunk az InternetOpenUrlA függvény használata mellett, mielőtt a mélyvízbe ugrunk.
Navigálásra itt azért nem lesz lehetőség, mint egy teljesértékű böngészőben, mégis nagyon sokféle feladat megoldására használható a szöveges tartalom megjelenítésén kívül, például URL-ek, esetleg email címek szűrésére.
Ez a program továbbá nem menti el a letoltott eroforrast, hanem a képernyőre írja ki. Persze parancssoros programról lévén szó, nyugodtan használhatjuk a standard kimenet átirányítását például a ‘> kimenet.html’ toldalék segítségével a program elindításakor.
A program működése két függvényen alapul, nem számítva a download_url() letöltőfüggvényt:

unsigned int search_token(unsigned char* szoveg, unsigned char* token, unsigned int startpos)

Egy megadott karaktersorozatot (token) keres a letöltött erőforrásban, a megadott stratpos indextől kiindulva.
A függvény visszatérési értéke a találat helyét/indexét adja meg.

unsigned int parse_text(unsigned char* szoveg, unsigned char* token, unsigned int startpos)

Ez a függvény a search_token által megtalált helytől kezdve kiolvassa a letöltött tartalomból a záró ”/>” tag-ig tartó szöveget.
Nem a végletekig kimunkált HTML elemző függvény ez persze, de a célunknak tökéletesen megfelel.
A fentiekből következik, hogy ez a program elsősorban szövegalapú erőforrásokban történő keresésre használható.

A parancssoros használat sémája:

pelda_02.exe https://www.XYZ.com/index.html cf1

A teljes forráskód:


#include <Windows.h>
#include <iostream>
#include <WinInet.h>
#include <string.h>
#include <stdio.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "user32.lib")

//Segédváltozók
unsigned char buffer[1024 * 1024 ];
unsigned char s_token[128];
unsigned int szoveg_poz=0;
unsigned char kimenet[1024 * 1024];

void download_URL(char* eleresiut);
unsigned int search_token(unsigned char* szoveg, unsigned char* token, unsigned int startpos);
unsigned int parse_text(unsigned char* szoveg, unsigned char* token, unsigned int startpos);

//Főprogram
int main(int argc, char* argv[])
{
if (argc != 3) printf("Hasznalat: PROGAMNEV URL TOKEN\n");
else
{
 strcpy((char*)s_token, argv[2]);
 download_URL(argv[1]);
 //printf("%s\n",buffer);
 do
 {//kereses vegrehajtasa a letoltott eroforrasban
  szoveg_poz = search_token(buffer, s_token, szoveg_poz + strlen((const char*)s_token));
  if (szoveg_poz != 0) parse_text2(buffer, s_token, szoveg_poz);
 } while (szoveg_poz != 0);
}
return 0;
}

//Erőforrás letöltése
void download_URL(char* eleresiut)
{
HINTERNET internet = InternetOpenA("Letolto_kereso", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, NULL);
if (internet)
{
 //A kapcsolat letrehozasa
 HINTERNET file_handle = InternetOpenUrlA(internet, eleresiut, NULL, 0, INTERNET_FLAG_RAW_DATA, 0);
 if (file_handle)
 {
  DWORD bytes_read = 0;

  //A tavoli eroforras letoltese
  InternetReadFile(file_handle, buffer, 1000000, &bytes_read);
 }

 InternetCloseHandle(internet);
}
}

//Tag keresése az erőforrásban
unsigned int search_token(unsigned char* szoveg, unsigned char* token, unsigned int startpos)
{
int i, j,token_talalat;//0-nincs,1-van

for (i = startpos; i < strlen((const char*)szoveg); ++i)
{
 token_talalat = 1;
 for (j = 0; j < strlen((const char*)token); ++j)
 {
  if (szoveg[i + j] != token[j])
  {
   token_talalat = 0;
   break;
  }
 }

 //talalat
 if (token_talalat == 1)
 {
  return i;
 }
}

return 0;
}

//Szöveg kikeresése tag-ből
unsigned int parse_text2(unsigned char* szoveg, unsigned char* token, unsigned int startpos)
{
int i, j = 0;//0-nincs,1-van

for (i = 0; i < 1024 * 1024; ++i) kimenet[i] = 0;
for (i = startpos; i < strlen((const char*)szoveg); ++i)
{
 if (szoveg[i] == '>' or szoveg[i] == '\n' or szoveg[i] == 0) break;
 else kimenet[j++] = szoveg[i];
 if (j == (1024 * 1024) - 1) break;
}

printf("%s\n", kimenet);
}


Példa a program kimenetére:

> pelda_02 http://XYZWEBOLDAL href

href="index.html"
href="webaruhaz.html"
href="blog/index.php"
href="videok.html"
href="demo-zona.html"



4 Kliens-szerver kommunikáció megvalósítása

Még nagyobb kontrollt szerezhetünk a dolgok felett, ha a Socket API felhasználásával írjuk meg a programjainkat. A socket egy kommunikációs csatorna, amin keresztül az alkalmazások TCP protokoll alapon kommunikálni tudnak egymással. A csatornák azonosítóval rendelkeznek, amit port-nak neveznek. A port egy 0-65535-ig terjedő egész szám lehet.
A Winsock használatához az alábbi könyvtárakat kell belinkelnünk programjainkba:


#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "Ws2_32.lib")


A következő példaprogramok csak párban próbálhatóak ki, ezért először mindkettőt le kell majd fordítanunk.

4.1 Parancssoros kliens program írása

A kliensprogram (PELDA_03) elküld egy kérést a localhost (127.0.0.1) szerver felé és kiírja a szerver válaszát a képernyőre.
A következő változókra lesz szükségünk a kérések elküldéséhez és a válaszok fogadásához:


#define BUFLEN 4096
char recvbuf[BUFLEN] = "";
char sendbuf[BUFLEN] = "";


A socket kezeléséhez az alábbi technikai objektumra lesz szükség:

WSADATA wsaData;


A kapcsolat létrehozásához egy SOCKET típusú segédobjektumra is szükség lesz.


SOCKET ConnectSocket = INVALID_SOCKET;


Ez egy előjel nélküli numerikus érték, ami az INVALID_SOCKET értéken kívül bármilyen más értéket is felvehet. A socket API függvény használja, ami egy csatolófelületet hoz létre egy adatátviteli szolgáltatáshoz.
Az adatátviteli címet és portot a sockaddr_in adatszerkezet tárolja, mi az AF_INET cím típust használjuk vele (sin_family adatmező értéke).


sockaddr_in clientService;

typedef struct sockaddr_in {
 ADDRESS_FAMILY sin_family;
 USHORT         sin_port;
 IN_ADDR        sin_addr;
 CHAR           sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;


Példa az adatszerkezet konfigurációjára:


sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
clientService.sin_port = htons(80);


Az inet_addr() és a htons() függvények segédfüggvények. Előbbi segítségével egyszerű sztring alakban adhatjuk meg a célszerver IP címét, ami át lesz adva az sin_addr adatszerkezetnek.
A htons függvény pedig az USHORT adattípust konvertálja át a hálózati byte sorrend nagy-endián sorrendjére.
Az inet_addr() függvénnyel kapcsolatban egy megjegyzés. Ez elvileg egy elavultnak nyilvánított függvény, ami helyett az InetPton() függvény használatát javasolja a Visual Studio is. No, annyira azért nem eszik forrón a kását, az  továbbra is használható a _WINSOCK_DEPRECATED_NO_WARNINGS előfordítói direktíva megadásával.
Az egésznek a fenti kód viszonylatában a mező értékének a beállítására van kihatása. Amennyiben mégis az InetPton() mellett tesszük le a voksot, akkor azt így kell használni:


in_addr cimtarolo;

InetPton(AF_INET, L"46.30.213.43", &cimtarolo);

clientService.sin_addr.s_addr = cimtarolo.S_un.S_addr;


A socket objektummal végzett minden művelet eredményét, mint amilyen például a tényleges kapcsolódás a szerverhez, adatküldés, de akár a kapcsolat bontása is, egy változóban tároljuk és ellenőrzésekhez használjuk majd.


int Result_error;


Az alábbiakban végigmegyünk a kapcsolatfelépítés és az adatküldés teljes folyamatán.
Ebben a példaprogramban a szerverhez küldött kérelem csak egy tesztszöveget tartalmaz. A későbbiekben majd látni fogjuk, hogy éles használatban ez a puffer a kérések metaadatait is tartalmazza. Erre általában fejlécként (header) szoktak hivatkozni.


strcpy_s(sendbuf, "Teszt keres");


Itt álljunk meg egy szóra! Ugyan, mi értelme van egy egyszerű szöveget elküldeni, ha az nem is felel meg a HTTP szabványnak? Azért, mert ez jól szemlélteti, hogy egy szervernek BÁRMIT el lehet küldeni. Egyedül a szerverprogram implementációján múlik, hogy mit kezd vele. Ez kitárja a kaput saját, egyedi kommunikációs protokollok megalkotása előtt. Izgalmas, nemde?
A következő lépésben inicializáljuk a Socket API-t, méghozzá a legfrissebb 2.2-es verziót feltételezve.


Result_error = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (Result_error != 0) {
 printf("Nem sikerült az inicializálás, hibakód: %d\n", Result_error);
 return 1;
}


Ezután létrehozzuk az adatkapcsolat konfigurációját:


ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
 printf("Nem sikerult csatlakozni!\n");
 WSACleanup();
 return 1;
}

sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
clientService.sin_port = htons(80);


Az adatkapcsolat létrehozását csak ezt követően kíséreljük meg, a connect() függvénnyel:


Result_error = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));


Menetközben mindig lekérhetjük a legutóbbi hiba kódját. Erre a WSAGetLastError() függvény szolgál.
Ha nincsen hiba, akkor ténylegesen is megszólítjuk a szervert, azaz kérést, adatokat küldünk felé. Erre szolgál a send() függvény.


Result_error = send(ConnectSocket, sendbuf, (int)strlen(sendbuf)+1, 0);

if (Result_error == SOCKET_ERROR) {
 printf("Adatkuldesi hiba: %d\n", WSAGetLastError());
 closesocket(ConnectSocket);
 WSACleanup();
 return 1;
}
printf("Elküldott bajtok szama: %ld\n", Result_error);


Ha eddig megússzuk hiba nélkül, akkor a kérés sikeresen el lett küldve. Az adatküldési folyamatot azonnal le is zárjuk, a shutdown() függvénnyel:


Result_error = shutdown(ConnectSocket, SD_SEND);
if (Result_error == SOCKET_ERROR) {
 printf("Befejezesi hiba: %d\n", WSAGetLastError());
 closesocket(ConnectSocket);
 WSACleanup();
 return 1;
}


A szerver válaszát az alábbi kóddal gyűjthetjük össze, szó szerint. Az eredmény egy sztring lesz:


do {

 Result_error = recv(ConnectSocket, recvbuf, BUFLEN, 0);
 if (Result_error > 0)
 {
  printf("Fogadott bajtok szama: %d\n", Result_error);
  printf("%s\n", recvbuf);
 }   
 else if (Result_error == 0)
  printf("Kapcsolat lezarva\n");
 else
  printf("Adatfogadasi hiba: %d\n", WSAGetLastError());

} while (Result_error > 0);


Amint fentebb látható, korántsem biztos, hogy minden megérkezik egyszerre, ezért kell egy ciklusban megoldani a válasz fogadását.
Végezetül bontjuk a teljes kapcsolatot a closesocket() függvénnyel és lezárjuk a kommunikációt:


closesocket(ConnectSocket);
WSACleanup();


A teljes forráskód:


#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#pragma comment (lib, "Ws2_32.lib")

//Segédváltozók
#define BUFLEN 100000
char recvbuf[BUFLEN] = "";
char sendbuf[BUFLEN] = "";
//Főprogram
int __cdecl main(int argc, char** argv)
{
 WSADATA wsaData;
 SOCKET ConnectSocket = INVALID_SOCKET;
 struct addrinfo* result = NULL,
   * ptr = NULL,
   hints;

 strcpy_s(sendbuf, "Teszt keres");

 int Result_error;

 // Winsock inicializalasa
 Result_error = WSAStartup(MAKEWORD(2, 2), &wsaData);
 if (Result_error != 0) {
   printf("Inicializalasi hiba: %d\n", Result_error);
   return 1;
 }

 //Kapcsolat felepitese
 ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 sockaddr_in clientService;
 clientService.sin_family = AF_INET;
 clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
 clientService.sin_port = htons(80);
 Result_error = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));


 if (ConnectSocket == INVALID_SOCKET) {
   printf("Kapcsolat nem jott letre!\n");
   WSACleanup();
   return 1;
 }

 // Adatkuldes
 Result_error = send(ConnectSocket, sendbuf, (int)strlen(sendbuf) + 1, 0);
 if (Result_error == SOCKET_ERROR) {
   printf("Adatkuldesi hiba: %d\n", WSAGetLastError());
   closesocket(ConnectSocket);
   WSACleanup();
   return 1;
 }
 printf("Elkuldott bajtok szama: %ld\n", Result_error);

 // Adatkuldes befejezese
 Result_error = shutdown(ConnectSocket, SD_SEND);
 if (Result_error == SOCKET_ERROR) {
   printf("Lezarasi hiba: %d\n", WSAGetLastError());
   closesocket(ConnectSocket);
   WSACleanup();
   return 1;
 }

 // Valasz osszeszedese
 do {

   Result_error = recv(ConnectSocket, recvbuf, BUFLEN, 0);
   if (Result_error > 0)
   {
     printf("Fogadott bajtok szama: %d\n", Result_error);
     printf("%s\n", recvbuf);
   }
   else if (Result_error == 0)
     printf("Kapcsolat lezarva\n");
   else
     printf("Adatfogadasi hiba: %d\n", WSAGetLastError());

 } while (Result_error > 0);

 // Befejezes
 closesocket(ConnectSocket);
 WSACleanup();

 return 0;
}


A programnak először is szüksége lesz egy szerveralkalmazásra, ami visszaküldi neki az eredeti kérést. Ezt nézzük meg most.

4.2 Parancssoros szerver írása

A szerver megírása nagymértékben hasonlít a kliensére, legalábbis az inicializálás és egyéb adminisztratív kódrészek tekintetében. Persze vannak különbségek is.
A szerverünk 64000 bájt méretű kéréseket fog tudni kezelni és a 27015-ös portot fogja lefoglalni működése során:


#define DEFAULT_BUFLEN 64000
#define DEFAULT_PORT "27015"


A bejövő kéréseket majd a recvbuf pufferben fogjuk tárolni, a válasz elküldésének sikerességét pedig az iSendResult változó segítségével fogjuk ellenőrizni. Az iResult változót általánosabban használjuk majd az egyes műveletek ellenőrzésére:


int iResult;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;


A szerver általánosabb kezelésére és a bejövő kérések figyelésére két külön socket objektumot fogunk használni:


SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ServerSocket = INVALID_SOCKET;


A szerver socket létrehozásának első lépése előtt le kell kérdeznünk a rendszer által rendelkezésre bocsátott járulékos adatokat.


iResult = getaddrinfo(NULL, DEFAULT_PORT, &server_config, &result);

if (iResult != 0) {
printf("getaddrinfo hiba: %d\n", iResult);
WSACleanup();
return 1;
}


A socket objektumot a beszédes nevű socket függvénnyel hozzuk létre:


ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

if (ListenSocket == INVALID_SOCKET) {
 printf("socket letrehozasi hiba: %ld\n", WSAGetLastError());
 freeaddrinfo(result);
 WSACleanup();
 return 1;
}


A bejövő kérések figyelésére szánt socketet a szerverünkkel (helyben létrehozott címmel) a bind() függvénnyel kapcsoljuk össze:


iResult = bind(ListenSocket, (SOCKADDR*)&serveraddr, sizeof(serveraddr));

if (iResult == SOCKET_ERROR) {
 printf("bind hiba: %d\n", WSAGetLastError());
 freeaddrinfo(result);
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}


A fentebb több helyen látott closesocket() és WSACleanup() függvényekkel hiba esetén az esetlegesen már létrehozott objektumokat megfelelő módon megszüntethetjük és lezárhatjuk a socket-et is.
Ha eddig a pontig nincsen hiba, akkor a result objektumot felszabadíthatjuk.


freeaddrinfo(result);


A bejövő kérések figyelése, majd feldolgozásának megkezdése a listen() és accept() függvényekkel történik.


printf("Listening...\n");

iResult = listen(ListenSocket, SOMAXCONN);

if (iResult == SOCKET_ERROR) {
 printf("Figyelesi hiba: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}

ServerSocket = accept(ListenSocket, NULL, NULL);

if (ServerSocket == INVALID_SOCKET) {
 printf("Fogadasi hiba: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}


Ezután a figyelési socket objektumot akár le is zárhatjuk.


closesocket(ListenSocket);


A bejövő kérések fogadása abból a szempontból trükkös, hogy adagonként kell összerakni. Mindenképpen meg kell várni, amíg a küldő program befejezi az adatküldést. Ezt egy do..while ciklussal lehet megoldani a legegyszerűbben.
Az adatok fogadása a recv(), míg a válasz elküldése a send() API függvénnyel lehetséges.


do {

   iResult = recv(ServerSocket, recvbuf, recvbuflen, 0);
   if (iResult > 0) {
     printf("Bajtok szama: %d\n", iResult);

     iSendResult = send(ServerSocket, recvbuf, iResult, 0);

     if (iSendResult == SOCKET_ERROR) {
       printf("Kuldesi hiba: %d\n", WSAGetLastError());
       closesocket(ServerSocket);
       WSACleanup();
       return 1;
     }
     printf("Bajtok szama: %d\n", iSendResult);
     printf("%s\n",recvbuf);
   }
   else if (iResult == 0)
     printf("Kapcsolat lezarasa...\n");
   else {
     printf("Fogadasi hiba: %d\n", WSAGetLastError());
     closesocket(ServerSocket);
     WSACleanup();
     return 1;
   }

 } while (iResult > 0);


Végezetül mindent lezárunk és megszüntetünk, ami a szerverrel magával kapcsolatos.


iResult = shutdown(ServerSocket, SD_SEND);

if (iResult == SOCKET_ERROR) {
 printf("Leallasi hiba: %d\n", WSAGetLastError());
 closesocket(ServerSocket);
 WSACleanup();
 return 1;
}

closesocket(ServerSocket);
WSACleanup();


A szerver a bejövő kéréseket fogadja, majd egy az egyben visszaküldi (“visszhangozza”) a kérés tulajdonosának.
A szerverünk ebben a formában egyetlen kérést szolgál ki, majd automatikusan leáll.
A kliens program így jeleníti meg a folyamatot:



A szerveren pedig a következőképpen követhetjük az eseményeket:



A teljes forráskód:


#undef UNICODE
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

#pragma comment (lib, "Ws2_32.lib")

#define DEFAULT_BUFLEN 64000
#define DEFAULT_PORT "27015"

//Segédváltozók
int iResult;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

WSADATA wsaData;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ServerSocket = INVALID_SOCKET;

struct addrinfo* result = NULL;
struct addrinfo server_config;

//Főprogram
int __cdecl main(void)
{
// Inicializalas
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
 printf("WSAStartup failed with error: %d\n", iResult);
 return 1;
}

ZeroMemory(&server_config, sizeof(server_config));
server_config.ai_family = AF_INET;
server_config.ai_socktype = SOCK_STREAM;
server_config.ai_protocol = IPPROTO_TCP;
server_config.ai_flags = AI_PASSIVE;

// Adatlekerdezes a socket letrehozasahoz
iResult = getaddrinfo(NULL, DEFAULT_PORT, &server_config, &result);
if (iResult != 0) {
 printf("getaddrinfo hiba: %d\n", iResult);
 WSACleanup();
 return 1;
}

// socket létrehozása
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
 printf("socket letrehozasi hiba: %ld\n", WSAGetLastError());
 freeaddrinfo(result);
 WSACleanup();
 return 1;
}

sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serveraddr.sin_port = htons(80);

// A keresfigyelo socket letrehozasa
iResult = bind(ListenSocket, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
//iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
 printf("bind hiba: %d\n", WSAGetLastError());
 freeaddrinfo(result);
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}

freeaddrinfo(result);

//Bejovo keresek figyelese
printf("Listening...\n");
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
 printf("Figyelesi hiba: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}

// a keres feldolgozasanak kezdete
ServerSocket = accept(ListenSocket, NULL, NULL);
if (ServerSocket == INVALID_SOCKET) {
 printf("Fogadasi hiba: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
}

// a figyeles vege
closesocket(ListenSocket);

// Adatok fogadasa, amig a kuldo fel le nem zarja a folyamatot
do {

 iResult = recv(ServerSocket, recvbuf, recvbuflen, 0);
 if (iResult > 0) {
  printf("Bajtok szama: %d\n", iResult);

  // Echo the buffer back to the sender
  iSendResult = send(ServerSocket, recvbuf, iResult, 0);
  if (iSendResult == SOCKET_ERROR) {
   printf("Kuldesi hiba: %d\n", WSAGetLastError());
   closesocket(ServerSocket);
   WSACleanup();
   return 1;
  }
  printf("Bajtok szama: %d\n", iSendResult);
  printf("%s\n",recvbuf);
  //shutdown(ServerSocket, SD_SEND);
 }
 else if (iResult == 0)
  printf("Kapcsolat lezarasa...\n");
 else {
  printf("Fogadasi hiba: %d\n", WSAGetLastError());
  closesocket(ServerSocket);
  WSACleanup();
  return 1;
 }

} while (iResult > 0);

// Kapcsolat lezarasa
iResult = shutdown(ServerSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
 printf("Leallasi hiba: %d\n", WSAGetLastError());
 closesocket(ServerSocket);
 WSACleanup();
 return 1;
}

// szerver megszüntetése
closesocket(ServerSocket);
WSACleanup();
system("PAUSE");

return 0;
}


5 GUI-s kiszolgáló (szerver) program írása

A leírás utolsó példája (PELDA_04) egy ablakos megjelenítésű szerverprogram lehetőségeit villantja fel, mely képes válaszolni akár egy böngésző programnak is!
A feladat a következő lesz. A szerver a 127.0.0.1 IP címen fog figyelni és egy nyomógomb segítségével aktiválhatjuk. Minden beérkező kérésre egy rövid HTML oldalt ad vissza.
A programot először az előzőekben már ismertetett parancssoros kliens programmal, majd Firefox böngészővel is tesztelni fogjuk. A kliens programban a cél port száma mindenképpen legyen 80! Erre lentebb még kitérek.
Ez a program már nem fog bezáródni egy beérkező kérés kezelése után, hanem ismét figyelő állapotba kerül, egészen a program bezárásáig. Bár nagymennyiségű kérés kiszolgálására még nem alkalmas, már így is egy professzionális webszerverhez hasonlóan tud viselkedni.
A teljes forráskód leközlésétől ezúttal eltekintek, mivel az Előszóban említett github oldalról elérhető a teljes kód. A Windows abalkos programjainak létrehozása után érdeklődő olvasók ‘A Windows keményvonalas programozása’ c. leírásomban részletes ismertetőt találnak a témáról, ezért ettől itt eltekinthetünk és csak a lényegre fogok koncentrálni.
A program továbbá naplózza is az eseményeket a LOG.txt  elnevezésű szövegfájlba, a

void log(const char* puffer, FILE* fp);

függvényen keresztül.
A hagyományos webszerverekhez hasonlóan ez a program is a 80-as portot fogja használni.

5.1 Előkészületek

A program úgy indul, mint bármelyik normál Windows alkalmazás. Az ablak létrejozásakor, tehát a WM_CREATE eseménykor jön létre, ill. törlődik a naplófájl, valamint a szerver is itt lesz beállítva, az open_server() függvény meghívásával. (A naplózás ebben az esetben angolul van lebonyolítva.)


void open_server(void)
{
serverisopen = false;
clientsocket = INVALID_SOCKET;
serversocket = INVALID_SOCKET;

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

log("Winsock init OK.\n", flog);
log(wsaData.szDescription, flog); log("\n", flog);

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

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

serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_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...\n", flog);
 return;
}
else log("Binding server socket OK...\n", flog);
}


A bejövő kérések figyelését az ablak ‘Szerver bekapcsolása’ címkéjű nyomógombjára történő kattintással aktiválhatjuk.



Fontos, hogy a figyelést végző függvény a főprogram külön végrehajtási szálában fog futni, máskülönben a programablak teljesen leblokkolna minden kattintás elől, így még rendesen be sem tudnánk zárni a programablakot. Maga a szerver gond nélkül működne, de ez így azért elég idétlen megoldás lenne.

A vonatkozó eseménykezelő kód:


case WM_COMMAND:
 switch (LOWORD(wParam))
 {
 case OBJ_ID100:
  THANDLER = (HANDLE)_beginthread(varakozas, 0, NULL);
  break;
 }

 return 0;


A varakozas() függvény igazából csak egy burok, ami a start_server_listening() függvényt hívja meg. Ez utóbbi végzi a beérkező kérések tényleges kezelését.


void varakozas(void* pParams)
{
ShowWindow(Button1, SW_HIDE);
while (1)
{
 open_server();
 start_server_listening();
 close_server();
}
}


A szerver aktiválása után a programablak felirata is megváltozik a “Bejövő kérések figyelése…” feliratra.



5.2 Kérés érkeztetése és válasz küldése

Most indítsuk el az előző fejezet kliens programját! A szerverprogramunk fejléce a kliens kérésének fogadásakor rövid időre a “Kliens kapcsolódott!” feliratra vált át, majd a válasz kiküldése után ismét figyelő állapotba helyezi magát.
A kliens oldalán az alábbiakat látjuk:



A szerver tehát egy HTML kódrészletet adott vissza. A szerver naplófájlja a következőket tartalmazza:

Winsock init OK.
WinSock 2.0
DESKTOP-K1H3N64
PORT: 80
Server socket created...
Binding server socket OK...
Server socket is listening...
CONNECTION OK...
Response sent OK...

*****Collecting request******
Reading request...

Client request:Teszt keres

Connection closing...
*****End of request******

Server socket is listening...

Ha az egyes sorokhoz az időbélyeget is naplózzuk, ill. nem töröljük minden futtatás elején a naplófájlt, akkor igen sok részletet megtudhatunk a szerverünk magánéletéről.

A figyelő állapot kódja, vastagon kiemelve a válasz elküldésének kódját:


void start_server_listening(void)
{
int i;
int iResult;
int iSendResult;
char recvbuf[2048];
int recvbuflen = 2048;

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...\n", flog);
 return;
}
else
{
 log("Server socket is listening...\n", flog);
 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...\n", flog);
else
{
 log("CONNECTION OK...\n", flog);
 SetWindowTextA(Form1, "Kliens kapcsolódott!");
 log("\n*****Collecting request******\n", flog);
 do {

  iResult = recv(clientsocket, recvbuf, recvbuflen - 2, 0);
  if (iResult > 0) {
   log("Reading request...\n\n", flog);
   int meret = sizeof(recvbuf);
   log("Client request:\n", flog);
   log(recvbuf, flog);
   log("\n\n", flog);
   shutdown(clientsocket, SD_RECEIVE);
   strcpy_s(htmlresp, "<!doctype HTML>\n<html><h3>A szerverkapcsolat létrejött!</h3></html>\0");
   
   iResult = send(clientsocket, htmlresp, (int)strlen(htmlresp) + 1, 0);
   if (iResult == SOCKET_ERROR)
    log("Sending response FAILED...\n", flog);
   else
   {
    log("Response sent OK...\n", flog);
    shutdown(clientsocket, SD_SEND);
   }
   closesocket(clientsocket);
   closesocket(serversocket);
   WSACleanup();
  }
  else if (iResult == 0)
   log("Connection closing...\n", flog);
  else {
   log("Reading request failed with error\n", flog);
   closesocket(clientsocket);
   WSACleanup();
   return;
  }

 } while (iResult > 0);  

 log("*****End of request******\n\n", flog);
}
}


Most pedig próbáljuk ki Firefox böngészővel a programot. A böngésző címsorába írjuk be:

127.0.0.1 , majd pedig 127.0.0.1/index.html

Mindkét esetben ‘A szerverkapcsolat létrejött!’ HTML kód jelenik meg a böngészőben.



Ha megnézzük a naplófájlt, akkor láthatjuk, hogy a Firefox a következő kérést küldte el:

Client request:
GET /index.html 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/109.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

Amikor egy böngésző címsorába beírunk egy címet, majd ENTER-t nyomunk, akkor a böngésző egy ehhez hasonló leírást generál, ami maga a kérés. A ‘GET’-tel kezdődő első két sor a lényeg.
Az első sor a lekérni kívánt erőforrás neve, a második pedig a kiszolgáló szerver IP címe.
Megjegyzem, hogy éles webszerverek a válaszukat is ugyanígy építik fel, az első sorban jelezve, hogy sikeres volt a kérés feldolgozása. A fejléc sorai után magának a HTML oldalaknak a forráskódja következik, lásd lenti példát egy éles szerver válaszával.

HTTP/1.1 200 OK
Date: Sat, 04 Feb 2023 22:46:58 GMT
Server: Apache
Last-Modified: Tue, 31 Jan 2023 00:14:01 GMT
Vary: Accept-Encoding
Content-Type: text/html
X-Varnish: 284964107
Age: 0
Via: 1.1 webcache2 (Varnish/trunk)
ETag: W/"66bb-5f38435790829-gzip"
Accept-Ranges: bytes
Connection: keep-alive
Transfer-Encoding: chunked

0066bb
<!DOCTYPE html><!-- HTML5 -->
...

De most vissza a mi szerverünkhöz! A Firefox minden esetben két kérést küld valójában. A másodikban a weboldal ikonját igyekszik lekérni.

GET /favicon.ico HTTP/1.1

Szóval ne lepődjünk meg, hogy 2 kérés lesz naplózva!

5.3 Lezárás

A programablak bezárásakor meghívjuk a close_server() függvényt, ami annak rendje s módja szerint megszünteti a socket specifikus objektumokat.


case WM_CLOSE:
close_server();
DestroyWindow(hwnd);
return 0;


A close_server() függvény kódja:


void close_server(void)
{
log("\nEnding...\n", flog);

log("WSA cleanup...\n", flog);
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...\n", flog);
}


A naplófájl végén feltűnhet a “Server socket failed to accept connection...” naplóbejegyzés. Ez akkor történhet meg, ha akkor próbáljuk meg bezárni a programablakot, amikor éppen socket műveletet hajt végre. Ennek elkerülése végett készíthetünk például egy változót, aminek az értékét minden érdemi műveletblokk előtt ellenőrzünk és csak akkor hajtjuk végre azt, ha a változó értéke még azt jelzi, hogy az alkalmazást még nem próbálják meg bezárni. Egyébként kilépünk a függvényből és a varakozas() szálfüggvényből is! Nem túl szofisztikált megoldás, de működhet.



6 A HTTP fejlécekről

Végezetül pár szót ejtek a HTTP protokollról, ami a webszerverekkel történő kommunikáció szabványa már nagyon régóta. Ilyeneket láttunk az előző fejezetben, amikor böngészőn keresztül szólítottuk meg a szerverünket.

GET /index.html 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/109.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

Bizonyos fejlécadatok mindig fel szoktak bukkanni, ezekből szemezgetek párat, mert ezek segítségével írhatunk olyan programokat is, amelyek egy böngészőt imitálnak, noha csupán az első két sor használata is célra vezethet.

• User-Agent: A böngészőprogram adatai, beleértve az operációs rendszert is.
• Accept: azon erőforrások típusa, amit válaszként értelmezni tud a kliens.
• Accept-Language: az erőforrás preferált nyelve.
• Accept-Accept-Encoding: tömörítés stb. elfogadása.
• Connection: lehetővé teszi, hogy a kapcsolat folyamatosan nyitva maradjon. A HTTP 2 és 3 már tiltja a használatukat.
• Sec-Fetch-Site: a kérés előzményének kiindulási helyét jelzi a kérésben szereplő erőforrás tekintetében. ‘None’ például egy előzmény nélküli kérést jelent, a same-origin pedig azt, hogy a kérés már a letöltött weboldal egyik helyéről indul. Lehetséges értékei még: cross-site, same-site.

A HTTP fejlécekről egy remek kis leírás található a Mozilla weboldalán is:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers



7 Záró gondolatok

Ez a leírás remek ugródeszka lehet számos segédprogram megírásához, melyek szerver-kliens kommunikáción alapulnak.
A bemutatott szerverprogramot továbbfejleszthetjük valódi webszerverré, mely a kérésben megadott erőforrásokat megkeresi a számítógépen és azokat küldi el. Továbbá megnövelhetjük a szerver kiszolgálói teljesítményét a kérések sorokba történő kezelésével. Ehhez az MSDN vonatkozó oldalai remek tippeket tartalmaznak.
A bemutatott kliens programok továbbfejlesztésével hatékony weboldal-elemző alkalmazások készíthetőek, vagy éppen automatizált teszteléshez használható eszközök.
Akár úgy is dönthetünk, hogy saját kommunikációs protokollt dolgozunk ki és kívülről nézve zárt, egyedi kommunikációt valósítunk meg alkalmazásainkkal.
A határ tényleg csak a csillagos ég…
Remélem a kedves olvasó is ugyanúgy élvezte ezt a leírást, mint annakidején én, amikor megismerkedtem ezzel a területtel!
KAPCSOLAT

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

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