"универсальный" TCP сервер на малине и ....(самодельный на Си)

Тема в разделе "Raspberry Pi", создана пользователем Igor68, 4 ноя 2016.

  1. Igor68

    Igor68 Гуру

    Собственно прошу одобрения на подробное описание собственного опыта реализации TCP сервера на C(C++) для Raspberry... ну или других Linux машин, где есть установленный GCC. Сервер можно "адаптировать" для своих целей.
    Рассматриваться будет:

    Со стороны Raspberry:
    1 - Код на Си (запуск с параметрами в функции Main, прослушивание входящих соединений, пооцесс(поток) обслуживания подключенных соединений(клиентов), собственно поток работы рабочего алгоритма - реализация назначения.
    2 - запуск/перезапуск/остановка сервера через WEB и т.п.
    Со стороны PC:
    2 - Код на Visual Studio реализации DLL-ки взаимодействия программы EXE с нашим сервером,
    3 - Пример проекта в Visual Studio взаимодействия с сервером через DLL.

    Собственно вопрос:
    Разрешено ли мне в этой теме описать вышеупомянутое с прилагаемыми файловыми документами - или это ФЛУД. Т.к. я на длительном больничном, а мыслей по доводке своего зверька до ума нет.

    Спасибо!
     
  2. rkit

    rkit Гуру

    Такие вопросы - флуд, на мой взгляд.
     
  3. Igor68

    Igor68 Гуру

    Значить следует не описывать и удалить тему... или начать выкладывать с описанием?
     
  4. ИгорьК

    ИгорьК Гуру

    Пишите. Любой опыт полезен.
     
  5. Igor68

    Igor68 Гуру

    1 - "делаем" Makefile:

    Код (Bash):
    ###########################
    # Simple Generic Makefile #
    ###########################

    CC=gcc
    CFLAGS = -c -Wall
    LDFLAGS = -lpthread

    SOURCES=$(shell ls *.c)

    OBJECTS=$(SOURCES:.c=.o)
    EXECUTABLE=WorkServer

    all: $(SOURCES) $(EXECUTABLE)

    $(EXECUTABLE): $(OBJECTS)
        $(CC) $(LDFLAGS) $(OBJECTS) -o $@

    .c.o:
        $(CC) $(CFLAGS) $(LIBS) $< -o $@

    install:
        install -m 0755 $(EXECUTABLE) $(HOME)/local/bin

    clean:
        rm -rf *o $(EXECUTABLE)
     
    Это файл "правила компиляции" - взят из шаблона, но с использованием документа:
    make.pdf
    Можно конечно и просто применяя команду gcc с параметрами, что не так удобно.
    значение:
    LDFLAGS = -lpthread
    Указывает о том, что мы будем применять потоки в нашей программе и будет загружена соответствующая библиотека.
    значение:
    EXECUTABLE=WorkServer
    определит название файла нашей программы после компиляции.

    В нашем случае все файлы с расширениями ".c" и ".h" находятся(должны) в одной директории с нашим Maketfile.

    Удобнее для себя подготовить свой Makefile(шаблон)... ну почти на все случаи жизни.
    Удобно!

    Будучи в командной строке и находясь в директории нашего проекта
    для компиляции команда make
    для удаления результатов компиляции make clean
     

    Вложения:

    • make.pdf
      Размер файла:
      846,5 КБ
      Просмотров:
      1.694
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  6. Igor68

    Igor68 Гуру

    Всё шаг за шагом - но рабочий архив будет с конце. Делаю долго - потому как комментирую в файлах и по возможности делаю их наглядными.

    2 - делаем основной код, где рассматривается запуск нашей программы на Си с параметрами. Файл сервера пока не подключаем
    Заголовочный файл для запускаемого кода:
    Код (C++):
    //tema_main.h


    #ifndef _tema_main_h_ //если компилятор этим не пользовался
    #define _tema_main_h_ //говорим что уже пользовался и определяем следующее

    //наша подсказка для параметра --help
    #define __help    "\
    WorkServer <parameters> :\n\
    \n\
        --help          - current info\n\
    \n\
        -p              - tcp port for listen\n\
    \r\n"



    extern int    ipport;    //наш порт прослушивания - объявляем для всех

    #endif
    Ну и сам файл с функцией main:
    Код (C++):
    //tema_main.c

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include "tema_main.h" //подключаем наш заголовочный файл

    int    ipport;    //наш порт прослушивания

    int main(int argc, char **argv)
    {
        int scnt = 1; //счётчик для анализа входных параметров
        int res = 1;
        //без параметров
        if(argc < 2)
        {
            printf("\nParametrs not defined!\r\n");
            printf("\"--help\" for information\n");
            return 1;
        }
        //
        while(scnt < argc)
        {
            //подсказка
            if(strcmp(argv[scnt], "--help") == 0)
            {
                printf(__help);
                return 0;
            }
            //номер открываемого порта TCP
            else if(strcmp(argv[scnt], "-p") == 0)
            {
                 scnt++;
                 if(!(scnt < argc))
                       return 2;
                  ipport = atoi(argv[scnt]);
                  if(ipport == 0)
                       return 3;
            }
            //вводимый параметр не опознан
            else
                return 4;
            scnt++;
        }
        //открытие прослушивающего сокета
        /*
        ПОКА КОММЕНТИРУЕМ ДЛЯ ОТРАБОТКИ НАШЕГО MAIN
        res = SocketStart();
        */

        printf("---- opening tcp/ip  %u ----\r\n", ipport);
        if(res != 0)
        {
            printf("ERROR starting server\n");
            return 5;
        }
        //наш цикл Main
        while(1)
        {
            //вставить вместо этого комментария какую-нибудь обработку - функцию
            usleep(50000); //ждём 50 милисекунд
        }
        //
        return 0;
    }
    Собственно у нас получилась программа, способная запускаться с параметрами
     
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  7. Igor68

    Igor68 Гуру

    3 - компилируем и запускаем
    компилируем:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ make
    gcc -c -Wall  main_tema.c -o main_tema.o
    gcc -lpthread main_tema.o -o WorkServer
    igor@debian-i:/home/ext_projects/tema$
     
    запускаем без параметров:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer

    Parametrs not defined!
    "--help" for information
    igor@debian-i:/home/ext_projects/tema$
    Тут предлагается --help

    запуск с параметром "--help"
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer --help
    WorkServer <parameters> :

        --help          - current info

        -p              - tcp port for listen

    igor@debian-i:/home/ext_projects/tema$
    запуск с номером порта прослушивания (сервер пока отсутствует):
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
    ERROR starting server
    igor@debian-i:/home/ext_projects/tema$
     
    Мы проверили ввод номера порта - в ряде случаем номер порта указывать при запуске, а не "железно" привязывать к коду. Кроме этих параметров удобно вводить и свои - смотрите код.
     
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  8. Igor68

    Igor68 Гуру

    Несколько слов про Makefile:
    Критичен к форматированию текста - пробел, табуляция и т.п. Потому в архиве выкладываю свой:

    Makefile.zip
     

    Вложения:

    • Makefile.zip
      Размер файла:
      405 байт
      Просмотров:
      453
    ИгорьК нравится это.
  9. Igor68

    Igor68 Гуру

    4 - приступаем собственно к сокетам. Простите за задержку... но пока подготовлю читабельный текст кода предлагаю документ - pdf копию документа html:
    sockets.pdf
     

    Вложения:

    • sockets.pdf
      Размер файла:
      252 КБ
      Просмотров:
      4.507
    ИгорьК нравится это.
  10. Igor68

    Igor68 Гуру

    4.1 - открываем сокет:
    добавляем заголовочный файл сокетов в проект
    Код (C++):
    //tema_socket.h


    #ifndef _tema_socket_h_
    #define _tema_socket_h_


    #define _max_sock    10    //максимальное количество входящих соединений

    extern int    SocketStart(void);    //объявляем для всех

    #endif
     
    и файл кода
    Код (C++):
    //tema_socket.c

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/time.h>
    #include "tema_socket.h"    //подключаем наш заголовочный файл для работы с сокетами
    #include "tema_main.h"        //подключаем заголовочный файл нашего MAIN

    int    listener;
    struct  sockaddr_in     addr;

    /* ПОКА КОМЕНТИРУЕМ
    pthread_mutex_t         ServerMutex;
    pthread_t     hListenProcess;
    U32           TServerMutex;
    SERV        MRTUServer;
    */


    int    SocketStart(void)
    {
           listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(listener < 0)
                    return -1;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(ipport);
            addr.sin_addr.s_addr = htonl(INADDR_ANY);
            memset(&addr.sin_zero, 0, 8);
            if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0)
                    return -2;
            if(listen(listener, _max_sock) < 0)
                    return -3;
        /* ПОКА КОМЕНТИРУЕМ
            //создаём мютекс
            TServerMutex = _ServerTimeMutex;
            if(!(pthread_mutex_init(&ServerMutex,NULL) == 0))
                    return -4;
            //запуск потока прослушивания
            MRTUServer.work = 1;
            pthread_create(&hListenProcess,NULL,(void*) ListenProcess,NULL);
        */

            sleep(1);       //wait one secoond
            //
            return 0;
    }
    также добавляем заголовочные файлы в наш MAIN и раскоментируем
    Код (C++):
    //tema_main.c

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include "tema_main.h"         //подключаем наш заголовочный файл
    #include "tema_socket.h"    //подключаем заголовочный файл для работы с сокетами

    int    ipport;    //наш порт прослушивания

    int main(int argc, char **argv)
    {
    .........
    .........
        //открытие прослушивающего сокета - запуск сервера
        res = SocketStart();
        printf("---- opening tcp/ip  %u ----\r\n", ipport);
    .........
    .........
    далее убираем результат прежней компиляции make clean и вновь компилируем make
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ make
    gcc -c -Wall  main_tema.c -o main_tema.o
    gcc -c -Wall  tema_socket.c -o tema_socket.o
    gcc -lpthread main_tema.o tema_socket.o -o WorkServer
    igor@debian-i:/home/ext_projects/tema$
     
    запускаем - в программе бесконечный цикл и после проверки выйдем по CNTRL+c
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
     
    в другой консоли проверяем наш сокет....
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ netstat -nlta
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:2120            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:2121            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:19150           0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:57006           0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN
    [B]tcp        0      0 0.0.0.0:1234            0.0.0.0:*               LISTEN [/B]
    tcp        0      0 192.168.8.10:53         0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:21              0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
    tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN
    tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:445             0.0.0.0:*               LISTEN
    tcp6       0      0 :::139                  :::*                    LISTEN
    tcp6       0      0 :::19150                :::*                    LISTEN
    tcp6       0      0 :::111                  :::*                    LISTEN
    tcp6       0      0 ::1:4304                :::*                    LISTEN
    tcp6       0      0 :::80                   :::*                    LISTEN
    tcp6       0      0 fe80::f4d9:71ff:fe8f:53 :::*                    LISTEN
    tcp6       0      0 :::22                   :::*                    LISTEN
    tcp6       0      0 ::1:631                 :::*                    LISTEN
    tcp6       0      0 ::1:5432                :::*                    LISTEN
    tcp6       0      0 :::445                  :::*                    LISTEN
    tcp6       0      0 :::53824                :::*                    LISTEN
    tcp6       1      0 ::1:54420               ::1:4304                CLOSE_WAIT
    tcp6       1      0 ::1:54421               ::1:4304                CLOSE_WAIT
    igor@debian-i:/home/ext_projects/tema$
    то, что жирным наш открытый сокет.... но процесс прослушивания и соответственно работа с входящими соединениями пока отсутствуют.

    Следует обратить внимание, что если номера портов заняты(уже открыты) и/или зарезервированы системой - будут ошибки открытия сокета, а прогамма(сервер) завершится с кодом 5 и соответственно с сообщением:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 21
    ---- opening tcp/ip  21 ----
    ERROR starting server
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 321
    ---- opening tcp/ip  321 ----
    ERROR starting server
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1000
    ---- opening tcp/ip  1000 ----
    ERROR starting server
     
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  11. Igor68

    Igor68 Гуру

    4.2 - как договорились - наш сервер многопоточный, т.е. ряд функций (прослушивание сокета, приём соединений, обслуживание соединений, обработка данных, наш цикл MAIN, может и еще что - работа с внешними устройствами например) будут работать "параллельно".
    Пока модифицируется код для придания вида предлагаю документ:

    pthread.pdf

    выкладываю последний шаг проекта (04.11.2016 18:12):
    tema.zip
    следует откомпилировать (make). Можно как на малине с Raspbian, так и на ПК с Debian, ну или с другим UNIX/LINUX. На том же устройстве следует и запускать.
     

    Вложения:

    • pthread.pdf
      Размер файла:
      317,1 КБ
      Просмотров:
      1.138
    • tema.zip
      Размер файла:
      6,1 КБ
      Просмотров:
      552
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  12. Securbond

    Securbond Гуру

    Смахивает на библиотеку Ethernet для ардуино. )
     
    ИгорьК нравится это.
  13. Igor68

    Igor68 Гуру

    точнее ардуино смахивает на это.
     
  14. Igor68

    Igor68 Гуру

    4.3 - запускаем потоки прослушивания и работы с соединениями. Следует отметить, что с клиентом мы пока не работаем. Не следим пока и за данными в рабочей структуре сервера. Всего навсего индицируем подключения.

    последний шаг проекта (04.11.2016 22:12):
    tema.zip
    после компиляции запускаем:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
     

    Для проверки используем утилиту nmap сканирования портов в другой консоли.
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ nmap localhost

    Starting Nmap 6.00 ( http://nmap.org ) at 2016-11-04 21:50 MSK
    Nmap scan report for localhost (127.0.0.1)
    Host is up (0.00047s latency).
    Other addresses for localhost (not scanned): 127.0.0.1
    Not shown: 989 closed ports
    PORT     STATE SERVICE
    21/tcp   open  ftp
    22/tcp   open  ssh
    80/tcp   open  http
    111/tcp  open  rpcbind
    139/tcp  open  netbios-ssn
    445/tcp  open  microsoft-ds
    631/tcp  open  ipp
    1234/tcp open  hotline
    2121/tcp open  ccproxy-ftp
    3306/tcp open  mysql
    5432/tcp open  postgresql

    Nmap done: 1 IP address (1 host up) scanned in 0.07 seconds
    igor@debian-i:/home/ext_projects/tema$
    Полезная утилита. Для её установки (из под root)
    apt-get install nmap
    Можно воспользоваться и Zenmap (GUI) - есть и под Windows.

    в свою очередь отчёт нашей программы
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
    -----CLIENT-----
    -- sock. 4 --
    ---- client connected ---
    -----CLIENT-----
    -- sock. 5 --
    ---- client connected ---
     
    утилита nmap localhost, запущена была 2 раза - наши (пока пустые) потоки клиентов тоже были запущены

    Более большие части кода в виде текста вставляться не будут. Будет только архив проекта, с максимально возможными комментариями. В проект добавлен types.h для удобства.
     

    Вложения:

    • tema.zip
      Размер файла:
      5,4 КБ
      Просмотров:
      541
    Последнее редактирование: 4 ноя 2016
    ИгорьК нравится это.
  15. Igor68

    Igor68 Гуру

    4.3.1 - определяемся с протоколом обмена. Без этого никак! Основное требование - минимальная задержка в процессе приёма и передачи, достаточность команд и правил и т.п.
    Обмен удобно производить "блоками" фиксированного обмена.
    Все правила взяты из уже готового сервера работы через ETHERNET с удалёнными устройствами через Linux машину по Modbus RTU (RS 485).
    1 - фиксированный размер - первые два байта (число типа uint_16) в начале блока. Это позволит фиксировано производить приём и передачу без лишних ожиданий;
    2 - тип запроса - для чтения мы просто забираем данные, готовые устройством. для записи передаём устройству для обработки. Ну или то и другое.
    3 - дополнительный байт команды - это для рабочего модуля(алгоритма) нашего сервера - существует, но пока резерв.
    4 - собственно данные.

    Следует отметить, что идентичные правила обмена далее мы будем использовать и со стороны ПК в проекте WisualStudio (Windows) - реализации клиента.

    Изменим файл tema_socket.h добавив:
    Код (C++):
    //описание формата обмена
    #define _sizeL_tcp_pkt        0x0000        //размер пакета TCP (L)
    #define _sizeH_tcp_pkt        0x0001        //размер пакета TCP (H)
    #define _type_tcp_pkt        0x0002        //тип пакета (запроса-ответа)
    #define _cmd_tcp_pkt        0x0003        //команда обращения
    #define _data_pkt        0x0004        //начало данных
    //значения - маски типа пакета
    #define _type_tcp_pkt_mask_rd   _bit_0     //данные передаются из модуля обработки клиенту
    #define _type_tcp_pkt_mask_wr   _bit_1     //данные принимаются в модуль обработки от клиента
     
    Данные мы просто передаём/принимаем как блок. Структуры применять в нашем случае не будем, хоть и можно и может удобно. Но... в ряде случаев с разными типами данных в структурах требуют выравнивания данных. Мы же будем работать не только с Linux с GCC, но и в Windows с другим компилятором. Тем не менее в дальнейшем вы можете по своему определить служебные данные в пакете... ну например для реализации программатора по I2C, SPI, Serial и т.д. на основе малины с доступом по сети.

    Добавление в файл tema_socket.c
    Функция приёма блока:
    Код (C++):
    inline int ReadSocket(int nSocket, BYTE *pBuffer)
    {
            //int     res                     = 1;
            int         iRC                     = 0;
            int         iReceiveStatus          = 0;
            int         iStillToReceive         = 2;    //начальный размер получения минимальных данных
            int         iCount     = 0;
        int        sz;
            struct timeval     ReceiveTimeout;
            fd_set         fds;
        U8        flag            = 0;    //флаг получения размера дляприёма
        //
            while(iStillToReceive > 0)
            {
                    //установка величины таймаута
                    FD_ZERO(&fds);
                    FD_SET(nSocket, &fds);
                    ReceiveTimeout.tv_sec  = 0;
                    ReceiveTimeout.tv_usec = _rSOCKET_TIMEOUT_;
                    iRC = select(nSocket + 1, &fds, NULL, NULL, &ReceiveTimeout);
                    //выход по таймауту
                    if(!iRC)
                return -1;
            //произошла какая то ошибка
                    if(iRC < 0)
                            return -2;
            //ошибка указания размера данных
                    if(iStillToReceive <= 0)
                            return -3;
                    //прием нескольких байт
            iReceiveStatus = recv(nSocket, (char*)(pBuffer), iStillToReceive, 0);
                    if(iReceiveStatus >  0)
                    {
                pBuffer += iReceiveStatus;
                            iStillToReceive -= iReceiveStatus;
                            iCount += iReceiveStatus;
                if((iCount >= 2) && (flag == 0))
                {
                    sz        = (*(U16*)(pBuffer - iCount));
                    iStillToReceive = sz - iCount;
                    flag = 0xFF;
                }
                    }
            else if(iReceiveStatus == 0)
                return -1;
            else
                return -2;
            }
        //дополнительная проверка размера
        if(sz != iCount)
            return -3;
            //
            return iCount;
    }
     
    Функция передачи - отправки клиенту
    Код (C++):
    inline int WriteSocket(int nSocket, BYTE *pBuffer, U16 iMessageLength)
    {
         int         iCount        = 0;
            int         iSC         = 0;
            int         iSendStatus     = 0;
            struct timeval     SendTimeout;
            fd_set         fds;
        //fcntl(nSocket, F_SETFL, O_NONBLOCK);
            while(iMessageLength > 0)
            {
            //установка величины таймаута
                SendTimeout.tv_sec  = 0;
                SendTimeout.tv_usec = _wSOCKET_TIMEOUT_;
                FD_ZERO(&fds);
                FD_SET(nSocket, &fds);
                    iSC = select(nSocket + 1, NULL, &fds, NULL, &SendTimeout);
                    if(!iSC)
                            return -1; //таймаут
                    if(iSC < 0)
                            return -2; //ошибка
            //отправить несколько байт
            iSendStatus = send(nSocket, (char*)(pBuffer), iMessageLength, 0);
                    if(iSendStatus > 0)
                    {
                            //обновить буфер и счетчик
                            iMessageLength -= iSendStatus;
                            pBuffer += iSendStatus;
                            iCount += iSendStatus;
                    }
            else if(iSendStatus == 0)
                return -1;
            else
                return -2;
            }
        return iCount;
    }
     
    В функции приёма анализируется размер для приёма - первые два байта. И соответственно возвращаемые значения.
     
    Последнее редактирование: 5 ноя 2016
    ИгорьК нравится это.
  16. AlexU

    AlexU Гуру

    В Linux уже есть универсальный сервер -- xinetd.
    В чём преимущество Вашей разработки?
     
  17. Igor68

    Igor68 Гуру

    Покажите как его использовать для реализации... ну к примеру управления ComMotion driver for 4 motors или Multiservo по I2C. И будет ли код управления ими частью сервера... ну допустим единой программой. Вообще-то я всего-то хотел просто показать ребятам... чтобы они не наступали на те грабли, на которые я сам наступал. Это проект шаг за шагом. Надеюсь кто-то пробует что-то сделать самостоятельно. Тут только пример... рабочий пример...
     
    ИгорьК нравится это.
  18. Igor68

    Igor68 Гуру

    4.3.2 - Добавляем простую обработку. И изменяем процесс клиента.

    Далее мы изменяем код в main_tema.c добавив тестовую функцию, которая вызывается из нацего цикла Main:
    Код (C++):
    //тестовый модуль
    void    MainWork(void)
    {
        pthread_mutex_lock(&ServerMutex);
        if(test < 999.999)
            test += 11.1;
        else
            test = 0.0;
        (*(float*)(&wbufto[0])) = test;
        pthread_mutex_unlock(&ServerMutex);
    }
    как видно тут мы увеличиваем значение test на 11.1 и записываем его значение в рабочий массив для отправки клиенту. Данные от клиента пока игнорируем. Вообще-то этот модуль можно применять для своих целей - как говорилось ранее.

    в файле tema_server.c изменили функции обработки связи с клиентом:
    Код (C++):
    //поток работы с входящим соединением
    void ClientProcess(WSERV * cs)
    {
        int        countlive    = _time_life;
        //int        setcntlive;
        int        csock;
        int        idclient;
        //int        rd;
        pthread_t    pth;
        U8        midcl;
        U8        rxbuf[_size_buf];
        U8        txbuf[_size_buf];
        int        txsz         = 0;
        U8        work;
        int        res;
        //
        printf("---- CLIENT START ----\n");
        //занимаем мютекс
        pthread_mutex_lock(&ServerMutex);
        //
        //countlive     = cs->setcntlive;
        //setcntlive    = cs->setcntlive;
        csock        = cs->csock;
        idclient    = cs->idclient;
        midcl        = cs->midcl[idclient];
        pth        = cs->pth[idclient];
        work        = cs->work;
        //
        pthread_mutex_unlock(&ServerMutex);
        //
        while(work)
        {
            //чтение сокета
            txsz = 0;
            memset(&rxbuf[0], 0, _size_buf);
            res = ReadSocket(csock, &rxbuf[0]);
            switch(res)
            {
                case 0:    //Null
                    break;
                case -1:             //таймаут
                    break;
                case -2:             //какая-то ошибка сокета
                    goto mexit;
                case -3:             //ошибка размера пакета
                    break;
                default:             //нормально - данные приняты
                    countlive = _time_life;
                    break;
            }
            //занимаем мютекс и копируем данные в/из рабочий/рабочего массива
            pthread_mutex_lock(&ServerMutex);
            if(rxbuf[_type_tcp_pkt] & _type_tcp_pkt_mask_rd)        //данные клиенту ?
            {
                memcpy(&txbuf[_data_pkt], &wbufto[0], _size_wbuf);    //копируем в буфер для передачи из рабочих данных
                //заполняем служебные данные
                memcpy(&txbuf[0], &rxbuf[0], 4);            //размер и параметры, что запросил клиент
                txsz = (int)(*(U16*)(&rxbuf[_sizeL_tcp_pkt]));        //установим размер для передачи
            }
            if(rxbuf[_type_tcp_pkt] & _type_tcp_pkt_mask_wr)        //данные от клиента ?
                memcpy(&wbuffrom[0], &rxbuf[_data_pkt], _size_wbuf);    //копируем в рабочие данные от клиента
            pthread_mutex_unlock(&ServerMutex);
            //передача данных
            if(txsz > 0)
            {
                //pthread_mutex_unlock(&ServerMutex);
                res = WriteSocket(csock, &txbuf[0], txsz);
                switch(res)
                {
                    case 0:
                        break;
                    case -1:        //таймаут
                        break;
                    case -2:        //какая-то ошибка сокета
                        goto mexit;
                    default:        //нормально - данные переданы
                        //countlive = _time_life;
                        break;
                }
            }
            //ожидание и новый цикл
            usleep(_cl_pr_ssleep);
            countlive--;
            if(!(countlive > 0)) //никому это соединение не нужно
                break;
        }
    mexit:    close(csock);
        pthread_mutex_lock(&ServerMutex);
        cs->pth[idclient]    = 0;
        cs->midcl[idclient]    = 0;
        cs->cntclient--;
        pthread_mutex_unlock(&ServerMutex);
        printf("---- CLIENT STOP ----\n");
        pthread_exit(0);
    }
     
    последний шаг (05.11.2016 22:26):
    tema.zip

    проводим последнее испытание без участия клиента, а только с помощью nmap.
    запускаем в консоли:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
    в другой консоли запускаем (можно и с другой машины указав IP):
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ nmap localhost

    Starting Nmap 6.00 ( http://nmap.org ) at 2016-11-06 14:46 MSK
    Nmap scan report for localhost (127.0.0.1)
    Host is up (0.00079s latency).
    Other addresses for localhost (not scanned): 127.0.0.1
    Not shown: 990 closed ports
    PORT     STATE SERVICE
    21/tcp   open  ftp
    22/tcp   open  ssh
    80/tcp   open  http
    111/tcp  open  rpcbind
    139/tcp  open  netbios-ssn
    445/tcp  open  microsoft-ds
    631/tcp  open  ipp
    1234/tcp open  hotline
    3306/tcp open  mysql
    5432/tcp open  postgresql

    Nmap done: 1 IP address (1 host up) scanned in 0.11 seconds
    igor@debian-i:/home/ext_projects/tema$
    видим:
    Код (Bash):
    igor@debian-i:/home/ext_projects/tema$ ./WorkServer -p 1234
    ---- opening tcp/ip  1234 ----
    -- CLIENT sock. 4 --
    ---- CLIENT START ----
    ---- CLIENT STOP ----
     
    Запуск потока клиента и тут же его завершение. Наш nmap только производил соединение и не посылал должного пакета.

    В связи с тем, что далее отладка потребует наличие кода клиента на стороне ПК и как оговаривалось мы будем работать через DLL с вышеупомянутым сервером предлагается копия документа по написанию своей DLL-ки и её отладки (тема есть в интернете - а это наглядно и ранее сам использовал):
    DLL2.pdf

    Одним словом потребуется работать и с GCC и с WisualStudio (я применял VS2005 в Windows XP, но можете без особого труда применить и более новое...).
    Соответственно отладка будет проходить параллельно.
     

    Вложения:

    • DLL2.pdf
      Размер файла:
      451,5 КБ
      Просмотров:
      858
    • tema.zip
      Размер файла:
      7,5 КБ
      Просмотров:
      539
    Последнее редактирование: 6 ноя 2016
  19. AlexU

    AlexU Гуру

    Для того, что бы чем-то управлять нужно создать программу, которая из стандартного ввода будет получать команды управления и в соответствии с этими командами управлять тем или иным устройством. А универсальный сервер xinetd настроить так, что бы он слушал определённый порт и принимал соединения. Все данные, которые придут из сети по этим соединениям, xinetd будет передавать в ту или иную программу через стандартный ввод. И, наоборот, всё что программа запишет в стандартный вывод xinetd отправить в сеть.
    Т.е. сервер xinetd уже реализует всё то, что Вы описали выше в плане сетевого взаимодействия. Остаётся только реализовать нужный функционал.

    Вот пример простейшего echo-сервиса для xinetd:
    Код (C++):
    #include <iostream>
    using namespace std;

    int main() {
        char buffer[256];
        while (true)
        {
            cin >> buffer;  // данные из сети помещаем в буфер
            cout << buffer;  // данные отправляем обратно в сеть
        }
        return 0;
    }
    Правда нужно учитывать особенности работы класса 'cin' -- клиент посылая данные в сеть, должен завершать их символом перевода строки '\n'. Но читать стандартный ввод можно по разному -- это всего лишь один из способов. Остается только создать конфигурацию для xinetd, где указать какой протокол использовать, какой порт и в какую программу отправлять данные.
    Пример конфигурации:
    Код (Text):
    service echoserver
    {
            port            = 23456
            socket_type     = stream
            protocol     = tcp
            wait            = no
            server          = /path/to/echo/server/program
    }
    Не совсем понял "что, как и зачем" будет управлять.
    Идея поделиться проблемами хорошая. Вот только идея реализации универсального tcp сервера скорее является "изобретением велосипеда", т.к. уже есть готовое решение. А вот идея поделиться приёмами реализации клиент-серверного приложения под конкретные нужды -- может быть не плохой.
     
    Igor68 нравится это.
  20. Igor68

    Igor68 Гуру

    Мне очень нравится ваш ответ. Спасибо! Это действительно удобная штука!
    То что я описываю "шаг за шагом" - это пример пары сервер-клиет на Си. Одна рабочая программа в устройстве. Обмен массивом. Без анализа команд - просто данные. В ряде случаев удобнее чем через обмен по cin и cout. В любом случае спасибо... не так давно эту идею применял для обмена между веб страницей на Apache и кодом на Си и Bash скриптом, правда без xinetd. Правда может было-бы удобней напрямую. Только один вопрос: как "экранировать" байт, значение которого соответствует '\n', который скорее всего будет в данных. Потому, как значение для каждого байта будут 0x00-0xFF.

    Пост #18 содержит готовые к тесту данные - следующий шаг проект в VisualStudio и совместная отладка обмена.
     
    Последнее редактирование: 5 ноя 2016