Do spisu tresci tematu 9

9.1.7 Nawiazywanie i zamykanie polaczen.



Spis tresci



Wprowadzenie

Do nawiazywania polaczen sluza dwie funkcje: dla serwera -accept(), a dla klienta - connect(). Obie te funkcje sa blokujace o ile nie ustawimy odpowiednich opcji , np. funkcja fcntl(). Serwer aby mogl przyjmowac polaczenia musi, zanim wywola accept(), wywolac funkcje listen() dla okreslenia wielkosci kolejki zgloszen klientow. Zamykanie polaczenia umozliwia funkcja shutdown(), ktora daje pewne mozliwosci sterowania przeplywem danych.


Funkcja systemowa accept()

-wykonywana przez serwer polaczeniowy do nawiazania polaczenia z klientem.

#include <sys/types.h>
#include <sys/socket.h>
int accept(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen);

ARGUMENTY:
  fd             - deskryptor gniazda. 
  upeer_sockaddr - wsk. na nazwe gniazda klienta. (jako dodatkowy wynik) 
  upeer_addrlen  - rozmiar nazwy upeer_sockaddr. (jako dodatkowy wynik) 

WYNIK:
  deskryptor nowo utworzonego gniazda, lub
  -1 - w przypadku bledu, np:
  		EBADF       - fd jest nieprawidlowym deskryptorem. 
		ENOTSOCK    - fd nie jest deskryptorem gniazda.
		EOPNOTSUPP  - gniazdo (zwiazane z fd) nie jest strumieniowe (SOCK_STREAM). 
		EFAULT      - obszar wskazywany parametrem upeer_sockaddr nie jest dostepny dla uzytkownika. 
		EWOULDBLOCK - operacja byla nieblokujaca, a akurat nie mozna od razu nawiazac polaczenia. 

Funkcja ta musi byc poprzedzona funkcjami :
  socket() - utworzenie gniazda, 
  bind() - dowiazanie nazwy do gniazda, 
  listen() - okreslenie kolejki do akceptowania polaczen. 
Implementacja :
{
   if (fd nie jest prawidlowym deskryptorem)
      return(-EBADF);
   if (fd nie jest deskryptorem gniazda) 
      return(-ENOTSOCK);
   if (jestesmy juz polaczeni z jakims gniazdem) 	
      return(-EINVAL);
  
   if (nie wykonalismy przedtem listen()) 
      return(-EINVAL);
  
   Alokujemy nowe gniazdo i przepisujemy na nie:
      - rodzaj gniazda fd,
      - rodzine protokolow zwiazana z fd.
  
   Wykonujemy funkcje rodziny duplikujaca gniazdo fd na nasze nowe gniazdo.
  
   Wywolujemy wlasciwa funkcja rodziny, ktora ustanowi polaczenie (unix_accept(),inet_accept()).

   Pobieramy deskryptor nowego gniazda (zostanie zwrocony jako wynik).
 
   if (upeer_sockaddr)
   {   
      Pobieramy nazwe partnera i dlugosc tej nazwy.
      Te wartosci przepisujemy na parametry upeer_sockaddr,upeer_addrlen.
   }
   Zwracamy nowy deskryptor.
}

Opis funkcji accept() z poziomu rodziny protokolow (unix_accept(), inet_accept()) :
Sprawdzamy kolejke ze zgloszeniami polaczen klientow. Jesli nie ma zadnych a funkcja nie jest nieblokujaca, to zostajemy blokowani az do momentu pojawienia sie jakiegos zgloszenia. Jesli jednak funkcja jest nieblokujaca i nie ma zgloszen to konczymy dzialanie.
Po odebraniu zgloszenia zapamietujemy na nowym gniezdzie nazwe klienta.
Jesli jest to rodzina Unixa, to budzimy klienta, wysylamy sygnaly SIGIO do grupy procesow zwiazanych z jego gniazdem i konczymy dzialanie.
Wpp. jesli jest to rodzina Inernetu, to wysylamy odpowiedz do klienta i blokujemy nasz proces w oczekiwaniu na ostateczne potwierdzenie. Dopiero po otrzymaniu potwierdzenia od klienta polaczenie jest ustanowione.

Po pomyslnym wykonaniu funkcji accept dla nowego gniazda mamy okreslona pelna asocjacje, ale dla gniazda fd nie, dzieki czemu mozemy ponownie odbierac zgloszenia od klienow na gniezdzie fd.

Funkcja systemowa connect()

-wykonywana przez klienta do nawiazania polaczenia z serwerem.

#include <sys/types.h>
#include <sys/socket.h>
int connect(int fd, struct sockaddr *uservaddr, int addrlen);

ARGUMENTY:
  fd        - deskryptor gniazda. 
  uservaddr - adres serwera. 
  addrlen   - rozmiar adresu serwera. 

WYNIK: 
  0  - w przypadku sukcesu, lub
  wartosc ujemna - w przypadku bledu, np:
		EBADF    - fd jest nieprawidlowym deskryptorem.
		ENOTSOCK - fd nie jest deskryptorem gniazda. 
		EISCONN  - juz jestesmy polaczeni.

Proces klienta nie musi wykonywac przedtem funkcji bind() dowiazujacej adres lokalny, bo zrobi to funkcja connect().

Implementacja :
{
   if (fd nie jest poprawnym deskryptorem)
      return(-EBADF);
   if (fd nie jest deskryptorem gniazda)
      return(-ENOTSOCK);

   Jesli jestesmy polaczeni a nasze gniazdo nie jest gniazdem datagramowym
      return -EISCONN;

   Wykonujemy funkcje rodziny (unix_connect(), inet_connect()), ktora nawiaze polaczenie.
  
   Jesli funkcja rodziny zakonczyla sie pomyslnie zwracamy 0,
   wpp. wartosc ujemna, ktora ta funkcja zwrocila.
}

Opis funkcji connect() z poziomu rodziny protokolow (unix_connect(), inet_connect()) :
Jesli fd jest deskryptorem gniazda datagramowego, to funkcja okresli tylko nazwe gniazda serwera dzieki czemu dla kazdego przesylanego datagramu nie bedziemy musieli za kazdym razem okreslac tej wartosci. W tym przypadku funkcja connect() nie ustanawia zadnego polaczenia. Dla gniazd datagramowych mozna wielokrotnie wykonywac connect().
Dla gniazda strumieniowego:
Wysylamy zgloszenie do serwera z nasza struktura sock budzac go i blokujemy sie (o ile funkcja nie jest nieblokujaca) w oczekiwaniu na odpowiedz od niego.
Jesli jest to rodzina Unixa, to po otrzymaniu odpowiedzi od serwera polaczenie uznajemy za ustanowione i konczymy dzialanie.
W przypadku rodziny Internetu po otrzymaniu takiej odpowiedzi wysylamy jeszcze do serwera ostateczne potwierdzenie i wtedy dopiero polaczenie jest ustanowione.

Funkcja systemowa listen()

- okresla kolejke zgloszen klientow.

#include <sys/types.h>
#include <sys/socket.h>
int listen(int fd, int backlog);

ARGUMENTY:
 fd      - deskryptor gniazda.
 backlog - maksymalna liczba zgloszen polaczenia z serwerem.

WYNIK:
 0 - w przypadku sukcesu, lub
 wartosc ujemna - w przypadku bledu, np:
	EBADF      - fd nie jest poprawnym deskryptorem.
	ENOTSOCK   - fd nie jest deskryptorem gniazda.
	EOPNOTSUPP - funkcja listen() nie jest zaimplementowana dla naszej rodziny. 

Implementacja:
{
   if (fd nie jest poprawnym deskryptorem)
      return(-EBADF);
   if (fd nie jest deskryptorem gniazda)
      return(-ENOTSOCK);

   if (jestesmy polaczeni) 
      return(-EINVAL);

   if (funkcja listen() jest okreslona dla naszej rodziny)
   {
      Wykonujemy funkcje rodziny (unix_listen(),inet_listen()). 
      if (funkcja rodziny zakonczyla sie pomyslnie)
         ustawiamy znacznik SO_ACCEPTCON.
   }
   Zwracamy 0 w przypadku sukcesu, lub warosc ujemna zwrocona przez funkcje rodziny.
}

Opis funkcji listen() z poziomu rodziny protokolow (unix_listen(), inet_listen()) :
Funkcja ustala maksymalna dlugosc kolejki klientow czekajacych na polaczenie. Wartosc ta jest pamietana w polu max_ack_backlog struktury sock i jest ograniczona stala SOMAXCONN=128.
W przypadku rodziny Unixa gniazdo musi byc strumeniowe i miec juz dowiazana nazwe.
W przypadku rodziny Inernetu jesli gniazdo nie ma nazwy, to listen() automatycznie ja dowiaze.

Funkcja systemowa shutdown()

- okresla sterowanie przeplywem przy zamykaniu gniazda.

#include <sys/types.h>
#include <sys/socket.h>
int shutdown(int sockfd, int how);

ARGUMENTY:
  sockfd - deskryptor gniazda.
  how    - wartosc okreslajaca sposob zamkniecia gniazda.

WYNIK:
  0  - w przypadku sukcesu, lub
  wartosc ujemna - w przypadku bledu, np:
		EBADF  - sockfd nie jest poprawnym deskryptorem.
		ENOTSOCK - sockfd nie jest deskryptorem gniazda.
		ENOTCONN - gniazdo jest niepolaczone z zadnym innym.

Implementacja:
{
   if (fd nie jest poprawnym deskryptorem)
      return(-EBADF);
   if (fd nie jest deskryptorem gniazda)
      return(-ENOTSOCK);
   Wykonujemy funkcje rodziny (unix_shutdown(),inet_shutdown()). 
   Zwracamy wynik funkcji rodziny.
}

Opis funkcji shutdown() z poziomu rodziny protokolow (unix_shutdown(), inet_shutdown()) :
Funkcja ustawia pole shutdown struktury sock stalymi RCV_SHUTDOWN, SEND_SHUTDOWN,SHUTDOWN_MASK w zaleznosci od parametru how. I tak:
how=0 - gniazdo nie moze juz przyjac zadnych komunikatow.
how=1 - gniazdo nie moze juz wysylac zadnych komunikatow.
how=2 - gniazdo nie moze przyjmowac, ani wysylac zadnych komunikatow.


Bibliografia

  1. Pliki zrodlowe Linuxa:
  2. R.Stevens -"Programowanie zastosowan sieciowych w systemie unix" - rozdzial 6 : "Gniazda BSD".

Autor: Tomasz Parczynski.