Эффективное программирование TCP-IP

       

Каркас TCP-сервера


Начнем с каркаса TCP-сервера. Затем можно приступить к созданию библиотеки, поместив в нее фрагменты кода из каркаса. В листинге 2.2 показана функция main.

Листинг 2.2. Функция main из каркаса tcpserver.skel

1    #include <stdio.h>

2    #include <stdlib.h>

3    #include <unistd.h>

4    #include <stdarg.h>

5    #include <string.h>

6    #include <errno.h>

7    #include <netdb.h>

8    #include <fcntl.h>

9    #include <sys/time.h>

10   #include <sys/socket.h>

11   #include <netinet/in.h>



12   #include <arpa/inet.h>

13   #include "skel.h"

14   char *program_name;

15   int main( int argc, char **argv )

17   struct sockaddr_in local;

18   struct sockaddr_in peer;

19   char *hname;

20   char *sname;

21   int peerlen;

22   SOCKET s1;

23   SOCKET s;

24   const int on = 1;

25   INIT ();

26   if ( argc == 2 )

27   {

28     hname = NULL;

29     sname = argv[ 1 ];

30   }

31   else

32   {

33     hname = argv[ 1 ];

34     sname = argv[ 2 ];

35   }

36   set_address( hname, sname, &local, "tcp" );

37   s = socket( AF_INET, SOCK_STREAM, 0 );

38   if ( !isvalidsock( s ) )

39     error ( 1, errno, "ошибка вызова socket" );

40   if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &on,

41     sizeof( on ) ) )

42     error( 1, errno, "ошибка вызова setsockopt" );

43   if ( bind( s, ( struct sockaddr * ) klocal,

44     sizeof( local ) ) )

45     error( 1, errno, "ошибка вызова bind" );

46   if ( listen ( s, NLISTEN ) )

47     error( 1, errno, "ошибка вызова listen" );

48   do

49   {

50     peerlen = sizeof( peer );

51     s1 = accept( s, ( struct sockaddr * )&peer, &peerlen );

52     if ( !isvalidsock( s1 ) )

53      error( 1, errno,  "ошибка вызова accept" );

54     server( s1, &peer );

55     CLOSE( s1 );

56   } while ( 1 );

57   EXIT( 0 );

58   }

Включаемые файлы и глобальные переменные


1- 14 Включаем заголовочные файлы, содержащие объявления используе­мых стандартных функций.

25 Макрос INIT выполняет стандартную инициализацию, в частности, установку глобальной переменной program_name для функции error и вызов функции WSAStartup при работе на платформе Windows.

Функция main

26-35 Предполагается, что при вызове сервера ему будут переданы адрес и номер порта или только номер порта. Если адрес не указан, то привязываем к сокету псевдоадрес INADDR_ANY, разрешающий прием соединений по любому сетевому интерфейсу. В настоящем приложении в командной строке могут, конечно, быть и другие аргументы, обрабатывать их надо именно в этом месте.

36 Функция set_address записывает в поля переменной local типа sockaddr_in указанные адрес и номер порта. Функция set_address показана в листинге 2.3.

37-45 Получаем сокет, устанавливаем в нем опцию SO_REUSEADDR (совет 23) и привязываем к нему хранящиеся в переменной local адрес и номер порта.

46-47 Вызываем listen, чтобы сообщить ядру о готовности принимать соединения от клиентов.

48-56Принимаем соединения и для каждого из них вызываем функцию server. Она может самостоятельно обслужить соединение или создать Для этого новый процесс. В любом случае после возврата из функции server соединение закрывается. Странная, на первый взгляд конструкция do-while позволяет легко изменить код сервера так, чтоб завершался после обслуживания первого соединения. Для этого достаточно вместо

while ( 1 );

написать

while ( 0 );

Далее обратимся к функции set__address. Она будет использована во всех каркасах. Это естественная кандидатура на помещение в библиотеку стандартных функций.

Листинг 2.3. Функция set_address

tcpserver.skel

1    static void set_address(char *hname, char *sname,

2      struct sockaddr_in *sap, char *protocol)

3    {

4    struct servant *sp;

5    struct hostent *hp;

6     char *endptr;

7    short port;

8    bzero (sap, sizeof(*sap));

9    sap->sin_family = AF_INET;

10   if (hname != NULL)

11   {



12     if (!inet_aton (hname, &sap->sin_addr))

13     {

14      hp = gethostbyname(hname);

15      if ( hp == NULL )

16       error( 1, 0, "неизвестный хост: %s\n", hname );

17      sap->sin_addr = *( struct in_addr * )hp->h_addr;

18     }

19   }

20   else

21     sap->sin_addr.s_addr = htonl( INADDR_ANY );

22   port = strtol( sname, &endptr, 0 );

23   if ( *endptr == '\0' )

24     sap->sin_port = htons( port );

25   else

26   {

27     sp = getservbyname( sname, protocol );

28     if ( sp == NULL )

29      error( 1, 0, "неизвестный сервис: %s\n", sname );

30     sap->sin_port = sp->s_port;

31   }

32   }

set_address

8- 9 Обнулив структуру sockaddr_in, записываем в поле адресного семейства AF_INET.

10-19 Если hname не NULL, то предполагаем, что это числовой адрес в стандартной десятичной нотации. Преобразовываем его с помощью функции inet_aton, если inet_aton возвращает код ошибки, - пытаемся преобразовать hname в адрес с помощью gethostbyname. Если и это не получается, то печатаем диагностическое сообщение и завершаем программу.

20-21 Если вызывающая программа не указала ни имени, ни адреса хоста, устанавливаем адрес INADDR_ANY.

22-24 Преобразовываем sname в целое число. Если это удалось, то записываем номер порта в сетевом порядке (совет 28).

27-30 В противном случае предполагаем, что это символическое название ервиса и вызываем getservbyname для получения соответствующего номера порта. Если сервис неизвестен, печатаем диагностическое сообщение и завершаем программу. Заметьте, что getservbyname уже возвращает номер порта в сетевом порядке.

Поскольку иногда приходится вызывать функцию set_address напрямую, лесь приводится ее прототип:

#include "etcp.h"

void set_address(char *host, char *port,

 struct sockaddr_in *sap, char *protocol);

Последняя функция - error - показана в листинге 2.4. Это стандартная диагностическая процедура.

#include "etcp.h"

void error(int status, int err, char *format,...);



Если status не равно 0, то error завершает программу после печати диагностического сообщения; в противном случае она возвращает управление. Если err не равно 0, то считается, что это значение системной переменной errno. При этом в конце сообщения дописывается соответствующая этому значению строка и числовое значение кода ошибки.

Далее в примерах постоянно используется функция error, поэтому добавим в библиотеку.

Листинг2.4. Функция error

tcpserver.skel

1    void error( int status, int err, char *fmt,  ... )

2    {

3    va_list ap;

4    va_start ( ар, fmt );

5    fprintf (stderr, "%s: ", program_name );

6    vfprintf( stderr, fmt, ap ) ;

7    va_end( ap ) ;

8    if ( err )

9      fprintf( stderr, ": %s (%d)\n", strerror( err ), err);

10   if ( status )

11     EXIT( status );

12   }

В каркас включена также заглушка для функции server:

static void server(SOCKET s, struct sockaddr_in *peerp)

{

}

Каркас можно превратить в простое приложение, добавив код внутрь этой за­глушки. Например, если скопировать файл tcpserver.skel в и заме­нить заглушку кодом

static void server(SOCKET s, struct sockaddr_in *peerp)

{

 send( s, "hello, world\n", 13, 0);

}

то получим сетевую версию известной программы на языке С. Если откомпилировать и запустить эту программу, а затем подсоединиться к ней с помощью программы telnet, то получится вполне ожидаемый результат:

bsd: $ hello 9000

[1] 1163

bsd: $ telnet localhost 9000

Trying 127 .0.0.1...

Connected to localhost

Escape character '^]'.

hello, world

Connection closed by foreign host.

Поскольку каркас tcpserver. skel описывает типичную для TCP-сервера ситуацию, поместим большую часть кода main в библиотечную функцию tcp_serv показанную в листинге 2.5. Ее прототип выглядит следующим образом:

#include "etcp.h"

SOCKET tcp_server( char *host, char *port );

Возвращаемое значение: сокет в режиме прослушивания (в случае ошибки завершает программу).

Параметр host указывает на строку, которая содержит либо имя, либо IP – адрес хоста, а параметр port - на строку с символическим именем сервиса или номером порта, записанным в виде ASCII-строки.



Далее будем пользоваться функцией tcp_server, если не возникнет необхомо модифицировать каркас кода.

Листинг 2.5. Функция tcp_server

1    SОСКЕТ tcp_server( char *hname, char *sname )

2    {

3    struct sockaddr_in local;

4    SOCKET s;

5    const int on = 1;

6    set_address( hname, sname, &local, "tcp" );

7    s = socket( AF_INET, SOCK_STREAM, 0 );

8    if ( !isvalidsock( s ) )

9      error( 1, errno, "ошибка вызова socket" );

10   if ( setsockopt ( s, SOL_SOCKET, SO_REUSEADDR,

11     ( char * )&on, sizeoff on ) ) )

12     error( 1, errno, "ошибка вызова setsockopt" );

13   if ( bind( s, ( struct sockaddr * } &local,

14     sizeof( local ) ) )

15     error( 1, errno, "ошибка вызова bind" );

16   if ( listen( s, NLISTEN ) )

17     error( 1, errno, "ошибка вызова listen" );

18   return s;

19   }


Содержание раздела