#define _POSIX_C_SOURCE 200809L #include /* assert */ #include /* err */ #include /* errno */ #include /* uint32_t, int64_t */ #include /* exit */ #include /* strlen, memcmp */ #include /* snprintf */ #include /* read, write, close, unlink, … */ #include /* socket, AF_* */ #include /* struct sockaddr_un */ #include /* atomic_uint */ #include /* pthread_* */ /* V tomto cvičení bude Vaším úkolem naprogramovat „kostru“ pro * vícevláknový server v podprogramu ‹thread_server›. Vstupem budou: * * 1. ‹sock_fd› je popisovač proudového socketu, který je svázán * s adresou a je nastaven do režimu poslouchání, * 2. ‹count› je maximální počet připojení (po jeho dosažení se * podprogram vrátí), * 3. ‹per_client› je funkční ukazatel, který realizuje komunikaci * s jedním klientem, * 4. ‹shared› je ukazatel, který je předán podprogramu * ‹per_client› při každé aktivaci. * * Podprogram ‹per_client› má dva parametry: popisovač socketu a * ukazatel ‹shared›. Návratová hodnota podprogramu ‹per_client› je * 0 v případě, že komunikace proběhla úspěšně, jinak -1. * Vlastnictví popisovače ‹fd› se na podprogram ‹per_client› * nepřenáší. * * Podobně u ‹thread_server› návratová hodnota 0 znamená, že bylo * úspěšně obslouženo ‹count› klientů, -1 znamená chybu. */ int thread_server( int sock_fd, int count, int ( *per_client )( int fd, void * shared ), void *shared ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* waitpid */ #include /* signal, SIG_IGN, SIGPIPE */ static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static void unlink_if_exists( const char *file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlink" ); } 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; } int per_client( int fd, void *shared ) { atomic_uint *state = shared; int read; char buffer[ 4 ]; char reply[ 4 ] = "ok\n"; while ( ( read = recv( fd, buffer, 4, MSG_WAITALL ) ) > 0 ) { if ( memcmp( buffer, "inc\n", 4 ) == 0 ) *state += 1; else if ( memcmp( buffer, "dec\n", 4 ) == 0 ) *state -= 1; else if ( memcmp( buffer, "get\n", 4 ) == 0 ) snprintf( reply, 4, "%02d\n", *state % 64 ); else return -1; if ( send( fd, reply, 3, 0 ) == -1 ) return -1; } return read == 0 ? 0 : -1; } static pid_t fork_server( int sock_fd, int clients, int init ) { pid_t pid = fork(); if ( pid == -1 ) err( 2, "fork" ); if ( pid == 0 ) { atomic_uint state = init; alarm( 3 ); int rv = thread_server( sock_fd, clients, per_client, &state ); close( sock_fd ); exit( rv ? 1 : 0 ); } close_or_warn( sock_fd, "server socket in client" ); return pid; } static const struct sockaddr_un test_addr = { .sun_family = AF_UNIX, .sun_path = "zt.a_socket" }; static int client_connect() { int fd = socket( AF_UNIX, SOCK_STREAM, 0 ); if ( fd == -1 ) err( 2, "socket" ); if ( connect( fd, ( const struct sockaddr * ) &test_addr, sizeof test_addr ) == -1 ) return -1; return fd; } static int client_cmd( int fd, const char *cmd, const char *expect ) { int r_len = strlen( expect ); char reply[ r_len ]; if ( send( fd, cmd, strlen( cmd ), 0 ) == -1 ) return -1; if ( recv( fd, reply, r_len, MSG_WAITALL ) != r_len ) return -1; return 0; } int main( void ) { if ( signal( SIGPIPE, SIG_IGN ) == SIG_ERR ) err( 2, "signal" ); char buffer[ 4 ]; unlink_if_exists( test_addr.sun_path ); int sock_fd = socket( AF_UNIX, SOCK_STREAM, 0 ); if ( sock_fd == -1 ) err( 2, "socket" ); if ( bind( sock_fd, ( const struct sockaddr * ) &test_addr, sizeof test_addr ) == -1 ) err( 2, "bind" ); if ( listen( sock_fd, 3 ) == -1 ) err( 2, "listen" ); pid_t pid = fork_server( sock_fd, 3, 15 ); int c1 = client_connect(); int c2 = client_connect(); assert( client_cmd( c1, "get\n", "15\n" ) == 0 ); assert( client_cmd( c1, "inc\n", "ok\n" ) == 0 ); assert( client_cmd( c2, "get\n", "16\n" ) == 0 ); int c3 = client_connect(); assert( send( c3, "g", 1, 0 ) == 1 ); assert( client_cmd( c2, "dec\n", "ok\n" ) == 0 ); assert( client_cmd( c1, "inc\n", "ok\n" ) == 0 ); assert( send( c3, "e", 1, 0 ) == 1 ); assert( client_cmd( c2, "inc\n", "ok\n" ) == 0 ); assert( send( c3, "t", 1, 0 ) == 1 ); assert( client_cmd( c2, "inc\n", "ok\n" ) == 0 ); assert( client_cmd( c1, "dec\n", "ok\n" ) == 0 ); close_or_warn( c1, "c1" ); assert( send( c3, "\n", 1, 0 ) == 1 ); assert( recv( c3, buffer, 3, MSG_WAITALL ) == 3 ); assert( memcmp( buffer, "17", 2 ) == 0 ); close_or_warn( c2, "c2" ); close_or_warn( c3, "c3" ); assert( reap( pid ) == 0 ); unlink_if_exists( test_addr.sun_path ); return 0; }