Treść zadania

  • Należy stworzyć projekt serwera oraz klienta
  • Serwer nasłuchuje na adresie 127.0.0.1 na porcie 8080
  • Klient pobiera od użytkownika najpierw jedną liczbę całkowitą n, a później n razy pobiera kolejne dane do przetworzenia (np. najpierw użytkownik wpisuje 3, po czym podaje trzy dane wejściowe np. 1, 2, 4)
  • Klient tworzy z tych danych komunikat w postaci: "liczba1 liczba2 ... liczbaN" czyli dla przykładu powyżej "1 2 4"
  • Klient podłącza się pod adres 127.0.0.1 na porcie 8080 i przesyła komunikat
  • Serwer odbiera komunikat i interpretuje go jako tablicę liczb całkowitych
  • Serwer znajduje maksimum w tej tablicy
  • Serwer przygotowuje komunikat tekstowy w postaci "liczbaMaksimum" czyli dla przykładu powyżej "4"
  • Serwer przesyła komunikat i kończy swoje działanie
  • Klient odbiera komunikat i wypisuje wynik na konsolę

Przykład poprawnego działania

  • Uruchom serwer
  • Uruchom klienta
  • Wpisz dane np. 3, a następnie 1 2 4
  • Trwa wymiana komunikatów serwer <-> klient
  • Odczytaj wynik w okienku klienta: 4

Praktyczne informacje

  • Do stworzenia tekstowego komunikatu wykorzystujemy funkcję sprintf() np. tak:
    char buffer[1024];
    memset(buffer, 0, 1024);
    sprintf(buffer, "%d", zmienna);
    (ten kod zapisze w formie tekstowej wartość w zmienna pod adres buffer)
  • Analogicznie, do analizy komunikatów można wykorzystać funkcję sscanf() np. tak:
    char buffer[1024];
    memset(buffer, 0, 1024);
    // wypełnij buffer danymi, np. tak:
    //     recv(socket, buffer, sizeof(buffer), 0);
    sscanf(buffer, "%d", &zmienna);
    (ten kod zapisze w zmienna wartość całkowitą odczytaną z napisu w buffer)
  • Komunikat przesyłany przez klienta ma postać "liczba1 liczba2 ... liczbaN". Najlepiej będzie go przetworzyć przy użyciu funkcji strtok() np. tak:
    char *s = "1 2 4";     // przykładowe dane
    char *delimiter = " "; // to co oddziela dane w napisie, tutaj znak spacji

    char *token = strtok(s, delimiter);
    while (token != NULL) {
        // w token znajduje się aktualnie znalezione słowo
        token = strtok(NULL, delimiter); // każde następne wywołanie strtok()
                                         // musi mieć pierwszy argument równy NULL
    }

Teoria

  • W programowaniu przy użyciu gniazd (socket) istnieją dwa podmioty: serwer oraz klient
  • Serwer nasłuchuje na pewnym porcie pod określonym adresem; innymi słowy, udostępnia możliwość komunikacji i jest to reprezentowane jako para ADRES:PORT np. localhost:8080 (localhost to specjalna nazwa sieciowa oznaczająca własny adres)
  • Klient musi znać ADRES:PORT serwera, z którym chce się połączyć; nawiązuje połączenie i od tej chwili możliwa jest dwustronna wymiana komunikatów
  • Komunikat to dowolny ciąg bajtów, może być to napis, może to być tablica liczb, itd.
  • Protokołem nazywamy zestaw komunikatów i ich wzajemny porządek, które to informacje jeśli są znane (i poprawnie zaimplementowane) przez serwer i klienta, umożliwiają osiągnięcie pewnego wspólnego celu
  • Przykładowo w protokole HTTP zdefiniowano komendę GET do pobierania nazwanych zasobów. Zarówno serwer HTTP "rozpoznaje" komunikat GET i wie co z nim zrobić, tak jak i każda przeglądarka www potrafi generować komendy GET by pobierać strony, obrazy, itd.

Projekt w CodeBlocks

  1. Projekt serwera: Attach:Client.zip
  2. Projekt klienta: Attach:Server.zip
  3. Wersja Linuksowa (serwer + klient): Attach:tcp.tar.gz
  4. Należy w programie dodać linię:
    #include <winsock2.h>
  5. Należy kliknąć prawym przyciskiem myszy na nazwie projektu i wybrać "Build options". W oknie należy wskazać zakładkę "Linker settings" i tam dodać (przycisk "Add") bibliotekę o nazwie: ws2_32

Kod serwera

  1. Przygotuj środowisko:
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2, 2), &wsadata);
  2. Stwórz gniazdo:
    SOCKET tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  3. Zdefiniuj adres:
    SOCKADDR_IN address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8080);
  4. Powiąż ze sobą gniazdo i adres:
    bind(tcpsocket, (SOCKADDR*)(&address), sizeof(address));
  5. Oznacz gniazdo jako nasłuchujące:
    listen(tcpsocket, 1);
  6. Zakceptuj połączenie:
    tcpsocket = accept(tcpsocket, NULL, NULL);
  7. Komunikacja
  8. Zamknij gniazdo:
    shutdown(tcpsocket, SD_BOTH);
    closesocket(tcpsocket);
    WSACleanup();

Kod klienta

  1. Przygotuj środowisko:
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2, 2), &wsadata);
  2. Stwórz gniazdo:
    SOCKET tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  3. Zdefiniuj adres:
    SOCKADDR_IN address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8080);
  4. Połącz się z wybranym adresem:
    connect(tcpsocket, (SOCKADDR*)(&address), sizeof(address));
  5. Komunikacja
  6. Zamknij gniazdo:
    shutdown(tcpsocket, SD_BOTH);
    closesocket(tcpsocket);
    WSACleanup();

Kod do komunikacji

  • Wysłanie komunikatu:
    send(tcpsocket, buffer, strlen(buffer), 0);
  • Odebranie komunikatu:
    char buffer[1024];
    recv(tcpsocket, buffer, 1024, 0);