Opis problemu
Komunikacja sieciowa jest bardzo elastycznym elementem komunikacji, jednakże niesie za sobą narzut obliczeniowy w postaci obsługi stosu TCP/IP po stronie klienta i serwera.
W przypadku gdy aplikacja klienta i serwera działają w tym samym systemie operacyjnym (na tym samym komputerze lub maszynie wirtualnej), wówczas narzut ten powoduje niepotrzebne wykorzystanie portów oraz obliczeń wykonywanych po stronie jądra.
Sockety unix – wprowadzenie i definicje
Struktura opisująca gniazdo unix wygląda następująco:
1 2 3 4 |
struct sockaddr_un { unsigned short sun_family; /* AF_UNIX */ char sun_path[108]; } |
Przypisanie wartości strukturze sockaddr_un:
1 2 3 4 5 6 |
int len; struct sockaddr_u unix_sock; unix_sock.sun_family = AF_UNIX; strcpy(unix_sock.sun_path, "/tmp/.myunixsocket"); unlink(unix_sock.sun_path); len = strlen(unix_sock.sun_path) + sizeof(unix_sock.sun_family); |
Funckje wykorzystane w celu utworzenia serwera:
- unsingned int socket(int domain, int type, int protocol); – funkcja tworząca gniazdo. W przypadku gdy chcemy utworzyć gniazdo typu UNIX, należy podać jako domain AF_UNIX, type SOCK_STREAM, a protocol wartość 0.
- int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); – po utworzeniu struktury sockfd jest ona zadeklarowana w pamięci, ale podobnie jak dla protokołu IP nie miała jeszcze żadnego adresu przypisanego, tak dla gniazd UNIX następuje powiązanie z adresem pliku (a konkretniej strukturą opisującą adres gniazda).
- int listen(int sockfd, int backlog); – funkcja oznaczająca gniazdo sockfd jako gniazdo, które będzie wykorzystywane do otrzymywania nadchodzących połączeń. W przypadku gdy system otrzyma więcej niż jedno połączenie naraz, zostanie utworzona kolejka FIFO o maksymalnym rozmiarze backlog. Funkcję można wywołać tylko dla gniazd połączeniowych (SOCK_STREAM).
- int accept(int sockfd, struct sockaddr *newsockfd, socklen_t *address_len); – wywołanie systemowe (czyli funkcja po stronie jądra) uruchamiane na utworzonym wczesniej gnieździe sockfd (będącym w trybie listen), w celu otrzymania połączenia. W przypadku gdy w danym momencie jest więcej niż jedno połączenie, system operacyjny zapewnia kolejkę oczekujących połączeń (FIFO). W takim przypadku jest brane pierwsze z kolejki. Tworzony i zwracany (jako int) jest nowy deskryptor newsockfd. Oryginalne gniazdo nie zmienia stanu. Wywołanie accept wykorzystywane jest tylko do połączeniowych protokołów (SOCK_STREAM).
Funkcje wykorzystywane w celu utworzenia klienta:
- funkcja socket – działa tak samo jak dla aplikacji serwera.
- int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); – wywołanie connect nawiązuje połączenie do adresu opisanego przez strukturę addr i łączy utworzonego połączenie do deskryptora sockfd.
Funkcje czytania i pisania nie zmieniają się w porównaniu do normalnych gniazd protokołu IP.
Przykłady
Przykładowa aplikacja serwera:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> int main(void) { int s1, s2, t, len; struct sockaddr_un server_sock, client_sock; char buff[1000]; if ((s1 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_sock.sun_family = AF_UNIX; strcpy(server_sock.sun_path, "/tmp/.mysocket"); unlink(server_sock.sun_path); len = strlen(server_sock.sun_path) + sizeof(server_sock.sun_family); if (bind(s1, (struct sockaddr *)&server_sock, len) == -1) { perror("bind"); exit(1); } if (listen(s1, 5) == -1) { perror("listen"); exit(1); } for(;;) { int done, n; printf("Oczekuję na połączenia...\n"); t = sizeof(client_sock); if ((s2 = accept(s1, (struct sockaddr *)&client_sock, &t)) == -1) { perror("accept"); exit(1); } printf("Otrzymałem połączenie.\n"); done = 0; do { n = recv(s2, buff, 100, 0); if (n <= 0) { if (n < 0) perror("recv"); done = 1; } if (!done) if (send(s2, buff, n, 0) < 0) { perror("send"); done = 1; } } while (!done); close(s2); } return 0; } |
Przykładowa aplikacji klienta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> int main(void) { int s1, n, len; struct sockaddr_un server_socket; char buff[1000]; if ((s1 = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } printf("Trying to connect...\n"); server_socket.sun_family = AF_UNIX; strcpy(server_socket.sun_path, "/tmp/.mysocket"); len = strlen(server_socket.sun_path) + sizeof(server_socket.sun_family); if (connect(s1, (struct sockaddr *)&server_socket, len) == -1) { perror("connect"); exit(1); } printf("Connected.\n"); while(printf("> "), fgets(buff, 100, stdin), !feof(stdin)) { if (send(s1, buff, strlen(buff), 0) == -1) { perror("send"); exit(1); } if ((n=recv(s1, buff, 100, 0)) > 0) { buff[n] = '\0'; printf("%s", buff); } else { if (len < 0) perror("recv"); else printf("Server closed the connection.\n"); exit(1); } } close(s1); return 0; } |
Zadanie:
W oparciu o kod z tych i poprzednich zajęć zaimplementować następujące przypadki:
- Przesyłanie bloków 1000-bajtowych za pomocą protokołu gniazd TCP z pomiarem czasu. Program powinien działać wg schematu pomiar czasu, socket, 1000 razysendto i recvfrom, close, pomiar czasu, podanie czasu podzielonego przez 100.
- Przesyłanie bloków 1000-bajtowych za pomocą protokołu gniazd UNIX z pomiarem czasu. Program powinien działać wg schematu pomiar czasu,socket, connect, 1000 razy send i recv, close, pomiar czasu, podanie czasu podzielonego przez 100.
Wyniki proszę pokazać prowadzącemu.
Uwaga:
Przykłady aplikacji klienta i serwera to modyfikacje programów ze strony http://beej.us/guide/bgipc/output/html/multipage/unixsock.html