Libev - обратные вызовы ввода / вывода
У меня есть чат-сервер в C/Linux с использованием сокетов TCP. При использовании libev я могу создать наблюдатель ev_io для событий чтения один раз для сокета. Что-то вроде:
ev_io* new_watcher = (ev_io*)malloc(sizeof(ev_io));
//initialize the watcher
ev_init(new_watcher, read_cb);
//set the fd and event to fire on write
ev_io_set(new_watcher, watcher->fd, EV_READ);
//start watching
ev_io_start(loop, new_watcher);
и это прекрасно работает, потому что событие чтения будет срабатывать только при наличии данных для чтения. Однако я должен по-разному относиться к событиям записи, потому что они постоянно запускаются, даже когда у меня нет данных для записи. Чтобы решить эту проблему, мой read_callback создает наблюдателя ev_io для записи данных только тогда, когда есть данные, готовые для записи, а затем write_callback удалит наблюдателя после того, как отправит свое сообщение.
Это означает, что я выделяю, инициализирую, настраиваю, наблюдаю, наблюдаю и освобождаю наблюдателя записи каждый раз, когда мне нужно обработать сообщение. Я волнуюсь, что я могу обращаться с этим неправильно и неэффективно.
Каков наилучший метод для обработки событий write_callback в libev?
Заранее спасибо.
3 ответа
Распределение может добавить некоторые накладные расходы, вы можете использовать статическую переменную вместо malloc или malloc один раз и только бесплатно после завершения цикла обработки событий. Вам нужно только установить перед записью и сбросить после того, как это успешно. Но да, вот как это должно быть сделано.
Легко, есть также ev_io_stop, так что вы не запускаете средство записи, если у вас нет ничего для записи, и внутри обратного вызова вы вызываете ev_io_stop, когда вы пишете весь буфер.
В общем случае, когда вы редко переполняете буфер записи (поскольку ваши данные невелики и вы не слишком часто пишете), вы можете сделать это более эффективным, пытаясь записать данные напрямую (если наблюдатель не активен) и только буферизация данных и запуск наблюдателя записи, если вы не можете полностью записать его.
Согласно приведенным выше предположениям это означает, что вам почти никогда не нужно запускать средство записи. Недостатком является значительно более сложный код, поэтому во многих случаях лучше начинать с простой логики "добавление данных в буфер записи, запуск наблюдателя, остановка внутри наблюдателя, если буфер был полностью записан".
Способ, которым я решил эту ситуацию, заключался в том, чтобы иметь функцию для записи данных, которая принимает указатель на буфер и длину. Он сохраняет указатель и длину в структуре данных очереди и включает событие записи.
Когда срабатывает обратный вызов события write, он проверяет очередь записи на наличие ожидающих записей. Если они есть, он выполняет следующую ожидающую запись в очереди и записывает ее в дескриптор файла. Затем, непосредственно перед завершением обратного вызова записи, он проверяет, пуста ли ожидающая очередь записи. Если так, то это отключает событие записи.
Если вы сделаете объекты событий чтения / записи глобальными переменными, они будут выделяться и освобождаться только один раз. Вы включаете событие записи всякий раз, когда знаете, что есть данные для записи, и отключаете его, когда больше нет данных для записи.
Мой код немного сложнее, чем приведенное выше описание, но я опубликую здесь ссылку, чтобы вы могли посмотреть. Код, о котором я говорю конкретно, находится в aiofd.h и aiofd.c (aiofd == дескриптор файла асинхронного ввода-вывода): https://bitbucket.org/wookie/cutil/
Надеюсь, это поможет.