Форум программистов, компьютерный форум, киберфорум
C/С++ под Linux
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.94/47: Рейтинг темы: голосов - 47, средняя оценка - 4.94
5 / 5 / 3
Регистрация: 14.11.2016
Сообщений: 94

Эхо-сервер с неблокирующим сокетом

13.09.2017, 23:10. Показов 9467. Ответов 7
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте, уважаемые форумчане. Пишу эхо-сервер, основной функционал работает, но нужно сделать его неблокирующим, для обслуживания множества клиентов. Делаю после создания слушающего сокета fcntl(listener, F_SETFL, O_NONBLOCK) и после клиентского в главном цикле
fcntl(acsoc, F_SETFL, O_NONBLOCK)- при запуске (если порт не занят конечно) выдает через perror ошибку Resource temporarily unavailable, также не показывает в консоли сообщение о доступности сервера на порту (порт выбирается из диапазона 50000 - 55000). Порт задается в качестве параметра при запуске сервера, либо идет по умолчанию, и если он занят - сервер автоматически подбирает из диапазона свободный порт

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
 
 int main(int argc, char* argv[])
 {
         int listener, acsock;
         struct sockaddr_in addr;
         int port;
         const int DEFAULT_CONN = 2;
         int clients;
         char buff[1024];
         int bytes_read;
 
         listener = socket(AF_INET, SOCK_STREAM, 0);
         if (listener < 0)
         {
                 perror("ServerSocketError");
                 exit(1);
         }
     fcntl(listener, F_SETFL, O_NONBLOCK);
 
         switch(argc)
         {
                 case 1:
                         port = 50000;
                         break;
                 case 2:
                         if (atoi(argv[1]) < 50000 || atoi(argv[1]) > 55000)
                         {
                                 perror("PortNumberOutOfRangeError");
                                 exit(1);
                                 break;
                         } else { port = atoi(argv[1]); break; }
                         break;
                 default:
                         printf("Too many parameters\n");
                         exit(0);
         }
 
         addr.sin_family = AF_INET;
         addr.sin_port = htons(port);
         addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
         if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) == EADDRINUSE)
         {
                 perror("BindError");
                 for (int i = 50000; i <= 55000; i++)
                 {
                         addr.sin_port = htons(i);
                         if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) == 0);
                         {
                                 printf("Server is accessible on port: %d\n", i);
                                 break;
                         }
                 }
         }
 
         clients = DEFAULT_CONN;
         if (listen(listener, clients) < 0)
         {
                 perror("ListenError");
                 exit(5);
         }
 
         while(1)
         {
                 acsock = accept(listener, NULL, NULL);
                 if (acsock < 0)
                 {
                         perror("AcceptError");
                         exit(1);
                 }
         fcntl(acsock, F_SETFL, O_NONBLOCK);
 
                 while(1)
                 {
                         bytes_read = recv(acsock, buff, sizeof(buff), 0);
                         if (bytes_read <= 0) break;
                         send(acsock, buff, bytes_read, 0);
                         for (int i = 0; i < 1024; i++)
                         {
                                 buff[i] = '\0';
                         }
                 }
                 close(acsock);
         }
         return 0;
 }
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
13.09.2017, 23:10
Ответы с готовыми решениями:

Работа с неблокирующим сокетом в Linux
Создаю неблокирующий сокет, выполняю connect. В отдельном потоке с помощью select проверяется готовность сокета на запись, чтение и наличие...

Комментарии к коду эхо-клиент/эхо-сервер
Ребятки ,нужна очень ваша помощь !!! кому не тяжело напишите пожалуйста коментарии к кодам (((код не мой ,но его нужно по заданию...

консоль эхо сервер
Разработать простейший TCP echo сервер. Требования Запускается на IP адресе 0.0.0.0 и TCP порту 2222 Получает сообщения...

7
Почетный модератор
 Аватар для Humanoid
11559 / 4353 / 453
Регистрация: 12.06.2008
Сообщений: 12,455
13.09.2017, 23:35
Странно всё сделано... сокеты сделаны неблокируемыми, а работаете с ними как с блокируемыми. Например, если посмотрите описание функции accept(), то там сказано, что если сокет неблокируемый и ещё нет запроса от клиента на подключение, то функция accept() не будет блокироваться, сразу вернёт -1 и выставит errno в значение EAGAIN. Это означает, что ошибки не было, просто никто не пытается подключиться к серверу. Поэтому у вас код сразу попадёт на perror("AcceptError") и завершится. Тоже самое касается и recv().

Вообще, можно не делать сокет неблокируемым, а использовать функции select/poll/epoll (для данной задачи я рекомендую epoll, т.к. там легко добавлять/удалять отслеживаемые сокеты), что бы ожидать какого-либо события от сокета, а не грузить процессор пустым циклом.

Кстати, весь ваш код заточен только под один клиент. Функция accept() не вызовется, пока не закроется предыдущее соединение с клиентом. Т.е. клиенты будут подключаться только по-очереди. И фактически алгоритм остаётся блокируемым.

Предлагаю сразу после listen() добавить listener в отслеживание через epoll. А в цикле запускать epoll_wait() и если он вернёт > 0 (т.е. произошло событие на каком-то сокете), то сравнить сработавший event.fd с listener. Если совпадает, то делаем accept() и новый сокет добавляем к epoll. Если не совпал, значит, это какой-то из ранее подключившихся клиентов прислал пакет и мы его просто принимаем через recv(). Если recv() вернул 0, значит клиент отключился, делаем close() и удаляем сокет из epoll. В этом случае даже не нужно хранить список клиентов в явном виде... всё хранится внутри epoll.
1
5 / 5 / 3
Регистрация: 14.11.2016
Сообщений: 94
14.09.2017, 23:32  [ТС]
Насчёт заточки под один клиент. А если я сделаю динамический массив дескрипторов сокетов и calloc' ом выделю память под необходимое количество подключений и в главном цикле сделаю цикл где для каждого элемента массива буду делать accept ? Мне бы понять как с селектом тут решить

Добавлено через 1 час 4 минуты
Для иллюстрации немного изменил код, тут для 3-х подключений
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
 
 int main(int argc, char* argv[])
 {
         int listener;
         struct sockaddr_in addr;
         int port;
 
         int connections = 3;
     int *clients = calloc(connections, 4);
         char buff[1024];
         int bytes_read;
 
         listener = socket(AF_INET, SOCK_STREAM, 0);
         if (listener < 0)
         {
                 perror("ServerSocketError");
                 exit(1);
         }
     fcntl(listener, F_SETFL, O_NONBLOCK);
 
         switch(argc)
         {
                 case 1:
                         port = 50000;
                         break;
                 case 2:
                         if (atoi(argv[1]) < 50000 || atoi(argv[1]) > 55000)
                         {
                                 perror("PortNumberOutOfRangeError");
                                 exit(1);
                                 break;
                         } else { port = atoi(argv[1]); break; }
                         break;
                 default:
                         printf("Too many parameters\n");
                         exit(0);
         }
 
         addr.sin_family = AF_INET;
         addr.sin_port = htons(port);
         addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
         if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) == EADDRINUSE)
         {
                 perror("BindError");
                 for (int i = 50000; i <= 55000; i++)
                 {
                         addr.sin_port = htons(i);
                         if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) == 0);
                         {
                                 printf("Server is accessible on port: %d\n", i);
                                 break;
                         }
                 }
         }
 
         if (listen(listener, connections) < 0)
         {
                 perror("ListenError");
                 exit(5);
         }
 
     int count = 0;
         while(1)
         {
         for (int i = 0; i < connections; i++)
         {
                     clients[i] = accept(listener, NULL, NULL);
                     if (clients[i] < 0)
                     {
                 count++;
                             perror("AcceptError");
                 if (count == connections)
                 {
                     exit(1);
                 }
                             continue;
                     }
             fcntl(clients[i], F_SETFL, O_NONBLOCK);
         }
 
                 while(1)
                 {
             for (int i = 0; i < connections; i++)
             {
                 bytes_read = recv(clients[i], buff, sizeof(buff), 0);
                             if (bytes_read <= 0) break;
                             send(clients[i], buff, bytes_read, 0);
                             for (int i = 0; i < 1024; i++)
                             {
                                     buff[i] = '\0';
                             }
             }
                 }
 
         for (int i = 0; i < connections; i++)
         {
                    close(clients[i]);
         }
         }
     free(clients);
         return 0;
 }
0
Почетный модератор
 Аватар для Humanoid
11559 / 4353 / 453
Регистрация: 12.06.2008
Сообщений: 12,455
15.09.2017, 01:21
Как-то сложно на мой взгляд... да и обрабатывать массив подключений неудобно. Предлагаю такой вариант:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/epoll.h>
 
int main(void)
{
    int listener;
    unsigned int port = 5000;
    struct sockaddr_in addr;
    int pollfd;
    char buff[1024];
    struct epoll_event ev = {
        .events = EPOLLIN,
    };
 
    listener = socket(AF_INET, SOCK_STREAM, 0);
    if (listener == -1)
        error(1, errno, "socket()");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listener, (struct sockaddr*)&addr, sizeof(addr))) {
        error(0, errno, "bind()");
        close(listener);
        return 1;
    }
    if (listen(listener, 5)) {
        error(0, errno, "listen()");
        close(listener);
        return 1;
    }
    pollfd = epoll_create1(0);  // создаём файловый дескриптор для epoll
    if (pollfd == -1) {
        error(0, errno, "epoll_create1()");
        close(listener);
        return 1;
    }
    ev.data.fd = listener;
    if (epoll_ctl(pollfd, EPOLL_CTL_ADD, listener, &ev)) {  // добавляем listener для отслеживания
        error(0, errno, "epoll_ctr(ADD)");
        close(pollfd);
        close(listener);
        return 1;
    }
 
    while (1) {
        int fd;
        int count = epoll_wait(pollfd, &ev, 1, 1000);  // ждём событие в течении 1000 мс
        if (count == -1) {  // если ошибка
            error(0, errno, "epoll_wait()");
            close(pollfd);
            close(listener);
            return 1;
        } else if (count == 0)  // если событий не было
            continue;
 
        fd = ev.data.fd;
        if (fd == listener) {  // если это событие от listener, значит кто-то хочет подключиться
            fd = accept(listener, NULL, NULL);
            printf("Connected client (fd: %d)\n", fd);
            ev.data.fd = fd;
            if (epoll_ctl(pollfd, EPOLL_CTL_ADD, fd, &ev)) {  // нового клиента добавляем к отслеживанию
                error(0, errno, "epoll_ctr(ADD)");
                close(pollfd);
                close(listener);
                return 1;
            }
        } else {  // если это кто-то из клиентов
            int sended;
            int bytes = recv(fd, buff, sizeof(buff), 0);
 
            if (bytes == -1) {
                error(0, errno, "recv()");
                close(pollfd);
                close(listener);
                return 1;
            } else if (bytes == 0) {  // если он отключился, то удаляем отслеживание событий
                if (epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, NULL)) {
                    error(0, errno, "epoll_ctr(DEL)");
                    close(pollfd);
                    close(listener);
                    return 1;
                }
                close(fd);
                printf("Disconnected client (fd: %d)\n", fd);
                continue;
            }
 
            sended = send(fd, buff, bytes, 0);  // отправляем эхо
            if (sended == -1) {
                error(0, errno, "send()");
                close(pollfd);
                close(listener);
                return 1;
            }
        }
    }
    // выхода из цикла не предусмотрено... решай сам, при каких условиях сервер будет завершатся
    close(pollfd);
    close(listener);
    return 0;
}
Данный код не использует свойство O_NONBLOCK, но не блокирует работу из-за того, что мы вызываем accept() или recv() не сразу, а только по событию. Так как тут используется epoll_wait(), а не пустой цикл, то эта программа не создаёт нагрузку на процессор (пустой цикл будет на 100% грузить одно ядро). Хранить клиентские сокеты сами не будем... они всё равно хранятся в epoll и он нам их возвращает через epoll_wait(), когда на этих сокетах происходит какой-то событие.
И не забывайте обрабатывать ошибки... иначе что-то будет глючить и вы даже не поймёте, что именно.
1
153 / 148 / 66
Регистрация: 20.02.2014
Сообщений: 556
15.09.2017, 17:54
Knyaz_Myshkin, а вообще по поводу неблокируемых сокетов: в линухе есть специальный вызов accept4(), который с флагом SOCK_NONBLOCK возвращает новый сокет в нон-блокед режиме.
0
5 / 5 / 3
Регистрация: 14.11.2016
Сообщений: 94
16.09.2017, 17:05  [ТС]
Humanoid, Спасибо за вариант, обязательно его опробую. Только у меня требуется именно через select сделать, задача такая). Смотрю примеры не очень понятно
0
Почетный модератор
 Аватар для Humanoid
11559 / 4353 / 453
Регистрация: 12.06.2008
Сообщений: 12,455
16.09.2017, 23:58
Лучший ответ Сообщение было отмечено Knyaz_Myshkin как решение

Решение

Я бы рекомендовал почитать какую-нибудь книгу. Лично я читал только Роберт Лав "Системное программирование Linux" - там очень хорошо всё объясняется. Всех тонкостей не помню, но смысл расскажу. Функция select() ищет среди указанных файловых дескрипторов те, у который случились события (нас интересуют только входящие события - например, получены данные). Описана функция так:
C
1
2
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
nfds - самый большой номер отслеживаемого файлового дескриптора + 1.
readfds - список файловых дескрипторов, у которых мы отслеживаем события чтения.
writefds - тоже самое, но события записи. Например, когда запись файл или передача по сети завершилась.
exceptfds - тоже самое, но события ошибок.
timeout - максимальное время ожидания событий.

Аргументы readfds, writefds и exceptfds имеют тип fd_set. По сути дела, этот тип представляет собой массив переменных типа long общим размером 1024 бита (32 элемента на 32-битных системах и 16 элементов на 64-битных). Правильнее это значение брать из константы FD_SETSIZE. Можно рассматривать этот тип как одно 1024-битное (FD_SETSIZE-битное) число. Если установлен только десятый бит, значит событие ожидается от файлового дескриптора, равного 10. Если установлен ещё и 125-ый бит, то и файловый дескриптор с номером 125 так же будет отслеживаться.

Для установки/снятия битов есть макросы:
FD_SET(fd, fdsetp) - устанавливает бит с номером fd... т.е. файловый дескриптор будет отслеживаться функцией select.
FD_CLR(fd, fdsetp) - снимает бит.
FD_ZERO(fdsetp) - очищает все биты.
FD_ISSET(fd, fdsetp) - проверяет, установлен ли бит. Возвращает true, если установлен или false, если нет.

Если мы хотим отслеживать возможность чтения файловых дескрипторов с номерами 5, 12, 6 и 8, то нам надо выполнить
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct timeval timeout = {
    .tv_sec = 0;
    .tv_usec = 500000; // 0.5 секунд
};
fd_set fds;
FD_ZERO(&fds);
FD_SET(5, &fds);
FD_SET(12, &fds);
FD_SET(6, &fds);
FD_SET(8, &fds);
 
ret = select(13, &fds, NULL, NULL, &timeout);
if (ret == -1)
  // произошла какая-то ошибка... надо смотреть errno
else if (ret == 0)
  // событий в течении 0.5 секунды не было. Читать нечего.
else {
  // теперь в fds установлены биты. Количество установленных бит = ret.
  // Каждый установленный бит - это файловый дескриптор, на котором было событие и который можно прочитать.
  for (int i = 0; i < 13; i++) {
    if (FD_ISSET(i, &fds)) {
      read(i, ......); // i - это файловый дескриптор, который можно читать
    }
  }
}
Т.к. функция select() меняет fds, то перед повторным запуском функции select() придётся перезаполнять fds.

В общем, та же самая программа, но переделанная на select()

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
 
int main(void)
{
    int listener;
    unsigned int port = 5000;
    struct sockaddr_in addr;
    char buff[1024];
    fd_set fds;
    int nfds;
 
    listener = socket(AF_INET, SOCK_STREAM, 0);
    if (listener == -1)
        error(1, errno, "socket()");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(listener, (struct sockaddr*)&addr, sizeof(addr))) {
        error(0, errno, "bind()");
        close(listener);
        return 1;
    }
    if (listen(listener, 5)) {
        error(0, errno, "listen()");
        close(listener);
        return 1;
    }
    FD_ZERO(&fds);
    FD_SET(listener, &fds);
    nfds = listener + 1;
 
    while (1) {
        int fd;
        fd_set fds_tmp;
        struct timeval tv = {
            .tv_sec = 1,
            .tv_usec = 0,
        };
        memcpy(&fds_tmp, &fds, sizeof(fd_set));
        int count = select(nfds, &fds_tmp, NULL, NULL, &tv);
        if (count == -1) {  // если ошибка
            error(0, errno, "select()");
            close(listener);
            return 1;
        } else if (count == 0)  // если событий не было
            continue;
 
        for (fd = 0; fd < nfds; fd++) {
            if (!FD_ISSET(fd, &fds_tmp))
                continue;
            if (fd == listener) {  // если это событие от listener, значит кто-то хочет подключиться
                fd = accept(listener, NULL, NULL);
                printf("Connected client (fd: %d)\n", fd);
                if (fd >= FD_SETSIZE) {
                    perror("fd имеет слишком большой номер и не может отслеживаться через select()\n");
                    close(fd);
                    continue;
                }
                FD_SET(fd, &fds);
                if (fd >= nfds)
                    nfds = fd + 1;
            } else {  // если это кто-то из клиентов
                int sended;
                int bytes = recv(fd, buff, sizeof(buff), 0);
 
                if (bytes == -1) {
                    error(0, errno, "recv()");
                    close(listener);
                    return 1;
                } else if (bytes == 0) {  // если он отключился, то удаляем отслеживание событий
                    FD_CLR(fd, &fds);
                    close(fd);
                    printf("Disconnected client (fd: %d)\n", fd);
                    continue;
                }
 
                sended = send(fd, buff, bytes, 0);  // отправляем эхо
                if (sended == -1) {
                    error(0, errno, "send()");
                    close(listener);
                    return 1;
                }
            }
        }
    }
    // выхода из цикла не предусмотрено... решай сам, при каких условиях сервер будет завершатся
    close(listener);
    return 0;
}
1
5 / 5 / 3
Регистрация: 14.11.2016
Сообщений: 94
17.09.2017, 22:23  [ТС]
Humanoid, Покорнейше благодарю, и за полезную информацию и за код))
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
17.09.2017, 22:23
Помогаю со студенческими работами здесь

Параллельный эхо-сервер
Добрый день! Мне нужно на базе шаблона параллельного эхо-сервера, использующего модель “один клиент – один процесс”, разработать UDP...

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

SIP - телефон: эхо сервер
Привет всем! Получил задание на курсовой написать эхо сервер для SIP-телефонии. Рассматриваю вариант application server based. Мне...

TCP-эхо клиент-сервер (WinSock)
Здравствуйте! Подскажите, пожалуйста, почему TCP-эхо клиент-сервер неправильно работает? Сервер отправляет клиенту правильно только первую...

Совместимость процессора с 478 сокетом и материнской платы с 479 сокетом
Всем привет !! подскажите плизз будет похать проц с 478 сокетом на матке с 479 сокетом ?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это дополнительная запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение: В этой книге («Подход, основанный на вариантах использования») Ивар утверждает, что архитектура программного обеспечения — это структуры,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru