Υποδοχές


Οι υποδοχές (sockets) παρέχουν σημειακή, αμφίδρομη επικοινωνία μεταξύ διαργασιών, συνήθως -αλλά όχι απαραίτητα- μέσω δικτύου. Αποτελούν μια πολύ ευέλικτη και θεμελιώδη μέθοδο δικτυακής επικοινωνίας μεταξύ διεργασιών ή συστημάτων. Μια υποδοχή είναι μια τερματική σύνδεση η οποία μπορεί να λάβει ένα όνομα (name). Επίσης έχει ένα τύπο (type) και σχετίζεται με μια ή περισσότερες διεργασίες.

Οι υποδοχές ορίζονται στα πλαίσια ενός πεδίου (domain). Το πεδίο καθορίζει, πρώτον, το χώρο διεθύνσεων -άρα τo μονοσήμαντο όνομα μιας υποδοχής (στα όρια του πεδίου)- και, δεύτερον, το πρωτόκολλο (protocol) που χρησιμοποιεί η υποδοχή για την επικοινωνία. Επομένως μπορεί να επιτευχθεί επικοινωνία μόνο μεταξύ υποδοχών που ανήκουν στο ίδιο πεδίο. Οι τύποι πεδίων ορίζονται στη πρότυπη βιβλιοθήκη sys/socket.h, όπου βρίσκονται και τα πρωτότυπα των συναρτήσεων. Οι συχνότερα χρησιμοποιούμεννοι τύποι πεδίων είναι οι UNIX και Internet.

Το πεδίο UNIX παρέχει ένα χώρο διευθύνσεων σε ένα μεμονωμένο σύστημα UNIX/Linux. Οι υποδοχές UNIX ονομάζονται με βάση το όνομα διαδρομής (path) μέσα στο σύστημα. Το πεδίο UNIX δηλώνεται ως PF_UNIXPF_LOCAL ή PF_FILE).

Το πεδίο Internet παρέχει ένα χώρο διεθύνσεων σε συστήματα δικτυωμένα μέσω του πρωτοκόλλου TCP/IP. Η ονομασία ακολουθεί τις γνωστές συβάσεις ονομασίας του Internet. Το πεδίο Internet δηλώνεται ως PF_INET.

Ο τύπος ορίζει τις ιδιότητες επικοινωνίας που είναι ορατές στην εφαρμογή που χρησιμοποιεί την υποδοχή. Επιτρέπεται επικοινωνία μεταξύ υποδοχών του αυτού τύπου. Ορισμένοι βασικοί τύποι υποδοχών:

Υποδοχή ροής (stream)
-- Παρέχει αμφίδρομη, ακολουθιακή, αξιόπιστη και μη-επαναλαμβανόμενη ροή δεδομένων χωρίς καθορισμένα όρια μηνύματος. Η επικοινωνία μοιάζει με τηλεφωνική επικοινωνία. Ο τύπος ορίζεται ως SOCK_STREAM, και, στο πεδίο του Internet, χρησιμοποιεί το πρωτόκολλο TCP.
Υποδοχή datagram
-- Υποστηρίζει αμφίδρομη ροή μηνυμάτων (datagrams). Η σειρά αποστολής και παραλαβής μηνυμάτων μπορεί να μην είναι  ακολουθιακή. Τα όρια των μηνυμάτων είναι καθορισμένα. Η επικοινωνία μοιάζει με ανταλλαγή επιστολών μέσω ταχυδρομείου.Ο τύπος ορίζεται ως SOCK_DGRAM, και, στο πεδίο του Internet, χρησιμοποιεί το πρωτόκολλο UDP.
Υποδοχή γενική 
-- Παρέχει πρόσβαση σε πρωτόκολλα χαμηλότερου επιπέδου. Ο τύπος ορίζεται ως SOCK_RAW. Δεν υπάρχει συγκεκριμένο πρωτόκολλο γι' αυτό το τύπο, ο οποίος χρησιμοποιείται μόνο για χαμηλού επιπέδου επικοινωνία με ευθύνη του προγραμματιστή.

Δημιουργία και Ονομασία Υποδοχών

Η συνάρτηση int socket(int domain, int type, int protocol) 

καλείται από μια διεργασία για τη δημιουργία μιας υποδοχής τύπου type, στο πεδίο domain και με πρωτόκολλο protocol. Αν το πρωτόκολλο δεν οριστεί (τιμή 0), το σύστημα χρησιμοποιεί ένα προεπιλεγμένο πρωτόκολλο για το συγκεκριμένο τύπο. Η συνάρτηση επιστρέφει τον περιγραφέα της υποδοχής.

Μια άλλη διεργασία που θέλει να επικοινωνήσει με τη προηγούμενη δεν έχει τρόπο να ονομάσει την υποδοχή, μέχρις ότου η υποδοχή να λάβει διεύθυνση. Στο πεδίο UNIX η διεύθυνση συνήθως αποτελείται από ένα ή δύο ονόματα διαδρομής. Στο πεδίο Internet η διεύθυνση αποτελείται από ένα συνδυασμό IP διεύθυνσης και αριθμού θύρας (port).

Η συνάρτηση int bind(int s, const struct sockaddr *name, int namelen) 

καλείται για να συνδέσει μια υποδοχή (με περιγραφέα s) με ένα όνομα διαδρομής ή μια IP διεύθυνση, δηλαδή το όνομα (που ορίζεται στη δομή sockadrr). Το μήκος του ονόματος δηλώνεται στο namelen.Υπάρχουν αρκετοί διαφορετικοί τρόποι κλήσης της συνάρτησης  bind(), ανάλογα με τo πεδίο της υποδοχής. Αυτό που αλλάζει σε κάθε κλήση είναι ο ορισμός της δομής sockaddr.

      Η δομή sockaddr ορίζεται στη βιβλιοθήκη sys/socket.h και έχει τη μορφή

      short int sa_family -- Ορίζει τη μορφή των δεδομένων. Λαμβάνει τη τιμή AF_UNIXAF_LOCAL ή AF_FILE)
    char sa_data[14] -- Το όνομα διαδρομής.       Η δομή sockaddr ορίζεται στη βιβλιοθήκη sys/un.h και έχει τη μορφή

      short int sun_family -- Ορίζει τη μορφή των δεδομένων. Λαμβάνει τη τιμή AF_UNIXAF_LOCAL ή AF_FILE)
   
char sun_path[108] -- Το όνομα διαδρομής.     Η δομή sockaddr ορίζεται στη βιβλιοθήκη netinet/in.h και έχει τη μορφή

      short int sin_family -- Ορίζει τη μορφή των δεδομένων. Λαμβάνει τη τιμή AF_INET.
   
struct in_addr sin_addr -- H IP διεύθυνση. Ορίζεται με αριθμητική μορφή ή ως όνομα, με πολλές παραλλαγές.
    unsigned short int sin_port -- Ο αριθμός θύρας (0 έως 65,535). Μπορεί να οριστεί και με όνομα.

Ακολουθούν ορισμένα παραδείγματα δημιουργίας και ονομασίας υποδοχών που εξηγούν τη χρήση των παραπάνω συναρτήσεων.

Δημιουργία υποδοχής στο πεδίο UNIX.  Όπως βλέπουμε η δημιουργία υποδοχής ισοδυναμεί με δημιουργία αρχείου (ή σωλήνωσης). Η διαγραφή της υποδοχής γίνεται με unlink() ή rm (). Σημειώστε τη διαδικασία υπολογισμού του μήκους του ονόματος. Στο παράδειγμα γίνεται αρκετά αναλυτικά, θα μπορούσε να γίνει απλούστερα με τη μακροεντολή SUN_LEN() που ορίζεται στη βιβλιοθήκη sys/un.h.

#include <stddef.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>

int make_named_socket (const char *filename)
{
struct sockaddr_un name;
int sock;
int size;

/* Create the socket. */
sock = socket (PF_UNIX, SOCK_DGRAM, 0);
if (sock < 0)
{
perror ("socket");
exit (EXIT_FAILURE);
}

/* Bind a name to the socket. */
name.sun_family = AF_LOCAL;
strncpy (name.sun_path, filename, sizeof (name.sun_path));
name.sun_path[sizeof (name.sun_path) - 1] = '\0';

/* The size of the address is the offset of the start of the filename,plus its length,
plus one for the terminating null byte. Alternatively you can just do: size = SUN_LEN (&name);
*/

size = (offsetof (struct sockaddr_un, sun_path) + strlen (name.sun_path) + 1);

if (bind (sock, (struct sockaddr *) &name, size) < 0)
{
perror ("bind");
exit (EXIT_FAILURE);
}

return sock;
}

Δημιουργία υποδοχής με αριθμητική IP διεύθυνση. Παρατηρούμε οτι σε αυτή τη περίπτωση η IP διεύθυνση sin_addr ορίζεται ως δομή με ένα μόνο πεδίο, το unsigned int s_addr (0 έως 2^32 - 1). 

Υπάρχουν αρκετές μακροεντολές που περιλαμβάνουν προκαθορισμένες διευθύνσεις, όπως INADDR_LOOPBACK, INADDR_BROADCAST κλπ. Η μακροεντολή INADDR_ANY επιπτρέπει στο σύστημα να αποδώσει αυτό οποιαδήποτε εισερχόμενη IP διεύθυνση ώστε να επιτευχθεί σύνδεση με απομακρυσμένο υπολογιστή που αναμένει αίτημα σύνδεσης στη συγκεκριμένη θύρα. 

Οι συναρτήσεις htons(), htonl() χρησιμποιούνται για να 'μεταφράσουν', αν είναι απαραίτητο, τη σειρά αποθήκευσης των bytes (byte order) ακεραίων (short  και long) μεταξύ του οικείου υπολογιστή και του δικτύου (host to network). Αυτό γίνεται για να εξασφαλιστεί επικοινωνία μεταξύ 'big-endian' και 'long-endian' συστημάτων. Αντίστοιχα υπάρχουν και οι συναρτήσεις ntohs(), ntohl() (network to host).

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>

int make_socket (unsigned short int port)
{
int sock;
struct sockaddr_in name;

/* Create the socket. */
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket");
exit (EXIT_FAILURE);
}

/* Give the socket a name. */
name.sin_family = AF_INET;
name.sin_port = htons (port);
name.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
{
perror ("bind");
exit (EXIT_FAILURE);
}

return sock;
}

Δημιουργία υποδοχής με χρήση συναρτήσεων και δηλώσεων που συλλέγουν πληροφορίες για τον υπολογιστή μέ βάση το όνομα hostname. Το όνομα hostname χρησιμοποιείται ως κλειδή αναζήτησης πληροφοριών σχετικά με τον υπολογιστή σε μια 'βάση δεδομένων υπολογιστών' (host database). Η απλούστερη host database είναι το αρχείο /etc/hosts που διατηρεί κάθε σύστημα UNIX/Linux.  

Η συνάρτηση gethostbyname()  αναζητά πληροφορίες για τον υπολογιστή με το όνομα hostname στη βάση δεδομένων και τις αποθηκεύει στη δομή hostinfo. Η δομή είναι αρκετά σύνθετη και ορίζεται στη βιβλιοθήκη netinet.h/in.h.  Από τη δομή hostinfo αντιγράφουμε στη δομή sockaddr την IP διεύθυνση του υπολογιστή. 

Αντίστοιχη διαδικασία μπορεί να υλοποιηθεί και για τη τιμή port, η οποία θα μπορούσε να εξαχθεί από μια βάση δεδομένων υπηρεσιών (service database) έτσι ώστε η θύρα λαμβάνει τη τιμή που αντιστοιχεί στο όνομα της αντιστοιχης υπηρεσίας. Η συνηθισμένη βάση δεδομένων είναι το αρχείο /etc/services. Υπάρχουν πολλές και ενδιαφέρουσες συναρτήσεις και επιλογές σχετικές  με το Internet, δείτε το εγχειρίδιο της βιβλιοθήκης GCC. 

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void init_sockaddr (struct sockaddr_in *name,
const char *hostname,
unsigned short int port)
{
struct hostent *hostinfo;

name->sin_family = AF_INET;
name->sin_port = htons (port);
hostinfo = gethostbyname (hostname);
if (hostinfo == NULL)
{
fprintf (stderr, "Unknown host %s.\n", hostname);
exit (EXIT_FAILURE);
}
name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
}

Σύνδεση Υποδοχών Ροής

Συνήθως η σύνδεση μέσω υποδοχών δεν είναι συμμετρική. Η μια διεργασία λειτουργεί ως διακομιστής και η άλλη ως πελάτης. Ο διακομιστής συνδέει την υποδοχή του σε προσυμφωνημένο όνομα διαδρομής ή θύρα. Στη συνέχεια αναμένει αιτήσεις επικοινωνίας άλλων διεργασιών μέσω της υποδοχής. 

Στη περίπτωση υποδοχών ροής (SOCK_STREAM), ο διακομιστής καλεί τη συνάρτηση 

int listen(int s, int backlog) 

που καθορίζει τον περιγραφέα της υποδοχής s, και τον αριθμό συνδέσεων σε ουρά backlog

Ο πελάτης συνδέεται στην υποδοχή του διακομιστή με τη συνάρτηση connect() που έχει σύνταξη και λειτουργία αντίστοιχη με τη συνάρτηση bind().

Αν η υποδοχή του πελάτη δεν έχει ονομαστεί με προηγούμενη χρήση της συνάρτησης bind(),το σύστημα αυτόματα επιλέγει όνομα και ονομάζει την υποδοχή. 

Στη περίπτωση που ο τύπος της υποδοχής είναι SOCK_STREAM ο διακομιστής απαντά στo αίτημα connect() με τη συνάρτηση accept() ώστε να ολοκληρωθεί η επικοινωνία.

Η συνάρτηση int accept(int s, struct sockaddr *addr, int *addrlen)

επιστρέφει ένα νέο περιγραφέα υποδοχής, που αναφέρεται μόνο στη συγκεκριμένη σύνδεση. Ένας διακομιστής μπορεί να έχει ταυτόχρονα πολλαπλές συνδέσεις SOCK_STREAM σε μια υποδοχή.

Μεταφορά Δεδομένων Ροής και Κλείσιμο

Υπάρχουν αρκετές συναρτήσεις για την μεταφορά δεδομένων σε υποδοχή SOCK_STREAM. Αυτές είναι οι write(), read(), int send(int s, const char *msg, int len, int flags), και int recv(int s, char *buf, int len, int flags). Οι send() και recv() είναι όμοιες με τις read() και write(), αλλά με περισσότερες επιλογές ελέγχου (παράμετρος flags)

Η παράμετρος flags διαμορφώνεται από το bitwise OR κάποιων από τα παρακάτω:

MSG_OOB
-- Αποστολή "out-of-band" δεδομένων σε υποδοχές SOCK_STREAM του πεδίου AF_INET μόνο.
MSG_DONTROUTE
-- Η επιλογή SO_DONTROUTE μετρά τη δρομολόγηση για διαγνωστικούς λόγους.
MSG_PEEK
-- "Βλέπει" τα δεδομένα που βρίσκονται στην υποδοχή χωρίς να τα καταναλώνει.

A SOCK_STREAM socket is discarded by calling close().

Υποδοχές Datagram (UDP) 

Μια υποδοχή datagram δεν απαιτεί τη πρότερη εγκαθίδρυση σύνδεσης. Κάθε μήνυμα περιέχει τη διεύθυνση προορισμού. Αν απαιτείται κάποια συγκεκριμένη τοπική διεύθυνση, πριν από κάθε μεταφορά δεδομένων πρέπει να υπάρχει μια κλήση bind(). Τα δεδομένα αποστέλονται με τις κλήσεις sendto() ή sendmsg(). Η συνάρτηση sendto() είναι ίδια με τη συνάρτηση send() με επιπλέον καθορισμό δίεύθυνσης. Η παραλαβή γίνεται με τις κλήσεις recvfrom() ή recvmsg(). Ενώ η συνάρτηση recv() απαιτεί ένα buffer για τα δεδομένα, η συνάρτηση recvfrom() απαιτεί δύο buffers, ένα για τα δεδομένα και ένα για τη διεύθυνση.

Οι υποδοχές datagram μπορούν να χρησιμοποιήσουν τη συνάρτηση connect() για σύνδεση με υποδοχή, σε συνδυασμό με τις συναρτήσεις.  send() και recv() για τη μεταφορά.

Οι συναρτήσεις accept() και listen() δεν χρησιμοποιούνται με υποδοχές datagram.


Παραδείγματα υποδοχών: πελάτης και διακομιστής

Ακολουθούν αρκετά ζεύγη προγραμμάτων πελάτη - διακομιστή με χρήση υποδοχών.

inet_socket_server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> 

int main(int argc, char *argv[])
{
    // Declaring process variables.
    int server_sockfd, client_sockfd;
    int server_len ;
    int rc ;
    char ch;
    unsigned client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;

    //Remove any old socket and create an unnamed socket for the server.
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htons(INADDR_ANY);
    server_address.sin_port = htons(7734) ;
    server_len = sizeof(server_address);

    rc = bind(server_sockfd, (struct sockaddr *) &server_address, server_len);
    printf("RC from bind = %d\n", rc ) ;
    
    //Create a connection queue and wait for clients
    rc = listen(server_sockfd, 5);
    printf("RC from listen = %d\n", rc ) ;

    client_len = sizeof(client_address);
    client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
    printf("after accept()... client_sockfd = %d\n", client_sockfd) ;

    while(1)
    {
        printf("server waiting\n");
       
        //Read from client
        rc = read(client_sockfd, &ch, 1);
        printf("RC from read = %d\n", rc ) ;        
        if (ch=='X') break ;
        ch++;
       
        //Write to client
        write(client_sockfd, &ch, 1);
    }

    printf("server exiting\n");

    //close(client_sockfd);
    close(client_sockfd);
    return 0;
}

inet_socket_client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>


int main(int argc, char *argv[])
{
    printf("This is the client program\n");

    int sockfd;
    int len, rc ;
    struct sockaddr_in address;
    int result;
    char ch = 'A';

   //Create socket for client.
    sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("Socket create failed.\n") ;
        return -1 ;
    }
    
    //Name the socket as agreed with server.
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(7734);
    len = sizeof(address);

    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1)
    {
        perror("Error has occurred");
        exit(-1);
    }

    while ( ch < 'Y') {

        //Read and write via sockfd
        rc = write(sockfd, &ch, 1);
        printf("write rc = %d\n", rc ) ;
        if (rc == -1) break ;
        
        read(sockfd, &ch, 1);
        printf("Char from server = %c\n", ch);
    }
    close(sockfd);

    exit(0);
}

stream_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3490"    // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold

void sigchld_handler(int s)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr IPV4 or IPV6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd, new_fd;          // listen on sock_fd, new connection on new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr;   // connector's address information
    socklen_t sin_size;
    struct sigaction sa;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;   // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {

        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        return 2;
    }

    freeaddrinfo(servinfo);          // all done with this structure

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler; // reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    while(1) {           // main accept() loop

        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
        if (new_fd == -1) {
            perror("accept");
            continue;
        }

        inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
        printf("server: got connection from %s\n", s);

        if (!fork()) {     // this is the child process
            close(sockfd); // child doesn't need the listener
            if (send(new_fd, "Hello, world!", 13, 0) == -1)
                perror("send");
            close(new_fd);
            exit(0);
        }

        close(new_fd);     // parent doesn't need this
    }

    return 0;
}

stream_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT "3490"        // the port client will be connecting to
#define MAXDATASIZE 100    // max number of bytes we can get at once

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes; 
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {

        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
    printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo); // all done with this structure

    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("recv");
        exit(1);
    }

    buf[numbytes] = '\0';
    printf("client: received '%s'\n",buf);
    close(sockfd);
    return 0;
}

datagram_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define MYPORT "4950"    // the port users will be connecting to
#define MAXBUFLEN 100

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct sockaddr_storage their_addr;
    char buf[MAXBUFLEN];
    size_t addr_len;
    char s[INET6_ADDRSTRLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {

        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("listener: socket");
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("listener: bind");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "listener: failed to bind socket\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    printf("listener: waiting to recvfrom...\n");

    addr_len = sizeof their_addr;
    if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
        (struct sockaddr *)&their_addr, &addr_len)) == -1) {
        perror("recvfrom");
        exit(1);
    }

    printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr), s, sizeof s));
    printf("listener: packet is %d bytes long\n", numbytes);
    buf[numbytes] = '\0';
    printf("listener: packet contains \"%s\"\n", buf);

    close(sockfd);
    return 0;
}

datagram_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT "4950"    // the port users will be connecting to

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;

    if (argc != 3) {
        fprintf(stderr,"usage: talker hostname message\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {

        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("talker: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "talker: failed to bind socket\n");
        return 2;
    }

    if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0, p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto");
        exit(1);
    }

    freeaddrinfo(servinfo);

    printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
    close(sockfd);
    return 0;
}

socket_server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>

#define NSTRS 3 /* no. of strings */
#define ADDRESS "mysocket" /* addr to connect */

/*
* Strings we send to the client.
*/
char *strs[NSTRS] = {
"This is the first string from the server.\n",
"This is the second string from the server.\n",
"This is the third string from the server.\n"
};

main()
{
char c;
FILE *fp;
int fromlen;
register int i, s, ns, len;
struct sockaddr_un saun, fsaun;

/*
* Get a socket to work with. This socket will
* be in the UNIX domain, and will be a
* stream socket.
*/
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("server: socket");
exit(1);
}

/*
* Create the address we will be binding to.
*/
saun.sun_family = AF_UNIX;
strcpy(saun.sun_path, ADDRESS);

/*
* Try to bind the address to the socket. We
* unlink the name first so that the bind won't
* fail.
*
* The third argument indicates the "length" of
* the structure, not just the length of the
* socket name.
*/
unlink(ADDRESS);
len = sizeof(saun.sun_family) + strlen(saun.sun_path);

if (bind(s, &saun, len) < 0) {
perror("server: bind");
exit(1);
}

/*
* Listen on the socket.
*/
if (listen(s, 5) < 0) {
perror("server: listen");
exit(1);
}

/*
* Accept connections. When we accept one, ns
* will be connected to the client. fsaun will
* contain the address of the client.
*/
if ((ns = accept(s, &fsaun, &fromlen)) < 0) {
perror("server: accept");
exit(1);
}

/*
* We'll use stdio for reading the socket.
*/
fp = fdopen(ns, "r");

/*
* First we send some strings to the client.
*/
for (i = 0; i < NSTRS; i++)
send(ns, strs[i], strlen(strs[i]), 0);

/*
* Then we read some strings from the client and
* print them out.
*/
for (i = 0; i < NSTRS; i++) {
while ((c = fgetc(fp)) != EOF) {
putchar(c);

if (c == '\n')
break;
}
}

/*
* We can simply use close() to terminate the
* connection, since we're done with both sides.
*/
close(s);

exit(0);
}

socket_client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>

#define NSTRS 3 /* no. of strings */
#define ADDRESS "mysocket" /* addr to connect */

/*
* Strings we send to the server.
*/
char *strs[NSTRS] = {
"This is the first string from the client.\n",
"This is the second string from the client.\n",
"This is the third string from the client.\n"
};

main()
{
char c;
FILE *fp;
register int i, s, len;
struct sockaddr_un saun;

/*
* Get a socket to work with. This socket will
* be in the UNIX domain, and will be a
* stream socket.
*/
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("client: socket");
exit(1);
}

/*
* Create the address we will be connecting to.
*/
saun.sun_family = AF_UNIX;
strcpy(saun.sun_path, ADDRESS);

/*
* Try to connect to the address. For this to
* succeed, the server must already have bound
* this address, and must have issued a listen()
* request.
*
* The third argument indicates the "length" of
* the structure, not just the length of the
* socket name.
*/
len = sizeof(saun.sun_family) + strlen(saun.sun_path);

if (connect(s, &saun, len) < 0) {
perror("client: connect");
exit(1);
}

/*
* We'll use stdio for reading
* the socket.
*/
fp = fdopen(s, "r");

/*
* First we read some strings from the server
* and print them out.
*/
for (i = 0; i < NSTRS; i++) {
while ((c = fgetc(fp)) != EOF) {
putchar(c);

if (c == '\n')
break;
}
}

/*
* Now we send some strings to the server.
*/
for (i = 0; i < NSTRS; i++)
send(s, strs[i], strlen(strs[i]), 0);

/*
* We can simply use close() to terminate the
* connection, since we're done with both sides.
*/
close(s);

exit(0);
}

Exercises

Exercise 12776

Configure the above socket_server.c and socket_client.c programs for you system and compile and run them. You will need to set up socket ADDRESS definition.



Dave Marshall
1/5/1999