Esempi di Client e Server Corso di laurea in Informatica Laboratorio di Reti di Calcolatori A.A. 0-0 Simone Bassis bassis@di.unimi.it #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 "90" #define BACKLOG 0 void sigchld_handler(int s) while(waitpid(-, NULL, WNOHANG) > 0); Simple MultiProcess Stream Server // ottieni il sockaddr, IPv o 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; struct addrinfo hints, *servinfo, *p; struct sockaddr_storage their_addr; socklen_t sin_size; struct sigaction sa; int yes=; 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; // usa il mio IP if ((rv = getaddrinfo(null, PORT, &hints, &servinfo))!= 0) fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return ; Invia la stringa Hallo World al client 76
Simple MultiProcess Stream Server // ciclo tra i risultati fino al primo bind corretto for(p = servinfo; p!= NULL; p = p->ai_next) if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -) perror("server: socket"); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -) perror("setsockopt"); if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -) close(sockfd); perror("server: bind"); break; if (p == NULL) fprintf(stderr, "server: failed to bind\n"); return ; freeaddrinfo(servinfo); // un po di pulizia if (listen(sockfd, BACKLOG) == -) perror("listen"); // occupiamoci dei figli // evitando che l amministratore di sistema si agiti sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(sigchld, &sa, NULL) == -) perror("sigaction"); printf("server: waiting for connections...\n"); 77 Simple MultiProcess Stream Server // ciclo accept() principale while() sin_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); if (new_fd == -) perror("accept"); 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()) // processo figlio close(sockfd); // il figlio non deve accettare connessioni if (send(new_fd, "Hello, world!",, 0) == -) perror("send"); close(new_fd); exit(0); close(new_fd); // al genitore non serve return 0; 5 Nota: tutto in un main per chiarezza espositiva: meglio suddividere il tutto in diverse funzioni 78
#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 "90" Simple Stream Client // porta destinazione // lunghezza massima del buffer di lettura #define MAXDATASIZE 00 // ottieni sockaddr, IPv 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); Ancora più semplice del server 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!= ) fprintf(stderr,"usage: client hostname\n"); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(argv[], PORT, &hints, &servinfo))!= 0) fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return ; Nota: potreste anche testare il server con telnet: $ telnet hostname 90 79 Simple Stream Client // cicla tra I risultati fino alla prima connect corretta for(p = servinfo; p!= NULL; p = p->ai_next) if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -) perror("client: socket"); if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -) close(sockfd); perror("client: connect"); break; if (p == NULL) fprintf(stderr, "client: failed to connect\n"); return ; 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); // un po di pulizia Nota: sappiamo le dimensioni della stringa inviata dal server e non ci preoccupiamo di controllare di averla letta tutta if ((numbytes = recv(sockfd, buf, MAXDATASIZE-, 0)) == -) perror("recv"); buf[numbytes] = '\0'; printf("client: received '%s'\n",buf); close(sockfd); return 0; 80
Chat Server I/O Multiplexing int main(void) fd_set master; fd_set read_fds; int fdmax; // master file descriptor list // temp file descriptor list per la select() // max file descriptor #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define PORT "90" // listening port int listener; // listening socket descriptor int newfd; // data socket descriptor struct sockaddr_storage remoteaddr; // client address socklen_t addrlen; char buf[56]; // buffer per lo scambio dati int nbytes; char remoteip[inet6_addrstrlen]; int yes=; // per setsockopt() SO_REUSEADDR int i, j, rv; struct addrinfo hints, *ai, *p; // ottieni il sockaddr, IPv 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); FD_ZERO(&master); FD_ZERO(&read_fds); memset(&hints, 0, sizeof hints); // svuota master e temp sets // socket e bind hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if ((rv = getaddrinfo(null, PORT, &hints, &ai))!= 0) fprintf(stderr, "selectserver: %s\n", gai_strerror(rv)); Servono master e temp set perché la select() modifica il temp set mostrando quali fd sono pronti 505 Chat Server I/O Multiplexing for(p = ai; p!= NULL; p = p->ai_next) listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (listener < 0) Ogni nuova connessione va aggiunta al master set Così come ogni chiusura necessita della rimozione dell fd dal master set // per gestire il messaggio d errore "address already in use" setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) close(listener); break; // arrivati a questo punto significa che qualcosa è andato storto if (p == NULL) fprintf(stderr, "selectserver: failed to bind\n"); exit(); freeaddrinfo(ai); // listen if (listen(listener, 0) == -) perror("listen"); exit(); // un po di pulizia // si aggiunge listener al set master FD_SET(listener, &master); // mantiene memoria del fd più grande fdmax = listener; // finora è questo 506
// main loop for(;;) Per questioni di portabilità meglio usare FD_COPY Chat Server I/O Multiplexing read_fds = master; // copia del master set if (select(fdmax+, &read_fds, NULL, NULL, NULL) == -) perror("select"); exit(); // si cicla per verificare quale fd è pronto per la lettura for(i = 0; i <= fdmax; i++) if (FD_ISSET(i, &read_fds)) // l i-esimo fd è pronto!! if (i == listener) // nuova connessione in arrivo addrlen = sizeof remoteaddr; newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen); if (newfd == -) perror("accept"); else FD_SET(newfd, &master); // si aggiunge il nuovo fd al master set if (newfd > fdmax) // e si aggiorna il max fd fdmax = newfd; printf("selectserver: new connection from %s on socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteip, INET6_ADDRSTRLEN), newfd); 5 507 Chat Server I/O Multiplexing else // un client ha inviato un messaggio if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) // errore o connessione chiusa dal client if (nbytes == 0) // connessione chiusa printf("selectserver: socket %d hung up\n", i); else perror("recv"); close(i); FD_CLR(i, &master); // un po di pulizia: chiusura socket e // rimozione fd dal set else // sono stati ricevuti dati dal client for(j = 0; j <= fdmax; j++) // inoltro agli altri client if (FD_ISSET(j, &master)) if (j!= listener && j!= i) // ad eccezione di listener e del client stesso if (send(j, buf, nbytes, 0) == -) perror("send"); // END loop tra I file descriptor // END for(;;) return 0; 6 508 5
Echo Client I/O Multiplexing - versione errata void str_cli(file *fp, int sockfd) int maxfdp; fd_set rset; char sendline[maxline], recvline[maxline]; FD_ZERO(&rset); for ( ; ; ) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp = max(fileno(fp), sockfd) + ; Select(maxfdp, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) /* socket is readable */ if (readline(sockfd, recvline, MAXLINE) == 0) perror("str_cli: server terminated prematurely"); fputs(recvline, stdout); if (FD_ISSET(fileno(fp), &rset)) /* input is readable */ if (fgets(sendline, MAXLINE, fp) == NULL) return; /* all done */ writen(sockfd, sendline, strlen(sendline)); Si leggono le singole linee da file e le si invia al server che provvederà a ritornarle Problema: se si arriva a fine file, l applicazione termina, anche se ci possono essere dati sulla socket. Per avere più controllo è opportuno usare shutdown() 509 void str_cli(file *fp, int sockfd) int maxfdp, stdineof; fd_set rset; char sendline[maxline], recvline[maxline]; stdineof = 0; FD_ZERO(&rset); for ( ; ; ) if (stdineof == 0) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp = max(fileno(fp), sockfd) + ; Select(maxfdp, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) /* socket is readable */ if (readline(sockfd, recvline, MAXLINE) == 0) if (stdineof == ) return; /* normal termination */ else perror("str_cli: server terminated prematurely"); Echo Client I/O Multiplexing - versione corretta Soluzione: con shutdown() viene inviato il segmento FIN alla controparte che dopo aver inviato tutti i dati terminerà con il suo FIN fputs(recvline, stdout); if (FD_ISSET(fileno(fp), &rset)) /* input is readable */ if (fgets(sendline, MAXLINE, fp) == NULL) stdineof = ; shutdown(sockfd, SHUT_WR); /* send FIN */ FD_CLR(fileno(fp), &rset); writen(sockfd, sendline, strlen(sendline)); 50 6