Сетевое программирование POSIX

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 11:29

Именно так: "Сетевое программирование POSIX", потому что в Windows сокеты и API сокетов отличаются (не сильно) ... ну, и меня слабо занимает программирование Windows.

Организация сети в Linux и сетевое программирование в POSIX API я описывал здесь: Сетевое программирование в Linux. В основу этого текста (и примеров) лёг курс лекций, который я заказным образом подготовил и прочитал для группы программистов-разработчиков такой международной софтверной компании как GlobalLogic.

Очень рекомендую прочитать этот текст ... в нём, хоть и очень бегло:
- рассматривается сетевая подсистема пользовательского уровня, сокеты BSD для TCP, UDP, SCTP ...
- рассматривается сетевая подсистема Linux в ядре, где нет уже сокетов, а сетевые пакеты IP фигурируют как сокетные буфера...
- и, неожиданно для меня самого, получилась прозрачная система прохождения всего сетевого трафика от пользовательского уровня одного хоста, до пользовательского уровня другого: через сокеты транспортного уровня, до сокетных буферов kernel Linux, и далее до физической среды передачи...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 11:32

Olej писал(а): Организация сети в Linux и сетевое программирование в POSIX API я описывал здесь: Сетевое программирование в Linux.
Но тема возникла не об этом ... потому что то дело давнее, ему 2 года как...

Но ко мне обратились за помощью в совершенно конкретной задаче.
Задача интересная.
И оказалось, что в форуме совершенно нет темы относительно сетевого программирования. :-?
Восполняем...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 11:38

Olej писал(а): Задача интересная.
Это известная задача "Быки и Коровы".
Для неё есть online WEB-версия, в которую можно поиграть :
Изображение

Там же изложены:
Правила игры

Компьютер задумывает четырехзначное число.
Цифры в числе не повторяются, 0 может стоять на первом месте.
Игрок делает ходы, чтобы узнать это число.
В ответ на каждый ход компьютер показывает число отгаданных цифр, стоящих на своих местах (число быков)
и число отгаданных цифр, стоящих не на своих местах (число коров).

Пример:

Компьютер задумал 0834. Игрок походил 8134. Компьютер ответил: 2 быка (цифры 3 и 4) и 1 корова (цифра 8).
(потому что ко мне формулировка задачи попала в куда более внятном, горбатом изложении)

Задача в том, чтобы этот алгоритм реализовать в клиент-серверном варианте, когда к игровому серверу могут подключаться сколь угодно много клиентов, и с каждым из них будет вестись независимая игра.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 12:03

Olej писал(а): Задача в том, чтобы этот алгоритм реализовать в клиент-серверном варианте, когда к игровому серверу могут подключаться сколь угодно много клиентов, и с каждым из них будет вестись независимая игра.
Прежде чем делать сетевой вариант, делаем локальный:

Код: Выделить всё

#include "common.h"
#include "bull_cow.c"

int main( int argc, char *argv[] ) {
   int opt;
   while ( ( opt = getopt( argc, argv, "p:v") ) != -1 )
      switch( opt ) {
         case 'v' :
            debug = true;
            break;
         default :
            return 1;
      }
   char ask[ MAXLINE ] = "\n";
   do printf( "%s", bull_cow( ask ) );
   while( fgets( ask, MAXLINE, stdin ) );
   exit( EXIT_SUCCESS );
}
Сама логика вынесена в отдельную функцию

Код: Выделить всё

char* bull_cow( char* s ) {
   static char numb[ LENGTH + 1 ] = {};
   static bool init = false;
   if( !init ) {
      srand( (unsigned int)time( NULL ) );
      init = true;
   }
   if( strrchr( s, '\n' ) ) *strrchr( s, '\n' ) = '\0';
   if( 0 == strlen( s ) ) {
      for( int i = 0; i < LENGTH; i++ )
         numb[ i ] = ' ';
      for( int i = 0; i < LENGTH; i++ ) {
         char c;
         do c = rand() % 10 + '0';
         while( strchr( numb, c ) );
         numb[ i ] = c;
      }
      if( debug ) printf( "[%05u] число: %s\n", getpid(), numb );
      sprintf( s, "новая игра, вводите число:\n" );
      return s;
   }
   bool ok = strlen( s ) == LENGTH;
   for( int i = 0; ok && i < LENGTH; i++ )
      ok = isdigit( s[ i ] );
   if( !ok ) {
      strcpy( s, "ошибочный ввод\n" );
      return s;
   }
   int bull = 0, // на своих местах
       cow = 0;  // не на своих местах
   for( int i = 0; i < LENGTH; i++ )
      if( strchr( numb, s[ i ] ) != NULL ) cow++;
   for( int i = 0; i < LENGTH; i++ )
      if( s[ i ] == numb[ i ] ) bull++;
   sprintf( s, "%d %d\n", bull, cow - bull );
   return s;
}

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 12:07

Olej писал(а): Сама логика вынесена в отдельную функцию

Код: Выделить всё

[olej@dell 74]$ ./local -v
число: 3675
новая игра, вводите число:
3724
1 1
3725
2 1
3765
2 2
3675
4 0
Или, без подсказки, отладочного режима (-v):

Код: Выделить всё

[olej@dell 74]$ ./local
новая игра, вводите число:
2345
1 1
3567
1 3
3675
4 0

новая игра, вводите число:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 12:12

Olej писал(а): Сама логика вынесена в отдельную функцию
Дальше превратить вариант локальный в сетевой довольно просто...

Сервер:

Код: Выделить всё

[olej@dell 74]$ cat server.c 
#include "common.h"
#include "bull_cow.c"

bool retrans( int sc ) {
   char data[ MAXLINE ];
   int rc = read( sc, data, MAXLINE );
   if( rc > 0 ) {
      if( debug ) printf( "[%05u] %s", getpid(), data );
      bull_cow( data );
      rc = write( sc, data, strlen( data ) + 1 );
      if ( rc < 0 ) perror( "write data failed" );
      return rc > 0;
   }
   if( rc < 0 ) 
      perror( "read data failed" );
   if( rc == 0 )  
      printf( "client close connection\n" ); 
   return false;
}

int main( int argc, char *argv[] ) {
   in_port_t port = FORK_PORT;
   int opt;
   while ( ( opt = getopt( argc, argv, "p:v") ) != -1 )
      switch( opt ) { 
         case 'p' :
            if( atoi( optarg ) > 0 ) port = atoi( optarg );
            break;
         case 'v' :
            debug = true;
            break;
         default :
            return 1;
      }
   int ls, rs;
   struct sockaddr_in addr;
   if( -1 == ( ls = socket( AF_INET, SOCK_STREAM, 0 ) ) ) { 
      printf( "create stream socket failed\n" );
      return 1;
   }
   if( setsockopt( ls, SOL_SOCKET, SO_REUSEADDR, &rs, sizeof( rs ) ) != 0 ) {
      printf( "set socket option failed\n" );
      return 1;
   }
   memset( &addr, 0, sizeof( addr ) );
   addr.sin_family = AF_INET;
   addr.sin_port = htons( port );
   addr.sin_addr.s_addr = htonl( INADDR_ANY );
   if( bind( ls, (struct sockaddr*)&addr, sizeof( struct sockaddr ) ) != 0 ) {
      printf( "bind socket address failed\n" );
      return 1;
   }
   if( listen( ls, 25 ) != 0 ) { 
      printf( "put socket in listen state failed\n" );
      return 1;
   }
   if( debug )
      printf( "[%05u] waiting on port %u\n", getpid(), port );
   while( true ) {
      if( ( rs = accept( ls, NULL, NULL ) ) < 0 ) { 
         printf( "accept error\n" );
         return 1;
      }     
      pid_t pid = fork();
      if( pid < 0 ) { 
         printf( "fork error\n" );
         return 1;         
      } 
      if( pid == 0 ) {           // дочерний серверный процесс
         close( ls );
         while( retrans( rs ) ); // обмен с клиентом
         close( rs );
         exit( EXIT_SUCCESS );
      }
      else close( rs );
   }
   exit( EXIT_SUCCESS );
}
Файл common.h - это всего лишь общие определения для клиента и сервера:

Код: Выделить всё

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>                    // расширение стандарта C99
#include <time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define FORK_PORT 52000
#define MAXLINE   120
#define LENGTH    4

static bool debug = false;
А с клиентом - это и вовсе просто:

Код: Выделить всё

#include "common.h"

int main( int argc, char *argv[] ) {
   char sadr[ MAXLINE ] = "localhost";
   in_port_t port = FORK_PORT;
   int opt;
   while ( ( opt = getopt( argc, argv, "p:v") ) != -1 ) 
      switch( opt ) {
         case 'p' :
            if( atoi( optarg ) > 0 ) port = atoi( optarg );
            break;
         case 'v' :
            debug = true;
            break;
         default :
            return 1;
      }   
   if( optind < argc ) strcpy( sadr, argv[ optind ] );
   printf( "host: %s TCP port = %u\n", sadr, port );
   int rc, ls;
   if( ( ls = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) {
      printf( "create stream socket failed\n" );
      return 1;
   }
   struct sockaddr_in addr;
   memset( &addr, 0, sizeof( addr ) );
   addr.sin_family = AF_INET;
   addr.sin_port = htons( port );
   inet_aton( sadr, &addr.sin_addr );
   if( ( rc = connect( ls, (struct sockaddr*)&addr, sizeof( struct sockaddr ) ) ) < 0 ) {
      printf( "connect failed\n" );
      return 1;
   }
   char ask[ MAXLINE ] = "\n";
   do {
      if( ( rc = write( ls, ask, strlen( ask ) + 1 ) ) <= 0 ) { 
         perror( "write data failed" ); 
         break; 
      }
      rc = read( ls, ask, MAXLINE );
      if( rc < 0 ) { 
         perror( "read data failed" );
         break;
      }
      if( rc == 0 ) { 
         printf( "server closed connection\n" );
         break;
      }
      printf( "%s", ask );
   } while( fgets( ask, MAXLINE, stdin ) );
   close( ls );
   exit( EXIT_SUCCESS );
}

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 12:18

Olej писал(а): Дальше превратить вариант локальный в сетевой довольно просто...
Поехали...
2 независимых клиента вперемешку работают с сервером:

Код: Выделить всё

[olej@dell 74]$ ./server -p32500 -v
[09336] waiting on port 32500
[09376]
[09376] число: 0951
[09376] 1234
[09386]
[09386] число: 8354
[09386] 1234
[09376] 1590
[09386] 3456
[09386] 3458
[09376] 0915
[09376] 0951
[09376]
[09376] число: 0748
[09376] 4780
[09376] 0748
[09376]
[09376] число: 2049
[09386] 8345
[09386] 8354
[09386]
[09386] число: 8915
^C

Код: Выделить всё

[olej@dell 74]$ ./client -p 32500 192.168.1.106
host: 192.168.1.106 TCP port = 32500
новая игра, вводите число:
1234
0 1
1590
0 4
0915
2 2
0951
4 0

новая игра, вводите число:
4780
1 3
0748
4 0

новая игра, вводите число:

server closed connection

Код: Выделить всё

[olej@dell 74]$ ./client -p 32500 192.168.122.1 -v
host: 192.168.122.1 TCP port = 32500
новая игра, вводите число:
1234
1 1
3456
1 2
3458
1 3
8345
2 2
8354
4 0

новая игра, вводите число:

server closed connection
2 IP адреса 192.168.1.106 и 192.168.122.1 - это 2 IP одного и того же хоста, но с разных сетевых интерфейсов.

Код: Выделить всё

[olej@dell own_LSD2_11]$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 5c:26:0a:03:73:e9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.107/24 brd 192.168.1.255 scope global dynamic eno1
       valid_lft 162768sec preferred_lft 162768sec
    inet6 fe80::5e26:aff:fe03:73e9/64 scope link 
       valid_lft forever preferred_lft forever
3: wlp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 58:94:6b:19:ef:28 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.106/24 brd 192.168.1.255 scope global dynamic wlp3s0
       valid_lft 162770sec preferred_lft 162770sec
    inet6 fe80::5a94:6bff:fe19:ef28/64 scope link 
       valid_lft forever preferred_lft forever
4: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 52:54:00:22:16:10 brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
       valid_lft forever preferred_lft forever
5: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc fq_codel master virbr0 state DOWN group default qlen 1000
    link/ether 52:54:00:22:16:10 brd ff:ff:ff:ff:ff:ff
Вложения
74.tgz
(39.37 КБ) 123 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Сетевое программирование POSIX

Непрочитанное сообщение Olej » 21 дек 2016, 12:28

Olej писал(а): Поехали...
Туда же, в архив, вложен текст моей давней статьи "Много серверов хороших и разных", на основе которой сделана основная часть ... чтобы не объясняться.
В статье продемонстрировано 7 (как минимум) схем, по которым можно строить сервера TCP, ... и подробно об этом было здесь в другой теме, см.
много серверов хороших и разных.

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 6 гостей