Подтвердить что ты не робот

Код для асинхронного ввода-вывода

В то время как асинхронные IO (неблокирующие дескрипторы с select/poll/epoll/kqueue и т.д.) не являются самой документированной вещью в Интернете, есть несколько хороших примеров.

Однако, все эти примеры, определяя дескрипторы, возвращаемые вызовом, имеют только "do_some_io(fd)" заглушку. Они действительно не объясняют, как наилучшим образом подойти к фактическому асинхронному IO в таком методе.

Блокировка ввода-вывода очень аккуратная и простая для чтения кода. С другой стороны, неблокирование, асинхронное IO, волосатое и грязное.

Какие подходы существуют? Что является надежным и читаемым?

void do_some_io(int fd) {
  switch(state) {
    case STEP1:
       ... async calls
       if(io_would_block)
          return;
       state = STEP2;
    case STEP2:
       ... more async calls
       if(io_would_block)
          return;
       state = STEP3;
    case STEP3:
       ...
  }
}

или, возможно, (ab) с использованием вычислений GCC:

#define concatentate(x,y) x##y
#define async_read_xx(var,bytes,line)       \
   concatentate(jmp,line):                  \
   if(!do_async_read(bytes,&var)) {         \
       schedule(EPOLLIN);                   \
       jmp_read = &&concatentate(jmp,line); \
       return;                              \
}

// macros for making async code read like sync code
#define async_read(var,bytes) \
    async_read_xx(var,bytes,__LINE__)

#define async_resume()            \
     if(jmp_read) {               \
         void* target = jmp_read; \
         jmp_read = NULL;         \
         goto *target;            \
     }

void do_some_io() {
   async_resume();
   async_read(something,sizeof(something));
   async_read(something_else,sizeof(something_else));
}

Или, возможно, исключения С++ и конечный автомат, поэтому рабочие функции могут запускать бит abort/resume или, возможно, управляемый таблицей государственный компьютер?

Это не то, как заставить его работать, и как сделать его поддерживаемым, чтобы я гонялся!

4b9b3361

Ответ 1

Я предлагаю взглянуть на: http://www.kegel.com/c10k.html, затем взгляните на существующие библиотеки, такие как libevent, Boost.Asio, которые уже выполняют работу и посмотреть, как они работают.

Дело в том, что для каждого типа системного вызова подход может быть различным:

  • выберите простой реактор
  • epoll имеет как интерфейс, связанный с краем, так и уровнем, требующий разного подхода.
  • iocp - это проактор, требующий другого подхода.

Предложение: используйте хорошую существующую библиотеку, например Boost.Asio для С++ или libevent для C.

EDIT: так ASIO обрабатывает этот

class connection {
   boost::asio:ip::tcp::socket socket_;
public:
   void run()
   {
         // for variable length chunks
         async_read_until(socket_,resizable_buffer,'\n',
               boost::bind(&run::on_line_recieved,this,errorplacehplder);
         // or constant length chunks
         async_read(socket_,buffer(some_buf,buf_size),
               boost::bind(&run::on_line_recieved,this,errorplacehplder);
   }
   void on_line_recieved(error e)
   {
        // handle it
        run();
   }

};

Поскольку ASIO работает как проактор, он уведомляет вас о завершении работы и обрабатывает EWOULDBLOCK внутренне.

Если вы говорите как реактор, вы можете имитировать это поведение:

 class conn {
    // Application logic

    void run() {
       read_chunk(&conn::on_chunk_read,size);
    }
    void on_chunk_read() {
         /* do something;*/
    }

    // Proactor wrappers

    void read_chunk(void (conn::*callback),int size, int start_point=0) {
       read(socket,buffer+start,size)
       if( complete )
          (this->*callback()
       else {
          this -> tmp_size-=size-read;
          this -> tmp_start=start+read;
          this -> tmp_callback=callback
          your_event_library_register_op_on_readable(callback,socket,this);
       }
    }
    void callback()
    {
       read_chunk(tmp_callback,tmp_size,tmp_start);
    }
 }

Что-то вроде этого.

Ответ 2

Государственные машины - один хороший подход. Это немного сложная задача, которая спасет вас от головных болей в будущем, когда будущее начнется действительно, очень скоро.; -)

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

Ответ 3

Для решения этой проблемы существует отличный шаблон дизайна "coroutine".

Это лучшее из обоих миров: аккуратный код, точно такой же, как синхронный поток io и отличная производительность без переключения контекста, например async io. Coroutine выглядит как одиночный синхронный поток, с одним указателем инструкции. Но многие сопрограммы могут работать в одном потоке ОС (так называемая "совместная многозадачность" ).

Пример кода сопрограммы:

void do_some_io() {
   blocking_read(something,sizeof(something));
   blocking_read(something_else,sizeof(something_else));
   blocking_write(something,sizeof(something));
}

Похож на синхронный код, но на самом деле поток управления использует другой способ, например:

void do_some_io() {
   // return control to network io scheduler, to handle another coroutine
   blocking_read(something,sizeof(something)); 
   // when "something" is read, scheduler fill given buffer and resume this coroutine 

   // return control to network io scheduler, to handle another coroutine
   CoroSleep( 1000 );
   // scheduler create async timer and when it fires, scheduler pass control to this coroutine
    ...
   // and so on 

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

Пример реализации С++ coroutines - это "boost.coroutine" (на самом деле это не часть boost:) http://www.crystalclearsoftware.com/soc/coroutine/ Эта библиотека полностью реализует механику coroutine и может использовать boost.asio в качестве планировщика и асинхронного слоя.

Ответ 4

У вас должен быть основной цикл, который предоставляет async_schedule(), async_foreach(), async_tick() и т.д. Эти функции, в свою очередь, помещают записи в глобальный список методов, которые будут выполняться при следующем вызове async_tick(). Затем вы можете написать код, который будет намного более аккуратным и не включает никаких операторов switch.

Вы можете просто написать:

async_schedule(callback, arg, timeout); 

Или:

async_wait(condition, callback, arg, timeout); 

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

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

Код здесь: https://github.com/mkschreder/fortmax-blocks/blob/master/common/kernel/async.c

Ответ 5

Вы хотите отделить "io" от обработки, после чего прочитанный вами код станет очень читаемым. В основном у вас есть:


    int read_io_event(...) { /* triggers when we get a read event from epoll/poll/whatever */

     /* read data from "fd" into a vstr/buffer/whatever */

     if (/* read failed */) /* return failure code to event callback */ ;

     if (/* "message" received */) return process_io_event();

     if (/* we've read "too much" */) /* return failure code to event callback */ ;

     return /* keep going code for event callback */ ;
    }


    int process_io_event(...) {
       /* this is where you process the HTTP request/whatever */
    }

... тогда реальный код находится в событии процесса, и даже если у вас есть несколько запросов ответов, он довольно читабельен, вы просто делаете "return read_io_event()" после установки состояния или чего-то еще.