关于librdkafka库的介绍,可以参考kafka的c/c++高性能客户端librdkafka简介,本文使用librdkafka库来进行kafka的简单的生产、消费

一、producer

librdkafka进行kafka生产操作的大致步骤如下:

1、创建kafka配置

  1. rd_kafka_conf_t *rd_kafka_conf_new (void)

2、配置kafka各项参数

  1. rd_kafka_conf_res_t rd_kafka_conf_set (rd_kafka_conf_t *conf,
  2. const char *name,
  3. const char *value,
  4. char *errstr, size_t errstr_size)

3、设置发送回调函数

  1. void rd_kafka_conf_set_dr_msg_cb (rd_kafka_conf_t *conf,
  2. void (*dr_msg_cb) (rd_kafka_t *rk,
  3. const rd_kafka_message_t *
  4. rkmessage,
  5. void *opaque))

4、创建producer实例

  1. rd_kafka_t *rd_kafka_new (rd_kafka_type_t type, rd_kafka_conf_t *conf,char *errstr, size_t errstr_size)

5、实例化topic

  1. rd_kafka_topic_t *rd_kafka_topic_new (rd_kafka_t *rk, const char *topic, rd_kafka_topic_conf_t *conf)

6、异步调用将消息发送到指定的topic

  1. int rd_kafka_produce (rd_kafka_topic_t *rkt, int32_t partition,
  2. int msgflags,
  3. void *payload, size_t len,
  4. const void *key, size_t keylen,
  5. void *msg_opaque)

7、阻塞等待消息发送完成

  1. int rd_kafka_poll (rd_kafka_t *rk, int timeout_ms)

8、等待完成producer请求完成

  1. rd_kafka_resp_err_t rd_kafka_flush (rd_kafka_t *rk, int timeout_ms)

9、销毁topic

  1. void rd_kafka_topic_destroy (rd_kafka_topic_t *app_rkt)

10、销毁producer实例

  1. void rd_kafka_destroy (rd_kafka_t *rk)

完整代码如下my_producer.c:

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <string.h>
  4. #include "../src/rdkafka.h"
  5. static int run = 1;
  6. static void stop(int sig){
  7. run = 0;
  8. fclose(stdin);
  9. }
  10. /*
  11. 每条消息调用一次该回调函数,说明消息是传递成功(rkmessage->err == RD_KAFKA_RESP_ERR_NO_ERROR)
  12. 还是传递失败(rkmessage->err != RD_KAFKA_RESP_ERR_NO_ERROR)
  13. 该回调函数由rd_kafka_poll()触发,在应用程序的线程上执行
  14. */
  15. static void dr_msg_cb(rd_kafka_t *rk,
  16. const rd_kafka_message_t *rkmessage, void *opaque){
  17. if(rkmessage->err)
  18. fprintf(stderr, "%% Message delivery failed: %s\n",
  19. rd_kafka_err2str(rkmessage->err));
  20. else
  21. fprintf(stderr,
  22. "%% Message delivered (%zd bytes, "
  23. "partition %"PRId32")\n",
  24. rkmessage->len, rkmessage->partition);
  25. /* rkmessage被librdkafka自动销毁*/
  26. }
  27. int main(int argc, char **argv){
  28. rd_kafka_t *rk;            /*Producer instance handle*/
  29. rd_kafka_topic_t *rkt;     /*topic对象*/
  30. rd_kafka_conf_t *conf;     /*临时配置对象*/
  31. char errstr[512];
  32. char buf[512];
  33. const char *brokers;
  34. const char *topic;
  35. if(argc != 3){
  36. fprintf(stderr, "%% Usage: %s <broker> <topic>\n", argv[0]);
  37. return 1;
  38. }
  39. brokers = argv[1];
  40. topic = argv[2];
  41. /* 创建一个kafka配置占位 */
  42. conf = rd_kafka_conf_new();
  43. /*创建broker集群*/
  44. if (rd_kafka_conf_set(conf, "bootstrap.servers", brokers, errstr,
  45. sizeof(errstr)) != RD_KAFKA_CONF_OK){
  46. fprintf(stderr, "%s\n", errstr);
  47. return 1;
  48. }
  49. /*设置发送报告回调函数,rd_kafka_produce()接收的每条消息都会调用一次该回调函数
  50. *应用程序需要定期调用rd_kafka_poll()来服务排队的发送报告回调函数*/
  51. rd_kafka_conf_set_dr_msg_cb(conf, dr_msg_cb);
  52. /*创建producer实例
  53. rd_kafka_new()获取conf对象的所有权,应用程序在此调用之后不得再次引用它*/
  54. rk = rd_kafka_new(RD_KAFKA_PRODUCER, conf, errstr, sizeof(errstr));
  55. if(!rk){
  56. fprintf(stderr, "%% Failed to create new producer:%s\n", errstr);
  57. return 1;
  58. }
  59. /*实例化一个或多个topics(`rd_kafka_topic_t`)来提供生产或消费,topic
  60. 对象保存topic特定的配置,并在内部填充所有可用分区和leader brokers,*/
  61. rkt = rd_kafka_topic_new(rk, topic, NULL);
  62. if (!rkt){
  63. fprintf(stderr, "%% Failed to create topic object: %s\n",
  64. rd_kafka_err2str(rd_kafka_last_error()));
  65. rd_kafka_destroy(rk);
  66. return 1;
  67. }
  68. /*用于中断的信号*/
  69. signal(SIGINT, stop);
  70. fprintf(stderr,
  71. "%% Type some text and hit enter to produce message\n"
  72. "%% Or just hit enter to only serve delivery reports\n"
  73. "%% Press Ctrl-C or Ctrl-D to exit\n");
  74. while(run && fgets(buf, sizeof(buf), stdin)){
  75. size_t len = strlen(buf);
  76. if(buf[len-1] == '\n')
  77. buf[--len] = '\0';
  78. if(len == 0){
  79. /*轮询用于事件的kafka handle,
  80. 事件将导致应用程序提供的回调函数被调用
  81. 第二个参数是最大阻塞时间,如果设为0,将会是非阻塞的调用*/
  82. rd_kafka_poll(rk, 0);
  83. continue;
  84. }
  85. retry:
  86. /*Send/Produce message.
  87. 这是一个异步调用,在成功的情况下,只会将消息排入内部producer队列,
  88. 对broker的实际传递尝试由后台线程处理,之前注册的传递回调函数(dr_msg_cb)
  89. 用于在消息传递成功或失败时向应用程序发回信号*/
  90. if (rd_kafka_produce(
  91. /* Topic object */
  92. rkt,
  93. /*使用内置的分区来选择分区*/
  94. RD_KAFKA_PARTITION_UA,
  95. /*生成payload的副本*/
  96. RD_KAFKA_MSG_F_COPY,
  97. /*消息体和长度*/
  98. buf, len,
  99. /*可选键及其长度*/
  100. NULL, 0,
  101. NULL) == -1){
  102. fprintf(stderr,
  103. "%% Failed to produce to topic %s: %s\n",
  104. rd_kafka_topic_name(rkt),
  105. rd_kafka_err2str(rd_kafka_last_error()));
  106. if (rd_kafka_last_error() == RD_KAFKA_RESP_ERR__QUEUE_FULL){
  107. /*如果内部队列满,等待消息传输完成并retry,
  108. 内部队列表示要发送的消息和已发送或失败的消息,
  109. 内部队列受限于queue.buffering.max.messages配置项*/
  110. rd_kafka_poll(rk, 1000);
  111. goto retry;
  112. }
  113. }else{
  114. fprintf(stderr, "%% Enqueued message (%zd bytes) for topic %s\n",
  115. len, rd_kafka_topic_name(rkt));
  116. }
  117. /*producer应用程序应不断地通过以频繁的间隔调用rd_kafka_poll()来为
  118. 传送报告队列提供服务。在没有生成消息以确定先前生成的消息已发送了其
  119. 发送报告回调函数(和其他注册过的回调函数)期间,要确保rd_kafka_poll()
  120. 仍然被调用*/
  121. rd_kafka_poll(rk, 0);
  122. }
  123. fprintf(stderr, "%% Flushing final message.. \n");
  124. /*rd_kafka_flush是rd_kafka_poll()的抽象化,
  125. 等待所有未完成的produce请求完成,通常在销毁producer实例前完成
  126. 以确保所有排列中和正在传输的produce请求在销毁前完成*/
  127. rd_kafka_flush(rk, 10*1000);
  128. /* Destroy topic object */
  129. rd_kafka_topic_destroy(rkt);
  130. /* Destroy the producer instance */
  131. rd_kafka_destroy(rk);
  132. return 0;
  133. }

二、consumer

librdkafka进行kafka消费操作的大致步骤如下:

1、创建kafka配置

  1. rd_kafka_conf_t *rd_kafka_conf_new (void)

2、创建kafka topic的配置

  1. rd_kafka_topic_conf_t *rd_kafka_topic_conf_new (void)

3、配置kafka各项参数

  1. rd_kafka_conf_res_t rd_kafka_conf_set (rd_kafka_conf_t *conf,
  2. const char *name,
  3. const char *value,
  4. char *errstr, size_t errstr_size)

4、配置kafka topic各项参数

  1. rd_kafka_conf_res_t rd_kafka_topic_conf_set (rd_kafka_topic_conf_t *conf,
  2. const char *name,
  3. const char *value,
  4. char *errstr, size_t errstr_size)

5、创建consumer实例

  1. rd_kafka_t *rd_kafka_new (rd_kafka_type_t type, rd_kafka_conf_t *conf, char *errstr, size_t errstr_size)

6、为consumer实例添加brokerlist

  1. int rd_kafka_brokers_add (rd_kafka_t *rk, const char *brokerlist)

7、开启consumer订阅

  1. rd_kafka_subscribe (rd_kafka_t *rk, const rd_kafka_topic_partition_list_t *topics)

8、轮询消息或事件,并调用回调函数

  1. rd_kafka_message_t *rd_kafka_consumer_poll (rd_kafka_t *rk,int timeout_ms)

9、关闭consumer实例

  1. rd_kafka_resp_err_t rd_kafka_consumer_close (rd_kafka_t *rk)

10、释放topic list资源

  1. rd_kafka_topic_partition_list_destroy (rd_kafka_topic_partition_list_t *rktparlist)

11、销毁consumer实例

  1. void rd_kafka_destroy (rd_kafka_t *rk)

12、等待consumer对象的销毁

  1. int rd_kafka_wait_destroyed (int timeout_ms)

完整代码如下my_consumer.c

  1. #include <string.h>
  2. #include <stdlib.h>
  3. #include <syslog.h>
  4. #include <signal.h>
  5. #include <error.h>
  6. #include <getopt.h>
  7. #include "../src/rdkafka.h"
  8. static int run = 1;
  9. //`rd_kafka_t`自带一个可选的配置API,如果没有调用API,Librdkafka将会使用CONFIGURATION.md中的默认配置。
  10. static rd_kafka_t *rk;
  11. static rd_kafka_topic_partition_list_t *topics;
  12. static void stop (int sig) {
  13. if (!run)
  14. exit(1);
  15. run = 0;
  16. fclose(stdin); /* abort fgets() */
  17. }
  18. static void sig_usr1 (int sig) {
  19. rd_kafka_dump(stdout, rk);
  20. }
  21. /**
  22. * 处理并打印已消费的消息
  23. */
  24. static void msg_consume (rd_kafka_message_t *rkmessage,
  25. void *opaque) {
  26. if (rkmessage->err) {
  27. if (rkmessage->err == RD_KAFKA_RESP_ERR__PARTITION_EOF) {
  28. fprintf(stderr,
  29. "%% Consumer reached end of %s [%"PRId32"] "
  30. "message queue at offset %"PRId64"\n",
  31. rd_kafka_topic_name(rkmessage->rkt),
  32. rkmessage->partition, rkmessage->offset);
  33. return;
  34. }
  35. if (rkmessage->rkt)
  36. fprintf(stderr, "%% Consume error for "
  37. "topic \"%s\" [%"PRId32"] "
  38. "offset %"PRId64": %s\n",
  39. rd_kafka_topic_name(rkmessage->rkt),
  40. rkmessage->partition,
  41. rkmessage->offset,
  42. rd_kafka_message_errstr(rkmessage));
  43. else
  44. fprintf(stderr, "%% Consumer error: %s: %s\n",
  45. rd_kafka_err2str(rkmessage->err),
  46. rd_kafka_message_errstr(rkmessage));
  47. if (rkmessage->err == RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION ||
  48. rkmessage->err == RD_KAFKA_RESP_ERR__UNKNOWN_TOPIC)
  49. run = 0;
  50. return;
  51. }
  52. fprintf(stdout, "%% Message (topic %s [%"PRId32"], "
  53. "offset %"PRId64", %zd bytes):\n",
  54. rd_kafka_topic_name(rkmessage->rkt),
  55. rkmessage->partition,
  56. rkmessage->offset, rkmessage->len);
  57. if (rkmessage->key_len) {
  58. printf("Key: %.*s\n",
  59. (int)rkmessage->key_len, (char *)rkmessage->key);
  60. }
  61. printf("%.*s\n",
  62. (int)rkmessage->len, (char *)rkmessage->payload);
  63. }
  64. /*
  65. init all configuration of kafka
  66. */
  67. int initKafka(char *brokers, char *group,char *topic){
  68. rd_kafka_conf_t *conf;
  69. rd_kafka_topic_conf_t *topic_conf;
  70. rd_kafka_resp_err_t err;
  71. char tmp[16];
  72. char errstr[512];
  73. /* Kafka configuration */
  74. conf = rd_kafka_conf_new();
  75. //quick termination
  76. snprintf(tmp, sizeof(tmp), "%i", SIGIO);
  77. rd_kafka_conf_set(conf, "internal.termination.signal", tmp, NULL, 0);
  78. //topic configuration
  79. topic_conf = rd_kafka_topic_conf_new();
  80. /* Consumer groups require a group id */
  81. if (!group)
  82. group = "rdkafka_consumer_example";
  83. if (rd_kafka_conf_set(conf, "group.id", group,
  84. errstr, sizeof(errstr)) !=
  85. RD_KAFKA_CONF_OK) {
  86. fprintf(stderr, "%% %s\n", errstr);
  87. return -1;
  88. }
  89. /* Consumer groups always use broker based offset storage */
  90. if (rd_kafka_topic_conf_set(topic_conf, "offset.store.method",
  91. "broker",
  92. errstr, sizeof(errstr)) !=
  93. RD_KAFKA_CONF_OK) {
  94. fprintf(stderr, "%% %s\n", errstr);
  95. return -1;
  96. }
  97. /* Set default topic config for pattern-matched topics. */
  98. rd_kafka_conf_set_default_topic_conf(conf, topic_conf);
  99. //实例化一个顶级对象rd_kafka_t作为基础容器,提供全局配置和共享状态
  100. rk = rd_kafka_new(RD_KAFKA_CONSUMER, conf, errstr, sizeof(errstr));
  101. if(!rk){
  102. fprintf(stderr, "%% Failed to create new consumer:%s\n", errstr);
  103. return -1;
  104. }
  105. //Librdkafka需要至少一个brokers的初始化list
  106. if (rd_kafka_brokers_add(rk, brokers) == 0){
  107. fprintf(stderr, "%% No valid brokers specified\n");
  108. return -1;
  109. }
  110. //重定向 rd_kafka_poll()队列到consumer_poll()队列
  111. rd_kafka_poll_set_consumer(rk);
  112. //创建一个Topic+Partition的存储空间(list/vector)
  113. topics = rd_kafka_topic_partition_list_new(1);
  114. //把Topic+Partition加入list
  115. rd_kafka_topic_partition_list_add(topics, topic, -1);
  116. //开启consumer订阅,匹配的topic将被添加到订阅列表中
  117. if((err = rd_kafka_subscribe(rk, topics))){
  118. fprintf(stderr, "%% Failed to start consuming topics: %s\n", rd_kafka_err2str(err));
  119. return -1;
  120. }
  121. return 1;
  122. }
  123. int main(int argc, char **argv){
  124. char *brokers = "localhost:9092";
  125. char *group = NULL;
  126. char *topic = NULL;
  127. int opt;
  128. rd_kafka_resp_err_t err;
  129. while ((opt = getopt(argc, argv, "g:b:t:qd:eX:As:DO")) != -1){
  130. switch (opt) {
  131. case 'b':
  132. brokers = optarg;
  133. break;
  134. case 'g':
  135. group = optarg;
  136. break;
  137. case 't':
  138. topic = optarg;
  139. break;
  140. default:
  141. break;
  142. }
  143. }
  144. signal(SIGINT, stop);
  145. signal(SIGUSR1, sig_usr1);
  146. if(!initKafka(brokers, group, topic)){
  147. fprintf(stderr, "kafka server initialize error\n");
  148. }else{
  149. while(run){
  150. rd_kafka_message_t *rkmessage;
  151. /*-轮询消费者的消息或事件,最多阻塞timeout_ms
  152. -应用程序应该定期调用consumer_poll(),即使没有预期的消息,以服务
  153. 所有排队等待的回调函数,当注册过rebalance_cb,该操作尤为重要,
  154. 因为它需要被正确地调用和处理以同步内部消费者状态 */
  155. rkmessage = rd_kafka_consumer_poll(rk, 1000);
  156. if(rkmessage){
  157. msg_consume(rkmessage, NULL);
  158. /*释放rkmessage的资源,并把所有权还给rdkafka*/
  159. rd_kafka_message_destroy(rkmessage);
  160. }
  161. }
  162. }
  163. done:
  164. /*此调用将会阻塞,直到consumer撤销其分配,调用rebalance_cb(如果已设置),
  165. commit offset到broker,并离开consumer group
  166. 最大阻塞时间被设置为session.timeout.ms
  167. */
  168. err = rd_kafka_consumer_close(rk);
  169. if(err){
  170. fprintf(stderr, "%% Failed to close consumer: %s\n", rd_kafka_err2str(err));
  171. }else{
  172. fprintf(stderr, "%% Consumer closed\n");
  173. }
  174. //释放topics list使用的所有资源和它自己
  175. rd_kafka_topic_partition_list_destroy(topics);
  176. //destroy kafka handle
  177. rd_kafka_destroy(rk);
  178. run = 5;
  179. //等待所有rd_kafka_t对象销毁,所有kafka对象被销毁,返回0,超时返回-1
  180. while(run-- > 0 && rd_kafka_wait_destroyed(1000) == -1){
  181. printf("Waiting for librdkafka to decommission\n");
  182. }
  183. if(run <= 0){
  184. //dump rdkafka内部状态到stdout流
  185. rd_kafka_dump(stdout, rk);
  186. }
  187. return 0;
  188. }

在linux下编译producer和consumer的代码:

  1. gcc my_producer.c -o my_producer  -lrdkafka -lz -lpthread -lrt
  2. gcc my_consumer.c -o my_consumer  -lrdkafka -lz -lpthread -lrt

在运行my_producer或my_consumer时可能会报错"error while loading shared libraries xxx.so", 此时需要在/etc/ld.so.conf中加入xxx.so所在的目录

在本地启动一个简单的kafka服务,设置broker集群为localhost:9092并创建一个叫“test_topic”的topic
启动方式可参考 kafka0.8.2集群的环境搭建并实现基本的生产消费

启动consumer:

启动producer,并发送一条数据“hello world”:

consumer处成功收到producer发送的“hello world”:

http://orchome.com/5

https://github.com/edenhill/librdkafka

https://github.com/mfontanini/cppkafka

https://github.com/zengyuxing007/kafka_test_cpp

c语言使用librdkafka库实现kafka的生产和消费实例(转)的更多相关文章

  1. c# .net 使用Confluent.Kafka针对kafka进行生产和消费

    首先说明一点,像Confluent.Kafka这种开源的组件,三天两头的更新.在搜索引擎搜索到的结果往往用不了,浪费时间.建议以后遇到类似的情况直接看官网给的Demo. 因为搜索引擎搜到的文章,作者基 ...

  2. spring整合kafka项目生产和消费测试结果记录(一)

    使用spring+springMVC+mybatis+kafka做了两个web项目,一个是生产者,一个是消费者. 通过JMeter测试工具模拟100个用户并发访问生产者项目,发送json数据给生产者的 ...

  3. C语言常用的库文件(头文件、函数库)

    C语言常用的库文件(头文件.函数库) C系统提供了丰富的系统文件,称为库文件.C的库文件分为两类,一类是扩展名为".h"的文件,称为头文件,在前面的包含命令中我们已多次使用过.在& ...

  4. 13-C语言字符串函数库

    目录: 一.C语言字符串函数库 二.用命令行输入参数 回到顶部 一.C语言字符串函数库 1 #include <string.h> 2 字符串复制 strcpy(参数1,参数2); 参数1 ...

  5. nodejs选择JavaScript作为开发语言,是因为一般的开发语言的标准库都是带有IO模块的,并且通常这个 模块是阻塞性的,所以nodejs选择了没有自带IO模块的Javascript

    Javascrip本身不带IO功能,nodejs选择JavaScript作为开发语言,是因为一般的开发语言的标准库都是带有IO模块的,并且通常这个 模块是阻塞性的,所以nodejs选择了没有自带IO模 ...

  6. Tesseract.js 一个几乎能识别出图片中所有语言的JS库

    Tesseract.js 一个几乎能识别出图片中所有语言的JS库. 官网:http://tesseract.projectnaptha.com/ git:https://github.com/napt ...

  7. C语言创建共享库(动态库)步骤

    C语言创建共享库(动态库)步骤: 创建sth.c,共享库源文件: //sth.c库源程序 unsigned long long sth(unsigned int x, unsigned int y) ...

  8. 一文说透 Go 语言 HTTP 标准库

    本篇文章来分析一下 Go 语言 HTTP 标准库是如何实现的. 转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/561 ...

  9. Kafka 使用Java实现数据的生产和消费demo

    前言 在上一篇中讲述如何搭建kafka集群,本篇则讲述如何简单的使用 kafka .不过在使用kafka的时候,还是应该简单的了解下kafka. Kafka的介绍 Kafka是一种高吞吐量的分布式发布 ...

随机推荐

  1. MySQL中character set与collation的理解(转)

    character set和collation的是什么? character set即字符集 我们常看到的UTF-8.GB2312.GB18030都是相互独立的character set.即对Unic ...

  2. centos 使用mutt发送邮件带附件

    1.安装mutt工具 yum install -y mutt 2.使用mutt发邮件并带附件echo "统计日志" | /usr/bin/mutt -s "统计日志&qu ...

  3. C#创建文件夹和文件

    一.创建文件夹,例: if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } 二.创建文件,例: global::Syste ...

  4. golang 自定义类型的排序sort

    sort包中提供了很多排序算法,对自定义类型进行排序时,只需要实现sort的Interface即可,包括: func Len() int {... } func Swap(i, j int) {... ...

  5. BASIC-10_蓝桥杯_十进制转十六进制

    示例代码: #include <stdio.h>#define N 16 void dg(int a){ int y = a%N; int next = (a-y)/N; if (next ...

  6. 利用spring的ApplicationListener实现springmvc容器的初始化加载--转

    1.我们在使用springmvc进行配置的时候一般初始化都是在web.xml里面进行的,但是自己在使用的时候经常会测试一些数据,这样就只有加载spring-mvc.xml的配置文件来实现.为了更方便的 ...

  7. eclipse一些操作记录

    1.eclipse debug的时候想知道一个表达式执行的结果值,可以选中,按ctrl+shift+i来看返回的结果值:   2.eclipse java build path有个source,将so ...

  8. 服务注册发现Eureka之三:Spring Cloud Ribbon实现客户端负载均衡(客户端负载均衡Ribbon之三:使用Ribbon实现客户端的均衡负载)

    在使用RestTemplate来消费spring boot的Restful服务示例中,我们提到,调用spring boot服务的时候,需要将服务的URL写死或者是写在配置文件中,但这两种方式,无论哪一 ...

  9. [转]MSSQL 判断临时表是否存在

    原文来自:http://www.cnblogs.com/szfhquan/p/4229150.html 方法一: 1 if exists (select * from tempdb.dbo.sysob ...

  10. shell 6基本运算符

    shell支持多种运算符: * 算数运算符 * 关系运算符 * 布尔运算符 * 字符串运算符 * 文件测试运算符 算数运算符 + 加 `expr $a + $b` 结果为 30 - 减 `expr $ ...