Δικτυακός (Sockets Programming) Εργαστήριο Γ Εξάμηνο, Τμήμα Πληροφορικής Πανεπιστήμιο Θεσσαλίας
Δήλωση Οι διαφάνειες βασίζονται στα βιβλία: (1) Δικτυακός, Douglas E. Comer, David L. Stevens, Εκδόσεις ΙΩΝ, 2005 (2) Unix Network Programming, W. Richard Stevens, Prentice-Hall, 1994
Δήλωση (συνέχεια) Για τη δημιουργία των διαφανειών χρησιμοποιήθηκε υλικό των μαθημάτων: (1) Κατανεμημένα Συστήματα, Σπύρος Λάλης & Αλέξανδρος Καρυπίδης, ΤΜΗΥΤΔ, Πανεπιστήμιο Θεσσαλίας (2) Εργαστήριο Δικτύων Υπολογιστών, Ε. Βαρβαρίγος & Κ. Βλάχος, ΤΜΗΥΠ, Πανεπιστήμιο Πατρών
Στόχοι της Ενότητας Η παρουσίαση αρχιτεκτονικών επικοινωνίας διεργασιών σε διαδίκτυο Η παρουσίαση των υποδοχών (POSIX sockets API) για τον προγραμματισμό δικτυακών εφαρμογών Η παρουσίαση βημάτων για την ανάπτυξη πελατών και εξυπηρετητών με χρήση των πρωτοκόλλων TCP και UDP 4
Εισαγωγή Υποδοχές και Θύρες (sockets and ports) Γνωστές Εφαρμογές πελάτη-εξυπηρετητή http (World Wide Web (WWW)), web client/server FTP e-mail real audio, real video Εφαρμογές peer-to-peer (p2p) 5
Περιβάλλον Πελάτη-Εξυπηρετητή (Client-Server Environment) 6
Γενική Αρχιτεκτονική Πελάτη- Εξυπηρετητή 7
Αρχιτεκτονική Πελάτη- Εξυπηρετητή Βάσης Δεδομένων 8
Κλάσεις Αρχιτεκτονικής Πελάτη-Εξυπηρετητή 9
Αρχιτεκτονική Πελάτη- Εξυπηρετητή 3 Επιπέδων (3-tier) 10
Ενδιάμεσο Λογισμικό (Middleware) I Τυποποιημένες διεπαφές και πρωτόκολλα μεταξύ πελατών και βάσεων δεδομένων Κρύβει την πολυπλοκότητα των πηγών δεδομένων από τον τελικό χρήστη Συμβατό με ένα εύρος επιλογών πελάτη και εξυπηρετητή Όλες οι εφαρμογές λειτουργούν πάνω από μια ομοιόμορφη διεπαφή προγραμματισμού εφαρμογών 11
Middleware II 12
Λογική Άποψη του Middleware 13
Μηχανισμοί Middleware Ενδιάμεσο Λογισμικό με Μηνύματα (Message- Oriented Middleware) Κλήσεις Απομακρυσμένης Διαδικασίας (Remote Procedure Calls RPC) Object Request Brokers ORBs 14
Υποδοχές (Sockets) Προγραμματιστική διεπαφή η οποία: Παρέχει συναρτήσεις για τη δημιουργία και καταστροφή υποδοχών Παρέχει συναρτήσεις για την αίτηση σύνδεσης και την αποδοχή σύνδεσης υποδοχών Παρέχει συναρτήσεις για την αποστολή και λήψη δεδομένων μέσω των υποδοχών Επιχειρεί να αποκρύψει τις λεπτομέρειες της υλοποίησης του δικτύου 15
Υποδοχές 16
Υποδοχές και Θύρες 17
Υποδοχές και Πολύπλεξη 18
Επικοινωνία Stream / Datagram Επικοινωνία Ροής (SOCK_STREAM): προσανατολισμένο σε σύνδεση (connection oriented) πρωτόκολλο μεταφοράς: TCP (παρέχει αξιόπιστη μεταφορά δεδομένων) Επικοινωνία Datagram (SOCK_DGRAM): χωρίς σύνδεση (connectionless) πρωτόκολλο μεταφοράς: UDP (μη αξιόπιστη μεταφορά) χρησιμοποιείται συνήθως σε εφαρμογές που απαιτούν υψηλές ταχύτητες μετάδοσης µε μικρά τμήματα πληροφορίας χωρίς ανάγκη απόλυτης αξιοπιστίας (επαφίεται στην εφαρμογή) 19
TCP: Παροχή Αξιόπιστης Υπηρεσίας στις Εφαρμογές Υπηρεσία με Σύνδεση (Connection- Oriented Service) ή Επικοινωνία Ροής (Stream)
Αλληλεπίδραση Πελάτη Εξυπηρετητή στο TCP μέσω Υποδοχών 21
Σύνοψη λειτουργιών και κλήσεων συστήματος 22
Κλήσεις συστήματος για επικοινωνία υποδοχών TCP Εξυπηρετητής: δημιουργία τελικού σημείου (socket()) δέσμευση σε διεύθυνση (bind()) προσδιορισμός μεγέθους ουράς (listen()) αναμονή για σύνδεση (accept()) μεταφορά δεδομένων (read(), write()) Πελάτης: δημιουργία τελικού σημείου (socket()) σύνδεση με τον εξυπηρετητή (connect()) μεταφορά δεδομένων (write(), read()) 23
Πελάτης TCP Βήματα πελάτη για τη σύνδεση: δημιουργεί μια υποδοχή δεσμεύει την υποδοχή στην επιθυμητή τοπική δικτυακή διεύθυνση (προαιρετικό) Συνήθως υπάρχει μόνο μια κάρτα δικτύου / διεύθυνση Γενικά το ΛΣ μπορεί να επιλέγει αυτόματα Υπάρχουν περιπτώσεις που απαιτείται (firewalls) ζητά από το ΛΣ να συνδέσει την υποδοχή με κάποια υποδοχή στη διεύθυνση αποδέκτη (τη διεύθυνση αποδέκτη την παρέχει ο πελάτης) 24
Δομή για την αποθήκευση τελικού σημείου στο Internet struct sockaddr_in {/*struct to hold an address*/ }; u_char sin_len; /*total length */ u_short sin_family; /*type of address */ u_short sin_port; /*protocol port number */ struct in_addr sin_addr; /*IP address (declared to be u_long on some systems */ char sin_zero[8]; /*unused (set to zero) */ Η δομή για την αναπαράσταση διευθύνσεων στην οικογένεια πρωτοκόλλων TCP/IP 25
Κλήση Συστήματος socket() retcode = socket ( family, type, protocol ); family int type int protocol int Οικογένεια πρωτοκόλλων ή διευθύνσεων (PF_INET για TCP/IP) Τύπος υπηρεσίας (SOCK_STREAM για TCP ή SOCK_DGRAM για UDP) Αριθμός πρωτοκόλλου που θα χρησιμοποιηθεί ή 0 για χρήση του καθορισμένου πρωτοκόλλου για συγκεκριμένο family και type 26
Δημιουργία Υποδοχής TCP int main(int argc, char *argv[]) { int sock, ret; sock = socket (PF_INET, SOCK_STREAM, 0); if (sock == -1) { // error check perror ("Failed to create socket"); return -1; } } PF_INET: Protocol Family Internet, SOCK_STREAM: Υποδοχή TCP. Επιστρέφει έναν ακέραιο (sock) που είναι ο περιγραφέας της υποδοχής (socket descriptor) 27
connect() int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); sockfd int serv_addr &sock_addr addrlen int Ο τοπικός περιγραφέας υποδοχής που θα συνδεθεί Η διεύθυνση τελικού σημείου του εξυπηρετητή με την οποία θα συνδεθεί ο περιγραφέας Το μέγεθος (σε bytes) της διεύθυνσης 28
Σύνδεση με διεργασία εξυπηρετητή struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(inaddr_loopback); address.sin_port = htons(15000); ret = connect (sock, (struct sockaddr *) &address, sizeof(address)); Σύνδεση με τη διεργασία εξυπηρετητή στη διεύθυνση address 29
Δομή δεδομένων για τήρηση διευθύνσεων ΙΡ IPv4 sockaddr_in length AF_INET αριθμός θύρας (16bit) διεύθυνση ΙΡ (32 bit) IPv6 sockaddr_in6 length AF_INET6 αριθμός θύρας (16bit) ετικέτα ροής (32 bit) (unused) διεύθυνση ΙΡv6 (128 bit) μέγεθος: 16 bytes 30 μέγεθος: 24 bytes
Δημιουργία σύνδεσης (κλήσεις συστήματος) socket() connect() (μπλοκάρει) client server socket(), bind(), listen(), accept() (μπλοκάρει) επιστρέφει η connect() αποστολή αίτησης χρόνος 31 χρόνος επιστρέφει η accept() ανάγνωση δεδομένων αποστολή απόκρισης
Διάταξη των bytes στο Δίκτυο και τον Κόμβο Ι Το TCP/IP αναπαριστά τους δυαδικούς ακέραιους που χρησιμοποιούνται στις επικεφαλίδες των πρωτοκόλλων με πρώτο το πιο σημαντικό byte (network byte order) s και l για λειτουργία με ακέραιους 16 και 32 bit H χρήση κατάλληλων ρουτινών μετατροπής κάνει τον κώδικα μεταφέρσιμο (ανεξάρτητα από τη διάταξη των bytes στο τοπικό μηχάνημα) 32
Διάταξη των bytes στο Δίκτυο και τον Κόμβο ΙΙ #include <netinet.h> uint16_t htons(uint16_t hostshort) Host to Network Short uint32_t htonl(uint32_t hostlong) Host to Network Long uint16_t ntohs(uint16_t netshort) Network to Host Short uint32_t ntohl(uint32_t netlong) Network to Host Long 33
Αναπαράσταση Διευθύνσεων I include <sys/socket.h> include <netinet/in.h> include <arpa/inet.h> char *inet_ntoa(struct in_addr in); int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); 34
Αναπαράσταση Διευθύνσεων II Οι συναρτήσεις αυτές μετατρέπουν από μια δομή in_addr σε δεκαδικό συμβολισμό με τελείες και αντίστροφα n = network, a = ASCII (για ιστορικούς λόγους). Οι λειτουργίες και τα ορίσματα είναι προφανή Η συνάρτηση inet_addr() είναι παλαιότερη και κάνει ότι και η inet_aton() 35
Εξυπηρετητής TCP Βήματα εξυπηρετητή για τη σύνδεση: δημιουργεί μια υποδοχή δεσμεύει την υποδοχή στην επιθυμητή τοπική δικτυακή διεύθυνση τελικού σημείου ζητά από το ΛΣ να ακούει για συνδέσεις εισέρχεται σε κατάσταση όπου αναμένει διαρκώς (σύγχρονα ή ασύγχρονα) να του παραδώσει μια σύνδεση το ΛΣ 36
Δημιουργία Υποδοχής TCP int main(int argc, char *argv[]) { int serversocket; serversocket = socket (PF_INET, SOCK_STREAM, 0); if (serversocket == -1) { // error check perror ("Failed to create socket"); return -1; } } PF_INET: Protocol Family Internet, SOCK_STREAM: Υποδοχή TCP 37
Δέσμευση Πρέπει να ορίσουμε τη διεύθυνση στην οποία θα αναμένουμε συνδέσεις Στο TCP/IP το ουσιαστικό ζητούμενο είναι η δημιουργία τελικού σημείου επικοινωνίας (endpoint) που αποτελείται από διεύθυνση ΙΡ και αριθμό θύρας πρωτοκόλλου Οι πελάτες πρέπει να γνωρίζουν τη διεύθυνση αυτή για να μπορέσουν να συνδεθούν Το ΛΣ δεν επιτρέπει σε άλλο πρόγραμμα να δεσμεύσει τη διεύθυνση αυτή 38
Διεύθυνση Αναμονής Σύνδεσης struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl (INADDR_ANY); address.sin_port = htons (15000); Δημιουργία Τελικού Σημείου Εξυπηρετητή sin_family: πρέπει να είναι αντίστοιχο του socket family sin_addr: ακέραιος 32bit. Αντιστοιχεί σε ένα μοναδικό μηχάνημα sin_port: ακέραιος 16bit. Αντιστοιχεί σε ένα μοναδικό πρόγραμμα στον εξυπηρετητή 39
Δέσμευση Υποδοχής στη Διεύθυνση Αναμονής (bind()) retcode = bind ( socket, localaddr, addrlen ); socket int localaddr &sockaddr addrlen int Περιγραφέας υποδοχής που δημιουργείται από τη socket() Η διεύθυνση μιας δομής που προσδιορίζει μια διεύθυνση IP και τον αριθμό θύρας πρωτοκόλλου Μέγεθος του δεύτερου ορίσματος σε bytes 40
bind() ret = bind (serversocket, (struct sockaddr *) &address, sizeof (address)); if (ret == -1) { // error check } perror ("Failed to bind socket"); return -1; Δέσμευση Υποδοχής σε Τοπική Διεύθυνση 41
Τοποθέτηση σε Παθητική Κατάσταση retcode = listen( socket, queuelen ); socket int Ένας περιγραφέας υποδοχής που έχει δημιουργηθεί από την κλήση socket() queuelen int Το μέγεθος της ουράς των εισερχόμενων αιτήσεων σύνδεσης (εξ ορισμού 5) 42
listen() ret = listen (serversocket, 10); if (ret == -1) { // error check } perror ("Failed to start listening on socket"); return -1; Τοποθέτηση υποδοχής εξυπηρετητή σε παθητική κατάσταση (δηλ. κατάσταση αναμονής αιτήσεων μέσω της υποδοχής, με μέγιστο μέγεθος ουράς 10 αιτήσεις) 43
accept() I int accept( int s, struct sockaddr *addr, socklen_t *addrlen ); Η accept() δέχεται ως ορίσματα: την υποδοχή που αναμένει για αιτήσεις σύνδεσης μια δομή διεύθυνσης που συμπληρώνει με τα στοιχεία του πελάτη τη διεύθυνση ακεραίου (socklen_t) που: αρχικά έχει το μέγεθος της δομής διεύθυνσης στην επιστροφή έχει το μέγεθος που χρησιμοποιήθηκε 44
accept() II int accept(int s, struct sockaddr *addr, socklen_t *addrlen); Επιστρέφει μια νέα υποδοχή! Η αρχική συνεχίζει να χρησιμοποιείται για την αναμονή αιτήσεων και τη δημιουργία συνδέσεων Η νέα χρησιμοποιείται για την ανταλλαγή δεδομένων για τη συγκεκριμένη σύνδεση 45
Αποδοχή Σύνδεσης struct sockaddr_in remoteaddress; int addresslength, clientsocket; addresslength = sizeof (remoteaddress); clientsocket = accept (serversocket, (struct sockaddr *) &remoteaddress, &addresslength); if (clientsocket == -1) { // error check perror ("Error while accepting connection"); return -1; } printf ("Connection has been established\n"); Παραδίδεται η πρώτη αίτηση σύνδεσης από την ουρά. Η δομή remoteaddress και η addresslength περιέχουν τη διεύθυνση τελικού σημείου του πελάτη 46
accept() ΙII Από την στιγμή που η accept() επιστρέφει τη νέα υποδοχή, η σύνδεση µε τον πελάτη έχει εγκατασταθεί Μέχρι να ξανακληθεί η accept(), o εξυπηρετητής δεν ικανοποιεί αιτήσεις Οι κλήσεις που γίνονται όσο ο εξυπηρετητής δεν βρίσκεται στην accept() τοποθετούνται σε μια ουρά (εξορισμού μεγέθους 5 μπορεί να αλλάξει µε την listen()) 47
accept() ΙV Ο συνήθης τρόπος επεξεργασίας της αίτησης από τον εξυπηρετητή είναι µε fork() Με την fork() ο πελάτης επικοινωνεί µε έναν «αφοσιωµένο» σε αυτόν αντίγραφο του εξυπηρετητή Ο «αρχικός» εξυπηρετητής επιστρέφει για να συνεχίσει να αναμένει για νέες αιτήσεις σύνδεσης (επιστροφή στην accept()) 48
Δημιουργία Σύνδεσης (κλήσεις συστήματος) socket() connect() (μπλοκάρει) client server socket(), bind(), listen(), accept() (μπλοκάρει) επιστρέφει η connect() αποστολή αίτησης χρόνος 49 χρόνος επιστρέφει η accept() ανάγνωση αίτησης αποστολή απόκρισης
Η κλήση fork() I πελάτης connect() Αρχικά ανοικτή μόνο η υποδοχή αναμονής αιτήσεων για σύνδεση accept(): επιστρέφει την υποδοχή που θα χρησιμοποιηθεί για την ανταλλαγή δεδομένων με τον πελάτη εξυπηρετητής serversock accept() clientsock fork() εξυπηρετητής (παιδί) serversock clientsock fork(): Δημιουργεί αντίγραφο διεργασίας και επιστρέφει 0 στο παιδί. Τα παιδιά κληρονομούν από τη διεργασία γονέα, ανοικτά αρχεία, υποδοχές, πρόγραμμα και δεδομένα 50
Η κλήση fork() II πελάτης connect() εξυπηρετητής serversock Η διεργασία γονέας κλείνει τον περιγραφέα υποδοχής που αφορά στη σύνδεση που δημιουργήθηκε και επιστρέφει στην αναμονή για αιτήσεις Η διεργασία παιδί κλείνει την υποδοχή αναμονής συνδέσεων και αρχίζει να εξυπηρετεί την αίτηση 51 fork() εξυπηρετητής (παιδί) clientsock
Πολυνηματικές και Μονονηματικές Διεργασίες Ο χειρισμός των αιτήσεων μπορεί να γίνει είτε επαναληπτικά είτε ταυτόχρονα και από μονονηματικές ή πολυνηματικές διεργασίες Στο Linux χρησιμοποιείται και η κλήση συστήματος clone() που υποστηρίζει μια ενδιάμεση μορφή μεταξύ νήματος και διεργασίας Η pthread_create() χρησιμοποιείται για τη δημιουργία νημάτων POSIX 52
Ανάλυση (resolve) ενός ονόματος #include <netdb.h> struct hostent *gethostbyname(const char *hostname); struct hostent { char *h_name; /*official host name */ char **h_aliases; /*other aliases */ int h_addrtype; /*address type */ int h_length; /*address length */ char **h_addr_list; /*list of addresses */ }; #define h_addr h_addr_list[0] 53
Η δομή hostent 54
Αντιστοίχηση Ονόματος με Διεύθυνση ΙΡ struct hostent *hptr; struct sockaddr_in peeraddr; char *examplenam = "merlin.cs.purdue.edu"; (ή char *examplenam = argv[1];) if (hptr = gethostbyname (examplenam )) { /* IP address is now in hptr ->h_addr */ } else { /* error in name - handle it */ } peeraddr.sin_family = AF_INET; peeraddr.sin_addr.s_addr = ((struct in_addr *) (hptr->h_addr))->s_addr; 55
Αναζήτηση Προκαθορισμένης Θύρας I #include <netdb.h> struct servent *getservbyname(const char *servname, const char *protoname); struct servent { char *s_name; /*official service name */ char **s_aliases; /*other aliases */ int s_port; /*port for this service */ char *s_proto; /*protocol to use */ }; #include <netdb.h> struct servent *getservbyport(int port, const char *protoname); 56
Αναζήτηση Προκαθορισμένης Θύρας II struct servent *sptr; if (sptr = getservbyname ("smtp","tcp" )) { /* port number is now in sptr ->s_port */ } else { /* error occurred - handle it */ } peeraddr.sin_port = sptr->s_port; Η χρήση συναρτήσεων όπως οι παραπάνω, οδηγεί σε μεγαλύτερη μεταφερσιμότητα, λειτουργικότητα και γενικότητα στην πλευρά του πελάτη, αφού είναι εφικτό το πέρασμα παραμέτρων 57
Αποστολή και Λήψη Δεδομένων σε Υποδοχές TCP Γίνεται όπως η ανάγνωση/εγγραφή σε αρχεία με χρήση των συναρτήσεων Ε/Ε: read()/write() Επίσης, μπορούν να χρησιμοποιηθούν οι συναρτήσεις: send()/recv() Η λειτουργικότητα είναι αντίστοιχη 58
Ενδιάμεση Αποθήκευση (TCP Buffers) Το ΛΣ παρέχει μια περιοχή ενδιάμεσης αποθήκευσης (TCP buffer) Τα πακέτα, με την άφιξή τους τοποθετούνται στον TCP buffer Η read() παραδίδει τα δεδομένα στη διεργασία Ανάλογη λειτουργικότητα με την listen() και την ουρά εισερχόμενων συνδέσεων 59
send() / recv() count = send(sock, buf, buflen, flags) int count, sock, buflen, flags; char *buf; count = recv(sock, buf, buflen, flags) int count, sock, buflen, flags; char *buf; 60
Αξιόπιστη Υπηρεσία Η send() μπλοκάρει αν ο παραλήπτης δεν μπορεί να λάβει τα δεδομένα με το ρυθμό που στέλνονται (έλεγχος ροής) Ο buffer του ΛΣ χρησιμοποιείται για την παράδοση των δεδομένων στην εφαρμογή με τη σειρά που εστάλησαν Ο buffer έχει κάθε φορά συγκεκριμένο παράθυρο λήψης (έλεγχος ροής) 61
Ανάγνωση από Σύνδεση TCP Το TCP κατά την παραλαβή δε διατηρεί την ομαδοποίηση με την οποία εστάλησαν τα δεδομένα (πρωτόκολλο ροής χαρακτήρων) Κάθε πρόγραμμα που διαβάζει δεδομένα από μια σύνδεση TCP πρέπει να είναι έτοιμο να δεχθεί τα δεδομένα σε διαφορετικά τμήματα από αυτά που εστάλησαν Γενικά, το πλήθος των αναγνώσεων μπορεί να μην είναι ίσο με το πλήθος των εγγραφών 62
Κώδικας Ανταλλαγής Δεδομένων σε Σύνδεση TCP I /* Example code segment */ #define BLEN 120 /*Buffer length to use*/ char *req = "request of some sort"; char buf [BLEN] ; /*Buffer for answer*/ char *bptr; /* Pointer to Buffer*/ int n; /*Number of bytes read*/ int buflen; /*space left in buffer*/ bptr = buf; buflen= BLEN; 63
Κώδικας Ανταλλαγής Δεδομένων σε Σύνδεση TCP II /*Send request*/ send(s, req, strlen(req), 0); /*Read response (may come in many pieces)*/ while ((n=recv(s, bptr, buflen, 0)>0) { bptr += n; buflen-=n; } 64
Ανάγνωση με Διακοπές Η ανάγνωση μπορεί να γίνεται με διακοπές: λόγω άφιξης σήματος επειδή η υποδοχή ορίστηκε O_NONBLOCK η μεταβλητή errno σε αυτή την περίπτωση λαμβάνει τιμή EINTR ή EAGAIN αντίστοιχα Ο κώδικας θα πρέπει να είναι γραμμένος έτσι ώστε να διαχωρίζει αυτές τις περιπτώσεις από περιπτώσεις λαθών 65
Τερματισμός Σύνδεσης Όταν τα προγράμματα τελειώσουν την ανταλλαγή δεδομένων, πρέπει να τερματίζουν τη σύνδεση (απελευθέρωση περιγραφέα, δομών δεδομένων για την υποδοχή, buffer ΛΣ) Το ΛΣ: ενημερώνει με ειδικά πακέτα (FIN) το άλλο άκρο ώστε να γνωρίζει ότι πλέον η σύνδεση είναι άκυρη απελευθερώνει τους πόρους που υποστηρίζουν τη λειτουργία της σύνδεσης 66
close() int close(int sock); Ίδια ακριβώς χρήση και στα δυο άκρα Δεδομένα που έχει στείλει το άλλο πρόγραμμα πριν κλείσει τη σύνδεση, παραδίδονται από την ενδιάμεση αποθήκευση του ΛΣ Μετά από μια κλήση στην close() ο περιγραφέας αχρηστεύεται 67
Μερικός Τερματισμός shutdown() errcode = shutdown ( sock, direction ); Ο πελάτης δηλώνει ότι δε θα μεταφέρει δεδομένα (προς κάποια κατεύθυνση) και δεν αποδεσμεύει την υποδοχή Η εφαρμογή εξυπηρετητή λαμβάνει ένα σήμα end-of-file Ο εξυπηρετητής θα κλείσει τη σύνδεση μετά την τελευταία απόκριση Ιδιαίτερα χρήσιμο για πρωτόκολλα που ανταλλάσσουν αυθαίρετα μεγάλες ποσότητες δεδομένων 68
UDP: Μη Αξιόπιστη Υπηρεσία στις Εφαρμογές Υπηρεσία Χωρίς Σύνδεση (Connectionless Service) ή Επικοινωνία Δεδομενογραφημάτων (Datagram)
Αλληλεπίδραση Πελάτη Εξυπηρετητή στο UDP μέσω Υποδοχών 70
Σύνοψη Λειτουργιών και Κλήσεων Συστήματος 71
Κλήσεις συστήματος για επικοινωνία υποδοχών UDP Εξυπηρετητής: δημιουργία τελικού σημείου (socket()) δέσμευση διεύθυνσης (bind(), όπως στο TCP) ανταλλαγή δεδομένων (sendto(), recvfrom()) Πελάτης: δημιουργία τελικού σημείου (socket()) ανταλλαγή δεδομένων (sendto(), recvfrom()) 72
Χαρακτηριστικά του UDP Χωρίς σύνδεση (datagram-based) πιθανή άφιξη των πακέτων με διαφορετική σειρά η παράδοση των δεδομένων στον παραλήπτη δεν είναι εγγυημένη Χωρίς έλεγχο ροής Αποθήκευση ολόκληρων πακέτων (δεν είναι εφικτή η ανάγνωση μέρους του πακέτου) Μέγιστο μέγεθος πακέτου τα 64ΚΒ (επικεφαλίδα + φορτίο (payload)) 73
Χωρίς Σύνδεση Μέσα από την ίδια υποδοχή UDP ένα πρόγραμμα μπορεί: να στείλει πακέτα σε οποιοδήποτε άλλο πρόγραμμα στο Internet να λάβει πακέτα από οποιοδήποτε άλλο πρόγραμμα στο Internet Συγκρίνετε με το TCP όπου: οι υποδοχές συνδέονται σε ζεύγη ανταλλαγή δεδομένων υπάρχει μόνο μεταξύ συνδεδεμένων ζευγών 74
Χώρος Θυρών TCP και UDP Η επικεφαλίδα του ΙΡ περιέχει το πρωτόκολλο επιπέδου μεταφοράς για το οποίο προορίζεται ένα πακέτο Οι χώροι διευθύνσεων τελικού σημείου (θύρες) UDP και TCP είναι διαφορετικοί (μια υποδοχή TCP με αριθμό θύρας Χ μπορεί να συνυπάρχει στο ίδιο σύστημα με μια υποδοχή UDP με αριθμό θύρας Χ) Τα πακέτα TCP με αριθμό θύρας Χ θα παραδοθούν σε διαφορετική διεργασία από τα πακέτα UDP με αριθμό θύρας X 75
Δημιουργία Πακέτων Σχηματισμός πακέτων: Στο TCP γίνεται από το ΛΣ διαδοχικές send() μπορεί να ενωθούν στο ίδιο πακέτο μεγάλες send() μπορεί να σπάσουν σε > 1 πακέτα Στο UDP ελέγχεται από τον προγραμματιστή κάθε sendto() μπαίνει σε ένα πακέτο κλήση στη sendto() με δεδομένα > 64K επιστρέφει λάθος 76
Ανάγνωση Δεδομένων Ο τρόπος δημιουργίας πακέτων επηρεάζει τον τρόπο ανάγνωσης δεδομένων: TCP η recv() λειτουργεί πάνω σε μια ροή (stream) εισερχόμενων bytes της ενδιάμεσης αποθήκευσης του ΛΣ, που δεν έχει καμιά σχέση με τη δημιουργία πακέτων UDP κάθε recvfrom() παραλαμβάνει ακριβώς ένα πακέτο αν στην recvfrom() δεν υπάρχει αρκετός χώρος στο σημείο όπου τοποθετούνται τα εισερχόμενα δεδομένα, τα bytes του πακέτου αποκόπτονται 77
Δημιουργία Υποδοχής UDP int main(int argc, char *argv[]) { int sock; sock = socket (PF_INET, SOCK_DGRAM, 0); if (sock == -1) { // error check perror ("Failed to create socket"); return -1; } } PF_INET: Protocol Family Internet, SOCK_DGRAM: Υποδοχή UDP. Επιστρέφει έναν ακέραιο (sock) που είναι ο περιγραφέας της υποδοχής (socket descriptor) 78
Αποστολή και Λήψη Δεδομένων count = sendto(sock, buf, buflen, flags, to, tolen) int count, sock, buflen, flags, tolen; char *buf; struct sockaddr *to; count = recvfrom(sock, buf, buflen, flags, from, fromlen) int count, sock, buflen, flags, *fromlen; char *buf; struct sockaddr *from; 79
sendto() sock int Ένας περιγραφέας υποδοχής buff &char Ένας δείκτης στο μήνυμα buflen int flags int Bits ελέγχου to Το μέγεθος του μηνύματος σε bytes &sockaddr Δείκτης στη δομή της διεύθυνσης tolen int Το μέγεθος της διεύθυνσης 80
recvfrom() sock int Ένας περιγραφέας υποδοχής buff &char Η προσωρινή μνήμη που θα μπει το μήνυμα buflen int Μέγεθος της προσωρινής μνήμης flags int Bits ελέγχου from &sockaddr Θα τοποθετηθεί η διεύθυνση του αποστολέα fromlen &int Επιστρέφει το μέγεθος του from 81
connect() Εφόσον αποστέλλουμε συνεχώς σε συγκεκριμένο παραλήπτη μπορούμε να τον παγιώσουμε με τη χρήση της connect() Η connect() στο UDP δε δημιουργεί σύνδεση Η υποδοχή έχει έναν προκαθορισμένο αποδέκτη (χρησιμοποιείται η send()) Μπορεί κανείς να στείλει και σε άλλους αποδέκτες καλώντας την sendto() αντί για την send() 82
Παράδειγμα int main(int argc, char *argv[]) { int sock, ret; struct sockaddr_in recvadr; recvadr.sin_family = AF_INET; inet_aton ( 197.2.1.14, &recvadr.sin_addr); recvadr.sin_port = htons (15000); connect (sock, &recvadr, sizeof (recvadr)); send (sock, data, DATASIZE, 0); send (sock, moredata, DATASIZE, 0); } Αφού χρησιμοποιηθεί η connect() δεν απαιτείται ο προσδιορισμός παραλήπτη στη send() 83
Σήματα (Signals) Signal id default SIGKILL 9 Terminate SIGALRM 14 Terminate SIGSYS 12 Core SIGCHLD 20 Ignore 84
Σήματα Μπορούμε να καθορίσουμε επακριβώς την συμπεριφορά μίας διεργασίας, ύστερα από την λήψη ενός σήματος εκτέλεση μίας συνάρτησης (signal handler) καμία ενέργεια (SIG_IGN) προκαθορισμένη συμπεριφορά (SIG_DFL) Τα σήματα SIGKILL και SIGSTOP δεν μπορούν ούτε να αγνοηθούν ούτε να «πιαστούν» από μία συνάρτηση (signal handler) 85
waitpid() 86
Χειρισμός κλήσεων συστήματος που διακόπτονται Όταν μια κλήση συστήματος που μπλοκάρει (π.χ. accept()) διακοπεί από ένα σήμα, μετά την επιστροφή από το χειριστή σήματος η κλήση συστήματος θέτει τη μεταβλητή errno ίση με EINTR Ο πυρήνας δεν είναι βέβαιο ότι θα επανεκιννήσει την κλήση συστήματος Θα πρέπει ο προγραμματιστής να φροντίζει για την επανεκκίνηση της κλήσης συστήματος 87
Χειρισμός κλήσεων συστήματος που διακόπτονται for ( ; ; ) { clilen = sizeof(cliaddr); if ((connfd = accept(listenfd, (sockaddr *)&cliaddr, &clilen)) < 0) { } if (errno == EINTR) continue; else perror( accept error ); 88