Files
KnowStreaming/docs/zh/Kafka客户端/c_client.md
2023-02-14 14:57:39 +08:00

7.0 KiB
Raw Blame History

kafka-manger-logo

一站式Apache Kafka集群指标监控与运维管控平台


3. Kafka C 示例

1. 申请资源

使用前请确保有下列资源,如果可以跳过!

  • topic[Topic 申请](../5. Kafka 云平台——资源申请/Topic 申请.md)
  • appId和password[应用申请](../5. Kafka 云平台——资源申请/应用申请.md)
  • clusterId可以在Topic详情中查看

程序启动前请确保AppID具有Topic的使用权限

2. 实例

使用librdkafka

2.1 Producer发送实例

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include "rdkafka.h"
static int run = 1;
static void stop(int sig){
    run = 0;
    fclose(stdin);
}
/*
    每条消息调用一次该回调函数,说明消息是传递成功(rkmessage->err == RD_KAFKA_RESP_ERR_NO_ERROR)
    还是传递失败(rkmessage->err != RD_KAFKA_RESP_ERR_NO_ERROR)
    该回调函数由rd_kafka_poll()触发,在应用程序的线程上执行
 */
static void dr_msg_cb(rd_kafka_t *rk,
                      const rd_kafka_message_t *rkmessage, void *opaque){
        if(rkmessage->err)
            fprintf(stderr, "%% Message delivery failed: %s\n",
                    rd_kafka_err2str(rkmessage->err));
        else
            fprintf(stderr,
                        "%% Message delivered (%zd bytes, "
                        "partition %"PRId32")\n",
                        rkmessage->len, rkmessage->partition);
        /* rkmessage被librdkafka自动销毁*/
}
int main(int argc, char **argv){
    rd_kafka_t *rk;            /*Producer instance handle*/
    rd_kafka_topic_t *rkt;     /*topic对象*/
    rd_kafka_conf_t *conf;     /*临时配置对象*/
    char errstr[512];
    char buf[512];
    const char *brokers;
    const char *topic;
    if(argc != 5){
        fprintf(stderr, "%% Usage: %s <broker> <topic>\n", argv[0]);
        return 1;
    }
    brokers = argv[1];
    topic = argv[2];
    char* username = argv[3];
    char* password = argv[4];
    /* 创建一个kafka配置占位 */
    conf = rd_kafka_conf_new();
    /*创建broker集群*/
    if (rd_kafka_conf_set(conf, "bootstrap.servers", brokers, errstr,
                sizeof(errstr)) != RD_KAFKA_CONF_OK){
        fprintf(stderr, "%s\n", errstr);
        return 1;
    }
    rd_kafka_conf_set(conf, "security.protocol", "SASL_PLAINTEXT", errstr, sizeof(errstr));
    rd_kafka_conf_set(conf, "sasl.mechanisms", "PLAIN", errstr, sizeof(errstr));
    rd_kafka_conf_set(conf, "sasl.username", username, errstr, sizeof(errstr));
    rd_kafka_conf_set(conf, "sasl.password", password, errstr, sizeof(errstr));
    rd_kafka_conf_set(conf, "api.version.request", "true", errstr, sizeof(errstr));
     
  
    //开启kafka日志
    rd_kafka_conf_set(conf, "debug", "topic", errstr, sizeof(errstr));
    /*设置发送报告回调函数rd_kafka_produce()接收的每条消息都会调用一次该回调函数
     *应用程序需要定期调用rd_kafka_poll()来服务排队的发送报告回调函数*/
    rd_kafka_conf_set_dr_msg_cb(conf, dr_msg_cb);
    /*创建producer实例
      rd_kafka_new()获取conf对象的所有权,应用程序在此调用之后不得再次引用它*/
    rk = rd_kafka_new(RD_KAFKA_PRODUCER, conf, errstr, sizeof(errstr));
    if(!rk){
        fprintf(stderr, "%% Failed to create new producer:%s\n", errstr);
        return 1;
    }
    /*实例化一个或多个topics(`rd_kafka_topic_t`)来提供生产或消费topic
    对象保存topic特定的配置并在内部填充所有可用分区和leader brokers*/
    rkt = rd_kafka_topic_new(rk, topic, NULL);
    if (!rkt){
        fprintf(stderr, "%% Failed to create topic object: %s\n",
                rd_kafka_err2str(rd_kafka_last_error()));
        rd_kafka_destroy(rk);
        return 1;
    }
    /*用于中断的信号*/
    signal(SIGINT, stop);
    fprintf(stderr,
                "%% Type some text and hit enter to produce message\n"
                "%% Or just hit enter to only serve delivery reports\n"
                "%% Press Ctrl-C or Ctrl-D to exit\n");
     while(run && fgets(buf, sizeof(buf), stdin)){
        size_t len = strlen(buf);
        if(buf[len-1] == '\n')
            buf[--len] = '\0';
        if(len == 0){
            /*轮询用于事件的kafka handle
            事件将导致应用程序提供的回调函数被调用
            第二个参数是最大阻塞时间如果设为0将会是非阻塞的调用*/
            rd_kafka_poll(rk, 0);
            continue;
        }
     retry:
         /*Send/Produce message.
           这是一个异步调用在成功的情况下只会将消息排入内部producer队列
           对broker的实际传递尝试由后台线程处理之前注册的传递回调函数(dr_msg_cb)
           用于在消息传递成功或失败时向应用程序发回信号*/
        if (rd_kafka_produce(
                    /* Topic object */
                    rkt,
                    /*使用内置的分区来选择分区*/
                    RD_KAFKA_PARTITION_UA,
                    /*生成payload的副本*/
                    RD_KAFKA_MSG_F_COPY,
                    /*消息体和长度*/
                    buf, len,
                    /*可选键及其长度*/
                    NULL, 0,
                    NULL) == -1){
            fprintf(stderr,
                "%% Failed to produce to topic %s: %s\n",
                rd_kafka_topic_name(rkt),
                rd_kafka_err2str(rd_kafka_last_error()));
            if (rd_kafka_last_error() == RD_KAFKA_RESP_ERR__QUEUE_FULL){
                /*如果内部队列满等待消息传输完成并retry,
                内部队列表示要发送的消息和已发送或失败的消息,
                内部队列受限于queue.buffering.max.messages配置项*/
                rd_kafka_poll(rk, 1000);
                goto retry;
            }
        }else{
            fprintf(stderr, "%% Enqueued message (%zd bytes) for topic %s\n",
                len, rd_kafka_topic_name(rkt));
        }
        /*producer应用程序应不断地通过以频繁的间隔调用rd_kafka_poll()来为
        传送报告队列提供服务。在没有生成消息以确定先前生成的消息已发送了其
        发送报告回调函数(和其他注册过的回调函数)期间要确保rd_kafka_poll()
        仍然被调用*/
        rd_kafka_poll(rk, 0);
     }
     fprintf(stderr, "%% Flushing final message.. \n");
     /*rd_kafka_flush是rd_kafka_poll()的抽象化,
     等待所有未完成的produce请求完成通常在销毁producer实例前完成
     以确保所有排列中和正在传输的produce请求在销毁前完成*/
     rd_kafka_flush(rk, 10*1000);
     /* Destroy topic object */
     rd_kafka_topic_destroy(rkt);
     /* Destroy the producer instance */
     rd_kafka_destroy(rk);
     return 0;
}
//编译示例
//gcc producer.c ~/librdkafka/src/librdkafka.a -I ~/librdkafka/src -lm -lsasl2   -lssl -lcrypto   -lcrypto   -lz   -ldl -lpthread -lrt
运行示例
//./a.out 10.179.24.223:9093 test2 95.kafka 12345