#define _POSIX_C_SOURCE 200809L #include /* assert */ #include /* errno */ #include /* fork, close */ #include /* send, recv */ #include /* bool */ #include /* ntohl */ #include /* uint8_t, uint32_t */ #include /* Budeme pokračovat v práci se systémovým voláním ‹recv› na * datagramovém socketu. Podprogram ‹check_sequence› ověří, že na * socket dorazí sekvence čtyřbajtových datagramů, které tvoří * souvislou posloupnost celých čísel (bez znaménka). * * Má tyto tři parametry: * * • ‹sock_fd› je popisovač socketu, na který jsou doručovány * datagramy, * • ‹start› je nejnižší číslo v posloupnosti, které očekáváme, * • ‹end› je první číslo, které již do posloupnosti nepatří (tzn. * je těsně za jejím koncem – jinak řečeno, ‹start› a ‹end› * popisují polouzavřený interval). * * Velikost intervalu nepřekročí 2¹⁵ (toto budeme považovat za * vstupní podmínku). * * Návratová hodnota bude: * * • -1 nastane-li systémová chyba, * • -2 nastane-li chyba protokolu (datagram v nesprávném formátu), * • -3 objeví-li se číslo mimo zadanou posloupnost nebo je nějaké * číslo zdvojeno, * • jinak 0. * * Je důležité si uvědomit, že v situaci, kdy dorazí méně paketů než * je čísel v zadané posloupnosti (a přitom všechny splňují uvedené * požadavky), podprogram ‹check_sequence› «neskončí» a dojde * k uváznutí. V realistických programech proto musí vždy existovat * mechanismus schopný takovou situaci detekovat a vypořádat se * s ní. * * Podobně nejsme schopni detekovat situaci, kdy nějaký datagram * mimo posloupnost (nebo ve špatném formátu) dorazí poté, co byla * kompletní posloupnost úspěšně sestavena. */ int check_sequence( int sock_fd, uint32_t start, uint32_t end ); /* Protože datagramy mohou dorazit v libovolném pořadí, musíme si * o každém prvku posloupnosti pamatovat zda dorazil. K tomu se nám * bude hodit procedura ‹set_bit›, která nastaví příslušný bit * (určený hodnotou ‹offset›) v bitovém poli. Návratová hodnota * indikuje, zda byl tento bit původně nastaven nebo nikoliv. */ bool set_bit( uint8_t *bits, int offset ) { const uint8_t mask = 1u << ( offset % 8 ); uint8_t *byte = bits + offset / 8; bool was_set = ( *byte & mask ) == mask; *byte |= mask; return was_set; } /* Nyní už můžeme přistoupit k implementaci samotného podprogramu * ‹check_sequence›. */ int check_sequence( int sock_fd, uint32_t start, uint32_t end ) { /* Nejprve si připravíme potřebné bitové pole (a zároveň ho * vynulujeme). Vstupní podmínku ověříme tvrzením. */ enum { max_span = 32768 }; uint8_t bits[ max_span / 8 ] = { 0 }; assert( end >= start ); assert( end - start <= max_span ); uint32_t buffer[ 2 ]; /* Podobně jako v předchozí ukázce budeme datagramy přijímat * v cyklu. Tentokrát ale máme zadaný maximální počet datagramů, * které lze přijmout. */ for ( uint32_t i = 0; i < end - start; ++i ) { /* Čekáme na datagram velikosti 4. Větší nebo menší datagram * považujeme za chybu protokolu. */ int recvd = recv( sock_fd, buffer, 5, 0 ); /* Samozřejmě nezapomeneme ošetřit možné chyby. */ if ( recvd == -1 ) return -1; if ( recvd != 4 ) return -2; /* Nyní již víme, že datagram byl v správném formátu. * V paměti ‹buffer› je ale hodnota samozřejmě uložená ve * stejném pořadí, v jakém dorazila na socket (tzn. používá * network byte order). Než budeme s touto hodnotou jakkoliv * pracovat, musíme ji převést do nativního pořadí (‹ntohl› * → «network to host long»). */ uint32_t value = ntohl( buffer[ 0 ] ); if ( value < start || value >= end ) return -3; if ( set_bit( bits, value - start ) ) return -3; } /* Na toto místo se program může dostat pouze v případě, že byla * úspěšně přijatá celá očekávaná posloupnost. */ return 0; } /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include #include static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static void send_or_die( int fd, const void *buffer, int nbytes ) { int bytes_sent = send( fd, buffer, nbytes, 0 ); if ( bytes_sent == -1 ) err( 1, "sending %d bytes", nbytes ); if ( bytes_sent != nbytes ) errx( 1, "unexpected short send: %d/%d sent", bytes_sent, nbytes ); } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "wait" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } static void sender( int fd ) { /* case 1 */ send_or_die( fd, "\x00\x00\x00\x00", 4 ); /* case 2 */ send_or_die( fd, "\x00\x00\x01\x00", 4 ); send_or_die( fd, "\x00\x00\x01\x01", 4 ); send_or_die( fd, "\x00\x00\x01\x02", 4 ); /* case 3 */ send_or_die( fd, "\x00\x10\x00\x01", 4 ); send_or_die( fd, "\x00\x10\x00\x03", 4 ); send_or_die( fd, "\x00\x10\x00\x02", 4 ); /* case 4 */ send_or_die( fd, "\x00\x00\x00\x03", 4 ); /* case 5 */ send_or_die( fd, "\x00\x00\x00\x00", 4 ); send_or_die( fd, "\x00\x00\x00\x00", 4 ); /* case 6 */ send_or_die( fd, "\x00\x00\x00\x00", 4 ); send_or_die( fd, "\x00\x00\x00\x00\00", 5 ); /* case 7 */ send_or_die( fd, "\x00\x00\x00", 3 ); close( fd ); } int main() { int fds[ 2 ]; if ( socketpair( AF_UNIX, SOCK_DGRAM, 0, fds ) == -1 ) err( 1, "socketpair" ); int server_fd = fds[ 0 ], client_fd = fds[ 1 ]; int client_pid = fork(); if ( client_pid == -1 ) err( 1, "fork" ); if ( client_pid == 0 ) { close_or_warn( server_fd, "server end of a socket" ); sender( client_fd ); return 0; } close_or_warn( client_fd, "client end of a socket" ); assert( check_sequence( server_fd, 0, 1 ) == 0 ); assert( check_sequence( server_fd, 0x0100, 0x0103 ) == 0 ); assert( check_sequence( server_fd, 0x100001, 0x100004 ) == 0 ); assert( check_sequence( server_fd, 0, 1 ) == -3 ); assert( check_sequence( server_fd, 0, 2 ) == -3 ); assert( check_sequence( server_fd, 0, 2 ) == -2 ); assert( check_sequence( server_fd, 0, 1 ) == -2 ); assert( reap( client_pid ) == 0 ); close_or_warn( server_fd, "server end of a socket" ); return 0; }