Библиотека сайта rus-linux.net
Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |
Клиент
Код клиента оказывается самым объёмным примером в нашем рассмотрении, ну, хотя бы просто потому, что серверов у нас много, а клиент — один, и все тонкие детали, какие возможно, отнесены из кодов серверов в код клиента. Сам код клиента (см. прилагаемый архив) размещён в файле cli.cpp, но он, совместно с сервером, использует общие файлы common.h и common.cc, эти файлы с краткими комментариями приведены ниже:
Файл common.h – общие определения:
#define __COMMON_H #include <iostream&qt; #include <iomanip&qt; using namespace std; #include <stdlib.h&qt; #include <stdio.h&qt; #include <errno.h&qt; #include <string.h&qt; #include <netdb.h&qt; #include "libdiag.h" #define EOK 0 const int PORT = 51000, // 9000 сменил на 51000, 9000 в Linux заняты SINGLE_PORT = PORT, FORK_PORT = PORT + 1, FORK_LARGE_PORT = PORT + 2, PREFORK_PORT = PORT + 3, XINETD_PORT = PORT + 4, // 51004 THREAD_PORT = PORT + 5, THREAD_POOL_PORT = PORT + 6, PRETHREAD_PORT = PORT + 7, QUEUE_PORT = PORT + 8; const int MAXLINE = 40; // критическая ошибка ... void errx( const char *msg, int err = EOK ); // ретранслятор тестовых пакетов TCP void retrans( int sc ); // создание и подготовка прослушивающего сокета int getsocket( in_port_t ); extern int debug; // уровень отладочного вывода сервера // параметры строки запуска сервера void setv( int argc, char *argv[] ); #endif
- здесь существенным, на что следует обратить внимание, является только определение тех номеров портов TCP (различных!), которые будут использовать альтернативные реализации сервера.
Файл common.cc — реализация тех рутинных процедур, которые описаны в common.h:
#include "common.h" // диагностика ошибки ... void errx( const char *msg, int err ) { perror( msg ); if( err != EOK ) errno = err; exit( EXIT_FAILURE ); }; // ретранслятор тестовых пакетов TCP static char data[ MAXLINE ]; void retrans( int sc ) { int rc = read( sc, data, MAXLINE ); if( rc > 0 ) { rc = write( sc, data, strlen( data ) + 1 ); if ( rc < 0 ) perror( "write data failed" ); } else if( rc < 0 ) { perror( "read data failed" ); return; } else if( rc == 0 ) { cout < "client closed connection" < endl; return; }; return; }; // создание и подготовка прослушивающего сокета static struct sockaddr_in addr; int getsocket( in_port_t p ) { int rc = 1, ls; if( -1 == ( ls = socket( AF_INET, SOCK_STREAM, 0 ) ) ) errx( "create stream socket failed" ); if( setsockopt( ls, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof( rc ) ) != 0 ) errx( "set socket option failed" ); memset( &addr, 0, sizeof( addr ) ); addr.sin_family = AF_INET; addr.sin_port = htons( p ); addr.sin_addr.s_addr = htonl( INADDR_ANY ); if( bind( ls, (struct sockaddr*)&addr, sizeof( sockaddr ) ) != 0 ) errx( "bind socket address failed" ); if( listen( ls, 25 ) != 0 ) errx( "put socket in listen state failed" ); cout < "waiting on port " < p < " ..." < endl; return ls; }; // уровень отладочного вывода сервера int debug = 0; void setv( int argc, char *argv[] ) { debug = ( argc > 1 && 0 == strcmp( argv[ 1 ], "-v" ) ) ? 1 : 0; if( debug ) cout < "verbose mode" < endl; }
Теперь мы можем рассмотреть и код используемого клиента:
#include "common.h" #include <arpa/inet.h> // установка параметров клиентов: адрес, порт и число повторений static void setkey( int argc, char *argv[], char *adr, in_port_t* port, int* num ) { int opt, val; while ( ( opt = getopt( argc, argv, "a:p:n:") ) != -1 ) { switch( opt ) { case 'a' : strcpy( adr, optarg ); break; case 'p' : if( sscanf( optarg, "%i", &val ) != 1 ) errx( "parse command line failed", EINVAL ); *port = (in_port_t)val; break; case 'n' : if( ( sscanf( optarg, "%i", &val ) != 1 ) || ( val <= 0 ) ) errx( "parse command line failed", EINVAL ); *num = val; break; default : errx( "parse command line failed", EINVAL ); break; } }; }; // клиент - источник потока тестовых пакетов TCP int main( int argc, char *argv[] ) { in_port_t listen_port = SINGLE_PORT; int num = 10; char data[ MAXLINE ], echo[ MAXLINE ], sadr[ MAXLINE ] = "localhost"; setkey( argc, argv, &sadr[ 0 ], &listen_port, &num ); cout << "wait ..." << flush; uint64_t cps = proc_hz(); // cycles per sec. cout << '\r' << "host: " << sadr << ", TCP port = " << listen_port << ", number of echoes = " << num << endl << "time of reply - Cycles [usec.] :" << endl; for( int i = 0; i < num; i++ ) { int rc, ls; if( ( ls = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) errx( "create stream socket failed" ); struct sockaddr_in addr; memset( &addr, 0, sizeof( addr ) ); addr.sin_family = AF_INET; addr.sin_port = htons( listen_port ); inet_aton( sadr, &addr.sin_addr ); if( ( rc = connect( ls, (struct sockaddr*)&addr, sizeof( sockaddr ) ) ) < 0 ) errx( "connect failed" ); sprintf( data, "%d", rand() ); uint64_t cycle = rdtsc(); if( ( rc = write( ls, data, strlen( data ) + 1 ) ) <= 0 ) { perror( "write data failed" ); break; }; rc = read( ls, echo, MAXLINE ); cycle = rdtsc() - cycle; if( rc < 0 ) { perror( "read data failed" ); break; }; if( rc == 0 ) { cout << "server closed connection" << endl; break; }; if( strcmp( data, echo ) != 0 ) { cout << "wrong data" << endl; break; }; cout << cycle << "[" << cycle * 1000000 / cps << "]"; // sec. / 10E6 if( i % 5 == 4 ) cout << endl; else cout << '\t'; cout << flush; close( ls ); delay( 100 ); // пауза 100 usec. }; if( num % 5 != 0 ) cout << endl; exit( EXIT_SUCCESS ); };
С клиентом, из текста кода, всё должно быть относительно понятно: после запуска клиент анализирует ключи запуска. Предусмотрены значения: -a — адрес сервера (по умолчанию это localhost), -p — значение TCP порта подключения (по умолчанию это 51000, что соответствует простому последовательному серверу) и -n — число запросов к серверу в серии (по умолчанию 10). Каждый запрос представляет собой случайное число, генерируемое клиентом, в символьной форме. Ретранслированный сервером ответ сверяется с запросом для дополнительного контроля.
По каждому TCP запросу клиент выводит число машинных тактов, ушедших на ожидание ответа от сервера, а в скобках – для справки – время в микросекундах, соответствующее этому числу тактов. Отметим здесь же, что и число тактов, и абсолютное значение задержки, в этих экспериментах носят очень условные характер: клиент фиксирует число тактов своего процессора (у меня это >3Ghz), в то время, как значительная часть задержки обеспечивается процессором, на котором работает сервер (у меня это 1.66Ghz).
Далее мы рассмотрим работу такого клиента с различными серверами.
Назад | Вперед |