Зачем нужен policy server?


В Unity, начиная с версии 3.0, для сборок под Web player используются механизмы обеспечения безопасности, похожие на те, что использует Adobe в Flash player. Суть его заключается в том, что при обращении к серверу клиент спрашивает у него «разрешение», и если сервер не «разрешает», то клиент не будет пробовать к нему подключиться. Данные ограничения работают для обращения к удаленным серверам через класс WWW и с помощью сокетов. Если вы хотите сделать какой-либо запрос по rest протоколу из вашего клиента на удаленный сервер, необходимо, чтобы в корне домена лежал специальный xml. Он должен называться crossdomain.xml и иметь следующий формат:

<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

Клиент перед запросом скачает файл политики безопасности, проверит его и, увидев, что разрешены все домены, продолжит выполнять сделанный вами запрос.

Если вам потребуется подключиться к удаленному серверу с помощью сокетов (tcp/udp), перед подключением клиент сделает запрос к серверу на 843 порт для получения файла политики безопасности, в котором будет описано к каким портам и с каких доменов можно подключаться:

<?xml version="1.0"?>
<cross-domain-policy>
   <allow-access-from domain="*" to-ports="1200-1220"/> 
</cross-domain-policy>"

Если данные клиента не удовлетворяют всем параметрам (домен, порт), то клиент сгенерирует исключение SecurityException и не будет пытаться подключиться к серверу.

В данной статье речь пойдет о написании сервера, который будет отдавать файлы политики безопасности, в дальнейшем я буду называть его Policy server.

Как должен работать Policy server?


Схема работы сервера проста:

  1. Сервер запускается и слушает 843 порт по tcp протоколу. Есть возможность переопределить порт Security.PrefetchSocketPolicy()
  2. Клиент подключается к серверу по tcp протоколу и отправляет xml c запросом файла политики безопасности:

    <policy-file-request/>
    
  3. Сервер разбирает запрос и отправляет клиенту xml с политикой безопасности

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

Что уже есть?


На текущий момент есть сервер, написанный на связке Java + Netty, исходный код с инструкцией и jar. Одним из ключевых его недостатков является зависимость от jre. В целом, развернуть jre на linux сервере не проблема, но часто разработчики игр — это клиентские программисты, которые хотят делать как можно меньше телодвижений, тем более они не хотят устанавливать jre и позже его администрировать. Поэтому было принято решение написать Policy server на C++, который работал бы как нативное приложение на linux машине.

Написанный на C++ Policy server не должен уступать по производительности старому, в идеале должен показывать результат намного лучше. Ключевыми метриками производительности будут: время, которое клиент тратит на ожидание файла политики безопасности, и количество клиентов, которые могут одновременно получать файлы политики безопасности, что, по сути, тоже сводится к времени ожидания файла политики.

Для тестирования я использовал этот скрипт. Работает он следующим образом:

  1. Вычиcляет средний пинг до сервера
  2. Запускает несколько потоков (количество указывается в скрипте)
  3. В каждом потоке запрашивает у Policy server'а файл политики безопасности
  4. Если файл политики соответствует тому, который ожидается, то для каждого запроса сохраняется время, потраченное на ожидание
  5. Выводит результаты в консоль. Нас интересуют следующие значения: минимальное время ожидания, максимальное время ожидания, среднее время ожидания и те же параметры без пинга

Скрипт написан на ruby, но так как в стандартном интерпретаторе ruby отсутствует поддержка потоков уровня операционной системы, для работы я использовал jruby. Удобней всего использовать rvm, команда для запуска скрипта будет выглядеть так:

rvm jruby do ruby test.rb

Результаты тестирования Policy server'а, написанного на Java+Netty:
Среднее, мс 245
Минимальное, мс 116
Максимальное, мс 693

Что понадобится?


По сути, задача — написать на C++ демон, который мог бы слушать несколько портов, при подключении клиентов создавать сокет, копировать в сокет текстовую информацию и закрывать его. Желательно иметь как можно меньше зависимостей, а если они все-таки будут, то они должны быть в репозиториях наиболее распространенных linux дистрибутивов. Для написания кода будем использовать стандарт c++11. В качестве минимального набора библиотек возьмем:

  • libev для работы с циклом событий приложения
  • boost program_options для работы с параметрами командной строки
  • Easylogging++ для работы с логами

Один порт — один поток


Структура приложения достаточно проста: понадобится функционал для работы с параметрами командной строки, классы для работы с потоками, функционал для работы с сетью, функционал для работы с логами. Это простые вещи, с которыми не должно возникнуть проблем, поэтому я не буду на них подробно останавливаться. Код можно посмотреть здесь. Проблемным местом является организация обработки клиентских запросов. Самое простое решение — после подключения клиентского сокета отправлять все данные и сразу же закрывать сокет. Т.е. код, отвечающий за обработку нового подключения будет выглядеть так:

void Connector::connnect(ev::io& connect_event, int )
{
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	int client_sd;

	client_sd = accept(connect_event.fd, (struct sockaddr *)&client_addr, &client_len);
	if(client_sd < 0)
		return;

	const char *data = this->server->get_text()->c_str();
	send(client_sd, (void*)data, sizeof(char) * strlen(data), 0);
	shutdown(client_sd, 2);
	close(client_sd);
}

При попытке протестировать на большом количестве потоков (300, по 10 подключений на каждый), я не смог дождаться окончания работы тестовго скрипта. Из чего можно сделать вывод, что данное решением нам не подходит.

Async


Операция передачи данных по сети является затратной по времени, очевидно, что необходмио разделить процесс создания клиентского сокета и процесс отправки данных. Также неплохо было бы отдавать данные несколькими потоками. Неплохое решение — использовать std::async, который появился в стандарте C++11 async. Код, отвечающий за обработку нового подключения будет выглядеть так:

void Connector::connnect(ev::io& connect_event, int )
{
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	int client_sd;

	client_sd = accept(connect_event.fd, (struct sockaddr *)&client_addr, &client_len);

	std::async(std::launch::async, [client_addr, this](int client_socket) {
		const char * data = this->server->get_text()->c_str();
		send(client_socket, (void*)data, sizeof(char) * strlen(data), 0);
		shutdown(client_socket, 2);
		close(client_socket);
	}, client_sd);
}

Минусом такого решения является безконтрольность ресурсов. Минимальным вмешательством в код мы получаем возможность асинхронно выполнять отправку данных клиенту, при этом мы не можем контролировать процесс порождения новых потоков. Процесс создания нового потока является затратным для операционной системы, также большое количество потоков может понизить производительность сервера.

Pub/Sub


Подходящим решением для данной задачи является паттерн издатель-подписчик. Схема работы сервера должна выглядеть следующим образом:
  • Несколько издателей, по одному на каждый порт, сохраняют в буфер идентификаторы сокетов клиентов, которым надо отдать файл политики безопасности
  • Несколько подписчиков получают из буфера идентификаторы сокетов, копируют в них файл политики безопасности и закрывают сокет.

В качестве буфера подходит очередь, первым подключился к серверу — первым получишь файл политики. В стандартной библиотеке C++ есть готовый контейнер очереди, но он нам не подойдёт, так как требуется потокобезопасная очередь. При этом нам необходимо, чтобы операция добавления нового элемента была не блокирующей, в то время как операция чтения была блокирующей. Т.е при старте сервера будут запущены несколько подписчиков, которые будут ждать пока очередь пуста. Как только там появляются данные, один или несколько обработчиков срабатывают. Издатели же асинхронно записывают в данную очередь идентификаторы сокетов.

Немного погуглив, я нашел несколько готовых реализаций:
  1. https://github.com/cameron314/concurrentqueue.
    В данном случае нас интересует blockingconcurrentqueue, которая просто копируется в проект как заголовочный .h файл. Достаточно удобно, и нет никаких зависимостей. Данное решение обладает следующими минусами:
    • Нет методов для остановки подписчиков. Единственный способ остановить их — добавлять в очередь данные, которые будут сигнализировать подписчикам о том, что надо остановить работу. Это достаточно неудобно и потенциально может вызвать дедлок
    • Поддерживается одним человеком, коммиты в последнее время появляются достаточно редко

  2. tbb concurrent queue.
    Многопоточная очередь из библиотеки tbb (Threading Building Blocks). Библиотека разрабатывается и поддерживается Intel, при этом имеет все что нам необходимо:
    • Блокирующее чтение из очереди
    • Неблокирующая запись в очередь
    • Возможность в любой момент остановить заблокированные на ожидании данных потоки

    Среди минусов можно отметить, что такое решение увеличивает количество зависимостей, т.е. конечным пользователям придется устанавливать на свой сервер tbb. В наиболее рапспространенных linux-репозиториях tbb можно установить через менеджер пакетов операционной системы, поэтому проблем с зависимостями быть не должно.

Таким образом код создания нового подключения будет выглядеть следующим образом:

void Connector::connnect(ev::io& connect_event, int )
{
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	int client_sd;

	client_sd = accept(connect_event.fd, (struct sockaddr *)&client_addr, &client_len);

	clients_queue()->push(client_sd);

	this->handled_clients++;
}

Код обработки клиентского сокета:

void Handler::run()
{
	LOG(INFO) << "Handler with thread id " << this->thread.get_id() << " started";

	while(this->is_run)
	{
		int socket_fd = clients_queue()->pop();
		this->handle(socket_fd);
	}

	LOG(INFO) << "Handler with thread id " << this->thread.get_id() << " stopped";
}

Код для работы с очередью:

void ClientsQueue::push(int client)
{
	if(!this->queue.try_push(client))
		LOG(WARNING) << "Can't push socket " << client << " to queue";
}

int ClientsQueue::pop()
{
	int result;

	try
	{
		this->queue.pop(result);
	}
	catch(...)
	{
		result = -1;
	}

	return result;
}

void ClientsQueue::stop()
{
	this->queue.abort();
}

Код всего проекта с инструкцией по установке можно найти здесь. Результат тестового запуска с десятью потоками обработчиками:
Среднее, мс 151
Минимальное, мс 100
Максимальное, мс 1322

Итог


Сравнительаня таблица результатов
Java+Netty C++ Pub/Sub
Среднее, мс 245 151
Минимальное, мс 116 100
Максимальное, мс 693 1322

Ссылки:

PS: На текущий момент Unity Web player переживает тяжелые времена, в связи с закрытием npapi в топовых браузерах. Но если кто либо еще использует его и держит сервера на linux машинах, то может воспользоваться данным сервером, надеюсь он окажется вам полезным. Отдельная благодарность themoonisalwaysspyingonyourfears за иллюстрацию к статье.

Комментарии (30)


  1. solver
    02.10.2015 11:11

    Вот это просто идеальная задача для Go.
    Простой сервис, один бинарник. Хорошая производительность.


    1. helper2424
      02.10.2015 11:24
      +7

      Было бы интересно увидеть ваш вариант на Go.


  1. Boeses_Genie
    02.10.2015 11:31

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


  1. StrangerInRed
    02.10.2015 11:35

    Делал чат по такому же принципу (один клиент — один поток) на чистом С. (http://habrahabr.ru/post/266365/)


  1. roman_kashitsyn
    02.10.2015 11:35
    +1

    Два вопроса:

    1. Зачем смешивать асинхронный цикл событий с блокирующей очередью и потоками? Раз уж подключили библиотеку с асинхронный циклом (ev), логично написать всё в асинхронном стиле.
    2. Мы можем модифицровать процесс работы сервера и отдавать клиенту файл политики безопасности сразу же после подключения

      Я правильно понимаю, отдаём всем клиентам статический файл? Если так, то почему бы не поднять nginx на нужном порту и не отдавать статический xml с диска?
      UPD: кажется, понял. Протокол не HTTP.

    То, что время обработки не сильно улучшилось (и даже по некоторым характеристикам ухудшилось) по сравнению с Java-сервером, вызывает подозрения. Или тесты проводились криво, или в сервере что-то не так. Отдавать статический файл из памяти 151мс — подозрительно.


    1. helper2424
      02.10.2015 12:24

      1. Необходим менеджмент очереди, т.е. если использовать только libev, то надо писать какой то балансировщик который правильно распределит между обработчиками нагрузку. Использование же блокирующей очереди является вполне классическим решением для подобной задачи.
      2. Да nginx можно использовать, об этом написали в комментарии ниже.

      По-поводу тестов, делал несколько замеров тем, что есть. Цифры выложил те, которые получились, безусловно есть вероятность, что где то может быть косяк.


      1. roman_kashitsyn
        02.10.2015 14:32

        балансировщик который правильно распределит между обработчиками нагрузку

        Вот это я не совсем понял. При подключении клиента вызовется ваш коллбэк, который пошлёт один блок данных клиенту, после чего закроет сокет. Псевдокодом:

        endpoint.async_accept(acceptSocket, [](socket& s) {
            s.async_write(globalBuffer, [](socket& s){ s.close() });
        });
        


        Задача балансировщика в том, чтобы клиенты, пришедшие раньше, обслуживались быстрее? Мне кажется, операционная система сама об этом позаботится.


        1. helper2424
          02.10.2015 14:57

          В libev есть асинхронный вызов async. Вопрос в том, что произойдет, если будет много одновременных запросов? Нам в любом случае надо аггрегировать клиентов, и ни в коем случае не терять их. Причем в документации даже написано, что очереди в async никакой нет, поэтому делать ее самим — ссылка.


          1. roman_kashitsyn
            02.10.2015 15:15

            Вопрос в том, что произойдет, если будет много одновременных запросов?


            Они будут лежать в очереди операционной системы, man listen

            async

            ev_async, как я понял, нужен для того, чтобы разбудить цикл из другого потока, например, для завершения цикла по приходу SIGTERM. Очередь там, как я понял, нужна для того, чтобы не терять эти самые сигналы, пришедшие в другом потоке. Никакого отношения к обработке множества tcp-клиентов, кмк, это не имеет.

            К слову, в Boost.Asio всё это можно сделать гораздо проще. Там из коробки идёт signal_set, который складывает сигналы в очередь, и можно обойтись вообще без дополнительных потоков. Впрочем, увеличить число потоков при желании будет очень просто.


            1. helper2424
              02.10.2015 15:29
              +1

              Да, я как раз об этом и говорю, что при большом количестве запросов будет срабатывать метод async_accept, при этом он сразу же будет вызывать s.async_write, и мы будем терять сигналы-обработчики. Клиенты будут просто висеть на подключении и не получат файл политики.

              Насчет Boost.Asio возможно, надо поглядеть. Как вернусь из поездки сразу гляну.


              1. roman_kashitsyn
                02.10.2015 15:45

                Клиенты будут просто висеть на подключении и не получат файл политики.

                Да счего это вдруг? async_write он на то и async, что не блокируется. Он говорит ОС «запиши данные», не дожидаясь завершения, и цикл сразу возвращается к обработке других клиентов.
                мы будем терять сигналы-обработчики

                Я говорил о UNIX-сигналах, которые посылаются при перезапуске сервиса или при запросе обновления конфигурации.


              1. roman_kashitsyn
                02.10.2015 15:58

                Кстати, у вас в коде обработки сигналов я не нашёл. Неплохо бы уметь релодить xml-файл по SIGHUP и завершать обработку клиентов по SIGTERM / SIGQUIT.


                1. nightrain912
                  02.10.2015 16:09

                  А я нашел: Server.cpp:38

                  	this->sigint.set<&Server::on_terminate_signal>();
                  	this->sigint.start(SIGINT);
                  
                  	this->sigterm.set<&Server::on_terminate_signal>();
                  	this->sigterm.start(SIGTERM);
                  
                  	this->sigkill.set<Server::on_terminate_signal>();
                  	this->sigkill.start(SIGKILL);
                  


                  Да, правда SIGHUP нет, но обновление xml — настолько редкое событие, что можно и просто перезапустить демон )


                  1. roman_kashitsyn
                    02.10.2015 16:44
                    +1

                    Ага, спасибо, не там искал. SIGKILL нельзя перехватить :)


  1. sba
    02.10.2015 11:49
    +1

    Свой C++ демон это хорошо. Без этой идеи не было бы статьи. Только зачем изобретать велосипед? Первая же ссылка nginx crossdomain.xml


    1. helper2424
      02.10.2015 12:35

      Да, вы правы, использовать nginx c модулем — неплохое решение. В свое время, когда встретился с задачей не особо над этим задумывался и написал свое решение за 30 минут на Java+Netty. Позже поделился с коллегами из других студий. Они попросили сделать демон на C++.

      Плюс, по первой ссылке в гугле народ ругается на модуль для nginx.


      1. sba
        02.10.2015 13:16
        +1

        Весьма странно. Там нечему не работать или плохо работать. nginx из коробки отдает статические файлы без каких либо модулей. Все что нужно — объявить новую секцию server и повесить ее на 843 порт.

            # Add this to your nginx.conf under http { }
            server {
                listen 843;
                server_name localhost;
                
                location / {
        	    rewrite ^(.*)$ /crossdomain.xml;
        	}
        
        	error_page 400 /crossdomain.xml;
        
        	location = /crossdomain.xml {
        	    root /var/www/crossdomain;
        	}
            }
        

        Это решение работает максимально быстро. Тяжело найти или придумать что-нибудь более производительное. В наших играх много лет пользуемся именно таким решением. Хотя начинал я аналогичным образом со своего с++ демона году эдак в 2008.


  1. HomoLuden
    02.10.2015 12:37

    [offtop]А почему чары на КДПВ здороваются левыми руками?[/offtop]


    1. helper2424
      02.10.2015 12:45
      +1

      Хороший вопрос, так решила фантазия художника. Как вариант — он когда рисует часто зеркалит изображение, возможно где то там кроется ответ.


      1. HomoLuden
        05.10.2015 10:02

        Если б отзеркалил, то было бы ++) вместо С++.

        Если только слои отдельно друг от друга зеркалились… Однако логотип Unity наклонен в правильную сторону. Странновато.


  1. shai_hulud
    02.10.2015 12:47
    +4

    Хорошая, годная статья, хоть и народ пишет что можно в десять строк накатать на других языках.
    У нас был аналогичный сервер на C#, как только мы получали сокет, сразу туда толкали байты из памяти через Async API и после закрывали. Без явных менеджеров и очередей. Думаю на питоне можно накатать еще проще.

    на злобу дня:


  1. Ivan_83
    03.10.2015 19:10
    +3

    «Что понадобится: libev, boost program_options, Easylogging++»
    WTF!?
    Что вы тут развели вообще!?
    Какие с++11!? какие нафик async!? Что за сферический програмизм в вакууме!?

    Всё что нужно это:
    man epoll
    man socket
    man bind
    man listen
    man accept4
    man send
    man close
    Между делом ещё open + read для подгрузки файла, либо sendfile(+mmap+lock для тяжёлых случаев чтобы производительность не просаживалась) вместо send+read.
    Может ещё понадобится fcntl для установки O_NONBLOCK — на старых линухах.
    Ещё вот так не помешает: setsockopt(skt, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(int)); если SO_NOSIGPIPE в системе определён.
    shutdown() — не нужен и даже вреден!

    В инете куча примеров как написать на epoll простой сервер который принимает клиентов и далее читает/пишет им.
    Там строк 500 кода с коментами и оно порвёт любой nginx по скорости, даже работая в один поток — да да, тут работы столько что одному ядру делать нечего, можно не напрягаться, но если что setsockopt(SO_REUSE_PORT) + pthread_create() и получаем многопоточное приложение, прямо как nginx в последних версиях.
    Даже память на клиента выделять не надо и за количеством клиентов следить — в худшем случае процесс упрётся в лимит файловых дескрипторов.

    На сях:
    https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
    http://byteandbits.blogspot.nl/2013/08/tcp-echo-server-using-epoll-example-for.html
    http://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/
    http://www.opennet.ru/base/dev/epoll_intro.txt.html

    Замечания от меня:
    1. там где они делают make_non_blocking — этого же можно добиться используя accept4(SOCK_NONBLOCK) и socket(SOCK_NONBLOCK).
    2. epoll_create(0) — смысла туда писать количество нет.
    3. эвенты лучше вычитывать по одному за раз, это сильно облегчает жизнь и код.

    На питоне:
    http://scotdoyle.com/python-epoll-howto.html
    http://habrahabr.ru/post/121103/

    Примерная схема:
    main() {
    читаем конфиг / параметры ком строки
    — пишем в глобальные переменные номер порта и ip куда биндится
    — открываем файл xml, делаем mmap + lock, файловый дескриптов в глобальные переменные
    — таймаут чтения/отправки пишем в глобальные
    — количество потоков
    создаём потоки которые уходят в: thread_func()
    }

    thread_func() {
    создаём сокет с SOCK_NONBLOCK
    делаем его SO_REUSE_PORT
    биндим
    слушаем
    создаём epoll
    пихаем в него созданный сокет
    крутимся в бесконечном цикле читая события epoll

    — слушающий сокет:
    бесконечный цикл с accept4(), новый сокет пихаем в epoll на read + создаём таймер и устанавливаем время (см ниже), при ошибке выходим в цикл с чтением событий
    — клиентский сокет:
    — read: делаем sendfile() и close / возможно меняем на write и уже на write отправляем файл и закрываем соединение
    }

    Ну да, ещё бы не плохо таймауты добавить через create_timer_fd(), в udata epoll при этом складывать:
    первые 24 бита — дескриптор сокета, вторые 24 бита — дескриптор таймера, оставшееся 16 бит — флаги: это_таймер = это событие таймера и нужно сделать close() для обоих дескрипторов, в противном случае нужно или перезапустить таймер или не забыть его тоже грохнуть тк дело сделано. Ещё 15 бит в запасе. Или 28+28+8 или 30+30+4.
    Без таймаутов сервер легко за DoSить просто подключившись и вися, к тому же иногда клиенты умирают молча сами (комп ребутнулся, роутер завис и после получил др адрес и тп), принудительный keepalive таких должен выявлять, но таймер позволит и живых молчунов отстреливать.

    Вот это всё.
    Больше тут придумывать нечего, только красоту наводить: добавить механизм завершения программы, возможность биндить пачку сокетов на разных портах и IPv6, можно принты/логи ошибок дописать.
    Можно ещё заюзать: setsockopt(skt, IPPROTO_TCP, TCP_DEFER_ACCEPT....).
    Можно потоком создавать по колличеству ядер и биндить потоки на ядра при старте потока (там как раз arg передаётся при создании потока, вот номер порядковый=номер ядра куда биндится туда и отдавать).
    Ещё можно что то придумать чтобы создавать только один таймер на поток, который тикает раз в секунду и когда он срабатывает нужно пробегать по какому то списку с сокетами + время таймаута для данного потока и закрывать старые сокеты. Но тогда придётся на каждый сокет выделять память для структуры в которой будет идент сокета, время таймаута и элемент для хранения в списке одно/двусвязном.

    Вот примерно так, дальше по скорости расти некуда: nginx далеко позади.

    Хотя маразм предела не знает: можно же написать модуль ядра для этого и всё делать прямо в ядре :)

    2 sba:
    А разве nginx не отдаёт http заголовки перед файлом?
    xml парсеры это не смущает?

    2 solver:
    Это идеальная задача для С. Обычного, без плюсов.
    Любители могут сделать тоже на асме. Тут даже память выделять не нужно, после инита/старта.
    Но можно и на любом другом языке где есть epoll() и всё что перечислено выше.


    1. StrangerInRed
      04.10.2015 12:36

      Плюсую, но сейчас все больше гонятся за скорость разработки. Столько man страниц прочитать это ж ужас. Да и язык без сборки мусора, такое кароче. Все хотят плюшек. P.S. на асме через syscall получится тоже неплохо


    1. solver
      05.10.2015 11:11

      > Это идеальная задача для С. Обычного, без плюсов.

      Для сферического программизма в вакууме, да. Так и есть.
      А для реального мира, это странно.
      В 2015 году выбирать язык, в котором можно очередями бахать себе и всем окружающим по ногам.
      В котором куча заморочек, которые знают немногие на этой планете.
      Будет только отчаянный бородатый дядька, который кроме С ничего не знает и не хочет знать))
      Тем более для задачи, с которой любой PHP справится на ура.
      Это решение перепишут на Go или аналог, при любой необходимости внести изменения, сразу после увольнения бородатого дядьки или перед этим)))


      1. Ivan_83
        10.10.2015 00:08

        GO явно не оптимален для этой задачи, как и пых.
        Си позволяет в данном случае работать максимально быстро с минимум накладных расходов, сложность/объём кода не большие, более того, можно даже прикинуть полный путь данных от железа до железа через весь код, включая ядерный, там не так уж и много.
        В принципе подойдёт любой язык который умеет то что я выше описал.

        Вся аргументация Go пограмистов отражает только одно: я боюсь брать ответственность за то что я сделал на себя, я хочу чтобы за меня думали.
        Впрочем, я лично считаю некоторые аргументы приводимые в пользу GO поводом обратится к психологу за консультацией тк на лицо ярко выраженные фобии: пациент не мотивированно боится писать программы.

        Мне пока не встречалась ни одна программа которая требует GO для своей сборки, те может какие то программы на Go кто то и пишет, но они никому не нужны.
        На ум приходит только GoVPN, который (тоже никому не нужен) даже гига прососать не может на современном железе около топового, при всех стараниях, тогда как на на сях эта задача легко решается, в том числе и запихиванием своего модуля в ядро.

        Хз что вы к этому GO так привязались, не вижу ни одной задачи которую он бы решал лучше других.
        Я лично за си не цепляюсь, когда нужно было с сетью и sql работать и запросы легко менять я выбрал перл, хотя и не знал его практически, когда потребовалось работать с http и xml-soap выбрал пых, хотя тоже не знал его, и ещё в паре случаев я выбрал эти языки и решал с их помощью свои/чужие проблемы. Кодил на жабаскрипте (+пхп пару строк) плагин к ruTorrent потому что только на нём и можно.
        Раньше с удовольствием кодил на Visual Basic, vb5 была просто класс как IDE и встроенная справка на все функции, и авто дополнения и прочие радости.

        А что в там в GO есть?
        Какие то нити, кто то написал что он с сетью типа работает хорошо?
        Я работал с сетью в винде, бсд и линухе, очень сомнительно что какой то язык с претензией на универсальность будет работать хоть отдалённо также как хорошо как нормально написанный код на си.
        Впрочем, boost (мега тяжёлый и мной не любимый) претендует на универсальное решение подобных проблем в си, но он по сути лишь прослойка, которая предоставляет унифицированный интерфейс к системным вещам на разных ОС. Всякие libev тоже.
        Не говоря про тонны кода который есть на си, пхп, перле, тонны либ и компонентов.

        Я уже повидал как загнулся Visual Basic, загибается ещё пара языков и куча технологий, потому я банально не хочу копаться во всяком GOвне только ради моды. Я знаю что си никуда не денется и через 10 и через 30 лет (мне бы столько прожить), знаю что мой код можно собрать под любую платформу, иногда вообще без переделок. Си в компьютере это как английский в жизни — куда не плюнь везде он.
        У меня у самого куча кода из которого можно собрать очень многое вот прямо так, сразу, а кое что можно по быстрому подтянуть из чужих либ.

        А теперь ещё раз: что есть такого в GO чтобы вообще запоминать что он существует?
        Кучка адептов — зомби-фанатиков, которые не осилили си, боятся ошибок хуже смерти, почему то считают остальные языки отстоем и цепляются за go?

        Классы, нити, лямбды, перегрузка — такое ощущение что люди за деревьями не видят леса.
        Столько разводить сопли по поводу того как лучше кодить и какой язык круче вместо того чтобы решать реальные задачи и проблемы.
        Есть проблема: раздать файл по сети.
        Я накорябал своё решение на «си»=средствами ОС (+дал пачку примеров с кодом на 60-70% реализации, недостающее или описано или гуглится, типа разбора ком строки).

        Где твоё решение на твоём супер-пупер GO?
        Что ты лично сделал для хип-хопа? :)

        Бородатым дядькам хватит работы ещё на долго, покуда GOвённые программы работают на не GOвённых ОС :)


        1. solver
          10.10.2015 13:29

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

          Он кроскмпилится, ни от чего не зависит, на выходе один бинарник, производительность достаточная для 99.99% задач. Весь тулинг для него тоже простой. Сам язык простой как палка.
          Это все как раз вот для таких простых случаев.

          >Си позволяет в данном случае работать максимально быстро с минимум накладных расходов, сложность/объём кода не большие, более того, можно даже прикинуть полный путь данных от железа до железа через весь код, включая ядерный, там не так уж и много.

          А вот это реально смешно. Вот эта фигня очень хорошо характеризует этих самых упоротых бородатых дядек. У которых весь мир построен на С. В задаче, в которой производительность вообще никому не уперлась, которую успешно решают даже на PHP в 5 строчек, они будут тащить слои вплоть до ядерного уровня…

          Такие смешные дядьки… и эти их детские потуги, как они важно раздувают щеки, что на них весь мир крутится))

          >Бородатым дядькам хватит работы ещё на долго, покуда GOвённые программы работают на не GOвённых ОС

          Открою тебе один большой секрет… все эти говеные ОС написали эти самые бородатые дядьки на С. Дырка на дырке и дыркой погоняет. Одни баги, все глючит, сыпется. Из-за этих вот криворучек с завышеным самомением… мол мы тут крутые-бородатые и щас на уровне ядра все разрулим, одни проблемы. Ибо понтов много, а софт в итоге говно. Но да, они таки лезут своими кривыми руками на уровеь ядра, и кичатся этим.

          Языки типа Go, как раз и позволяют так как ты, крутых ядерщиков, держать подальше от этого саого ядра.


          1. StrangerInRed
            10.10.2015 20:40

            Если бы не Mach и FreeBSD, который педалился теми самыми бородатыми дядьками в свитерах, все хипстеры планеты немножко бы загрустили. О, а еще Unix, о а еще… и таких а еще хуева гора. Софт не говнно когда делается комплексно.


            1. solver
              11.10.2015 11:26

              Да я с этим не спорю.
              Просто люди кичатся, что они на С тут щас все красиво сделают, а результат… Оно конечно в целом работает, но баг на баге и дырка на дырке. Все эти Go, Rust и иже с ними, они не просто по приколу появляются. Это борьба с человеческим фактором. Если бы все люди были идеальны, то можно было на С остановиться)) А так косячат люди с завидной регулярностью и с этим надо что-то делать.


          1. Ivan_83
            12.10.2015 04:27

            Выдыхай!
            Иногда лучше жевать, чем говорить.

            Автор замерял время исполнения, значит производительность для него имеет какое то значение.

            Для простых случаев никакая GOмосятина не нужна!
            Такие простые случаи, когда производительность не особо нужна, уже лет 20 решаются штатными средствами системы: inetd + cat:
            В файл: /etc/services
            добавляем:
            policy-srv 843/tcp # policy-file-request

            В файл: /etc/inetd.conf
            добавляем:
            policy-srv stream tcp nowait/400 root /bin/cat cat /etc/inetd.conf
            policy-srv stream tcp6 nowait/400 root /bin/cat cat /etc/inetd.conf

            [пере]запускаем inetd.

            400 и /etc/inetd.conf — меняем по потребностям.
            man inetd.conf

            Язык программирования не может уберечь от всех ошибок, например от алгоритмических.
            Столько раз видел как люди даже в Visual Basic умудрялись накосячить, что потом за ними приходилось дебажить.
            А дебажить в Visual Basic легко и просто, ибо удобная IDE: натыкал точек останова и сидишь мышкой наводишь смотришь значения, это не в каком то GO непонятно где и как ползать и не си с принтами, gdb и медитациями :) (виндовая IDE для си также удобна как и для вб была)
            Помимо алгоритмических ошибок часто были ошибки автоматического приведения типов.
            Много ошибок типа: скопипастили кусок и забыли поменять пару переменных.
            Да даже с черепахой можно накосячить :)
            Поэтому человеческий фактор — не устраним.

            Я не могу говорить за весь мир, но намного чаще встречаю именно алгоритмические ошибки (не то условие, не так посчитали… что не приводит к падению), чем ошибки связанные с переполнением / выходом за границы и прочим.
            На си это лечится разбивкой на мелкие модули, оборачиванием типичных операций в апи, в плюсах юзают классы, которые часть операций автоматизируют.

            Вот эти все го, руст — они просто куча таких оборачиваний и автоматизаций собранная вместе.
            Мне то в принципе пофик, у каждого додика своя методика, но серебряной пули из них явно не выйдет: там решены пара проблем и за это уплачена своя цена.
            Это при том, что языков программирования и без того слишком дохрена, на фоне этого многообразия новинки ничем особенным не выделяются.

            Дядьки: 2
            солвер: 0

            Приходи когда родишь нечто рабочее.


            1. solver
              12.10.2015 10:27

              >Дядьки: 2
              >солвер: 0

              Омг… Детский сад, штаны на лямках…

              P.S. Приходи, когда дядек приведешь. А пока смотри, что-бы песочек в ботиночки не попал, когда куличики лепить будешь)