Programowanie aplikacji klient-serwer
Interfejs gniazd
Gniazdo
Gniazdo: końcowy punkt kanału komunikacji, poprzez który proces może wysyłać lub otrzymywać dane z innych procesów.
Gniazdo jako obiekt systemowy
Struktura danych dla gniazd
a
Gniazdo może być używane do:
zainicjowania połączenia - gniazdo aktywne (ang. active socket)
przyjęcia połączenia zainicjowanego przez inny komputer - gniazdo bierne (ang. passive socket)
W obydwu przypadkach operacja tworzenia gniazda jest ta sama.
Przed użyciem do gniazda musi być wprowadzona informacja związana z jego przeznaczeniem - adres punktu końcowego dla komunikacji.
Każda rodzina protokołów może mieć własną reprezentację adresów.
Komunikacja serwer-klient (serwer połączeniowy)
Komunikacja serwer-klient (serwer bezpołączeniowy)
Funkcja socket
- utworzenie gniazda (klient, serwer)
SKŁADNIA
#include <sys.types.h>
#include <sys/socket.h>
mysocket=socket(int domain,
int type,
int protocol);
OPIS
Funkcja socket tworzy nowe gniazdo do komunikacji w sieci.
Parametry:
domain -rodzina protokołów używanych przez gniazda (protokoły komunikacji lokalnej, IPv4, IPv6)
Rodzina |
Znaczenie |
PF_INET |
protokół IPv4 |
PF_UNIX, PF_LOCAL |
protokoły UNIXa dla lokalnej komunikacji |
type - typ żądanej usługi komunikacyjnej (transmisja strumieniowa, datagramowa)
Typ |
Znaczenie |
SOCK_STREAM |
gniazdo strumieniowe |
SOCK_DGRAM |
gniazdo datagramowe |
protocol - protokół transportowy w ramach rodziny; 0 oznacza protokół domyślny
Funkcja zwraca deskryptor gniazda (liczba >0) lub -1 jeśli wystąpił błąd
Przykład:
/* Utwórz gniazdo serwera. */
int gniazdo_serwera;
gniazdo_serwera = socket(PF_UNIX, SOCK_STREAM, 0);
Funkcje strony serwera: bind
- nadanie adresu gniazdu serwera
SKŁADNIA
#include <sys/socket.h>
#include <sys/socket.h>
int bind (int sockfd,
struct sockaddr *my_addr,
int addrlen);
OPIS
Funkcja bind przypisuje gniazdu lokalny adres protokołowy.
sockfd - deskryptor utworzonego gniazda
my_addr - adres lokalny, z którym gniazdo ma być związane
addrlen - długość adresu lokalnego
Zwraca wartość 0, gdy operacja zakończona powodzeniem, w przeciwnym wypadku zwraca -1.
Postać adresu zależy od rodziny protokołów.
Ogólna postać reprezentacji adresu punktu końcowego:
struct sockaddr {
u_char sa_len; /* całkowita długość adresu */
u_char sa_family; /* rodzina adresu */
char sa_data[14]; /* właściwy adres */
};
Adresowanie w rodzinie AF_INET
W rodzinie AF_INET adres jest zdefiniowany jako kombinacja adresu IP i numeru portu:
struct sockaddr_in {
/* rodzina: AF_INET*/
short int sin_family;
/* port w sieciowym porządku bajtów */
unsigned short int sin_port;
/* adres IP */
struct in_addr;
};
/* adres IP */
struct in_addr {
/* adres IPv4 w sieciowym porządku bajtów */
unsigned long int s_addrr;
};
Przykład:
int serwer_dl;
int gniazdo_serwera;
struct sockaddr_in serwer_adres;
/* Przypisz adres gniazdu */
serwer_adres.sin_family = AF_INET;
serwer_adres.sin_addr.s_addr =
inet_addr("127.0.0.1");
serwer_adres.sin_port = 9001;
serwer_dl = sizeof(serwer_adres);
bind(gniazdo_serwera,
(struct sockaddr *)&serwer_adres, serwer_dl);
Adresowanie w rodzinie AF_UNIX
W rodzinie AF_UNIX adres jest identyfikowany za pomocą nazwy pliku.
#define UNIX_MAX_PATH 108
struct sockaddr_un
{
/* rodzina: AF_UNIX */
short sun_family;
/*nazwa ścieżkowa */
char sun_path[UNIX_PATH_MAX];
};
Przykład:
int gniazdo_serwera;
int serwer_dl;
struct sockaddr_un serwer_adres;
/* Przypisz adres gniazdu */
serwer_adres.sun_family = AF_UNIX;
strcpy(serwer_adres.sun_path, "gniazdo_serwera");
serwer_dl = sizeof(serwer_adres);
bind(gniazdo_serwera,
(struct sockaddr *)&serwer_adres, serwer_dl);
Funkcje strony serwera: listen
- czekanie na połączenie
SKŁADNIA
#include <sys/socket.h>
#include <sys/socket.h>
int listen (int s, int backlog);
OPIS
Funkcja listen ustawia tryb gotowości gniazda do przyjmowania połączeń
Zwraca wartość 0, gdy operacja zakończona powodzeniem, w przeciwnym wypadku zwraca -1.
Parametry:
s - deskryptor gniazda związanego z lokalnym adresem
backlog - maksymalna liczba oczekujących połączeń dla danego gniazda
Przykład:
listen(serwer_gniazdo, 5);
Funkcje strony serwera: accept
- przyjęcie połączenia
SKŁADNIA
#include <sys/socket.h>
#include <sys/socket.h>
int accept (int sockfd,
struct sockaddr *addr,
int *addrlen);
OPIS
Funkcja accept nawiązuje połączenie z klientem.
Parametry:
sockfd - deskryptor gniazda, przez które serwer przyjmuje połączenia
addr - adres odległego komputera, który nawiązał połączenie
addrlen - długość adresu
Zwraca deskryptor nowego gniazda, które będzie wykorzystywane dla połączenia z klientem, zaś gdy operacja nie zakończona powodzeniem wartość -1.
Przykład zastosowanie funkcji accept
int gniazdo_klienta;
int gniazdo_serwera;
int serwer_dlugosc, klient_dlugosc;
struct sockaddr_un serwer_adres;
struct sockaddr_un klient_adres;
gniazdo_serwera = socket(PF_UNIX, SOCK_STREAM, 0);
serwer_adres.sun_family = AF_UNIX;
strcpy(serwer_adres.sun_path, "gniazdo_serwera");
serwer_dlugosc = sizeof(serwer_adres);
bind(gniazdo_serwera,
(struct sockaddr *)&serwer_adres, serwer_dlugosc);
gniazdo_klienta = accept(gniazdo_serwera,
(struct sockaddr *)&klient_adres,
&klient_dlugosc);
Funkcje strony klienta: connect
- nawiązanie połączenia z serwerem
SKŁADNIA
#include <sys/socket.h>
#include <sys/socket.h>
int connect (int sockfd,
struct sockaddr *serv_addr,
int addrlen);
OPIS
Funkcja connect nawiązuje połączenie z odległym serwerem
Zwraca wartość 0, gdy operacja zakończona powodzeniem, w przeciwnym wypadku zwraca -1..
Parametry:
sockfd - deskryptor gniazda, które będzie używane na komputerze klienta do połączenia
serv_addr - adres sewera (adres punktu końcowego)
addrlen - długość adresu
Przykład:
int gniazdo, dlugosc, wynik;
struct sockaddr_un adres;
/* Utwórz gniazdo dla klienta */
gniazdo = socket(PF_UNIX, SOCK_STREAM, 0);
/* Nadaj adres uzgodniony z serwerem */
adres.sun_family = AF_UNIX;
strcpy(adres.sun_path, "gniazdo_serwera");
dlugosc = sizeof(adres);
/* Połącz z gniazdem serwera. */
wynik = connect(gniazdo,
(struct sockaddr *)&adres, dlugosc);
if(wynik == -1) {
perror("Blad: klient");
exit(1);
}
Przykład zastosowanie funkcji connect
int gniazdo, dlugosc, wynik;
struct sockaddr_in adres;
/* Utwórz gniazdo dla klienta */
gniazdo = socket(PF_INET, SOCK_STREAM, 0);
/* Nadaj adres uzgodniony z serwerem */
adres.sin_family = AF_INET;
adres.sin_addr.s_addr = inet_addr("127.0.0.1");
adres.sin_port = 9001;
dlugosc = sizeof(adrss);
/* Połącz z gniazdem serwera. */
wynik = connect(gniazdo,
(struct sockaddr *)&adres, dlugosc);
if(wynik == -1)
{
perror("Blad: klient");
exit(1);
}
Funkcja close
- zamknięcie gniazda (klient, serwer )
SKŁADNIA
#include <unistd.h>
int close (int fd);
OPIS
Funkcja close zamyka gniazdo.
Parametry:
fd - deskryptor zwalnianego gniazda
Zwraca wartość 0, gdy operacja zakończona powodzeniem, w przeciwnym wypadku zwraca -1.
Przykład:
W programie serwera:
gniazdo_klienta = accept(gniazdo_serwera,
(struct sockaddr *)&klient_adres,
&klient_dlugosc);
...
...
close(gniazdo_klienta);
W programie klienta:
gniazdo = socket(PF_INET, SOCK_STREAM, 0);
....
....
close(gniazdo);
Funkcja shutdown
- zamknięcie gniazda (klient, serwer )
SKŁADNIA
#include <sys/socket.h>
int shutdown (int s, int how);
OPIS
Funkcja shutdown zamyka gniazdo. Wykorzystywana w transmisji strumieniowej, aby przyspieszyć zamknięcie.
Parametry:
s - deskryptor zwalnianego gniazda
how - sposób zamknięcia; 0 - zamknięcie gniazdka do czytania, 1 - zamknięcie gniazdka do pisania, 2 - zamknięci gniazdka do czytania i pisania
Zwraca wartość 0, gdy operacja zakończona powodzeniem, w przeciwnym wypadku zwraca -1.
Sieciowa kolejność bajtów
Komputery stosują dwie różne metody wewnętrznej reprezentacji liczb całkowitych:
najpierw młodszy bajt (ang. little endian) - komórka pamięci o najniższym adresie zawiera najmniej znaczący bajt liczby
najpierw starszy bajt (ang. big endian) - komórka pamięci o najniższym adresie zawiera najstarszy bajt liczby
Protokół TCP/IP ustala standardową kolejność bajtów dla wszystkich komputerów w sieci: najpierw starszy bajt.
Potrzebne są funkcje konwersji.
Funkcje konwersji liczb całkowitych
Liczby całkowite krótkie
#include <sys/types.h>
#include <netinet/in.h>
/*host to network*/
u_short htons (u_short hostshort);
/*network to host */
u_short ntohs (u_short netshort);
Liczby całkowite długie
#include <sys/types.h>
#include <netinet/in.h>
/*host to network*/
u_long htons (u_long hostlong)
/* network to host */
u_long ntohs (u_long netlong)
Przykłady wykorzystania funkcji konwersji liczb całkowitych
/* Klient */
adres.sin_family = AF_INET;
adres.sin_addr.s_addr = inet_addr("127.0.0.1");
adres.sin_port = htons(9001);
dlugosc = sizeof(adres);
wynik = connect(gnizado,
(struct sockaddr *)&adres, dlugosc);
/* Serwer */
serwer_adres.sin_family = AF_INET;
serwer_adres.sin_addr.s_addr = htonl(INADDR_ANY);
serwer_adres.sin_port = htons(9001);
serwer_dlugosc = sizeof(serwer_adres);
bind(gniazdo_serwera,
(struct sockaddr *)&serwer_adres, serwer_dl);
Przykład programu klienta
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int sockfd, len, result;
struct sockaddr_un address;
char ch = 'A';
/* Utwórz gniazdo klienta. */
sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
/* Przypisz adres gniazdu */
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "gniazdo_klienta");
len = sizeof(address);
/* Połącz z gniazdem serwera. */
result=connect(sockfd,(struct sockaddr *)&address, len);
if(result == -1)
{
perror("Blad: klient1");
exit(1);
}
/* Wymień dane z serwerem. */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("otrzymano z serwera = %c\n", ch);
close(sockfd);
exit(0);
}
Przykład programu serwera
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main() {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
char ch;
/* Usun stare gniazda. */
unlink("server_socket");
/* Utwórz nowe gniazdo strumieniowe rodziny AF_UNIX */
server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
/* Przypisz adres gniazdu */
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");
server_len = sizeof(server_address);
bind(server_sockfd,
(struct sockaddr *)&server_address, server_len);
Przykład programu serwera c.d.
/* Utwórz kolejkę i czekaj na zgłoszenie klienta */
listen(server_sockfd, 5);
while(1)
{
printf("Serwer czeka\n");
/* Przyjmij połączenie */
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
/* Wymień dane z klientem */
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
1
2
Interfejs gniazd
Struktura wewnętrzna pliku 0
3:
write()
read()
close()
read()
write()
Zainicjuj połączenie
connect()
Pobierz z kolejki połączeń
accept()
Załóż kolejkę połączeń
listen()
Przypisz gniazdu nazwę
bind()
Utwórz gniazdo
socket()
Serwer
Utwórz gniazdo
socket()
Warstwa aplikacyjna
( TELNET, FTP, NFS)
Warstwa transportowa
(TCP, UDP)
Klient
Warstwa sieciowa (IP)
Warstwa łącza danych
Stos protokołów w jądrze systemu
Proces użytkownika
Interfejs gniazd
close()
przetwarzanie żądania
Ustanowienie połączenia
żądanie
odpowiedź
Utwórz gniazdo
socket()
Utwórz gniazdo
socket()
Przypisz gniazdu nazwę
bind()
Odbieraj dane od klienta
recvfrom()
0:
Wysyłaj dane do klienta
sendto()
Wysyłaj żądanie do serwera
sendto()
żądanie
przetwarzanie żądania
odpowiedź
Odbieraj dane
z serwera
recvfrom()
Struktura danych opisująca gniazdo
close()
Tablica deskryptorów
(oddzielna dla każdego procesu)
Klient
Serwer
1:
2:
Id. rodziny: PF_INET
Id. usługi: SOCK_STREAM
Lokalny adres IP:
Odległy adres IP:
Numer lokalnego portu:
Numer odległego portu:
Struktura wewnętrzna pliku 1
Struktura wewnętrzna pliku 2