Forskjell mellom versjoner av «FAQ Nettverksprogrammering»

Fra mn/ifi/INF1060
Hopp til: navigasjon, søk
 
Linje 14: Linje 14:
 
  setsockopt(your_socket, SOL_SOCKET, SO_REUSEADDR, &activate, sizeof(int));
 
  setsockopt(your_socket, SOL_SOCKET, SO_REUSEADDR, &activate, sizeof(int));
  
 +
= Hvordan skriver og leser jeg fra nettverket? =
 +
Såfremt du har en gyldig socket til disposisjon kan du bruke funksjonene read og write. (man 2 read, man 2 write).
  
= Kan jeg anta at read og write leser og skriver det jeg håper de vil? =
+
= Kan jeg anta at read og write leser og skriver så mange byte jeg håper de vil? =
  
 
Det er ingen garanti for hvor mange byte read og write leser og skriver. read vil aldri lese mer enn det som oppgis i count, men den kan godt lese mindre. Det samme gjelder write; den vil aldri skrive mer enn count, men den kan skrive mindre. Det kan være ulike grunner til at de gjør nettopp det, og det er ingen god idé å anta at de ikke gjør det. Når du kaller write to ganger, er det ingen måte å skille mellom dataene fra disse to kallene på andre siden. To kall på write (på samme socket) rett etter hverandre som skriver 10 byte hver, vil sannsynligvis leses som 20 byte på andre siden. Det er dette applikasjonslagsprotokollen skal løse; hvordan skille mellom ulike meldinger.
 
Det er ingen garanti for hvor mange byte read og write leser og skriver. read vil aldri lese mer enn det som oppgis i count, men den kan godt lese mindre. Det samme gjelder write; den vil aldri skrive mer enn count, men den kan skrive mindre. Det kan være ulike grunner til at de gjør nettopp det, og det er ingen god idé å anta at de ikke gjør det. Når du kaller write to ganger, er det ingen måte å skille mellom dataene fra disse to kallene på andre siden. To kall på write (på samme socket) rett etter hverandre som skriver 10 byte hver, vil sannsynligvis leses som 20 byte på andre siden. Det er dette applikasjonslagsprotokollen skal løse; hvordan skille mellom ulike meldinger.

Nåværende revisjon fra 13. nov. 2010 kl. 05:10

Hvordan bruker jeg Berkeley Sockets?

Det står en meget bra artikkel på wikipedia.

cannot bind to socket: address already in use?

Dette kan skje hvis du avslutter server/klient og starter opp igjen med en gang. Når du kjører bind vil porten være bundet til en socket, og vil da ikke være mulig å binde den samme porten på nytt. Når du avslutter programmet vil socketen havne i TIME_WAIT-tilstand, og porten vil ikke kunne brukes igjen før denne timeouten er ferdig.

Ved å bruke flagget SO_REUSEADDR vil det likevel være mulig å bruke en port som er bundet til en socket i TIME_WAIT-tilstand.

Legg til følgende i koden på egnet sted (før bind-kallet):

int activate = 1;
setsockopt(your_socket, SOL_SOCKET, SO_REUSEADDR, &activate, sizeof(int));

Hvordan skriver og leser jeg fra nettverket?

Såfremt du har en gyldig socket til disposisjon kan du bruke funksjonene read og write. (man 2 read, man 2 write).

Kan jeg anta at read og write leser og skriver så mange byte jeg håper de vil?

Det er ingen garanti for hvor mange byte read og write leser og skriver. read vil aldri lese mer enn det som oppgis i count, men den kan godt lese mindre. Det samme gjelder write; den vil aldri skrive mer enn count, men den kan skrive mindre. Det kan være ulike grunner til at de gjør nettopp det, og det er ingen god idé å anta at de ikke gjør det. Når du kaller write to ganger, er det ingen måte å skille mellom dataene fra disse to kallene på andre siden. To kall på write (på samme socket) rett etter hverandre som skriver 10 byte hver, vil sannsynligvis leses som 20 byte på andre siden. Det er dette applikasjonslagsprotokollen skal løse; hvordan skille mellom ulike meldinger.


select

Link som forklarer det mer praktiske, dog kortfattet: http://www.labbook.cs.purdue.edu/cpointers.php

select sjekker hvorvidt en mengde fildeskriptorer er klare for lesing/skriving, eller har "exceptional condition pending", altså noe uvanlig, altså feil (vanligvis).

select kan da brukes til å lytte etter nye tilkoblinger på lytte-socketen, samt de andre socketene som brukes til å kommunisere med klientene.

Så kan man spørre, hvorfor ikke bare sjekke det manuelt ved å prøve å lese?

Vel, på vanlig vis så blokker fildeskriptorer (En fildeskriptor er en FILE*, eller en socket, eller noe annet liknende. sockets i socket.h oppfører seg som vanlige filer på mange måter). Blokking betyr at du blir ventende på input, og programmet fortsetter ikke før noe input har dukket opp. Dette vil du ikke ha, da din server skal kunne behandle flere klienter samtidig, og kan dermed ikke vente på en bruker uten å behandle andre som kanskje er klare.

Man kan unngå dette med O_NONBLOCK, som gjør at en fildeskriptor ikke blokker, men returnerer en kode som sier "jeg er tom". Dette vil imidlertid bety at du sjekker om og om igjen, til noe kommer - noe som spiser prosessortid. Resultat: Treg, samt uelegant server.

Det er her select kommer inn: Gitt x antall pipes, finn ut om noen har data i dem, uten å spise opp prosessoren. select blokker hvis INGEN av dem har data, og ingen har dødd - da venter vi til noen får data.

Ellers returnerer select en kode som sier noe om dette - og ett av argumentene til select holder på informasjon om hvilke pipes som har data.

Så kan du behandle de aktuelle pipene, og kalle select nok en gang for å vente på neste "runde". (se linken)

fd_set

Dette er et set med alle fildeskriptorene du ønsker å lytte på:

fd_set readfds;

Denne bruker så select for å vite hvilke fildeskriptorer den skal sjekke.

For å endre dette settet brukes ulike makroer. (Makroer kan ses på som enkle funksjoner).

  • FD_SET(int fd, fd_set *set); - Legger fd til i set.
  • FD_CLR(int fd, fd_set *set); - Fjerner fd fra set.
  • FD_ISSET(int fd, fd_set *set); - Returnerer true hvis fd er satt i set.
  • FD_ZERO(fd_set *set); - Fjerner alle deskriptorer fra set.

select-funksjonen

select tar flere parametre, ikke alle er like viktige for oppgaven. Signaturen ser ut som følger:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • Returverdi: Antall endrede fildeskriptorer. 0 Hvis timeout. -1 ved error.
  • arg1: int nfds: høysteste fildeskriptor + 1. Hvis du f.eks. lytter på 0, 3, 5 og 8, skal denne verdien være 9.
  • arg2: fd_set *readfds: Settet som definerer hvilke fildeskriptorer som skal sjekkes for lesing.
  • arg3: fd_set *writefds: Settet som definerer hvilke fildeskriptorer som skal sjekkes for skriving.
  • arg4: fd_set *exceptfds Settet som definerer hvilke fildeskriptorer som skal sjekkes for exceptions.
  • arg5: struct timeval *timeout: Definerer timout for select-funksjonen.

Returverdi:

Hvis man angir en timeout, vil select returnere enten når det er aktivitet på en fildeskriptor, eller når timeouten går ut. Ved timeout vil den returnere 0. Hvis man ikke angir timout vil select blokkere helt noe skjer på en av fildeskriptorene. Hvis noe skjer på flere av fildeskriptorene samtidig, vil retuverdien til select angi antallet fildeskriptorer med aktivitet. Hvis en feil oppstår vil select returnere -1.

fd_set readfds;

Denne definerer hvilke fildeskriptorer som skal sjekkes for lesing. Hvis man ønsker å lytte på tastaturet gjør du følgende:

fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(STDIN, &read_fds); // STDIN == 0
int ret = select(STDIN+1, &read_fds, NULL, NULL, NULL);

// Here you can read from keyboard with fgets.
....

Merk at select endrer read_fds ved at den markerer fildeskriptorene der det ligger data. Det betyr at denne må nullstilles manuelt for hver gang select kalles. Hvis dette ikke gjøres vil FD_ISSET returnere true på fildeskriptorer som tidligere ble markert av select, og ikke bare etter sist kall.

fd_set *writefds

Ikke nødvendig å bruke i hjemmeeksamen 2. NULL brukes for å "deaktivere" denne.

fd_set *exceptfds

Ikke nødvendig å bruke i hjemmeeksamen 2. NULL brukes for å "deaktivere" denne.

struct timeval *timeout

Ikke nødvendig å bruke i hjemmeeksamen 2, men brukes slik:

 struct timeval tv;
 tv.tv_sec = 1; // One second
 tv.tv_usec = 200000; // 200000 microseconds == 200 ms
 select(STDIN+1, &readfds, NULL, NULL, &tv);

For å deaktivere timeout, bruk NULL.

Pseudokode for håndtering av tilkoblinger i SMS-serveren:

fd_set readfds, tmp_fds;
//Set up fd_set with listener socket.

while(1) {

 // Remember to reset the fd-set with fresh values before calling select.
 int ret = select(...);

 if (ret == -1) {
 // Handle error
 }

 for (i = 0; ...) {

   FD_ISSET(i, &tmp_fds) {

     if (i == listen_sock) {
     // Handle new connection
     }
     else {
     // Handle network data on socket i
     }
   }
 }
}