1 /* 2 Copyright (c) 2009-2012 Roger Light <roger@atchoo.org> 3 All rights reserved. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are met: 7 8 1. Redistributions of source code must retain the above copyright notice, 9 this list of conditions and the following disclaimer. 10 2. Redistributions in binary form must reproduce the above copyright 11 notice, this list of conditions and the following disclaimer in the 12 documentation and/or other materials provided with the distribution. 13 3. Neither the name of mosquitto nor the names of its 14 contributors may be used to endorse or promote products derived from 15 this software without specific prior written permission. 16 17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <errno.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #ifndef WIN32 35 #include <unistd.h> 36 #else 37 #include <process.h> 38 #include <winsock2.h> 39 #define snprintf sprintf_s 40 #endif 41 42 #include <mosquitto.h> 43 44 static char **topics = NULL; 45 static int topic_count = 0; 46 static int topic_qos = 0; 47 static char *username = NULL; 48 static char *password = NULL; 49 int verbose = 0; 50 bool quiet = false; 51 52 void my_message_callback(void *obj, const struct mosquitto_message *message) 53 { 54 //收到消息时,打印 55 if(verbose){ 56 if(message->payloadlen){ 57 printf("%s %s\n", message->topic, message->payload); 58 }else{ 59 printf("%s (null)\n", message->topic); 60 } 61 fflush(stdout); 62 }else{ 63 if(message->payloadlen){ 64 printf("%s\n", message->payload); 65 fflush(stdout); 66 } 67 } 68 } 69 70 void my_connect_callback(void *obj, int result) 71 { 72 //连接建立后订阅主题 73 struct mosquitto *mosq = obj; 74 75 int i; 76 if(!result){ 77 for(i=0; i<topic_count; i++){ 78 mosquitto_subscribe(mosq, NULL, topics[i], topic_qos); 79 } 80 }else{ 81 switch(result){ 82 case 1: 83 if(!quiet) fprintf(stderr, "Connection Refused: unacceptable protocol version\n"); 84 break; 85 case 2: 86 if(!quiet) fprintf(stderr, "Connection Refused: identifier rejected\n"); 87 break; 88 case 3: 89 if(!quiet) fprintf(stderr, "Connection Refused: broker unavailable\n"); 90 break; 91 case 4: 92 if(!quiet) fprintf(stderr, "Connection Refused: bad user name or password\n"); 93 break; 94 case 5: 95 if(!quiet) fprintf(stderr, "Connection Refused: not authorised\n"); 96 break; 97 default: 98 if(!quiet) fprintf(stderr, "Connection Refused: unknown reason\n"); 99 break; 100 } 101 } 102 } 103 104 void my_subscribe_callback(void *obj, uint16_t mid, int qos_count, const uint8_t *granted_qos) 105 { 106 //打印订阅的相关信息 107 int i; 108 109 if(!quiet) printf("Subscribed (mid: %d): %d", mid, granted_qos[0]); 110 for(i=1; i<qos_count; i++){ 111 if(!quiet) printf(", %d", granted_qos[i]); 112 } 113 if(!quiet) printf("\n"); 114 } 115 116 void print_usage(void) 117 { 118 //打印mosquitto_sub帮助信息 119 printf("mosquitto_sub is a simple mqtt client that will subscribe to a single topic and print all messages it receives.\n\n"); 120 printf("Usage: mosquitto_sub [-c] [-h host] [-k keepalive] [-p port] [-q qos] [-v] -t topic ...\n"); 121 printf(" [-i id] [-I id_prefix]\n"); 122 printf(" [-d] [--quiet]\n"); 123 printf(" [-u username [-P password]]\n"); 124 printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n\n"); 125 printf(" -c : disable 'clean session' (store subscription and pending messages when client disconnects).\n"); 126 printf(" -d : enable debug messages.\n"); 127 printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); 128 printf(" -i : id to use for this client. Defaults to mosquitto_sub_ appended with the process id.\n"); 129 printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n"); 130 printf(" broker is using the clientid_prefixes option.\n"); 131 printf(" -k : keep alive in seconds for this client. Defaults to 60.\n"); 132 printf(" -p : network port to connect to. Defaults to 1883.\n"); 133 printf(" -q : quality of service level to use for the subscription. Defaults to 0.\n"); 134 printf(" -t : mqtt topic to subscribe to. May be repeated multiple times.\n"); 135 printf(" -u : provide a username (requires MQTT 3.1 broker)\n"); 136 printf(" -v : print published messages verbosely.\n"); 137 printf(" -P : provide a password (requires MQTT 3.1 broker)\n"); 138 printf(" --quiet : don't print error messages.\n"); 139 printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); 140 printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); 141 printf(" length message will be sent.\n"); 142 printf(" --will-qos : QoS level for the client Will.\n"); 143 printf(" --will-retain : if given, make the client Will retained.\n"); 144 printf(" --will-topic : the topic on which to publish the client Will.\n"); 145 printf("\nSee http://mosquitto.org/ for more information.\n\n"); 146 } 147 148 int main(int argc, char *argv[]) 149 { 150 char *id = NULL; 151 char *id_prefix = NULL; 152 int i; 153 char *host = "localhost"; 154 int port = 1883; 155 int keepalive = 60; 156 bool clean_session = true; 157 bool debug = false; 158 struct mosquitto *mosq = NULL; 159 int rc; 160 char hostname[21]; 161 char err[1024]; 162 163 uint8_t *will_payload = NULL; 164 long will_payloadlen = 0; 165 int will_qos = 0; 166 bool will_retain = false; 167 char *will_topic = NULL; 168 169 for(i=1; i<argc; i++){ 170 if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port")){ //-p 171 if(i==argc-1){ 172 fprintf(stderr, "Error: -p argument given but no port specified.\n\n"); 173 print_usage(); 174 return 1; 175 }else{ 176 port = atoi(argv[i+1]); 177 if(port<1 || port>65535){ 178 fprintf(stderr, "Error: Invalid port given: %d\n", port); 179 print_usage(); 180 return 1; 181 } 182 } 183 i++; 184 }else if(!strcmp(argv[i], "-c") || !strcmp(argv[i], "--disable-clean-session")){ //-c,在client断开连接之后,仍保持订阅 185 clean_session = false; 186 }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ //-d 187 debug = true; 188 }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--host")){ //-h 189 if(i==argc-1){ 190 fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); 191 print_usage(); 192 return 1; 193 }else{ 194 host = argv[i+1]; 195 } 196 i++; 197 }else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--id")){ //-i 198 if(id_prefix){ 199 fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); 200 print_usage(); 201 return 1; 202 } 203 if(i==argc-1){ 204 fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); 205 print_usage(); 206 return 1; 207 }else{ 208 id = argv[i+1]; 209 } 210 i++; 211 }else if(!strcmp(argv[i], "-I") || !strcmp(argv[i], "--id-prefix")){ //-I 212 if(id){ 213 fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); 214 print_usage(); 215 return 1; 216 } 217 if(i==argc-1){ 218 fprintf(stderr, "Error: -I argument given but no id prefix specified.\n\n"); 219 print_usage(); 220 return 1; 221 }else{ 222 id_prefix = argv[i+1]; 223 } 224 i++; 225 }else if(!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keepalive")){ //-k,keepalive,定义客户端多久向server发送PING以保持连接 226 if(i==argc-1){ 227 fprintf(stderr, "Error: -k argument given but no keepalive specified.\n\n"); 228 print_usage(); 229 return 1; 230 }else{ 231 keepalive = atoi(argv[i+1]); 232 if(keepalive>65535){ 233 fprintf(stderr, "Error: Invalid keepalive given: %d\n", keepalive); 234 print_usage(); 235 return 1; 236 } 237 } 238 i++; 239 }else if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--qos")){ //-q 240 if(i==argc-1){ 241 fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); 242 print_usage(); 243 return 1; 244 }else{ 245 topic_qos = atoi(argv[i+1]); 246 if(topic_qos<0 || topic_qos>2){ 247 fprintf(stderr, "Error: Invalid QoS given: %d\n", topic_qos); 248 print_usage(); 249 return 1; 250 } 251 } 252 i++; 253 }else if(!strcmp(argv[i], "--quiet")){ //-quiet 254 quiet = true; 255 }else if(!strcmp(argv[i], "-t") || !strcmp(argv[i], "--topic")){ //-t,可订阅多个主题 256 if(i==argc-1){ 257 fprintf(stderr, "Error: -t argument given but no topic specified.\n\n"); 258 print_usage(); 259 return 1; 260 }else{ 261 topic_count++; 262 topics = realloc(topics, topic_count*sizeof(char *)); 263 topics[topic_count-1] = argv[i+1]; 264 } 265 i++; 266 }else if(!strcmp(argv[i], "-u") || !strcmp(argv[i], "--username")){ //-u 267 if(i==argc-1){ 268 fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); 269 print_usage(); 270 return 1; 271 }else{ 272 username = argv[i+1]; 273 } 274 i++; 275 }else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")){ //-v,详细打印消息,格式:topic payload 276 verbose = 1; 277 }else if(!strcmp(argv[i], "-P") || !strcmp(argv[i], "--pw")){ //-P 278 if(i==argc-1){ 279 fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); 280 print_usage(); 281 return 1; 282 }else{ 283 password = argv[i+1]; 284 } 285 i++; 286 }else if(!strcmp(argv[i], "--will-payload")){ //--will-payload 287 if(i==argc-1){ 288 fprintf(stderr, "Error: --will-payload argument given but no will payload specified.\n\n"); 289 print_usage(); 290 return 1; 291 }else{ 292 will_payload = (uint8_t *)argv[i+1]; 293 will_payloadlen = strlen((char *)will_payload); 294 } 295 i++; 296 }else if(!strcmp(argv[i], "--will-qos")){ //--will-qos 297 if(i==argc-1){ 298 fprintf(stderr, "Error: --will-qos argument given but no will QoS specified.\n\n"); 299 print_usage(); 300 return 1; 301 }else{ 302 will_qos = atoi(argv[i+1]); 303 if(will_qos < 0 || will_qos > 2){ 304 fprintf(stderr, "Error: Invalid will QoS %d.\n\n", will_qos); 305 return 1; 306 } 307 } 308 i++; 309 }else if(!strcmp(argv[i], "--will-retain")){ //--will-retain 310 will_retain = true; 311 }else if(!strcmp(argv[i], "--will-topic")){ //--will-topic 312 if(i==argc-1){ 313 fprintf(stderr, "Error: --will-topic argument given but no will topic specified.\n\n"); 314 print_usage(); 315 return 1; 316 }else{ 317 will_topic = argv[i+1]; 318 } 319 i++; 320 }else{ 321 fprintf(stderr, "Error: Unknown option '%s'.\n",argv[i]); 322 print_usage(); 323 return 1; 324 } 325 } 326 if(clean_session == false && (id_prefix || !id)){ //设置了-c,就必须使用-i,不能用-I 327 if(!quiet) fprintf(stderr, "Error: You must provide a client id if you are using the -c option.\n"); 328 return 1; 329 } 330 if(id_prefix){ 331 id = malloc(strlen(id_prefix)+10); 332 if(!id){ 333 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 334 return 1; 335 } 336 snprintf(id, strlen(id_prefix)+10, "%s%d", id_prefix, getpid()); 337 }else if(!id){ 338 id = malloc(30); 339 if(!id){ 340 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 341 return 1; 342 } 343 memset(hostname, 0, 21); 344 gethostname(hostname, 20); 345 snprintf(id, 23, "mosq_sub_%d_%s", getpid(), hostname); 346 } 347 348 if(topic_count == 0){ 349 fprintf(stderr, "Error: You must specify a topic to subscribe to.\n"); 350 print_usage(); 351 return 1; 352 } 353 if(will_payload && !will_topic){ 354 fprintf(stderr, "Error: Will payload given, but no will topic given.\n"); 355 print_usage(); 356 return 1; 357 } 358 if(will_retain && !will_topic){ 359 fprintf(stderr, "Error: Will retain given, but no will topic given.\n"); 360 print_usage(); 361 return 1; 362 } 363 if(password && !username){ 364 if(!quiet) fprintf(stderr, "Warning: Not using password since username not set.\n"); 365 } 366 mosquitto_lib_init(); //任何mosquitto functions之前都必须调用的函数,初始化操作 367 mosq = mosquitto_new(id, NULL); //新建一个 mosquitto client实例 368 if(!mosq){ 369 if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); 370 return 1; 371 } 372 if(debug){ 373 mosquitto_log_init(mosq, MOSQ_LOG_DEBUG | MOSQ_LOG_ERR | MOSQ_LOG_WARNING 374 | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO, MOSQ_LOG_STDERR); 375 } 376 if(will_topic && mosquitto_will_set(mosq, true, will_topic, will_payloadlen, will_payload, will_qos, will_retain)){ 377 if(!quiet) fprintf(stderr, "Error: Problem setting will.\n"); 378 return 1; 379 } 380 if(username && mosquitto_username_pw_set(mosq, username, password)){ 381 if(!quiet) fprintf(stderr, "Error: Problem setting username and password.\n"); 382 return 1; 383 } 384 mosquitto_connect_callback_set(mosq, my_connect_callback); //设置client与broker建立连接后所调用的函数 385 mosquitto_message_callback_set(mosq, my_message_callback); //设置client从broker收到消息后所调用的函数 386 if(debug){ 387 mosquitto_subscribe_callback_set(mosq, my_subscribe_callback); //设置当broker相应一个订阅请求时所调用的函数 388 } 389 390 rc = mosquitto_connect(mosq, host, port, keepalive, clean_session); //建立连接 391 if(rc){ 392 if(!quiet){ 393 if(rc == MOSQ_ERR_ERRNO){ 394 #ifndef WIN32 395 strerror_r(errno, err, 1024); 396 #else 397 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); 398 #endif 399 fprintf(stderr, "Error: %s\n", err); 400 }else{ 401 fprintf(stderr, "Unable to connect (%d).\n", rc); 402 } 403 } 404 return rc; 405 } 406 407 do{ 408 rc = mosquitto_loop(mosq, -1); //主网络循环监控client,以保持client和broker之间的通信正常 409 }while(rc == MOSQ_ERR_SUCCESS); 410 411 mosquitto_destroy(mosq); //释放mosquitto实例的内存空间 412 mosquitto_lib_cleanup(); //释放library所使用的资源 413 414 return rc; 415 }