Библиотека сайта 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).
Далее мы рассмотрим работу такого клиента с различными серверами.
| Назад | Вперед |
