Advertisement
Guest User

Untitled

a guest
May 25th, 2017
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 16.66 KB | None | 0 0
  1. /*
  2.  * chatterbox Progetto del corso di LSO 2017 (a.a 2016-2017)
  3.  *
  4.  * Dipartimento di Informatica Università di Pisa
  5.  * Docenti: Prencipe, Torquati
  6.  *
  7.  */
  8. /**
  9.  * @file client.c
  10.  * @brief Semplice client di test.
  11.  *
  12.  *
  13.  */
  14. #define _POSIX_C_SOURCE 200809L
  15. #include <time.h>
  16. #include <errno.h>
  17. #include <string.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <getopt.h>
  21. #include <unistd.h>
  22. #include <signal.h>
  23. #include <sys/types.h>
  24. #include <sys/stat.h>
  25. #include <fcntl.h>
  26. #include <unistd.h>
  27. #include <sys/mman.h>
  28.  
  29.  
  30. #include <connections.h>
  31. #include <ops.h>
  32.  
  33. // tipo di operazione (usata internamente)
  34. typedef struct {
  35.     char  *sname;   // nickname del sender
  36.     char  *rname;   // nickname o groupname del receiver
  37.     op_t   op;      // tipo di operazione (se OP_END e' una operazione interna)
  38.     char  *msg;     // messaggio testuale o nome del file
  39.     long   size;    // lunghezza del messaggio
  40.     long   n;       // usato per -R -r
  41. } operation_t;
  42.  
  43. /* -------------------- globali -------------------------- */
  44. // array di messaggi ricevuti ma non ancora gestiti
  45. static message_t  *MSGS = NULL;
  46. static const int   msgbatch = 100;
  47. static size_t      msgcur=0;
  48. static size_t      msglen=0;    
  49. /* ------------------------------------------------------- */
  50.  
  51. // usage function
  52. static void use(const char * filename) {
  53.     fprintf(stderr,
  54.         "use:\n"
  55.         " %s -l unix_socket_path -k nick -c nick -[gad] group -t milli -S msg:to -s file:to -R n -h\n"
  56.         "  -l specifica il socket dove il server e' in ascolto\n"
  57.         "  -k specifica il nickname del client\n"
  58.         "  -c specifica il nickname che deve essere creato\n"
  59.         "  -C chiede che il nickname venga deregistrato\n"
  60.         "  -g specifica il groupname che deve essere creato\n"
  61.         "  -a aggiunge 'nick' al gruppo 'group'\n"
  62.         "  -d rimuove  'nick' dal gruppo 'group'\n"
  63.         "  -L richiede la lista degli utenti online\n"
  64.         "  -p richiede di recuperare la history dei messaggi\n"
  65.         "  -t specifica i millisecondi 'milli' che intercorrono tra la gestione di due comandi consecutivi\n"
  66.         "  -S spedisce il messaggio 'msg' al destinatario 'to' che puo' essere un nickname o groupname\n"
  67.         "  -s come l'opzione -S ma permette di spedire files\n"
  68.         "  -R riceve un messaggio da un nickname o groupname, se viene ricevuto un identificatore di file\n"
  69.         "     il file viene scaricato dal server. In base al valore di n il comportamento e' diverso, se:\n"
  70.         "      n > 0 : aspetta di ricevere 'n' messaggi e poi passa al comando successivo (se c'e')\n"
  71.         "      n <= 0: aspetta di ricevere uno o piu' messaggi indefinitamente (eventuali comandi che \n"
  72.         "             seguono non verranno eseguiti)\n",
  73.         filename);
  74. }
  75.  
  76. // legge un messaggio (testuale o file) e lo memorizza in MSGS (array globale)
  77. static int readMessage(int connfd, message_hdr_t *hdr) {
  78.    
  79.     if (readData(connfd, &MSGS[msgcur].data) <= 0) {
  80.     perror("reading data");
  81.     return -1;
  82.     }
  83.  
  84.     // NOTA: la gestione di MSGS e' molto brutale: non si libera mai memoria
  85.    
  86.     MSGS[msgcur].hdr = *hdr;
  87.     msgcur++;
  88.     if (msgcur>=msglen) {      
  89.     msglen += msgbatch;
  90.     MSGS= realloc(MSGS, msglen);
  91.     if (MSGS == NULL) {
  92.         perror("realloc");
  93.         fprintf(stderr, "ERRORE: Out of memory\n");
  94.         exit(EXIT_FAILURE);
  95.     }
  96.     for(size_t i=msgcur;i<msglen;++i) MSGS[i].data.buf=NULL;
  97.     }      
  98.     return 1;
  99. }
  100.  
  101. // effettua la richiesta di download di un file
  102. static int downloadFile(int connfd, char *filename, char *sender) {
  103.     // mando la richiesta di download
  104.     message_t msg;
  105.     setHeader(&msg.hdr, GETFILE_OP, sender);
  106.     setData(&msg.data, "", filename, strlen(filename)+1);
  107.     if (sendRequest(connfd, &msg) == -1)  return -1;
  108.     for( ; ;) {
  109.     // aspetto di ricevere la risposta alla richiesta
  110.     if (readHeader(connfd, &msg.hdr) <= 0) return -1;
  111.  
  112.     // differenti tipi di risposta che posso ricevere
  113.     switch(msg.hdr.op) {
  114.     case OP_OK: {
  115.         if (readData(connfd, &msg.data) <= 0) return -1;
  116.         return 0;
  117.     } break;
  118.     case TXT_MESSAGE:
  119.     case FILE_MESSAGE: {       
  120.         /* Non ho ricevuto la risposta ma messaggi da altri client,
  121.          * li conservo in MSGS per gestirli in seguito.
  122.          */
  123.         if (readMessage(connfd, &msg.hdr)<=0) return -1;
  124.     } break;
  125.     default: {
  126.         fprintf(stderr, "ERRORE: ricevuto messaggio non valido\n");
  127.         return -1;
  128.     }
  129.     }
  130.     }
  131.     return -1;
  132. }
  133.  
  134. // gestisce operazioni di tipo richiesta-risposta
  135. static int execute_requestreply(int connfd, operation_t *o) {
  136.     char *sname = o->sname;
  137.     char *rname = o->rname?o->rname:"";
  138.     op_t op     = o->op;
  139.     message_t msg;
  140.     char  *mappedfile = NULL;
  141.    
  142.     setData(&msg.data, "", NULL, 0);
  143.     setHeader(&msg.hdr, op, sname);
  144.     if (op == POSTTXT_OP || op == POSTTXTALL_OP || op == POSTFILE_OP) {
  145.     if (o->size == 0) {
  146.         fprintf(stderr, "ERRORE: size non valida per l'operazione di POST\n");
  147.         return -1;
  148.     }      
  149.     if (op == POSTFILE_OP) {
  150.         int fd = open(o->msg, O_RDONLY);
  151.         if (fd<0) {
  152.         perror("open");
  153.         fprintf(stderr, "ERRORE: aprendo il file %s\n", o->msg);
  154.         close(fd);
  155.         return -1;
  156.         }
  157.         // mappiamo il file da spedire in memoria
  158.         mappedfile = mmap(NULL, o->size, PROT_READ,MAP_PRIVATE, fd, 0);
  159.         if (mappedfile == MAP_FAILED) {
  160.         perror("mmap");
  161.         fprintf(stderr, "ERRORE: mappando il file %s in memoria\n", o->msg);
  162.         close(fd);
  163.         return -1;
  164.         }
  165.         close(fd);
  166.         setData(&msg.data, rname, o->msg, strlen(o->msg)+1); // invio il nome del file
  167.     } else
  168.         setData(&msg.data, rname, o->msg, o->size);    
  169.     }
  170.    
  171.     // spedizione effettiva
  172.     if (sendRequest(connfd, &msg) == -1) {
  173.     perror("request");
  174.     return -1;
  175.     }
  176.     if (mappedfile) { // devo inviare il file
  177.     message_data_t data;
  178.     setData(&data, "", mappedfile, o->size);
  179.     if (sendData(connfd, &data) == -1) { // invio il contenuto del file
  180.         perror("sending data");
  181.         fprintf(stderr, "ERRORE: spedendo il file %s\n", o->msg);
  182.         munmap(mappedfile, o->size);
  183.         return -1;
  184.     }
  185.     munmap(mappedfile, o->size);
  186.     } else if (msg.data.buf) free(msg.data.buf);
  187.  
  188.     // devo ricevere l'ack
  189.     int ackok = 0;
  190.     while(!ackok) {
  191.     // aspetto di ricevere la risposta alla richiesta
  192.     if (readHeader(connfd, &msg.hdr) <= 0) {
  193.         perror("reply header");
  194.         return -1;
  195.     }
  196.    
  197.     // differenti tipi di risposta che posso ricevere
  198.     switch(msg.hdr.op) {
  199.     case OP_OK:  ackok = 1;     break;
  200.     case TXT_MESSAGE:
  201.     case FILE_MESSAGE: {       
  202.         /* Non ho ricevuto la risposta ma messaggi da altri client,
  203.          * li conservo in MSGS per gestirli in seguito.
  204.          */
  205.         if (readMessage(connfd, &msg.hdr)<=0) return -1;
  206.     } break;
  207.     case OP_NICK_ALREADY:
  208.     case OP_NICK_UNKNOWN:
  209.     case OP_MSG_TOOLONG:
  210.     case OP_FAIL: {
  211.         if (msg.data.buf) fprintf(stderr, "Operazione %d FALLITA: %s\n", op, msg.data.buf);
  212.         else          fprintf(stderr, "Operazione %d FALLITA\n", op);
  213.         return -msg.hdr.op; // codice di errore ritornato
  214.     } break;
  215.     default: {
  216.         fprintf(stderr, "ERRORE: risposta non valida\n");
  217.         return -1;
  218.     }
  219.     }
  220.     }
  221.     // Ho ricevuto l'ack dal server, ora sulla base dell'operazione che avevo
  222.     // richiesto devo ...
  223.     switch(op) {
  224.     case REGISTER_OP:
  225.     case CONNECT_OP:
  226.     case USRLIST_OP: {  // ... ricevere la lista degli utenti
  227.     if (readData(connfd, &msg.data) <= 0) {
  228.         perror("reply data");
  229.         return -1;
  230.     }  
  231.     int nusers = msg.data.hdr.len / (MAX_NAME_LENGTH+1);
  232.     assert(nusers > 0);
  233.     printf("Lista utenti online:\n");
  234.     for(int i=0,p=0;i<nusers; ++i, p+=(MAX_NAME_LENGTH+1)) {
  235.         printf(" %s\n", &msg.data.buf[p]);
  236.     }
  237.     } break;
  238.     case GETPREVMSGS_OP: { // ... ricevere la lista dei vecchi messaggi
  239.     if (readData(connfd, &msg.data) <= 0) {
  240.         perror("reply data");
  241.         return -1;
  242.     }  
  243.     // numero di messaggi che devo ricevere
  244.     size_t nmsgs = *(size_t*)(msg.data.buf);
  245.     char *FILENAMES[nmsgs]; // NOTA: si suppone che nmsgs non sia molto grande
  246.     size_t nfiles=0;
  247.     for(size_t i=0;i<nmsgs;++i) {
  248.         message_t pmsg;
  249.         // leggo l'intero messaggio
  250.         if (readMsg(connfd, &pmsg) == -1) {
  251.         perror("reply data");
  252.         return -1;
  253.         }  
  254.         if (pmsg.hdr.op == FILE_MESSAGE) {
  255.         FILENAMES[nfiles] = strdup(pmsg.data.buf);
  256.         nfiles++;
  257.         printf("[%s vuole inviare il file '%s']\n", pmsg.hdr.sender, pmsg.data.buf);
  258.         } else
  259.         printf("[%s:] %s\n", pmsg.hdr.sender, (char*)pmsg.data.buf);
  260.     }      
  261.  
  262.     // scarico i file che ho ricevuto
  263.     for(size_t i=0;i<nfiles;++i) {
  264.         if (downloadFile(connfd, FILENAMES[i], sname) == -1) {
  265.         fprintf(stderr, "ERRORE: cercando di scaricare il file %s\n", FILENAMES[i]);
  266.         return -1;
  267.         }
  268.         printf("[Il file '%s' e' stato scaricato correttamente]\n",FILENAMES[i]);
  269.     }
  270.     } break;
  271.     case POSTTXT_OP:
  272.     case POSTTXTALL_OP:
  273.     case POSTFILE_OP:
  274.     case DISCONNECT_OP:
  275.     case UNREGISTER_OP:
  276.     case CREATEGROUP_OP:
  277.     case ADDGROUP_OP:
  278.     case DELGROUP_OP: break;  // ... fare nulla
  279.     default: {
  280.     fprintf(stderr, "ERRORE: messaggio non valido\n");
  281.     return -1;
  282.     }
  283.     }
  284.     return 0;  
  285. }
  286.  
  287. // gestisce operazioni di tipo richiesta-risposta
  288. static int execute_receive(int connfd, operation_t *o) {
  289.     char *sname = o->sname;
  290.     size_t m    = (size_t)-1;
  291.    
  292.     if (o->n>0) m = (ssize_t)o->n;
  293.    
  294.     size_t c=0;
  295.     for(size_t i=0; i<msgcur;++i) {
  296.     if (MSGS[i].data.buf != NULL) {
  297.         if (MSGS[i].hdr.op == FILE_MESSAGE) {
  298.         char *filename = MSGS[i].data.buf;
  299.  
  300.         printf("[%s vuole inviare il file '%s']\n", MSGS[i].hdr.sender, filename);
  301.  
  302.         if (downloadFile(connfd, filename, sname) == -1) {
  303.             fprintf(stderr, "ERRORE: cercando di scaricare il file %s\n", filename);
  304.             return -1;
  305.         }
  306.         printf("[Il file '%s' e' stato scaricato correttamente]\n",filename);
  307.         } else
  308.         printf("[%s:] %s\n", MSGS[i].hdr.sender, (char*)MSGS[i].data.buf);
  309.  
  310.         if (++c == m) break;
  311.     }
  312.     }
  313.     for(size_t i=c; i<m; ++i) {
  314.     message_t msg;
  315.     // leggo header e data
  316.     if (readMsg(connfd, &msg) == -1) {
  317.         perror("reply data");
  318.         return -1;
  319.     }  
  320.     switch(msg.hdr.op) {
  321.     case TXT_MESSAGE: {
  322.         printf("[%s:] %s\n", msg.hdr.sender, (char*)msg.data.buf);
  323.     } break;
  324.     case FILE_MESSAGE: {
  325.         char *filename = strdup(msg.data.buf);
  326.         free(msg.data.buf);
  327.         printf("[%s vuole inviare il file '%s']\n", msg.hdr.sender, filename);
  328.  
  329.         if (downloadFile(connfd, filename, sname) == -1) {
  330.         fprintf(stderr, "ERRORE: cercando di scaricare il file %s\n", filename);
  331.         return -1;
  332.         }
  333.         printf("[Il file '%s' e' stato scaricato correttamente]\n",filename);
  334.     } break;
  335.     default: {
  336.         fprintf(stderr, "ERRORE: ricevuto messaggio non valido\n");
  337.         return -1;
  338.     }
  339.     }      
  340.     }
  341.     return 0;
  342. }
  343.  
  344. int main(int argc, char *argv[]) {
  345.     const char optstring[] = "l:k:c:C:g:a:d:t:S:s:R:pLh";
  346.     int optc;
  347.     char *spath = NULL, *nick = NULL;
  348.     operation_t *ops = NULL;
  349.     long msleep=0;
  350.  
  351.     if (argc <= 4) {
  352.     use(argv[0]);
  353.     return -1;
  354.     }
  355.  
  356.     // al piu' ci saranno argc-3 comandi da eseguire
  357.     ops = (operation_t*)malloc(sizeof(operation_t)*(argc-3));
  358.     if (!ops) {
  359.     perror("malloc");
  360.     return -1;
  361.     }
  362.     int k=0, nickneeded=0, coption=0;
  363.     // parse command line options
  364.     while ((optc = getopt(argc, argv,optstring)) != -1) {
  365.     switch (optc) {
  366.         case 'l': spath=optarg;                   break;
  367.     case 't': msleep= strtol(optarg,NULL,10); break;
  368.     case 'k': {
  369.         nick = strdup(optarg);
  370.         if (strlen(nick)>MAX_NAME_LENGTH) {
  371.         fprintf(stderr, "ERRORE: Nickname troppo lungo\n");
  372.         return -1;
  373.         }
  374.         ops[k].sname = nick;
  375.         ops[k].rname = NULL;
  376.         ops[k].op    = CONNECT_OP;
  377.         ops[k].msg   = NULL;
  378.         ops[k].size  = 0;
  379.         ++k;
  380.     } break;
  381.         case 'c': {
  382.         coption++;   // -c puo' comparire solo 1 volta
  383.         ops[k].sname = strdup(optarg);
  384.         ops[k].rname = NULL;
  385.         ops[k].op    = REGISTER_OP;
  386.         ops[k].msg   = NULL;
  387.         ops[k].size  = 0;
  388.         ++k;
  389.     } break;
  390.         case 'C': {
  391.         nickneeded = 1;
  392.         ops[k].sname = nick;
  393.         ops[k].rname = strdup(optarg);
  394.         ops[k].op    = UNREGISTER_OP;
  395.         ops[k].msg   = NULL;
  396.         ops[k].size  = 0;
  397.         ++k;
  398.     } break;
  399.         case 'g': {
  400.         nickneeded = 1;
  401.         ops[k].sname = nick;
  402.         ops[k].rname = strdup(optarg);
  403.         ops[k].op    = CREATEGROUP_OP;
  404.         ops[k].msg   = NULL;
  405.         ops[k].size  = 0;
  406.         ++k;
  407.     } break;
  408.         case 'a': {
  409.         nickneeded = 1;
  410.         ops[k].sname = nick;
  411.         ops[k].rname = strdup(optarg);
  412.         ops[k].op    = ADDGROUP_OP;
  413.         ops[k].msg   = NULL;
  414.         ops[k].size  = 0;
  415.         ++k;
  416.     } break;
  417.         case 'd': {
  418.         nickneeded = 1;
  419.         ops[k].sname = nick;
  420.         ops[k].rname = strdup(optarg);
  421.         ops[k].op    = DELGROUP_OP;
  422.         ops[k].msg   = NULL;
  423.         ops[k].size  = 0;
  424.         ++k;
  425.     } break;
  426.     case 'L': {
  427.         nickneeded = 1;
  428.         ops[k].sname = nick;
  429.         ops[k].rname = NULL;
  430.         ops[k].op    = USRLIST_OP;
  431.         ops[k].msg   = NULL;
  432.         ops[k].size  = 0;
  433.         ++k;
  434.     } break;
  435.     case 'p': {
  436.         nickneeded = 1;
  437.         ops[k].sname = nick;
  438.         ops[k].rname = NULL;
  439.         ops[k].op    = GETPREVMSGS_OP;
  440.         ops[k].msg   = NULL;
  441.         ops[k].size  = 0;
  442.         ++k;
  443.     } break;
  444.     case 'S': {
  445.         nickneeded = 1;
  446.         char *arg = strdup(optarg);
  447.         char *p;
  448.         p = strchr(arg, ':');
  449.         if (!p) {
  450.         use(argv[0]);
  451.         return -1;
  452.         }
  453.         *p++ = '\0';   
  454.  
  455.         if (arg[0] == '\0') {
  456.         fprintf(stderr, "ERRORE: messaggio vuoto\n");
  457.         return -1;
  458.         }
  459.  
  460.         ops[k].sname = nick;    
  461.         ops[k].rname = strlen(p)?p:NULL;
  462.         ops[k].op    = strlen(p)?POSTTXT_OP:POSTTXTALL_OP;
  463.         ops[k].msg   = arg;
  464.         ops[k].size  = strlen(arg)+1;
  465.  
  466.         ++k;
  467.     } break;
  468.     case 's': {
  469.         nickneeded = 1;
  470.         char *arg = strdup(optarg);
  471.         char *p;
  472.         p = strchr(arg, ':');
  473.         if (!p) {
  474.         use(argv[0]);
  475.         return -1;
  476.         }
  477.         *p++ = '\0';
  478.         if (strlen(p)==0) {
  479.         fprintf(stderr, "ERRORE: nell'opzione -s e' necessario definire il destinatario\n");
  480.         use(argv[0]);
  481.         return -1;
  482.         }
  483.         ops[k].sname = nick;
  484.         ops[k].rname = p;
  485.         ops[k].op    = POSTFILE_OP;
  486.        
  487.         // controllo che il file esista
  488.         struct stat st;
  489.         if (stat(arg, &st)==-1) {
  490.         perror("stat");
  491.         fprintf(stderr, "ERRORE: nella stat del file %s\n", arg);
  492.         use(argv[0]);
  493.         return -1;
  494.         }
  495.         if (!S_ISREG(st.st_mode)) {
  496.         fprintf(stderr, "ERRORE: il file %s non e' un file regolare\n", arg);
  497.         use(argv[0]);
  498.         return -1;
  499.         }
  500.         ops[k].msg  = arg;        // nome del file
  501.         ops[k].size = st.st_size; // size del file
  502.         ++k;
  503.     } break;
  504.     case 'R': {
  505.         nickneeded = 1;
  506.         ops[k].op    = OP_END; // operazione interna non invio nessuna richiesta al server
  507.         ops[k].n     = strtol(optarg,NULL,10);
  508.         ops[k].sname = nick;
  509.         ops[k].rname = NULL;
  510.         ops[k].msg   = NULL;
  511.         ops[k].size  = 0;
  512.         ++k;
  513.     } break;
  514.         case 'h':
  515.     default: {
  516.         use(argv[0]);
  517.         return -1;
  518.     }
  519.     }
  520.     }
  521.     // verifichiamo alcune opzioni
  522.     if (spath==NULL) {
  523.     fprintf(stderr, "ERRORE: L'opzione -l deve essere presente\n\n");
  524.     return -1;
  525.     }
  526.     if (nick == NULL && nickneeded) {
  527.     fprintf(stderr, "ERRORE: L'opzione -k non e' stata specificata\n\n");
  528.     return -1;
  529.     }
  530.     // qualora -k non venisse prima delle altre opzioni
  531.     if (nickneeded) {
  532.     for(int i=0;i<k;++i)
  533.         if (ops[i].op != REGISTER_OP) ops[i].sname = nick;
  534.     }
  535.     // vincolo su -c
  536.     if (coption>1) {
  537.     fprintf(stderr, "ERRORE: L'opzione -c puo' comparire una sola volta\n\n");
  538.     return -1;
  539.     }
  540.  
  541.     int connfd;
  542.     // faccio 10 tentativi aspettando 1 secondo tra due tentativi
  543.     if ((connfd=openConnection(spath, 10, 1))<0) {
  544.     fprintf(stderr, "ERRORE: riprovo a riconnettermi...\n");
  545.     return -1;
  546.     }
  547.  
  548.     // ignoro SIGPIPE per evitare di essere terminato da una scrittura su un socket chiuso
  549.     struct sigaction s;
  550.     memset(&s,0,sizeof(s));    
  551.     s.sa_handler=SIG_IGN;
  552.     if ( (sigaction(SIGPIPE,&s,NULL) ) == -1 ) {  
  553.     perror("sigaction");
  554.     return -1;
  555.     }
  556.     struct timespec req = { msleep/1000, (msleep%1000)*1000000L };  
  557.  
  558.     MSGS = malloc(msgbatch*sizeof(message_t));
  559.     if (!MSGS) {
  560.     perror("malloc");
  561.     fprintf(stderr, "ERRORE: Out of memory\n");
  562.     return -1;
  563.     }
  564.     msglen = msgbatch;
  565.  
  566.     int r=0;
  567.     for(int i=0;i<k;++i) {
  568.     if (ops[i].op != OP_END)
  569.         r = execute_requestreply(connfd, &ops[i]);
  570.     else
  571.         r = execute_receive(connfd, &ops[i]);
  572.     if (r == 0)  printf("Operazione %d eseguita con successo!\n", i);
  573.     else break;  // non appena una operazione fallisce esco
  574.    
  575.     // tra una operazione e l'altra devo aspettare msleep millisecondi
  576.     if (msleep>0) nanosleep(&req, (struct timespec *)NULL);
  577.     }
  578.     close(connfd);
  579.     if (ops) free(ops);
  580.     if (MSGS) free(MSGS);
  581.     return r;
  582. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement