Наши партнеры

UnixForum





Библиотека сайта 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).

Далее мы рассмотрим работу такого клиента с различными серверами.


Назад Вперед