Prosty serwer
Początkowy etap przygotowania programu jest identyczny dla klienta i serwera jest bardzo podobny podstawowe różnice sprowadzają się do odpowiedniego uzupełnienia struktury: struct sockaddr_in serwer_adr
//obs ługa serwera
struct sockaddr_in klient_adr
//obs ługa klienta
oraz użycia wywołań systemowych bind() i listen() zamiast connect() a następnie accept() do obsługi połączenia.
Etapy uruchomienia jednowątkowego/jednoprocesowego serwera TCP/IP
1. utworzenie gniazdka sockfd
2. zarezerwowanie pamięci dla struktury sockaddr_in serwera 3. uzupełnienie struktury typu sockaddr_in serwera
◦ dla nasłuchiwania na wszystkich lokalnych adresach dla pola struktury serwer_addr.sin_addr.s_addr ustawiamy INADDR_ANY
◦ wybranie portu serwer_addr.sin_port = htons(wybrany_port) 4. skojarzenie utworzonego gniazda z adresem i portem za pomocą wywołania systemowego bind()
5. Przejście serwera w tryb nasłuchiwania za pomocą wywołania systemowego listen()
6. akceptowanie nadchodzącego połączenia wywołaniem za pomocą accept() 7. obsługa połączenia – read/write send/recv 8. po zamknięciu połączenia serwer może zakończyć działanie lub przejść do obsługi kolejnego połączenia.
Opisy funkcji
bind
-pr
zypisuje nazwę do gniazda
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); działanie:
skojarzenie gniazda z adresem nazywanie też „przypisaniem nazwy do gniazda” -
etap poprzedzający rozpoczęcie nasłuchiwania serwera argumenty:
sockfd – deskryptor gniazdka (jeżeli do niego jest przypisany adres to zostanie użyty) addr
- struktura definiująca adresy nasłuchiwania i port (patrz punkt 3) addrlen - wielkość struktury adresowej
wynik:
0 – w przypadku powodzenia
-1 – w przypadku błędu
listen
– n
asłuchuje nadchodzące połączenia
#include <sys/socket.h>
int listen(int sockfd, int backlog);
działanie:
przełącza gniazdo w stan pasywny – nasłuchiwania połączeń argumenty:
sockfd – deskryptor gniazda
backlog – maksymalna długość kolejki oczekujących na połączenie klientów jeżeli kolejka przekroczy tę liczbę nowy klient otrzymuje komunikat o błędzie wynik:
0 – w przypadku powodzenia
-1 – w przypadku błędu
accept
– akc
eptuje przychodzące połączenia od klientów
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); działanie:
wybiera pierwsze nadchodzące połączenie z kolejki skojarzonej z deskryptorem sockfd, otwiera to połączenie zwracając jego nowy deskryptor, argumenty:
sockfd – gniazdo na którym nasłuchuje serwer addr – wskaźnik do struktury adresowej klienta np sockaddr_in klient_adr
addrlen – wskaźnik do liczby określającej rozmiar struktury adresowej klienta np: kli_len = sizeof(klient_adr);
wynik:
zwraca liczbę >0 będącą deskryptorem zaakceptowanego połączenia UWAGA read/write recv/send powinny odnosić się do tego gniazdka Scenariusze obsługi połączeń
Obsługę nadchodzącego połączenia można zrealizować na kilka sposobów:
•
Najprostszy – obsługa tylko jednego połączenia i zamknięcie serwera. Rzadko stosowany
•
Pętla z sekwencyjną obsługą kolejnych nadchodzących połączeń. Stosowany w przypadku krótkich odpowiedzi serwera np protokołem UDP ale także TCP – daytime, DNS itp.
Zwykle uruchamia się pętlę nieskończoną for(;;){ ...} lub whlie(1){...}
for (;;){
poloczenie = accept(...)
read/write send/recv
close(poloczenie)
}
•
Pętla z utworzeniem nowego procesu lub wątku (każdy obsługuje jedno połączenie).
Stosowany tam gdzie konieczna jest dłuższa komunikacja między serwerem i klientem (wszystkie bardziej zaawansowane serwery).Pętla wygląda tak jak w poprzednim wypadku
ale po zaakceptowaniu połączenia tworzony jest np nowy proces, który je obsługuje.
int pid,poloczenie;
...
poloczenie = accept(...)
//sprawdzenie czy nadchodz ce po
ą
łączenie jest poprawne
pid = fork()
if(pid == 0 ) {
//jesteśmy w procesie potomnym
close(sockfd);
// odcinamy si od g
ę
łównego gniazda
write(poloczenie, ...); // tu ca ła obs ługa nowego po łą czenia
...
close(poloczenie)
//po zako c
ń zeniu zamykamy nasze gniazdo
exit(0);
// wyjście z podprocesu
}
close(poloczenie)
//proces rodzicielski nie potrzebuje
•
poll() - jest to wywołanie systemowe, które oczekuje na zdarzenia związane z jakimś deskryptorem np deskryptorem gniazda
•
select() - monitoruje wiele deskryptorów, jeżeli któryś jest aktywny to umożliwia komunikację we/wy z nim
fork
– t
worzy nowy proces
wynik:
0 – w procesie potomnym
>0 – w procesie rodzica – jest to numer procesu dziecka
<0 - błąd
Zadanie 1
Napisać serwer TCP, którego port jest podawany z linii poleceń. Po połączeniu z serwerem ma odpowiadać:
połączyłeś się z adresu: <tu ma się pojawić adres IP klienta> autor:<Imię i nazwisko>
potem serwer zamyka połączenie i kończy działanie Zadanie 2
Napisać serwer TCP, którego port jest podawany z linii poleceń. Po połączeniu z serwerem ma odpowiadać:
połączyłeś się z adresu: <tu ma się pojawić adres IP klienta> autor:<Imię i nazwisko>
Serwer ma obsługiwać maksymalnie 3 oczekujące połączenia