LINUX.ORG.RU

Сервер на СИ

 ,


1

2

Здравствуйте.

В данное время я медленно, но с удовольствием постигаю прекрасный язык СИ (до этого было делфи, пхп, js и ещё что-то).

ПРОЛОГ

Мне понадобился маленький тср-серверок, который бы слушал порт, принимал от клиента некий символ (например букву А) и далее отправлял этот символ в микроконтроллер подключённый к серверу по USB. (Всё это связано с пресловутым «умным домом»)

СУТЬ

На просторах интернета я нашёл не малое количество примеров и остановился вот на этом - http://masandilov.ru/network/guide_to_network_programming6#6.1

После некоторых изменений, код приобрёл нижеследующий облик:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <time.h>

#define BACKLOG 10     // как много может быть ожидающих соединений

#define MAXBUFLEN 100

char PORT[5]={0,};
char device[14]={0,};
char er_log_str[50]={0,};

void sigchld_handler()
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

void *get_in_addr(struct sockaddr *sa) // получаем адрес сокета, ipv4 или ipv6:
{
    if(sa->sa_family == AF_INET) 
     {
       return &(((struct sockaddr_in*)sa)->sin_addr);
     }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void error_log() // запись ошибки в файл ErrorSer.log
{  
    time_t t;
    time(&t);
    FILE *f;
    f = fopen("ErrorSer.log", "a"); 
    fprintf(f , "%s. ", er_log_str);
    fprintf(f , "%s", ctime( &t));  
    printf("Write to ErrorSer.log\n");
    fclose(f);
    exit(0);
}

int main(int argc, char *argv[])
{
   if(argc!=3) 
    {
      printf("Primer zapyska - ./arduserver 3490 /dev/ttyACM1\n");
      sprintf(er_log_str,"%s","Not use PORT");
      error_log();
    }
  
   sprintf(PORT,"%s", argv[1]);
   sprintf(device,"%s", argv[2]);

    int sockfd, new_fd;  // слушаем на sock_fd, новые соединения - на new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr; // информация об адресе клиента
    socklen_t sin_size;
    struct sigaction sa;
    int yes=1;
    // char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) 
      {
        sprintf(er_log_str,"%s","Error - getaddrinfo");
        error_log();
      }

    for(p = servinfo; p != NULL; p = p->ai_next) // цикл через все результаты, чтобы забиндиться на первом возможном
      {
        if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) 
          {
            perror("server: socket");
            continue;
          }

        if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) 
          {
            perror("setsockopt");
            exit(1);
          }

        if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) 
          {
            close(sockfd);
            perror("server: bind");
            continue;
          }

        break;
      }

    if(p == NULL)  
     {
        sprintf(er_log_str,"%s","Error - failed to bindn");
        error_log();
     }
   
    freeaddrinfo(servinfo); // всё, что можно, с этой структурой мы сделали

    if(listen(sockfd, BACKLOG) == -1) 
     {
        sprintf(er_log_str,"%s","Error - listen");
        error_log();
     }

    sa.sa_handler = sigchld_handler; // обрабатываем мёртвые процессы
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    if(sigaction(SIGCHLD, &sa, NULL) == -1) 
     {
        sprintf(er_log_str,"%s","Error - sigaction");
        error_log();
     }

    printf("server: waiting for connections...\n");

    while(1) // главный цикл accept()
     {  
        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, NULL,  &sin_size);

        if(new_fd == -1) 
         {
            perror("accept");
            continue;
         }

        printf("server: OK connection\n");

///////////////////////////////////////////////////////////////////////////////////////////

        if(!fork()) // тут начинается дочерний процесс
           { 
            close(sockfd); // дочернему процессу не нужен слушающий сокет
          
            char buffer[7] = {0,};

            int n = read(new_fd, buffer, 6);

            if(n < 0)
              {
                sprintf(er_log_str,"%s","Error - reading from socket");
                error_log();
              }

            printf("\n"); 
            printf("Ot klienta polycheno: %c\n\n", buffer[5]); 

            char to_Ardu[14] = {0,}; 
            sprintf(to_Ardu,"echo 'Y+=Z%c' > %s", buffer[5], device);
            system(to_Ardu); // отправляем ардуине пакетик

            close(new_fd);

            exit(0);
        }

        close(new_fd);  // а этот сокет больше не нужен родителю

    } // конец while

    return 0;
}

/// ./arduserver 3490 /dev/ttyACM1

ВОПРОС

Очень хочется, чтоб уважаемое сообщество указало на ошибки в коде и объяснило некоторые моменты.

В частности:

1. Корректно ли я инициализирую массивы.

char PORT[5]={0,};

2. Нужен ли в данном примере дочерний процесс

if(!fork())
...
Это пока для меня тёмный лес, поэтому я был бы премного благодарен, если кто-то растолкует популярным языком, в каких случая надо запускать дочерние процессы.

Зараннее спасибо.



Последнее исправление: CYB3R (всего исправлений: 6)

Я такие штуки вебсокетами делаю. И нет необходимости поллить сервер по 10 раз в секунду, чтобы более-менее приличную асинхронность обеспечить.

Потоки в данном случае удобней процессов потому, что проще осуществить взаимосвязь (хватит пары мьютексов для выполнения контроля доступа к железяке). Да и ресурсов потоки меньше жрут.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Я такие штуки вебсокетами делаю

Имеет смысл только для управления через браузер, а если есть нормальный клиент, то лучше юзать TCP

Потоки в данном случае удобней процессов потому, что проще осуществить взаимосвязь (хватит пары мьютексов для выполнения контроля доступа к железяке).

В данном случае вообще одного потока с event loop хватит за глаза

Да и ресурсов потоки меньше жрут.

Спорное утверждение: в *никсах потоки реализуются через процессы, сэкономить можно только на разделяемых данных в куче, которых будет много только в весьма специфичных задачах (напр., база данных в памяти)

annulen ★★★★★
()
Ответ на: комментарий от Eddy_Em

Да и ресурсов потоки меньше жрут.


чем кто?

anonymous
()
Ответ на: комментарий от annulen

если есть нормальный клиент

Нафиг оно нужно — еще клиентом париться?

event loop

Делать нефиг, очереди перебирать?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Нафиг оно нужно — еще клиентом париться?

Можно хоть из башевого скрипта через nc данные отправлять. Это же намного более Ъ, чем каким-то веб-говном пользоваться, не так ли?

Делать нефиг, очереди перебирать?

Жжешь, пеши исчо.

annulen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.