#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 /* V této ukázce přidáme systémové volání ‹send›. Myšlenka bude * podobná jako v předchozím (budeme kontrolovat posloupnost čísel), * ale tentokrát se bude jednat o interaktivní protokol. Vynutíme * tím pořadí doručení datagramů a získáme možnost detekovat většinu * anomálií, které v předchozí „jednostranné“ verzi vedly na * uváznutí. * * Změna bude spočívat pouze v tom, že odesílatel sekvence musí před * odesláním dalšího čísla vyčkat na potvrzení přijetí čísla * předchozího. Toto potvrzení bude mít jediný bajt – 1 pro * potvrzení správnosti, 0 pro ohlášení chyby. * * Rozmyslete si, že přesto nadále existují situace, kdy může systém * uváznout (a tyto situace není těžké si představit – nezapomeňte * zejména, že datagram může po odeslání „zmizet beze stopy“ – tzn. * aniž by se o této skutečnosti odesílatel dověděl). * * Podprogram ‹pong_sequence› má opět 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 tentokrát není omezena, protože implementace * je schopna pracovat s konstantním množstvím paměti. * * Návratová hodnota bude: * * • -1 nastane-li systémová chyba, * • -2 nastane-li chyba protokolu (datagram v nesprávném formátu), * • -3 byla-li detekována chyba v přijaté posloupnosti, * • jinak 0. */ int pong_sequence( int sock_fd, uint32_t start, uint32_t end ) { uint32_t buffer[ 2 ] = { 0 }; int result = 0; uint8_t ack; /* Vstupní podmínku opět ověříme tvrzením. */ assert( end >= start ); for ( uint32_t expect = start; expect < end; ++expect ) { /* Č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 a převést * hodnotu do správného formátu. */ if ( recvd == -1 ) result = -1; if ( recvd != 4 ) result = -2; uint32_t value = ntohl( buffer[ 0 ] ); if ( !result && value != expect ) result = -3; /* Určíme hodnotu potvrzovacího datagramu a tento odešleme. * Všimněte si, že o odeslání se pokusíme i v případě, kdy * na příjmu došlo k systémové chybě. Protože doručení * datagramu není zaručeno za žádných okolností, */ ack = result ? 0 : 1; if ( send( sock_fd, &ack, 1, 0 ) == -1 ) return -1; if ( result ) return result; } /* Na toto místo se program může dostat pouze v případě, že byla * úspěšně přijata 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_and_ack( int fd, const void *buffer, int nbytes, uint8_t ok ) { uint8_t ack[ 2 ]; 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 ); int bytes_recvd = recv( fd, &ack, 2, 0 ); if ( bytes_recvd == -1 ) err( 1, "receiving an ack" ); assert( ack[ 0 ] == ok ); } 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_and_ack( fd, "\x00\x00\x00\x00", 4, 1 ); /* case 2 */ send_and_ack( fd, "\x00\x00\x01\x00", 4, 1 ); send_and_ack( fd, "\x00\x00\x01\x01", 4, 1 ); send_and_ack( fd, "\x00\x00\x01\x02", 4, 1 ); /* case 3 */ send_and_ack( fd, "\x00\x10\x00\x01", 4, 1 ); send_and_ack( fd, "\x00\x10\x00\x03", 4, 0 ); /* case 4 */ send_and_ack( fd, "\x00\x10\x00\x01", 4, 1 ); send_and_ack( fd, "\x00\x10\x00\x02", 4, 1 ); send_and_ack( fd, "\x00\x10\x00\x03", 4, 1 ); /* case 5 */ send_and_ack( fd, "\x00\x00\x00\x03", 4, 0 ); /* case 6 */ send_and_ack( fd, "\x00\x00\x00\x00", 4, 0 ); /* case 7 */ send_and_ack( fd, "\x00\x00\x00\x00", 4, 1 ); send_and_ack( fd, "\x00\x00\x00\x00\00", 5, 0 ); /* case 8 */ send_and_ack( fd, "\x00\x00\x00", 3, 0 ); 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( pong_sequence( server_fd, 0, 1 ) == 0 ); assert( pong_sequence( server_fd, 0x0100, 0x0103 ) == 0 ); assert( pong_sequence( server_fd, 0x100001, 0x100004 ) == -3 ); assert( pong_sequence( server_fd, 0x100001, 0x100004 ) == 0 ); assert( pong_sequence( server_fd, 0, 1 ) == -3 ); assert( pong_sequence( server_fd, 1, 2 ) == -3 ); assert( pong_sequence( server_fd, 0, 2 ) == -2 ); assert( pong_sequence( server_fd, 0, 1 ) == -2 ); assert( reap( client_pid ) == 0 ); close_or_warn( server_fd, "server end of a socket" ); return 0; }