Enuntul problemei:
Sa se scrie o pereche de programe client/server care comunica prin socketuri TCP. Clientul va trimite serverului o fraza, iar serverul ii raspunde cu numarul de caractere spatiu din sirul primit.
Protocolul:
- Clientul trimite serverului sirul de caractere continut de acesta impreuna cu caracterul NULL (0, ‘\0’) ce marcheaza sfarsitul sirului. Important!!! In lipsa unui delimitator la sfarsitul sirului, serverul nu are de unde sa stie unde se termina sirul trimis de client.
- La primirea caracterului NULL serverul considera sirul trimis de client terminat.
- Daca serverul nu primeste caracterul NULL timp de 10 de secunde (sau exista o pauza de maxim 10 de secunde de la caracterul trimis anterior de client) serverul inchide conexiunea clientului incercand sa ii trimita acestuia (este posibil ca clientul sa se fi blocat sau sa fi pierdut conexiunea cu clientul) in prealabil intregul -1 ca si cod de eroare. Protocolul specifica faptul ca serverul trebuie sa implementeze un astfel de mecanism de time-out, insa nu protocolul nu trebuie sa specifice modul in care trebuie implementat acest mecanism.
- Serverul poate contoriza caracterele spatiu la primirea fiecarui caracter sau la primirea intregului sir. Din punct de vedere al protocolului acest lucru nu e important
- Serverul returneaza un intreg N (cu semn) in format binar, reprezentat pe 4 octeti cu urmatoarea semnificatie:
- N >= 0 : reprezinta de spatii din sirul primit;
- N = -1 : eroare la primirea sirului de caractere de la client, clientul nu respecta protocolul;
- N = -2 : daca numarul de caractere spatiu contorizate de catre server nu se poate reprezenta pe 4 octeti cu semn (e mai mare ca 231 – 1 (pe patru octeti – 32 de biti – cu semn pot reprezenta numere intregi in intervalul -231 .. +231-1).
Implementare a protocolului si rezolvarea problemei:
client.c
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 |
#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <stdint.h> #define max 100 int main() { int c, cod; int32_t r; // Observatie: Deoarece dimensiunea tipului int difera de la platforma la platforma, // (spre exemplu, in Borland C in DOS e reprezentat pe 2 octeti, iar in C sub Linux pe // 4 octeti) este necesara utilizarea unor tipuri intregi standard. A se vedea // stdint.h. struct sockaddr_in server; char s[max]; c = socket(PF_INET, SOCK_STREAM, 0); if (c < 0) { fprintf(stderr, "Eroare la creare socket client.\n"); return 1; } memset(&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(4321); server.sin_addr.s_addr = inet_addr("127.0.0.1"); cod = connect(c, (struct sockaddr *) &server, sizeof(struct sockaddr_in)); if (cod < 0) { fprintf(stderr, "Eroare la conectarea la server.\n"); return 1; } printf("Dati o fraza pentru trimis la server: "); fgets(s, max, stdin); // !!! important - trimit lungimea sirului + 1 pentru a trimite pe socket si caracterul NULL (0) care marcheaza sfarsitului sirului. // paragraful 1 din protocol cod = send(c, s, strlen(s) + 1, 0); if (cod != strlen(s) + 1) { fprintf(stderr, "Eroare la trimiterea datelor la server.\n"); return 1; } // paragraful 5 din protocol cod = recv(c, &r, sizeof(int32_t), MSG_WAITALL); r = ntohl(r); if (cod != sizeof(int)) { fprintf(stderr, "Eroare la primirea datelor de la client.\n"); return 1; } printf("Serverul a returnat %d caractere spatiu in sirul trimis.\n", r); close(c); } |
server.c
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <stdint.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> int c; // Mecanismul de time-out. Paragraful 3. void time_out(int semnal) { int32_t r = -1; r = htonl(r); printf("Time out.\n"); send(c, &r, sizeof(int32_t), 0); close(c); // desi nu am primit nimic de la client in 10 secunde, inchidem civilizat conexiunea cu acesta exit(1); } void tratare() { int cod; int32_t r; uint8_t b; // Observatie: Deoarece dimensiunea tipului int difera de la platforma la platforma, // (spre exemplu, in Borland C in DOS e reprezentat pe 2 octeti, iar in C sub Linux pe // 4 octeti) este necesara utilizarea unor tipuri intregi standard. A se vedea // stdint.h. struct sockaddr_in server; if (c < 0) { fprintf(stderr, "Eroare la stabilirea conexiunii cu clientul.\n"); exit(1); } else printf("Un nou client s-a conectat cu succes.\n"); signal(SIGALRM, time_out); alarm(10); r = 0; // rezultatul, numarul de caractere spatii primite de la client do { cod = recv(c, &b, 1, 0); printf("Am primit %d caractere.\n", cod); if (cod == 1) // citire cu succes a unui caracter alarm(10); // resetam timerul si asteptam din nou 10 secunde urmatorul caracter if (cod != 1) { r = -1; break; } if (b == ' ') { // Paragraful 5 din protocolul if (r == INT32_MAX) { // intregul maxim pozitiv pe 4 octetii cu semn sau 0x7FFFFFFF (a se vedea stdint.h) r = -2; break; } r++; } } while (b != 0); // sirul de caractere de la client se considera terminat la intalnirea caracterului 0 (NULL, '\0') // Paragraful 2 - terminam citirea sirului de la client la primirea caracterului NULL alarm(0); // oprim timerul r = htonl(r); send(c, &r, sizeof(int32_t), 0); r = ntohl(r); close(c); if (r >= 0) printf("Am inchis cu succes conexiunea cu un client. I-am trimis %d spatii.\n", r); else { printf("Am inchis cu eroare conexiunea cu un client. Cod de eroare %d.\n", r); exit(1); } exit(0); // Terminam procesul fiu - foarte important!!! altfel numarul de procese creste exponential. // Fiul se termina dupa ce deserveste clientul. } int main() { int s, l, cod; struct sockaddr_in client, server; s = socket(PF_INET, SOCK_STREAM, 0); if (s < 0) { fprintf(stderr, "Eroare la creare socket server.\n"); return 1; } memset(&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(4321); server.sin_addr.s_addr = INADDR_ANY; cod = bind(s, (struct sockaddr *) &server, sizeof(struct sockaddr_in)); if (cod < 0) { fprintf(stderr, "Eroare la bind. Portul este deja folosit.\n"); return 1; } listen(s, 5); while (1) { // deserveste oricati clienti memset(&client, 0, sizeof(client)); l = sizeof(client); printf("Astept sa se conecteze un client.\n"); c = accept(s, (struct sockaddr *) &client, &l); printf("S-a conectat clientul cu adresa %s si portul %d.\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); if (fork() == 0) { // server concurent, conexiunea va fi tratata de catre un proces fiu separat tratare(); } // parintele continua bucla while asteptand un nou client } } |