зарегистрировать обратный вызов в одном приложении для извлечения в другом - PullRequest
10 голосов
/ 23 апреля 2011
gcc 4.6.0 c89

У меня есть тип приложения клиент-сервер. На сервере есть некоторый код в цикле событий, который будет ожидать события от клиента.

Это не клиентский сервер, который будет использовать сокеты UDP / TCP. Но клиент и сервер будут работать на одной машине Linux. Я думаю, это как app1, разговаривающая с app2, работающим на том же сервере.

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

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

while(running) {
    switch(event) {
        case START_SDL:
        /* DO something */
        break;

        case DELETE_EDL:
        /* Do something */
        break;
    }
}

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

Большое спасибо за любые предложения,

Ответы [ 5 ]

7 голосов
/ 01 мая 2011

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

Структура задачи и очередь

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

struct task {
    /* function callback */
    void (*fun)(void *);
    /* parameter to pass to callback */
    void *arg;
};

Синхронизация

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

/* this has to be initialized in main */
sem_t sem;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

Рабочая функция

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

static void *worker(void *arg)
{
    struct task t;

    /* detach */
    pthread_detach(pthread_self());

    /* loop forever */
    while (1) {
        /* block until we have work to do */
        sem_wait(&sem);

        /* we've got work to do */
        pthread_mutex_lock(&mtx);
        /* get the task */
        t = get_task();
        pthread_mutex_unlock(&mtx);

        /* we are safe now, nobody can touch t */
        /* execute the callback - here is your function pointer*/
        (*t.fun)(t.arg);
    }
    return NULL;
}

Основная функция

Роль главной функции - инициализация вещей и выполнение задач.

pthread_t t1;

/* initialize unnamed semaphore */
sem_init(&sem, 0, 0);

/* start worker thread */
if (0 != pthread_create(&t1, NULL, worker, NULL)) {
    perror("pthread_create");
    exit(1);
}

Задачи нажатия

На этом этапе рабочий поток ожидает задачи, которые вы можете отправить из основного.

pthread_mutex_lock(&mtx);
push_task(my_task);
pthread_mutex_unlock(&mtx);

Как этот сервер узнает, что клиент запускает события? Вам решать, есть много способов сделать IPC в Unix. Я предлагаю использовать очередь сообщений .

Пример очереди сообщений сервера

#define MSGSIZE 1024

int main()
{
    mqd_t mq;
    struct mq_attr attr;
    char message[MSGSIZE];
    int read_bytes;

    attr.mq_maxmsg = 10;
    attr.mq_msgsize = MSGSIZE;

    mq = mq_open("/queue", O_RDWR | O_CREAT, 0700, &attr);
    if ((mqd_t)-1 == mq) {
        perror("mq_open");
        exit(1);
    }

    while (1) {
        /* get message from queue */
        read_bytes = mq_receive(mq, message, MSGSIZE, NULL);
        if (-1 == read_bytes) {
            perror("mq_receive");
            exit(1);
        }

        /* do what you wish with the message */
    }
}

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

Это всего лишь (возможно, сломанные) кусочки одной большой головоломки. Ваша задача собрать их во все, что вы строите.

5 голосов
/ 01 мая 2011

Если это не домашнее задание, а не изобретать велосипед, я бы рекомендовал использовать одну из многих доступных библиотек IPC:

1 голос
/ 06 мая 2011

Я не знаю много о C или C ++, но в java я бы использовал Future Task с комбинацией Non-Blocking IO в качестве решения этой проблемы.Пожалуйста, взгляните только на идею.

1 голос
/ 06 мая 2011

Один серверный процесс с одним клиентским процессом .. выглядит как производитель - потребитель. Это легко обрабатывается с помощью труб.

bash# client | server 

Ваш клиент записывает события в стандартный вывод, сервер читает из стандартного ввода. Если вам нужно более одного клиента, см. mmutz's answer

1 голос
/ 23 апреля 2011

On * nix,

Я бы предложил использовать select () или poll () , или, если вы действительно хотите, threads .

На окнах, this является отличным руководством по Winsock API. Я не очень разбираюсь в программировании под Windows, но, насколько мне известно, Winsocks - это путь для низкоуровневого ввода-вывода сокетов.

EDIT;

Я вижу комментарии и, видимо, вы используете Linux и темы.

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

Однако это определенно возможно.

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

Если вы находитесь в производственной среде, все не так просто. Во-первых, я бы предложил вам прочитать this . Затем рассмотрим пул потоков. По сути, вы начинаете с нескольких потоков (т. Е. Пула), а затем завершаете задачи по ходу работы. Очень хорошее описание можно найти в Википедии . И в PDF-файле, на который я ссылаюсь, показаны и другие способы.

Если вы не хотите использовать поток после прочтения всего этого, я все равно рекомендую select () и poll (), они очень просты в использовании.

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

...